openmausbot 0.1.94 → 0.1.95
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/assets/{DesktopViewer-bxM7NV3N.js → DesktopViewer-BwWpc4RK.js} +1 -1
- package/dist/assets/{abnfDiagram-O67JEVCF-BhMdJhRJ.js → abnfDiagram-O67JEVCF-jsQkIqTn.js} +1 -1
- package/dist/assets/{arc-B5itKDcx.js → arc-7kei4Hsu.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-C5d7UY6U.js → architectureDiagram-NJMV4G6O-CRWDFDMq.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-BrZIhMmu.js → blockDiagram-BEXU5L5S-CTKW_IKs.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-7I0ArfbD.js → c4Diagram-YGBWAQC7-BBTW7-6M.js} +1 -1
- package/dist/assets/channel-DS2NbT5x.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-BnEsyN36.js → chunk-2Q5K7J3B-M_Ox-MkD.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-BXATQ52s.js → chunk-3FUC2YCW-D2YiN-Ud.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-B0BpTkEx.js → chunk-5VM5RSS4-BmVYJ0f8.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-BtLTz6FR.js → chunk-GTNCS2PH-BtiLVVnZ.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-D6gcZOYt.js → chunk-GWA4HPMP-DFYSb6J6.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-DCLrosvf.js → chunk-JWPE2WC7-mknKBzgV.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-B9JsknV9.js → chunk-NETBCI7D-DgSYGiOu.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-CsoTxw7z.js → chunk-WEXAMYUT-C7ZvuguH.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-0F_frB0c.js → chunk-XXDRQBXY-CPqxINpA.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-DkrpnflX.js → classDiagram-v2-NBCMYWYE-Cd6xfJaC.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-BNDwYBt9.js → cose-bilkent-JH36ORCC-BSXY5qRh.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-JUkNr1Mk.js → cynefin-OW5HDTMX-D_lhEMj1.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-DKeh9N4M.js → cynefinDiagram-VND7K2PF-Da9FrFwk.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-ve-6T_wX.js → dagre-6A5THRUB-T77jMOXe.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-BuJ2d8rT.js → diagram-22UHCM2B-Dkj96X5E.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-BgOs_Ni-.js → diagram-3UASUU5V-CMi9zlD_.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-DMK6VmcP.js → diagram-ATOU4E4O-CXK6YUfS.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-DksASlcY.js → diagram-CDSNMT55-BE7B1u9Q.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-Bgn8I5k6.js → diagram-MLGK6HIB-CCMQphKr.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-zK0jqO8N.js → diagram-MPIPVDR6-iHJ_-tGH.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-vKrbzTwr.js → ebnfDiagram-ZINNZB2B-DONsCWQ7.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-Bzv2mPNf.js → elk-276RUBZZ-CU_WOaXk.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-CvOyYuxu.js → erDiagram-OPXOYQCR-BeYwM04G.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-CtbnDbc0.js → flowDiagram-KWPJA3E3-Bdw9DqEl.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-c4dMRYyG.js → ganttDiagram-FUAMR5RP-C9iwq3qm.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-B34uaQho.js → gitGraphDiagram-X574FWY7-Dx-VWzUg.js} +1 -1
- package/dist/assets/{index-CPiqc_DN.js → index-KygHRE3E.js} +1 -1
- package/dist/assets/index-NF7XCHAy.css +1 -0
- package/dist/assets/index-X_5ydXIE.js +610 -0
- package/dist/assets/{infoDiagram-VRGFBTTK-o64ma4CI.js → infoDiagram-VRGFBTTK-BG7uaEd4.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-DV4C9BAm.js → ishikawaDiagram-OU5B5YK6-D72WAXnM.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-Bf0AHk1H.js → journeyDiagram-ZHPQQLJL-DKH1gjYT.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-B1UMSKmE.js → kanban-definition-PNTS6WVX-3upm9JUT.js} +1 -1
- package/dist/assets/{linear-BpKhuyLm.js → linear-CcR4pc0-.js} +1 -1
- package/dist/assets/{mermaid.core-B_BKNCw5.js → mermaid.core-C6yU5FSS.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-DeARruT1.js → mindmap-definition-NLK3R4M7-DOI2xEuv.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-BOQEk_1D.js → pegDiagram-GJSIUBJH-BVWJSgLd.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-EIfoiTZo.js → pieDiagram-5QR66LMP-jtbYezhq.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-GgZwT-fi.js → quadrantDiagram-O4NWA36T-g0u8mtPX.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-D9rDQomj.js → railroadDiagram-XR7U4H2S-k8689Wny.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-DAh1MIeo.js → requirementDiagram-PLB6GJNP-DsJ22PDt.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-CPr6Uz64.js → sankeyDiagram-IPEJSGJF-CneWBRgG.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-BU6VIdcm.js → sequenceDiagram-PO4LG4MO-DWsFCWap.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-qF2HO41r.js → sizeCapture-INFHLROL-Bvf0YE3S.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-DlJ8G4MD.js → stateDiagram-v2-GCMORJYK-Bxb3Iei8.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-DzYE8I54.js → swimlanes-2SLR337P-DLkk7W4A.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-C6bZtBXk.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-B7wzEQ0A.js → timeline-definition-EJHVYXUP-KR9bIHYC.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-BvGgrxIP.js → usecaseDiagram-POWQR4AR-DfsOrj8t.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-CEECdgQg.js → vennDiagram-UO4OBE2U-DRSTzG3h.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-XQHA0P_P.js → wardleyDiagram-VNRHLVJA-DtEdhPgT.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-B5mLcZI2.js → xychartDiagram-PMCCYNJV-B-XhTxjG.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/cloud-home-start.js +2 -1
- package/dist-server/companion/src/routes.js +39 -0
- package/dist-server/container-mcp.js +2 -2
- package/dist-server/drivers/agents-proxy.js +58 -38
- package/dist-server/enterprise/server/index.js +2 -1
- package/dist-server/{browser-proxy.js → harness-mcp-proxy.js} +35 -19
- package/dist-server/index.js +3159 -2313
- package/dist-server/local-computer.js +2 -1
- package/dist-server/openmausbot.js +314 -1336
- package/dist-server/pair-cli.js +314 -1336
- package/dist-server/proxy-paths.js +2 -1
- package/dist-server/server/account-signin.js +0 -2
- package/dist-server/server/activity.js +201 -0
- package/dist-server/server/auto-approve.js +6 -0
- package/dist-server/server/automatic-recovery.js +1 -1
- package/dist-server/server/boat-create-idempotency.js +0 -7
- package/dist-server/server/boat-delete-journal.js +0 -4
- package/dist-server/server/bot-package.js +1 -19
- package/dist-server/server/browser-runtime.js +1 -2
- package/dist-server/server/chief-of-staff.js +2 -3
- package/dist-server/server/cloud-backend.js +0 -4
- package/dist-server/server/cloud-computer-tools.js +110 -0
- package/dist-server/server/cloud-home.js +0 -1
- package/dist-server/server/cloud-lending.js +0 -3
- package/dist-server/server/composio.js +31 -2
- package/dist-server/server/computer-control.js +0 -15
- package/dist-server/server/computer-observation.js +0 -74
- package/dist-server/server/config.js +29 -1
- package/dist-server/server/container-computer.js +7 -19
- package/dist-server/server/contracts.js +9 -0
- package/dist-server/server/control-client.js +4 -10
- package/dist-server/server/drivers/acp/core.js +7 -5
- package/dist-server/server/drivers/agents-call.js +42 -29
- package/dist-server/server/drivers/agents-catalog.js +24 -11
- package/dist-server/server/drivers/agents-proxy.js +8 -30
- package/dist-server/server/drivers/antigravity.js +1 -4
- package/dist-server/server/drivers/boatagent.js +23 -4
- package/dist-server/server/drivers/cerebras.js +3 -3
- package/dist-server/server/drivers/chat-mcp-tools.js +4 -7
- package/dist-server/server/drivers/claude.js +16 -14
- package/dist-server/server/drivers/openai-chat.js +3 -6
- package/dist-server/server/drivers/retry.js +8 -4
- package/dist-server/server/engine-install.js +1 -5
- package/dist-server/server/harness/registry.js +0 -1
- package/dist-server/server/{browser-proxy.js → harness-mcp-proxy.js} +40 -20
- package/dist-server/server/index.js +789 -256
- package/dist-server/server/lending-memory.js +12 -8
- package/dist-server/server/memory-entries.js +38 -8
- package/dist-server/server/memory-organize.js +1 -8
- package/dist-server/server/memory-topics.js +4 -4
- package/dist-server/server/memory-upkeep.js +13 -15
- package/dist-server/server/message-file.js +5 -9
- package/dist-server/server/outbound-counts.js +79 -0
- package/dist-server/server/outbound-requests.js +60 -0
- package/dist-server/server/package-import.js +1 -1
- package/dist-server/server/peer-delivery.js +1 -0
- package/dist-server/server/proxy-paths.js +2 -1
- package/dist-server/server/room-handoffs.js +5 -13
- package/dist-server/server/routine-requests.js +1 -1
- package/dist-server/server/routines.js +5 -1
- package/dist-server/server/setup-mode.js +0 -2
- package/dist-server/server/skill-learn.js +0 -1
- package/dist-server/server/skill-library.js +0 -15
- package/dist-server/server/store.js +9 -1
- package/dist-server/server/surface.js +65 -20
- package/dist-server/server/system-prompt.js +4 -4
- package/dist-server/server/team-memory.js +209 -0
- package/dist-server/server/thread-events.js +2 -2
- package/dist-server/server/vps-computer.js +4 -14
- package/dist-server/server/workspace.js +85 -47
- package/dist-server/shared/activity.js +3 -0
- package/dist-server/shared/bot-avatar.js +8 -0
- package/dist-server/shared/connector-scopes.js +78 -0
- package/dist-server/shared/outbound.js +128 -0
- package/dist-server/shared/tool-surface.js +3 -2
- package/dist-server/vps-container-mcp.js +2 -2
- package/enterprise/server/index.js +2 -1
- package/package.json +1 -1
- package/dist/assets/channel-CdJX6i8k.js +0 -1
- package/dist/assets/index-CPYrGsv1.js +0 -610
- package/dist/assets/index-CzT42GR8.css +0 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-D-U4GtLQ.js +0 -8
- package/dist-server/server/drivers/chat-boat-tools.js +0 -90
- package/dist-server/server/drivers/grokagent.js +0 -4
- package/dist-server/server/openmaus-status-capsule.js +0 -329
|
@@ -39,6 +39,14 @@ export const WORKING_FOLDER_FILES = [".mcp.json", ".claude/settings.json", ".cla
|
|
|
39
39
|
/** Folders of skills, agents and commands an engine discovers in its working
|
|
40
40
|
* folder: each entry, and a skill's SKILL.md. */
|
|
41
41
|
export const WORKING_FOLDER_DIRS = [".claude/skills", ".claude/agents", ".claude/commands", ".agents/skills"];
|
|
42
|
+
/** The two lists above by first path segment, as the walker looks for them:
|
|
43
|
+
* each name at the top of a working folder, with the names inside it (none
|
|
44
|
+
* for a file there, like .mcp.json). These lists alone decide what is watched. */
|
|
45
|
+
const WORKING_FOLDER_TOP = new Map();
|
|
46
|
+
for (const path of [...WORKING_FOLDER_FILES, ...WORKING_FOLDER_DIRS]) {
|
|
47
|
+
const [top, inner] = path.split("/");
|
|
48
|
+
WORKING_FOLDER_TOP.set(top, [...WORKING_FOLDER_TOP.get(top) ?? [], ...(inner ? [inner] : [])]);
|
|
49
|
+
}
|
|
42
50
|
/** Instruction files an engine reads from its working folder and every
|
|
43
51
|
* folder above it (Claude Code's project memory; Codex's AGENTS.md). */
|
|
44
52
|
export const ANCESTOR_FILES = ["CLAUDE.md", "CLAUDE.local.md", "AGENTS.md", "AGENTS.override.md"];
|
|
@@ -222,9 +230,10 @@ export function memoryFiles(workspace, workingFolders = [], alsoAbove = []) {
|
|
|
222
230
|
const top = listing(folder, ROOT_ENTRY_CAP, true);
|
|
223
231
|
if (top) {
|
|
224
232
|
listedRoots.set(folder, top);
|
|
225
|
-
for (const name of present(folder, [
|
|
233
|
+
for (const name of present(folder, [...WORKING_FOLDER_TOP.keys()], top)) {
|
|
226
234
|
const path = join(folder, name);
|
|
227
|
-
|
|
235
|
+
const wanted = WORKING_FOLDER_TOP.get(name);
|
|
236
|
+
if (!wanted.length) {
|
|
228
237
|
add(path, path);
|
|
229
238
|
continue;
|
|
230
239
|
}
|
|
@@ -235,9 +244,8 @@ export function memoryFiles(workspace, workingFolders = [], alsoAbove = []) {
|
|
|
235
244
|
add(path, path);
|
|
236
245
|
if (!inner && !isLink(path))
|
|
237
246
|
continue;
|
|
238
|
-
const wanted = name === ".claude" ? ["settings.json", "settings.local.json", "CLAUDE.md", "skills", "agents", "commands"] : ["skills"];
|
|
239
247
|
for (const entry of inner ? present(path, wanted, inner) : wanted) {
|
|
240
|
-
if (entry
|
|
248
|
+
if (WORKING_FOLDER_DIRS.includes(`${name}/${entry}`))
|
|
241
249
|
capped(join(path, entry));
|
|
242
250
|
else
|
|
243
251
|
add(join(path, entry), join(path, entry));
|
|
@@ -290,10 +298,6 @@ export function fingerprintOf(files) {
|
|
|
290
298
|
hash.update(`${name}\u0000${files[name]}\u0000`);
|
|
291
299
|
return hash.digest("hex");
|
|
292
300
|
}
|
|
293
|
-
/** Back-compat for callers that only need the hash of one workspace. */
|
|
294
|
-
export function memoryFingerprint(workspace, workingFolders = []) {
|
|
295
|
-
return fingerprintOf(memoryFiles(workspace, workingFolders));
|
|
296
|
-
}
|
|
297
301
|
/** What the record keeps of each file: enough to say which ones changed. */
|
|
298
302
|
function digests(files) {
|
|
299
303
|
return Object.fromEntries(Object.entries(files).map(([name, described]) => [name, createHash("sha256").update(described).digest("hex").slice(0, 24)]));
|
|
@@ -2,12 +2,21 @@
|
|
|
2
2
|
// `- YYYY-MM-DD · from <source> · text[ · until YYYY-MM-DD]`, with a
|
|
3
3
|
// struck-through body for a superseded fact. Pure helpers shared by the
|
|
4
4
|
// prompt loader (which hides expired lines), background capture (which
|
|
5
|
-
// must not add a fact the notebook already holds)
|
|
5
|
+
// must not add a fact the notebook already holds), the nightly tidy-up and
|
|
6
|
+
// the rule that keeps MEMORY.md within what loads (server/workspace.ts).
|
|
6
7
|
export const UNTIL_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
7
8
|
const DATED = /^(- (\d{4}-\d{2}-\d{2}) · (?:from [^·\n]* · )?)(.*)$/;
|
|
8
9
|
const UNTIL_MARK = / · until (\d{4}-\d{2}-\d{2})(?= · |$)/;
|
|
9
|
-
const TRAILING_MARKS = /(?: · (?:updated|confirmed|until|expired|superseded) \d{4}-\d{2}-\d{2})+$/;
|
|
10
|
+
const TRAILING_MARKS = /(?: · (?:updated|confirmed|until|expired|superseded|moved) \d{4}-\d{2}-\d{2})+$/;
|
|
10
11
|
const BULLET = /^\s*(?:[-*•]|\d+[.)])\s+/;
|
|
12
|
+
/** A safety or health fact must load into every turn: neither the organize
|
|
13
|
+
* step nor the size rule moves one out of MEMORY.md, whatever else happens.
|
|
14
|
+
* Seen live — a model moved "is vegetarian" into a food topic despite being
|
|
15
|
+
* told diet is core. */
|
|
16
|
+
const ALWAYS_CORE = /\b(?:allerg\w*|anaphyla\w*|intoleran\w*|vegetarian|vegan|halal|kosher|gluten|lactose|diet\w*|diabet\w*|asthma\w*|epilep\w*|pregnan\w*|medicat\w*|medicine|medical|disabilit\w*|wheelchair|blind|deaf|health)\b/i;
|
|
17
|
+
export function alwaysCore(body) {
|
|
18
|
+
return ALWAYS_CORE.test(body);
|
|
19
|
+
}
|
|
11
20
|
/** Whether `value` is a real calendar date in YYYY-MM-DD form. */
|
|
12
21
|
export function isMemoryDate(value) {
|
|
13
22
|
if (typeof value !== "string" || !UNTIL_DATE.test(value))
|
|
@@ -18,20 +27,41 @@ export function isMemoryDate(value) {
|
|
|
18
27
|
export function untilMark(until) {
|
|
19
28
|
return until ? ` · until ${until}` : "";
|
|
20
29
|
}
|
|
30
|
+
/** A line with an odd number of fences opens or closes a code block. */
|
|
31
|
+
const togglesFence = (line) => (line.match(/```/g)?.length ?? 0) % 2 === 1;
|
|
21
32
|
/** Every dated entry in a MEMORY.md text, in file order. Hand-written lines
|
|
22
|
-
* without a date are not entries and are never touched by upkeep.
|
|
33
|
+
* without a date are not entries and are never touched by upkeep. An entry
|
|
34
|
+
* also owns the lines under it, up to the next dated line: indented ones
|
|
35
|
+
* (Markdown's list rule; how an entry with a code block is written) and a
|
|
36
|
+
* code block it opens, through its closing fence (how such entries were
|
|
37
|
+
* written before). A fence that never closes claims nothing. */
|
|
23
38
|
export function parseMemoryEntries(text) {
|
|
24
39
|
const out = [];
|
|
25
|
-
text.split("\n")
|
|
40
|
+
const lines = text.split("\n");
|
|
41
|
+
for (let line = 0; line < lines.length;) {
|
|
42
|
+
const raw = lines[line];
|
|
26
43
|
const m = DATED.exec(raw);
|
|
27
|
-
if (!m)
|
|
28
|
-
|
|
44
|
+
if (!m) {
|
|
45
|
+
line += 1;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
let open = togglesFence(raw);
|
|
49
|
+
let end = line + 1;
|
|
50
|
+
for (let next = line + 1; next < lines.length && !DATED.test(lines[next]); next += 1) {
|
|
51
|
+
if (!open && !/^[ \t]/.test(lines[next]) && !lines[next].startsWith("```"))
|
|
52
|
+
break;
|
|
53
|
+
if (togglesFence(lines[next]))
|
|
54
|
+
open = !open;
|
|
55
|
+
if (!open)
|
|
56
|
+
end = next + 1;
|
|
57
|
+
}
|
|
29
58
|
const rest = m[3];
|
|
30
59
|
const struck = rest.startsWith("~~");
|
|
31
60
|
const until = UNTIL_MARK.exec(rest)?.[1] ?? null;
|
|
32
61
|
const body = rest.replace(TRAILING_MARKS, "").replace(/^~~/, "").replace(/~~$/, "").trim();
|
|
33
|
-
out.push({ line, raw, prefix: m[1], body, date: m[2], until, struck });
|
|
34
|
-
|
|
62
|
+
out.push({ line, end, raw, prefix: m[1], body, date: m[2], until, struck });
|
|
63
|
+
line = end;
|
|
64
|
+
}
|
|
35
65
|
return out;
|
|
36
66
|
}
|
|
37
67
|
/** A live entry whose `until` day has passed. The until day itself still counts. */
|
|
@@ -8,19 +8,12 @@
|
|
|
8
8
|
// aliases recall matches. An entry judged core is remembered and not asked
|
|
9
9
|
// about again. Pure here: the caller reads, calls the model, writes and
|
|
10
10
|
// journals.
|
|
11
|
-
import { factIdentity, isExpired, parseMemoryEntries } from "./memory-entries.js";
|
|
11
|
+
import { alwaysCore, factIdentity, isExpired, parseMemoryEntries } from "./memory-entries.js";
|
|
12
12
|
import { topicFileName } from "./memory-capture.js";
|
|
13
13
|
export const ORGANIZE_MARKER = "You are the ORGANIZE step of a memory system";
|
|
14
14
|
/** One pass moves at most this many entries. */
|
|
15
15
|
export const MAX_MOVES = 20;
|
|
16
16
|
const PROMPT_ENTRIES = 60;
|
|
17
|
-
/** Lines the model may not move, whatever it answers: a safety or health
|
|
18
|
-
* fact must load into every turn. Seen live — a model moved "is vegetarian"
|
|
19
|
-
* into a food topic despite being told diet is core. */
|
|
20
|
-
const ALWAYS_CORE = /\b(?:allerg\w*|anaphyla\w*|intoleran\w*|vegetarian|vegan|halal|kosher|gluten|lactose|diet\w*|diabet\w*|asthma\w*|epilep\w*|pregnan\w*|medicat\w*|medicine|medical|disabilit\w*|wheelchair|blind|deaf|health)\b/i;
|
|
21
|
-
export function alwaysCore(body) {
|
|
22
|
-
return ALWAYS_CORE.test(body);
|
|
23
|
-
}
|
|
24
17
|
/** Live, unexpired entries not already judged core: what the call is asked about. */
|
|
25
18
|
export function organizeCandidates(text, today, core) {
|
|
26
19
|
return parseMemoryEntries(text)
|
|
@@ -90,8 +90,8 @@ export function readTopicHead(path) {
|
|
|
90
90
|
closeSync(fd);
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
|
-
/** One line per topic, capped by count and length. The archive
|
|
94
|
-
*
|
|
93
|
+
/** One line per topic, capped by count and length. The archive is listed
|
|
94
|
+
* last and labelled, so it is not mistaken for current notes. */
|
|
95
95
|
export function renderTopicIndex(topics) {
|
|
96
96
|
if (!topics.length)
|
|
97
97
|
return "";
|
|
@@ -104,8 +104,8 @@ export function renderTopicIndex(topics) {
|
|
|
104
104
|
omitted += 1;
|
|
105
105
|
continue;
|
|
106
106
|
}
|
|
107
|
-
const about = name === "archive.md"
|
|
108
|
-
? "
|
|
107
|
+
const about = name === "archive.md"
|
|
108
|
+
? "older notes moved out of MEMORY.md, kept for the record"
|
|
109
109
|
: [header.title, header.description].filter(Boolean).join(" — ");
|
|
110
110
|
const also = header.aliases.length ? ` (also: ${header.aliases.join(", ")})` : "";
|
|
111
111
|
const line = `- memory/${name}${about ? ` — ${about}` : ""}${also}`.replace(/\s+/g, " ");
|
|
@@ -25,13 +25,13 @@ import { mergeTopicText } from "./memory-topics.js";
|
|
|
25
25
|
import { applyMoves, MAX_MOVES, organizeCandidates, organizePrompt, parseMoves } from "./memory-organize.js";
|
|
26
26
|
import { recordMemoryChange } from "./memory-journal.js";
|
|
27
27
|
import { applyTidy, contradictionCandidates, contradictionPrompt, parseContradictions, planChanges, planTidy } from "./memory-tidy.js";
|
|
28
|
-
import { ensureWorkspace, listMemoryTopics, memoryDate, memoryEntry, memoryTopicIndex, readMemoryText, updateMemory, workspaceDir, writeMemoryFile, writeMemoryTopic } from "./workspace.js";
|
|
28
|
+
import { appendMemoryArchive, ARCHIVE_TOPIC, ensureWorkspace, listMemoryTopics, memoryDate, memoryEntry, memoryTopicIndex, readMemoryText, updateMemory, workspaceDir, writeMemoryFile, writeMemoryTopic } from "./workspace.js";
|
|
29
29
|
export const CAPTURE_MAX_TURNS = 6;
|
|
30
30
|
export const MODEL_TIMEOUT_MS = 60_000;
|
|
31
31
|
export const TIDY_CHECK_MS = 10 * 60_000;
|
|
32
32
|
/** Below this many live entries there is nothing a contradiction pass may change. */
|
|
33
33
|
export const MIN_ENTRIES_FOR_CONTRADICTIONS = 5;
|
|
34
|
-
|
|
34
|
+
const ARCHIVE_PATH = `memory/${ARCHIVE_TOPIC}`;
|
|
35
35
|
/** A topic file past this size gains nothing more from capture. */
|
|
36
36
|
export const TOPIC_MAX_BYTES = 64 * 1024;
|
|
37
37
|
/** On unless switched off: every bot keeps its memory in shape by default. */
|
|
@@ -139,25 +139,25 @@ export function createMemoryUpkeep(deps) {
|
|
|
139
139
|
// From here to the journal rows there is no await, so no write interleaves.
|
|
140
140
|
if (!upkeepEnabled(deps.bot(bot.id)))
|
|
141
141
|
return off;
|
|
142
|
-
const { parsed, added, topics
|
|
142
|
+
const { parsed, added, topics } = writing(bot.id, () => {
|
|
143
143
|
const before = readRaw(bot.id, "MEMORY.md");
|
|
144
|
+
const archiveBefore = readRaw(bot.id, ARCHIVE_PATH);
|
|
144
145
|
const parsed = parseCandidates(answer, today);
|
|
145
146
|
const fresh = newCandidates(parsed, before ?? "");
|
|
146
147
|
const source = `${deps.sourceLabel(bot.id, batch.threadId)} (noticed)`;
|
|
147
148
|
let added = 0;
|
|
148
|
-
let full = false;
|
|
149
149
|
const byTopic = new Map();
|
|
150
150
|
for (const candidate of fresh) {
|
|
151
151
|
if (candidate.topic) {
|
|
152
152
|
byTopic.set(candidate.topic, [...(byTopic.get(candidate.topic) ?? []), candidate]);
|
|
153
153
|
continue;
|
|
154
154
|
}
|
|
155
|
-
|
|
156
|
-
if (result.ok)
|
|
155
|
+
if (updateMemory(bot.id, { action: "append", text: candidate.text, ...(candidate.until ? { until: candidate.until } : {}) }, { source, now: now() }).ok)
|
|
157
156
|
added += 1;
|
|
158
|
-
else if (result.code === "over-budget")
|
|
159
|
-
full = true;
|
|
160
157
|
}
|
|
158
|
+
// Older entries the appends moved out are journaled first, as the
|
|
159
|
+
// tidy-up does, so undoing the newest row (MEMORY.md) brings them back.
|
|
160
|
+
recordMemoryChange(bot.id, { path: ARCHIVE_PATH, actor: "upkeep", via: "capture", threadId: batch.threadId, before: archiveBefore, after: readRaw(bot.id, ARCHIVE_PATH) });
|
|
161
161
|
if (added)
|
|
162
162
|
recordMemoryChange(bot.id, { path: "MEMORY.md", actor: "upkeep", via: "capture", threadId: batch.threadId, before, after: readRaw(bot.id, "MEMORY.md") });
|
|
163
163
|
// Detail goes to a topic file the bot keeps for that subject, created
|
|
@@ -188,7 +188,7 @@ export function createMemoryUpkeep(deps) {
|
|
|
188
188
|
existing.set(name.toLowerCase(), name);
|
|
189
189
|
topics += lines.length;
|
|
190
190
|
}
|
|
191
|
-
return { parsed, added, topics
|
|
191
|
+
return { parsed, added, topics };
|
|
192
192
|
});
|
|
193
193
|
// Only the owner's own words reach About me: on a shared workspace
|
|
194
194
|
// another person's facts are not the owner's profile. Every parsed fact
|
|
@@ -199,7 +199,7 @@ export function createMemoryUpkeep(deps) {
|
|
|
199
199
|
// whoever wrote MEMORY.md — the bot, the person or this capture — detail
|
|
200
200
|
// that is not core moves to its topic now, not only at night
|
|
201
201
|
const organized = await organize(bot.id);
|
|
202
|
-
const report = { at, added, topics, aboutMe, organized
|
|
202
|
+
const report = { at, added, topics, aboutMe, organized };
|
|
203
203
|
record(bot.id, { lastCapture: report });
|
|
204
204
|
if (added || topics || aboutMe)
|
|
205
205
|
log(`memory upkeep: noticed ${added} fact(s) for MEMORY.md, ${topics} for topic files and ${aboutMe} for About me — ${bot.name} (${bot.id}) from ${batch.threadId}`);
|
|
@@ -284,11 +284,9 @@ export function createMemoryUpkeep(deps) {
|
|
|
284
284
|
});
|
|
285
285
|
}
|
|
286
286
|
function appendArchive(botId, archived) {
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
writeMemoryTopic(botId, ARCHIVE_TOPIC, `${head}${head.endsWith("\n") ? "" : "\n"}${archived.join("\n")}\n`);
|
|
291
|
-
recordMemoryChange(botId, { path: archivePath, actor: "upkeep", via: "tidy", before: archiveBefore, after: readRaw(botId, archivePath) });
|
|
287
|
+
const archiveBefore = readRaw(botId, ARCHIVE_PATH);
|
|
288
|
+
appendMemoryArchive(botId, archived);
|
|
289
|
+
recordMemoryChange(botId, { path: ARCHIVE_PATH, actor: "upkeep", via: "tidy", before: archiveBefore, after: readRaw(botId, ARCHIVE_PATH) });
|
|
292
290
|
}
|
|
293
291
|
async function runTidy(botId) {
|
|
294
292
|
const bot = deps.bot(botId);
|
|
@@ -259,16 +259,12 @@ function safeDisplayName(value) {
|
|
|
259
259
|
return boundedDisplayName(clean || "download");
|
|
260
260
|
}
|
|
261
261
|
/**
|
|
262
|
-
*
|
|
263
|
-
* exact standalone transport tag
|
|
264
|
-
* and near-matching paths do not grant
|
|
265
|
-
*
|
|
266
|
-
* become an arbitrary host-file reader.
|
|
262
|
+
* The user-facing name of the requested attachment when a stored user
|
|
263
|
+
* message carries it as an exact standalone transport tag, else null. Plain
|
|
264
|
+
* prose, inline examples, fenced code, and near-matching paths do not grant
|
|
265
|
+
* access. The HTTP route pairs this message capability with ATTACHMENTS_DIR
|
|
266
|
+
* containment, so this can never become an arbitrary host-file reader.
|
|
267
267
|
*/
|
|
268
|
-
export function messageReferencesAttachment(text, requested) {
|
|
269
|
-
return messageAttachmentName(text, requested) !== null;
|
|
270
|
-
}
|
|
271
|
-
/** Return the user-facing name carried by an authorised attachment tag. */
|
|
272
268
|
export function messageAttachmentName(text, requested) {
|
|
273
269
|
let wanted;
|
|
274
270
|
try {
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// How many outbound calls each bot has made today. The daily cap in a
|
|
2
|
+
// bot's outbound policy is only as good as this number, so it lives on
|
|
3
|
+
// disk (0600, like every other record that names what a bot did) and is
|
|
4
|
+
// keyed by the local day, so it rolls over at midnight and not at the
|
|
5
|
+
// next restart.
|
|
6
|
+
//
|
|
7
|
+
// One day is kept per bot: the count exists to enforce today's cap, and
|
|
8
|
+
// the activity log already keeps the history.
|
|
9
|
+
import { readFileSync } from "node:fs";
|
|
10
|
+
import { writeFileAtomic } from "./atomic.js";
|
|
11
|
+
const dayKey = (date) => `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
|
12
|
+
export class OutboundCounts {
|
|
13
|
+
file;
|
|
14
|
+
stored;
|
|
15
|
+
readable = true;
|
|
16
|
+
constructor(file) {
|
|
17
|
+
this.file = file;
|
|
18
|
+
this.stored = this.load();
|
|
19
|
+
}
|
|
20
|
+
load() {
|
|
21
|
+
try {
|
|
22
|
+
const value = JSON.parse(readFileSync(this.file, "utf8"));
|
|
23
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
24
|
+
throw new Error("invalid outbound counts");
|
|
25
|
+
const stored = {};
|
|
26
|
+
for (const [botId, entry] of Object.entries(value)) {
|
|
27
|
+
if (!entry || typeof entry !== "object")
|
|
28
|
+
throw new Error("invalid outbound count");
|
|
29
|
+
const { day, count } = entry;
|
|
30
|
+
if (typeof day === "string" && typeof count === "number" && Number.isInteger(count) && count >= 0) {
|
|
31
|
+
stored[botId] = { day, count };
|
|
32
|
+
}
|
|
33
|
+
else
|
|
34
|
+
throw new Error("invalid outbound count");
|
|
35
|
+
}
|
|
36
|
+
return stored;
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (error.code !== "ENOENT")
|
|
40
|
+
this.readable = false;
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
save() {
|
|
45
|
+
writeFileAtomic(this.file, JSON.stringify(this.stored, null, 2), { mode: 0o600 });
|
|
46
|
+
}
|
|
47
|
+
/** How many outbound calls this bot has made on the day `now` falls in. */
|
|
48
|
+
today(botId, now = new Date()) {
|
|
49
|
+
if (!this.readable)
|
|
50
|
+
throw new Error("the daily allowance could not be read");
|
|
51
|
+
const entry = this.stored[botId];
|
|
52
|
+
return entry && entry.day === dayKey(now) ? entry.count : 0;
|
|
53
|
+
}
|
|
54
|
+
/** Count one more, and return the new total for the day. */
|
|
55
|
+
record(botId, now = new Date()) {
|
|
56
|
+
return this.reserve(botId, 1, Number.MAX_SAFE_INTEGER, now);
|
|
57
|
+
}
|
|
58
|
+
/** Reserve before any provider await. An uncertain send keeps its slot:
|
|
59
|
+
* refunding transport failures could repeat a send already delivered. */
|
|
60
|
+
reserve(botId, amount, cap, now = new Date()) {
|
|
61
|
+
const day = dayKey(now);
|
|
62
|
+
const entry = this.stored[botId];
|
|
63
|
+
const count = this.today(botId, now) + amount;
|
|
64
|
+
if (count > cap)
|
|
65
|
+
return null;
|
|
66
|
+
this.stored[botId] = { day, count };
|
|
67
|
+
try {
|
|
68
|
+
this.save();
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
if (entry)
|
|
72
|
+
this.stored[botId] = entry;
|
|
73
|
+
else
|
|
74
|
+
delete this.stored[botId];
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
return count;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Outbound calls held for a person's answer.
|
|
2
|
+
//
|
|
3
|
+
// The connector relay opens one of these when a bot's tool call would send
|
|
4
|
+
// something and the bot's policy says ask. The relay then waits on
|
|
5
|
+
// `answer`; the respond route resolves it when the card is answered. The
|
|
6
|
+
// contract mirrors the other card services: a request is claimed exactly
|
|
7
|
+
// once, a second answer says so instead of doing anything, and nothing
|
|
8
|
+
// waits forever — the relay has its own deadline, so the hold has one too.
|
|
9
|
+
//
|
|
10
|
+
// In-memory on purpose. A restart drops every held relay along with the
|
|
11
|
+
// proxy process that was waiting on it; the card left on the thread then
|
|
12
|
+
// settles as unavailable, the same way an engine's own request does.
|
|
13
|
+
import { randomUUID } from "node:crypto";
|
|
14
|
+
export class OutboundRequestService {
|
|
15
|
+
held = new Map();
|
|
16
|
+
/** Hold one call. `answer` settles with the person's verdict or a timeout. */
|
|
17
|
+
open(input) {
|
|
18
|
+
const requestId = randomUUID();
|
|
19
|
+
let settle;
|
|
20
|
+
const answer = new Promise((resolve) => {
|
|
21
|
+
settle = resolve;
|
|
22
|
+
});
|
|
23
|
+
const timer = setTimeout(() => this.settle(requestId, "timeout"), input.timeoutMs);
|
|
24
|
+
timer.unref?.();
|
|
25
|
+
this.held.set(requestId, { botId: input.botId, threadId: input.threadId, tool: input.tool, settle, timer });
|
|
26
|
+
return { requestId, answer };
|
|
27
|
+
}
|
|
28
|
+
settle(requestId, answer) {
|
|
29
|
+
const entry = this.held.get(requestId);
|
|
30
|
+
if (!entry || entry.answered)
|
|
31
|
+
return;
|
|
32
|
+
entry.answered = answer;
|
|
33
|
+
clearTimeout(entry.timer);
|
|
34
|
+
entry.settle(answer);
|
|
35
|
+
}
|
|
36
|
+
/** The person's answer, from the respond route. */
|
|
37
|
+
resolve(input) {
|
|
38
|
+
const entry = this.held.get(input.requestId);
|
|
39
|
+
if (!entry || entry.threadId !== input.threadId)
|
|
40
|
+
return { claimed: false };
|
|
41
|
+
if (entry.answered)
|
|
42
|
+
return { claimed: true, state: "already_settled", behavior: entry.answered };
|
|
43
|
+
const answer = input.behavior === "allow" ? "allow" : "deny";
|
|
44
|
+
this.settle(input.requestId, answer);
|
|
45
|
+
return { claimed: true, state: answer === "allow" ? "allowed" : "denied" };
|
|
46
|
+
}
|
|
47
|
+
/** Requests still waiting in a thread, oldest first. */
|
|
48
|
+
pending(threadId) {
|
|
49
|
+
return [...this.held.entries()]
|
|
50
|
+
.filter(([, entry]) => entry.threadId === threadId && !entry.answered)
|
|
51
|
+
.map(([requestId]) => requestId);
|
|
52
|
+
}
|
|
53
|
+
/** Forget a settled request once its waiter has read the answer. */
|
|
54
|
+
forget(requestId) {
|
|
55
|
+
const entry = this.held.get(requestId);
|
|
56
|
+
if (entry)
|
|
57
|
+
clearTimeout(entry.timer);
|
|
58
|
+
this.held.delete(requestId);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -41,7 +41,7 @@ export class PackageImportError extends Error {
|
|
|
41
41
|
export function orgInstallId(adminOrigin, organizationId, packageId) {
|
|
42
42
|
return createHash("sha256").update(`omb-install:v1\n${adminOrigin}\n${organizationId}\n${packageId}`, "utf8").digest("hex").slice(0, 32);
|
|
43
43
|
}
|
|
44
|
-
function memberFromAgent(agent) {
|
|
44
|
+
export function memberFromAgent(agent) {
|
|
45
45
|
return {
|
|
46
46
|
key: agent.key,
|
|
47
47
|
name: agent.name,
|
|
@@ -43,6 +43,7 @@ export function peerDeliveryReceipt(receipt) {
|
|
|
43
43
|
detail: clip(receipt.detail) || "no detail recorded",
|
|
44
44
|
...(receipt.taskId ? { taskId: receipt.taskId } : {}),
|
|
45
45
|
...(receipt.requestId ? { requestId: receipt.requestId } : {}),
|
|
46
|
+
...(receipt.threadId ? { threadId: receipt.threadId } : {}),
|
|
46
47
|
};
|
|
47
48
|
}
|
|
48
49
|
const named = (receipt) => peerName(receipt.botName ?? "") || peerName(receipt.botId) || "teammate";
|
|
@@ -31,7 +31,8 @@ export function resolveProxy(relative) {
|
|
|
31
31
|
* the smoke test can assert each one actually exists in a packaged layout —
|
|
32
32
|
* the check that would have caught the 0.1.24 breakage. */
|
|
33
33
|
export const SPAWNED_PROXIES = {
|
|
34
|
-
browser:
|
|
34
|
+
// The built-in browser and the cloud computer: argv[2] picks which.
|
|
35
|
+
harnessMcp: resolveProxy("harness-mcp-proxy"),
|
|
35
36
|
localComputer: resolveProxy("local-computer-proxy"),
|
|
36
37
|
permission: resolveProxy("permission-proxy"),
|
|
37
38
|
containerMcp: resolveProxy("container-mcp"),
|
|
@@ -225,20 +225,12 @@ export class RoomHandoffs {
|
|
|
225
225
|
if (path.some(n => n.botId === target.botId && (!n.groupId || !target.groupId || n.groupId === target.groupId))) {
|
|
226
226
|
throw new Error("Cannot assign work back to an ancestor; results return automatically");
|
|
227
227
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
228
|
+
// The same text to the same teammate and place under one parent is one
|
|
229
|
+
// request, so a repeat lands on it. Only rework=true runs a finished or
|
|
230
|
+
// failed one again.
|
|
231
|
+
const existing = this.children(parent.id).findLast(n => n.botId === target.botId && n.groupId === target.groupId && n.text === text);
|
|
232
|
+
if (existing && !(rework && terminal(existing)))
|
|
233
233
|
return { node: existing, duplicate: true };
|
|
234
|
-
}
|
|
235
|
-
if (requestBatchKey && target.groupId) {
|
|
236
|
-
const batch = this.children(parent.id).filter(n => n.requestBatchKey === requestBatchKey && n.groupId === target.groupId);
|
|
237
|
-
if (batch.some(n => n.text !== text || n.threadId !== target.threadId))
|
|
238
|
-
throw new Error("request_key was already used for different room work");
|
|
239
|
-
if (batch.some(n => n.startedAt !== undefined))
|
|
240
|
-
throw new Error("This shared room request has already started; use a new request_key for additional recipients");
|
|
241
|
-
}
|
|
242
234
|
if (!rework && this.children(parent.id).some(n => n.kind === kind &&
|
|
243
235
|
n.groupId === target.groupId && n.botId === target.botId && n.status === "completed")) {
|
|
244
236
|
throw new Error("This agent already completed your assignment. Do not send acknowledgements or approvals as new work. Finish with your decision; results return automatically. Only use rework=true for concrete additional work.");
|
|
@@ -677,7 +677,7 @@ function cardCopy(operation, manager, timeZone, now) {
|
|
|
677
677
|
const nextRunAt = nextForOperation(operation, manager, now);
|
|
678
678
|
const scheduleTimeZone = definition.schedule.type === "cron" ? definition.schedule.timeZone : timeZone;
|
|
679
679
|
const when = operation.action === "run_now" ? "Now" : scheduleText(definition.schedule, timeZone);
|
|
680
|
-
const destination = definition.runOn === "cloud" ? "Boat
|
|
680
|
+
const destination = definition.runOn === "cloud" ? "Bot’s current model on its Boat cloud computer" : "Bot’s current model and configured computer";
|
|
681
681
|
const current = operation.action === "create"
|
|
682
682
|
? null
|
|
683
683
|
: manager.listRoutines().find((routine) => routine.id === operation.routineId) ?? null;
|
|
@@ -1381,7 +1381,11 @@ export class RoutineManager {
|
|
|
1381
1381
|
if (event.denials?.length)
|
|
1382
1382
|
run.denials = [...new Set([...(run.denials ?? []), ...event.denials])];
|
|
1383
1383
|
if (!event.ok) {
|
|
1384
|
-
|
|
1384
|
+
const genericStopReason = event.stopReason === "error" || event.stopReason === "tool_error";
|
|
1385
|
+
this.failRun(run, (genericStopReason ? run.error : undefined) ??
|
|
1386
|
+
event.stopReason ??
|
|
1387
|
+
run.error ??
|
|
1388
|
+
"The bot did not complete this run");
|
|
1385
1389
|
queueMicrotask(() => void this.tick());
|
|
1386
1390
|
return cloneRun(run);
|
|
1387
1391
|
}
|
|
@@ -51,8 +51,6 @@ function buildSetupPrompt(profileAside, cwd) {
|
|
|
51
51
|
" Full Access does not supply answers, credentials, or broader permissions for another bot. A credential request still needs the user's secure entry. Report failed or cancelled changes honestly." +
|
|
52
52
|
" Finish by saying exactly what remains for the user to do by hand — authorizing an app or account (OAuth), creating a third-party application or bot token, or deciding a pending review — and point them to the Access section of the bot's settings for the app connections.");
|
|
53
53
|
}
|
|
54
|
-
/** The setup block naming skill_manage, for a turn with skill authoring on. */
|
|
55
|
-
export const SETUP_PROMPT = buildSetupPrompt(SKILL_MANAGE_ASIDE);
|
|
56
54
|
export function setupSystemPrompt(active, options) {
|
|
57
55
|
if (!active)
|
|
58
56
|
return "";
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
// prompt and recognises the slash command, so it works on every engine
|
|
7
7
|
// that mounts the agents tools.
|
|
8
8
|
import { SAVE_RUN_AS_SKILL_LINE } from "../shared/learn-request.js";
|
|
9
|
-
export const LEARN_COMMAND = "/learn";
|
|
10
9
|
export const LEARN_SOURCE_PREFIX = "learn:";
|
|
11
10
|
export const LEARN_PROMPT_MARKER = "[/learn]";
|
|
12
11
|
export const LEARN_DESCRIPTION_SOFT_MAX = 60;
|
|
@@ -106,9 +106,6 @@ export function mergeSkills(bundled, user) {
|
|
|
106
106
|
}
|
|
107
107
|
return [...byId.values()];
|
|
108
108
|
}
|
|
109
|
-
export function skillInstructionsFor(text, capabilities, skills, options) {
|
|
110
|
-
return renderSkillInstructions(selectBundledSkills(text, capabilities, skills), options);
|
|
111
|
-
}
|
|
112
109
|
export function selectBundledSkills(text, capabilities, skills) {
|
|
113
110
|
const haystack = text.toLowerCase();
|
|
114
111
|
const available = new Set(capabilities);
|
|
@@ -309,15 +306,3 @@ export function setLibrarySkillReviewState(name, reviewState, root = skillsLibra
|
|
|
309
306
|
skillLibraryEvents.emit("invalidate", { kind: "review-state", name });
|
|
310
307
|
return librarySkillListing(root, entry);
|
|
311
308
|
}
|
|
312
|
-
/** The lane's one resolution rule, stated once: a skill name means the
|
|
313
|
-
* bot-private copy when one exists, else the library entry, else the
|
|
314
|
-
* bundled skill. Later slices route every lookup through this order. */
|
|
315
|
-
export function resolveSkillSourceOrder(name, sources) {
|
|
316
|
-
if (sources.private !== undefined)
|
|
317
|
-
return { source: "private", name, value: sources.private };
|
|
318
|
-
if (sources.library !== undefined)
|
|
319
|
-
return { source: "library", name, value: sources.library };
|
|
320
|
-
if (sources.bundled !== undefined)
|
|
321
|
-
return { source: "bundled", name, value: sources.bundled };
|
|
322
|
-
return null;
|
|
323
|
-
}
|
|
@@ -1463,7 +1463,7 @@ export class Store {
|
|
|
1463
1463
|
const full = { id: newId(), at: Date.now(), ...redactBotAuthored(message), parentId: anchorId };
|
|
1464
1464
|
const children = t.messages.filter((m) => m.parentId === anchorId);
|
|
1465
1465
|
t.messages.push(full);
|
|
1466
|
-
mdb.
|
|
1466
|
+
mdb.insertMessage(threadId, full); // the leaf stays where it was, in SQLite too
|
|
1467
1467
|
if (full.kind === "screen") {
|
|
1468
1468
|
for (const pruned of this.pruneScreenFrames(t)) {
|
|
1469
1469
|
mdb.updateMessage(threadId, pruned);
|
|
@@ -2376,6 +2376,14 @@ export class Store {
|
|
|
2376
2376
|
taskByThread(botId, threadId) {
|
|
2377
2377
|
return this.bot(botId)?.tasks?.find((t) => t.threadId === threadId);
|
|
2378
2378
|
}
|
|
2379
|
+
/** The thread `fromBotId` opened on this bot from its conversation
|
|
2380
|
+
* `fromThreadId` with coordinate_bots: one per conversation and teammate,
|
|
2381
|
+
* so whatever that conversation sends later continues it. A thread the
|
|
2382
|
+
* person archived is left alone. */
|
|
2383
|
+
workThread(botId, fromBotId, fromThreadId) {
|
|
2384
|
+
return this.tasks(botId).find((t) => t.openedBy?.kind === "work" && t.openedBy.botId === fromBotId &&
|
|
2385
|
+
t.openedBy.threadId === fromThreadId && t.archivedAt === undefined);
|
|
2386
|
+
}
|
|
2379
2387
|
/** A turn gets an independent snapshot without changing the selected task
|
|
2380
2388
|
* or mutating the bot's defaults while another turn is running. */
|
|
2381
2389
|
projectBotForTask(botId, threadId) {
|