exempclaw 0.4.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/LICENSE +21 -0
- package/README.md +306 -0
- package/dist/agent/agent.d.ts +91 -0
- package/dist/agent/agent.js +258 -0
- package/dist/agent/agent.js.map +1 -0
- package/dist/agent/config.d.ts +49 -0
- package/dist/agent/config.js +58 -0
- package/dist/agent/config.js.map +1 -0
- package/dist/agent/persona.d.ts +39 -0
- package/dist/agent/persona.js +81 -0
- package/dist/agent/persona.js.map +1 -0
- package/dist/agents/registry.d.ts +21 -0
- package/dist/agents/registry.js +51 -0
- package/dist/agents/registry.js.map +1 -0
- package/dist/cli/approve.d.ts +17 -0
- package/dist/cli/approve.js +50 -0
- package/dist/cli/approve.js.map +1 -0
- package/dist/cli/chat.d.ts +16 -0
- package/dist/cli/chat.js +148 -0
- package/dist/cli/chat.js.map +1 -0
- package/dist/cli/demo.d.ts +7 -0
- package/dist/cli/demo.js +82 -0
- package/dist/cli/demo.js.map +1 -0
- package/dist/cli/init.d.ts +17 -0
- package/dist/cli/init.js +89 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/live.d.ts +10 -0
- package/dist/cli/live.js +109 -0
- package/dist/cli/live.js.map +1 -0
- package/dist/cli/offline.d.ts +23 -0
- package/dist/cli/offline.js +236 -0
- package/dist/cli/offline.js.map +1 -0
- package/dist/cli/probe.d.ts +23 -0
- package/dist/cli/probe.js +140 -0
- package/dist/cli/probe.js.map +1 -0
- package/dist/cli/render.d.ts +15 -0
- package/dist/cli/render.js +50 -0
- package/dist/cli/render.js.map +1 -0
- package/dist/cli/tui.d.ts +101 -0
- package/dist/cli/tui.js +334 -0
- package/dist/cli/tui.js.map +1 -0
- package/dist/config/index.d.ts +33 -0
- package/dist/config/index.js +48 -0
- package/dist/config/index.js.map +1 -0
- package/dist/connectors/connector.d.ts +58 -0
- package/dist/connectors/connector.js +30 -0
- package/dist/connectors/connector.js.map +1 -0
- package/dist/connectors/email/email-connector.d.ts +43 -0
- package/dist/connectors/email/email-connector.js +364 -0
- package/dist/connectors/email/email-connector.js.map +1 -0
- package/dist/connectors/github/github-connector.d.ts +52 -0
- package/dist/connectors/github/github-connector.js +271 -0
- package/dist/connectors/github/github-connector.js.map +1 -0
- package/dist/connectors/http.d.ts +34 -0
- package/dist/connectors/http.js +78 -0
- package/dist/connectors/http.js.map +1 -0
- package/dist/connectors/index.d.ts +34 -0
- package/dist/connectors/index.js +86 -0
- package/dist/connectors/index.js.map +1 -0
- package/dist/connectors/notion/notion-connector.d.ts +45 -0
- package/dist/connectors/notion/notion-connector.js +222 -0
- package/dist/connectors/notion/notion-connector.js.map +1 -0
- package/dist/connectors/slack/slack-connector.d.ts +43 -0
- package/dist/connectors/slack/slack-connector.js +291 -0
- package/dist/connectors/slack/slack-connector.js.map +1 -0
- package/dist/core/errors.d.ts +36 -0
- package/dist/core/errors.js +40 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/logger.d.ts +14 -0
- package/dist/core/logger.js +44 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/run-log.d.ts +37 -0
- package/dist/core/run-log.js +37 -0
- package/dist/core/run-log.js.map +1 -0
- package/dist/core/usage.d.ts +22 -0
- package/dist/core/usage.js +58 -0
- package/dist/core/usage.js.map +1 -0
- package/dist/dashboard/data.d.ts +62 -0
- package/dist/dashboard/data.js +84 -0
- package/dist/dashboard/data.js.map +1 -0
- package/dist/dashboard/page.d.ts +9 -0
- package/dist/dashboard/page.js +421 -0
- package/dist/dashboard/page.js.map +1 -0
- package/dist/dashboard/server.d.ts +19 -0
- package/dist/dashboard/server.js +44 -0
- package/dist/dashboard/server.js.map +1 -0
- package/dist/demo/bootstrap.d.ts +25 -0
- package/dist/demo/bootstrap.js +60 -0
- package/dist/demo/bootstrap.js.map +1 -0
- package/dist/demo/claude.d.ts +31 -0
- package/dist/demo/claude.js +230 -0
- package/dist/demo/claude.js.map +1 -0
- package/dist/demo/demo-connector.d.ts +19 -0
- package/dist/demo/demo-connector.js +168 -0
- package/dist/demo/demo-connector.js.map +1 -0
- package/dist/demo/world.d.ts +60 -0
- package/dist/demo/world.js +117 -0
- package/dist/demo/world.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +396 -0
- package/dist/index.js.map +1 -0
- package/dist/ingest/ingest.d.ts +63 -0
- package/dist/ingest/ingest.js +258 -0
- package/dist/ingest/ingest.js.map +1 -0
- package/dist/llm/claude.d.ts +97 -0
- package/dist/llm/claude.js +163 -0
- package/dist/llm/claude.js.map +1 -0
- package/dist/memory/compaction.d.ts +22 -0
- package/dist/memory/compaction.js +79 -0
- package/dist/memory/compaction.js.map +1 -0
- package/dist/memory/file-store.d.ts +28 -0
- package/dist/memory/file-store.js +110 -0
- package/dist/memory/file-store.js.map +1 -0
- package/dist/memory/store.d.ts +32 -0
- package/dist/memory/store.js +2 -0
- package/dist/memory/store.js.map +1 -0
- package/dist/orchestrator/orchestrator.d.ts +63 -0
- package/dist/orchestrator/orchestrator.js +181 -0
- package/dist/orchestrator/orchestrator.js.map +1 -0
- package/dist/orchestrator/scheduler.d.ts +33 -0
- package/dist/orchestrator/scheduler.js +67 -0
- package/dist/orchestrator/scheduler.js.map +1 -0
- package/dist/orchestrator/seen-events.d.ts +21 -0
- package/dist/orchestrator/seen-events.js +71 -0
- package/dist/orchestrator/seen-events.js.map +1 -0
- package/dist/plugins/apply.d.ts +9 -0
- package/dist/plugins/apply.js +17 -0
- package/dist/plugins/apply.js.map +1 -0
- package/dist/plugins/define.d.ts +29 -0
- package/dist/plugins/define.js +30 -0
- package/dist/plugins/define.js.map +1 -0
- package/dist/plugins/loader.d.ts +31 -0
- package/dist/plugins/loader.js +61 -0
- package/dist/plugins/loader.js.map +1 -0
- package/dist/plugins/scaffold.d.ts +5 -0
- package/dist/plugins/scaffold.js +72 -0
- package/dist/plugins/scaffold.js.map +1 -0
- package/dist/tools/builtin.d.ts +8 -0
- package/dist/tools/builtin.js +63 -0
- package/dist/tools/builtin.js.map +1 -0
- package/dist/tools/tool.d.ts +84 -0
- package/dist/tools/tool.js +70 -0
- package/dist/tools/tool.js.map +1 -0
- package/dist/ui/agent-view.test.d.ts +1 -0
- package/dist/ui/agent-view.test.js +54 -0
- package/dist/ui/agent-view.test.js.map +1 -0
- package/dist/ui/agents-data.d.ts +7 -0
- package/dist/ui/agents-data.js +25 -0
- package/dist/ui/agents-data.js.map +1 -0
- package/dist/ui/app.d.ts +24 -0
- package/dist/ui/app.js +59 -0
- package/dist/ui/app.js.map +1 -0
- package/dist/ui/app.test.d.ts +1 -0
- package/dist/ui/app.test.js +47 -0
- package/dist/ui/app.test.js.map +1 -0
- package/dist/ui/components/key-hints.d.ts +4 -0
- package/dist/ui/components/key-hints.js +6 -0
- package/dist/ui/components/key-hints.js.map +1 -0
- package/dist/ui/components/menu.d.ts +11 -0
- package/dist/ui/components/menu.js +20 -0
- package/dist/ui/components/menu.js.map +1 -0
- package/dist/ui/create-wizard.test.d.ts +1 -0
- package/dist/ui/create-wizard.test.js +58 -0
- package/dist/ui/create-wizard.test.js.map +1 -0
- package/dist/ui/doctor-data.d.ts +6 -0
- package/dist/ui/doctor-data.js +29 -0
- package/dist/ui/doctor-data.js.map +1 -0
- package/dist/ui/history-data.d.ts +2 -0
- package/dist/ui/history-data.js +18 -0
- package/dist/ui/history-data.js.map +1 -0
- package/dist/ui/screens/agent.d.ts +8 -0
- package/dist/ui/screens/agent.js +95 -0
- package/dist/ui/screens/agent.js.map +1 -0
- package/dist/ui/screens/agents.d.ts +7 -0
- package/dist/ui/screens/agents.js +47 -0
- package/dist/ui/screens/agents.js.map +1 -0
- package/dist/ui/screens/create.d.ts +7 -0
- package/dist/ui/screens/create.js +141 -0
- package/dist/ui/screens/create.js.map +1 -0
- package/dist/ui/screens/doctor.d.ts +5 -0
- package/dist/ui/screens/doctor.js +13 -0
- package/dist/ui/screens/doctor.js.map +1 -0
- package/dist/ui/screens/history.d.ts +7 -0
- package/dist/ui/screens/history.js +50 -0
- package/dist/ui/screens/history.js.map +1 -0
- package/dist/ui/screens/home.d.ts +8 -0
- package/dist/ui/screens/home.js +35 -0
- package/dist/ui/screens/home.js.map +1 -0
- package/dist/ui/screens/plugins.d.ts +7 -0
- package/dist/ui/screens/plugins.js +40 -0
- package/dist/ui/screens/plugins.js.map +1 -0
- package/dist/ui/services.d.ts +33 -0
- package/dist/ui/services.js +67 -0
- package/dist/ui/services.js.map +1 -0
- package/dist/ui/start.d.ts +1 -0
- package/dist/ui/start.js +16 -0
- package/dist/ui/start.js.map +1 -0
- package/dist/ui/theme.d.ts +6 -0
- package/dist/ui/theme.js +26 -0
- package/dist/ui/theme.js.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type Anthropic from "@anthropic-ai/sdk";
|
|
2
|
+
import type { MemoryEntry, MemoryStore } from "./store.js";
|
|
3
|
+
/**
|
|
4
|
+
* Default MemoryStore backed by JSON files on disk. Simple, dependency-free,
|
|
5
|
+
* and good enough to run real agents; swap for SQLite + a vector index when
|
|
6
|
+
* retrieval needs to scale. One directory per agent under <dataDir>/agents/<id>.
|
|
7
|
+
*
|
|
8
|
+
* Writes go through a temp file + rename so a crash mid-write can't corrupt
|
|
9
|
+
* the store. Mutations are serialized behind a per-store queue so concurrent
|
|
10
|
+
* tool calls can't interleave read-modify-write cycles.
|
|
11
|
+
*/
|
|
12
|
+
export declare class FileMemoryStore implements MemoryStore {
|
|
13
|
+
private readonly memoryPath;
|
|
14
|
+
private readonly historyPath;
|
|
15
|
+
private writeQueue;
|
|
16
|
+
constructor(dataDir: string, agentId: string);
|
|
17
|
+
private readJson;
|
|
18
|
+
private writeJsonAtomic;
|
|
19
|
+
/** Serializes a mutation behind the store's write queue. */
|
|
20
|
+
private enqueue;
|
|
21
|
+
addMemory(entry: Omit<MemoryEntry, "id" | "createdAt">): Promise<MemoryEntry>;
|
|
22
|
+
removeMemory(id: string): Promise<boolean>;
|
|
23
|
+
searchMemory(query: string, limit?: number): Promise<MemoryEntry[]>;
|
|
24
|
+
allMemories(): Promise<MemoryEntry[]>;
|
|
25
|
+
loadHistory(): Promise<Anthropic.MessageParam[]>;
|
|
26
|
+
saveHistory(messages: Anthropic.MessageParam[]): Promise<void>;
|
|
27
|
+
clearHistory(): Promise<void>;
|
|
28
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* Default MemoryStore backed by JSON files on disk. Simple, dependency-free,
|
|
6
|
+
* and good enough to run real agents; swap for SQLite + a vector index when
|
|
7
|
+
* retrieval needs to scale. One directory per agent under <dataDir>/agents/<id>.
|
|
8
|
+
*
|
|
9
|
+
* Writes go through a temp file + rename so a crash mid-write can't corrupt
|
|
10
|
+
* the store. Mutations are serialized behind a per-store queue so concurrent
|
|
11
|
+
* tool calls can't interleave read-modify-write cycles.
|
|
12
|
+
*/
|
|
13
|
+
export class FileMemoryStore {
|
|
14
|
+
memoryPath;
|
|
15
|
+
historyPath;
|
|
16
|
+
writeQueue = Promise.resolve();
|
|
17
|
+
constructor(dataDir, agentId) {
|
|
18
|
+
const base = join(dataDir, "agents", agentId);
|
|
19
|
+
this.memoryPath = join(base, "memory.json");
|
|
20
|
+
this.historyPath = join(base, "history.json");
|
|
21
|
+
}
|
|
22
|
+
async readJson(path, fallback) {
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
if (err.code === "ENOENT")
|
|
28
|
+
return fallback;
|
|
29
|
+
throw err;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async writeJsonAtomic(path, value) {
|
|
33
|
+
await mkdir(dirname(path), { recursive: true });
|
|
34
|
+
const tmp = `${path}.${randomUUID().slice(0, 8)}.tmp`;
|
|
35
|
+
await writeFile(tmp, JSON.stringify(value, null, 2), "utf8");
|
|
36
|
+
await rename(tmp, path);
|
|
37
|
+
}
|
|
38
|
+
/** Serializes a mutation behind the store's write queue. */
|
|
39
|
+
enqueue(op) {
|
|
40
|
+
const next = this.writeQueue.catch(() => undefined).then(op);
|
|
41
|
+
this.writeQueue = next;
|
|
42
|
+
return next;
|
|
43
|
+
}
|
|
44
|
+
addMemory(entry) {
|
|
45
|
+
return this.enqueue(async () => {
|
|
46
|
+
const memories = await this.allMemories();
|
|
47
|
+
const full = { ...entry, id: randomUUID(), createdAt: new Date().toISOString() };
|
|
48
|
+
memories.push(full);
|
|
49
|
+
await this.writeJsonAtomic(this.memoryPath, memories);
|
|
50
|
+
return full;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
removeMemory(id) {
|
|
54
|
+
return this.enqueue(async () => {
|
|
55
|
+
const memories = await this.allMemories();
|
|
56
|
+
const remaining = memories.filter((m) => m.id !== id && !m.id.startsWith(id));
|
|
57
|
+
if (remaining.length === memories.length)
|
|
58
|
+
return false;
|
|
59
|
+
await this.writeJsonAtomic(this.memoryPath, remaining);
|
|
60
|
+
return true;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
async searchMemory(query, limit = 10) {
|
|
64
|
+
const memories = await this.allMemories();
|
|
65
|
+
const terms = [...new Set(query.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((t) => t.length > 1))];
|
|
66
|
+
if (terms.length === 0)
|
|
67
|
+
return memories.slice(-limit).reverse();
|
|
68
|
+
// Term-frequency scoring with a tag/source boost and recency tiebreak.
|
|
69
|
+
const scored = memories.map((m) => {
|
|
70
|
+
const text = m.text.toLowerCase();
|
|
71
|
+
const meta = `${m.tags.join(" ")} ${m.source}`.toLowerCase();
|
|
72
|
+
let score = 0;
|
|
73
|
+
for (const term of terms) {
|
|
74
|
+
score += countOccurrences(text, term);
|
|
75
|
+
if (meta.includes(term))
|
|
76
|
+
score += 2;
|
|
77
|
+
}
|
|
78
|
+
return { m, score };
|
|
79
|
+
});
|
|
80
|
+
return scored
|
|
81
|
+
.filter((s) => s.score > 0)
|
|
82
|
+
.sort((a, b) => b.score - a.score || b.m.createdAt.localeCompare(a.m.createdAt))
|
|
83
|
+
.slice(0, limit)
|
|
84
|
+
.map((s) => s.m);
|
|
85
|
+
}
|
|
86
|
+
allMemories() {
|
|
87
|
+
return this.readJson(this.memoryPath, []);
|
|
88
|
+
}
|
|
89
|
+
loadHistory() {
|
|
90
|
+
return this.readJson(this.historyPath, []);
|
|
91
|
+
}
|
|
92
|
+
saveHistory(messages) {
|
|
93
|
+
return this.enqueue(() => this.writeJsonAtomic(this.historyPath, messages));
|
|
94
|
+
}
|
|
95
|
+
clearHistory() {
|
|
96
|
+
return this.enqueue(async () => {
|
|
97
|
+
await rm(this.historyPath, { force: true });
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function countOccurrences(haystack, needle) {
|
|
102
|
+
let count = 0;
|
|
103
|
+
let idx = haystack.indexOf(needle);
|
|
104
|
+
while (idx !== -1) {
|
|
105
|
+
count++;
|
|
106
|
+
idx = haystack.indexOf(needle, idx + needle.length);
|
|
107
|
+
}
|
|
108
|
+
return count;
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=file-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-store.js","sourceRoot":"","sources":["../../src/memory/file-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAI1C;;;;;;;;GAQG;AACH,MAAM,OAAO,eAAe;IACT,UAAU,CAAS;IACnB,WAAW,CAAS;IAC7B,UAAU,GAAqB,OAAO,CAAC,OAAO,EAAE,CAAC;IAEzD,YAAY,OAAe,EAAE,OAAe;QAC1C,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC9C,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;QAC5C,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;IAChD,CAAC;IAEO,KAAK,CAAC,QAAQ,CAAI,IAAY,EAAE,QAAW;QACjD,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAM,CAAC;QACvD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;gBAAE,OAAO,QAAQ,CAAC;YACtE,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,eAAe,CAAC,IAAY,EAAE,KAAc;QACxD,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAChD,MAAM,GAAG,GAAG,GAAG,IAAI,IAAI,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC;QACtD,MAAM,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QAC7D,MAAM,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,4DAA4D;IACpD,OAAO,CAAI,EAAoB;QACrC,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC7D,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QACvB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,SAAS,CAAC,KAA4C;QACpD,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE;YAC7B,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;YAC1C,MAAM,IAAI,GAAgB,EAAE,GAAG,KAAK,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;YAC9F,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACpB,MAAM,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;YACtD,OAAO,IAAI,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC;IAED,YAAY,CAAC,EAAU;QACrB,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE;YAC7B,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;YAC1C,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC;YAC9E,IAAI,SAAS,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM;gBAAE,OAAO,KAAK,CAAC;YACvD,MAAM,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;YACvD,OAAO,IAAI,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,KAAa,EAAE,KAAK,GAAG,EAAE;QAC1C,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;QAC1C,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACrG,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;QAEhE,uEAAuE;QACvE,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YAChC,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YAClC,MAAM,IAAI,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,WAAW,EAAE,CAAC;YAC7D,IAAI,KAAK,GAAG,CAAC,CAAC;YACd,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,KAAK,IAAI,gBAAgB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;gBACtC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;oBAAE,KAAK,IAAI,CAAC,CAAC;YACtC,CAAC;YACD,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC;QACtB,CAAC,CAAC,CAAC;QACH,OAAO,MAAM;aACV,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC;aAC1B,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;aAC/E,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC;aACf,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACrB,CAAC;IAED,WAAW;QACT,OAAO,IAAI,CAAC,QAAQ,CAAgB,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED,WAAW;QACT,OAAO,IAAI,CAAC,QAAQ,CAA2B,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IACvE,CAAC;IAED,WAAW,CAAC,QAAkC;QAC5C,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC9E,CAAC;IAED,YAAY;QACV,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE;YAC7B,MAAM,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9C,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AAED,SAAS,gBAAgB,CAAC,QAAgB,EAAE,MAAc;IACxD,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACnC,OAAO,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC;QAClB,KAAK,EAAE,CAAC;QACR,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type Anthropic from "@anthropic-ai/sdk";
|
|
2
|
+
/**
|
|
3
|
+
* A single durable memory: a fact, lesson, or piece of context the agent
|
|
4
|
+
* accumulated. Kept deliberately small and atomic so retrieval can be selective.
|
|
5
|
+
*/
|
|
6
|
+
export interface MemoryEntry {
|
|
7
|
+
id: string;
|
|
8
|
+
/** Where it came from: "email", "slack", "onboarding", "self", … */
|
|
9
|
+
source: string;
|
|
10
|
+
/** Free-text content of the memory. */
|
|
11
|
+
text: string;
|
|
12
|
+
/** Optional tags for retrieval/filtering. */
|
|
13
|
+
tags: string[];
|
|
14
|
+
createdAt: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Persistence boundary for a single agent's state. The agent runtime depends on
|
|
18
|
+
* this interface only — back it with files (default), SQLite, Postgres, or a
|
|
19
|
+
* vector DB without touching the runtime.
|
|
20
|
+
*/
|
|
21
|
+
export interface MemoryStore {
|
|
22
|
+
/** Durable knowledge / context. */
|
|
23
|
+
addMemory(entry: Omit<MemoryEntry, "id" | "createdAt">): Promise<MemoryEntry>;
|
|
24
|
+
/** Keyword retrieval for the default store; replace with embeddings later. */
|
|
25
|
+
searchMemory(query: string, limit?: number): Promise<MemoryEntry[]>;
|
|
26
|
+
allMemories(): Promise<MemoryEntry[]>;
|
|
27
|
+
removeMemory(id: string): Promise<boolean>;
|
|
28
|
+
/** Conversation/episodic history — the running message transcript. */
|
|
29
|
+
loadHistory(): Promise<Anthropic.MessageParam[]>;
|
|
30
|
+
saveHistory(messages: Anthropic.MessageParam[]): Promise<void>;
|
|
31
|
+
clearHistory(): Promise<void>;
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/memory/store.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { RuntimeConfig } from "../config/index.js";
|
|
2
|
+
import type { Logger } from "../core/logger.js";
|
|
3
|
+
import { type ClaudeLike } from "../llm/claude.js";
|
|
4
|
+
import { RunLog } from "../core/run-log.js";
|
|
5
|
+
import { Agent, type RunHooks, type RunResult } from "../agent/agent.js";
|
|
6
|
+
import type { AgentConfig } from "../agent/config.js";
|
|
7
|
+
import type { MemoryStore } from "../memory/store.js";
|
|
8
|
+
import { type ApprovalRequest, type Tool } from "../tools/tool.js";
|
|
9
|
+
import { type InboundEvent } from "../connectors/index.js";
|
|
10
|
+
import type { TriggerKind } from "../core/run-log.js";
|
|
11
|
+
export interface DispatchOptions {
|
|
12
|
+
hooks?: RunHooks;
|
|
13
|
+
trigger?: {
|
|
14
|
+
kind: TriggerKind;
|
|
15
|
+
detail?: string;
|
|
16
|
+
};
|
|
17
|
+
/** Per-run cancellation (combined with the orchestrator's shutdown signal). */
|
|
18
|
+
signal?: AbortSignal;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Runs and supervises many agents at once. Responsibilities:
|
|
22
|
+
* - build each agent's Claude client, tool registry (builtins + connector tools),
|
|
23
|
+
* memory store, run log, and dedup state;
|
|
24
|
+
* - initialize the agent's connectors;
|
|
25
|
+
* - route inbound connector events to the owning agent's run loop (with
|
|
26
|
+
* at-least-once dedup persisted across restarts);
|
|
27
|
+
* - fire the agent's schedules;
|
|
28
|
+
* - serialize each agent's runs so a single agent never overlaps itself.
|
|
29
|
+
*/
|
|
30
|
+
export declare class Orchestrator {
|
|
31
|
+
private readonly config;
|
|
32
|
+
private readonly log;
|
|
33
|
+
private readonly approve;
|
|
34
|
+
private readonly claude;
|
|
35
|
+
private readonly managed;
|
|
36
|
+
private readonly queues;
|
|
37
|
+
private readonly abort;
|
|
38
|
+
private readonly extraTools;
|
|
39
|
+
/** Optional observer for inbound events that pass dedup (CLI flash lines). */
|
|
40
|
+
onInboundEvent?: (agentId: string, event: InboundEvent) => void;
|
|
41
|
+
constructor(config: RuntimeConfig, log: Logger, approve: (req: ApprovalRequest) => Promise<boolean>, opts?: {
|
|
42
|
+
claude?: ClaudeLike;
|
|
43
|
+
extraTools?: Tool[];
|
|
44
|
+
});
|
|
45
|
+
/** Builds an agent from its config and registers it (does not start listening). */
|
|
46
|
+
addAgent(cfg: AgentConfig): Promise<Agent>;
|
|
47
|
+
/** Feeds one input to an agent, serialized behind that agent's queue. */
|
|
48
|
+
dispatch(agentId: string, input: string, options?: DispatchOptions): Promise<RunResult>;
|
|
49
|
+
/** Direct access to an agent's stores, for the memory/history/costs CLI. */
|
|
50
|
+
resources(agentId: string): {
|
|
51
|
+
memory: MemoryStore;
|
|
52
|
+
runLog: RunLog;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Starts every agent's connectors listening, arms schedules, and routes
|
|
56
|
+
* inbound events to the owning agent. Resolves after shutdown() aborts and
|
|
57
|
+
* listeners have settled.
|
|
58
|
+
*/
|
|
59
|
+
start(): Promise<void>;
|
|
60
|
+
private handleEvent;
|
|
61
|
+
shutdown(): Promise<void>;
|
|
62
|
+
agentIds(): string[];
|
|
63
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { ClaudeClient } from "../llm/claude.js";
|
|
2
|
+
import { RunLog } from "../core/run-log.js";
|
|
3
|
+
import { Agent } from "../agent/agent.js";
|
|
4
|
+
import { FileMemoryStore } from "../memory/file-store.js";
|
|
5
|
+
import { ToolRegistry } from "../tools/tool.js";
|
|
6
|
+
import { builtinTools } from "../tools/builtin.js";
|
|
7
|
+
import { createConnector, renderEventInput, } from "../connectors/index.js";
|
|
8
|
+
import { abortedPromise } from "../connectors/connector.js";
|
|
9
|
+
import { Scheduler } from "./scheduler.js";
|
|
10
|
+
import { SeenEvents } from "./seen-events.js";
|
|
11
|
+
/**
|
|
12
|
+
* Runs and supervises many agents at once. Responsibilities:
|
|
13
|
+
* - build each agent's Claude client, tool registry (builtins + connector tools),
|
|
14
|
+
* memory store, run log, and dedup state;
|
|
15
|
+
* - initialize the agent's connectors;
|
|
16
|
+
* - route inbound connector events to the owning agent's run loop (with
|
|
17
|
+
* at-least-once dedup persisted across restarts);
|
|
18
|
+
* - fire the agent's schedules;
|
|
19
|
+
* - serialize each agent's runs so a single agent never overlaps itself.
|
|
20
|
+
*/
|
|
21
|
+
export class Orchestrator {
|
|
22
|
+
config;
|
|
23
|
+
log;
|
|
24
|
+
approve;
|
|
25
|
+
claude;
|
|
26
|
+
managed = new Map();
|
|
27
|
+
queues = new Map();
|
|
28
|
+
abort = new AbortController();
|
|
29
|
+
extraTools;
|
|
30
|
+
/** Optional observer for inbound events that pass dedup (CLI flash lines). */
|
|
31
|
+
onInboundEvent;
|
|
32
|
+
constructor(config, log, approve, opts = {}) {
|
|
33
|
+
this.config = config;
|
|
34
|
+
this.log = log;
|
|
35
|
+
this.approve = approve;
|
|
36
|
+
// Demo mode injects a scripted brain; everything else is identical.
|
|
37
|
+
this.claude = opts.claude ?? new ClaudeClient(config.anthropicApiKey, config.defaultModel, log);
|
|
38
|
+
this.extraTools = opts.extraTools ?? [];
|
|
39
|
+
}
|
|
40
|
+
/** Builds an agent from its config and registers it (does not start listening). */
|
|
41
|
+
async addAgent(cfg) {
|
|
42
|
+
if (this.managed.has(cfg.id)) {
|
|
43
|
+
throw new Error(`agent "${cfg.id}" already added`);
|
|
44
|
+
}
|
|
45
|
+
const log = this.log.child({ scope: "orchestrator", agentId: cfg.id });
|
|
46
|
+
const memory = new FileMemoryStore(this.config.dataDir, cfg.id);
|
|
47
|
+
const runLog = new RunLog(this.config.dataDir, cfg.id);
|
|
48
|
+
const seen = new SeenEvents(this.config.dataDir, cfg.id);
|
|
49
|
+
const tools = new ToolRegistry();
|
|
50
|
+
for (const t of builtinTools(memory))
|
|
51
|
+
tools.register(t);
|
|
52
|
+
for (const t of this.extraTools)
|
|
53
|
+
tools.register(t);
|
|
54
|
+
const connectors = [];
|
|
55
|
+
for (const connectorId of cfg.connectors) {
|
|
56
|
+
const { connector, config } = createConnector(connectorId);
|
|
57
|
+
await connector.init({ log, config });
|
|
58
|
+
for (const t of connector.tools())
|
|
59
|
+
tools.register(t);
|
|
60
|
+
connectors.push(connector);
|
|
61
|
+
}
|
|
62
|
+
const agent = new Agent({
|
|
63
|
+
id: cfg.id,
|
|
64
|
+
persona: cfg.persona,
|
|
65
|
+
model: cfg.model ?? this.config.defaultModel,
|
|
66
|
+
effort: cfg.effort,
|
|
67
|
+
maxIterations: cfg.maxIterations,
|
|
68
|
+
toolPolicies: cfg.toolPolicies,
|
|
69
|
+
contextBudgetTokens: this.config.contextBudgetTokens,
|
|
70
|
+
}, {
|
|
71
|
+
claude: this.claude,
|
|
72
|
+
tools,
|
|
73
|
+
memory,
|
|
74
|
+
log,
|
|
75
|
+
actionPolicy: this.config.actionPolicy,
|
|
76
|
+
approve: this.approve,
|
|
77
|
+
runLog,
|
|
78
|
+
});
|
|
79
|
+
this.managed.set(cfg.id, { agent, config: cfg, connectors, memory, runLog, seen });
|
|
80
|
+
log.info("agent ready", {
|
|
81
|
+
persona: cfg.persona.name,
|
|
82
|
+
tools: tools.size,
|
|
83
|
+
connectors: cfg.connectors.join(",") || "none",
|
|
84
|
+
schedules: cfg.schedules.length,
|
|
85
|
+
});
|
|
86
|
+
return agent;
|
|
87
|
+
}
|
|
88
|
+
/** Feeds one input to an agent, serialized behind that agent's queue. */
|
|
89
|
+
async dispatch(agentId, input, options = {}) {
|
|
90
|
+
const managed = this.managed.get(agentId);
|
|
91
|
+
if (!managed)
|
|
92
|
+
throw new Error(`no such agent: ${agentId}`);
|
|
93
|
+
const signal = options.signal ? AbortSignal.any([this.abort.signal, options.signal]) : this.abort.signal;
|
|
94
|
+
const prior = this.queues.get(agentId) ?? Promise.resolve();
|
|
95
|
+
const next = prior
|
|
96
|
+
.catch(() => undefined) // a failed prior run must not poison the queue
|
|
97
|
+
.then(() => managed.agent.run(input, {
|
|
98
|
+
signal,
|
|
99
|
+
hooks: options.hooks,
|
|
100
|
+
trigger: options.trigger,
|
|
101
|
+
}));
|
|
102
|
+
this.queues.set(agentId, next);
|
|
103
|
+
return next;
|
|
104
|
+
}
|
|
105
|
+
/** Direct access to an agent's stores, for the memory/history/costs CLI. */
|
|
106
|
+
resources(agentId) {
|
|
107
|
+
const managed = this.managed.get(agentId);
|
|
108
|
+
if (!managed)
|
|
109
|
+
throw new Error(`no such agent: ${agentId}`);
|
|
110
|
+
return { memory: managed.memory, runLog: managed.runLog };
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Starts every agent's connectors listening, arms schedules, and routes
|
|
114
|
+
* inbound events to the owning agent. Resolves after shutdown() aborts and
|
|
115
|
+
* listeners have settled.
|
|
116
|
+
*/
|
|
117
|
+
async start() {
|
|
118
|
+
const listeners = [];
|
|
119
|
+
let listenerCount = 0;
|
|
120
|
+
for (const [agentId, managed] of this.managed) {
|
|
121
|
+
// Connector listeners → dedup → agent queue.
|
|
122
|
+
for (const connector of managed.connectors) {
|
|
123
|
+
if (!connector.listen)
|
|
124
|
+
continue;
|
|
125
|
+
listenerCount++;
|
|
126
|
+
const onEvent = (event) => void this.handleEvent(agentId, managed, event);
|
|
127
|
+
listeners.push(connector.listen(onEvent, this.abort.signal).catch((err) => {
|
|
128
|
+
this.log.error("connector listener crashed", {
|
|
129
|
+
agentId,
|
|
130
|
+
connector: connector.id,
|
|
131
|
+
error: err.message,
|
|
132
|
+
});
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
// Schedules → agent queue.
|
|
136
|
+
if (managed.config.schedules.length > 0) {
|
|
137
|
+
managed.scheduler = new Scheduler(managed.config.schedules, (schedule) => {
|
|
138
|
+
const detail = schedule.every ? `every ${schedule.every}` : `daily at ${schedule.dailyAt}`;
|
|
139
|
+
this.log.info("schedule fired", { agentId, detail });
|
|
140
|
+
void this.dispatch(agentId, schedule.input, { trigger: { kind: "schedule", detail } }).catch((err) => this.log.error("scheduled run failed", { agentId, error: err.message }));
|
|
141
|
+
});
|
|
142
|
+
managed.scheduler.start();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
this.log.info("orchestrator started", { agents: this.managed.size, listeners: listenerCount });
|
|
146
|
+
await abortedPromise(this.abort.signal);
|
|
147
|
+
await Promise.allSettled(listeners);
|
|
148
|
+
}
|
|
149
|
+
async handleEvent(agentId, managed, event) {
|
|
150
|
+
try {
|
|
151
|
+
const fresh = await managed.seen.markSeen(`${event.connector}:${event.eventId}`);
|
|
152
|
+
if (!fresh) {
|
|
153
|
+
this.log.debug("duplicate event dropped", { agentId, eventId: event.eventId });
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
this.log.info("inbound event", { agentId, connector: event.connector, type: event.type });
|
|
157
|
+
this.onInboundEvent?.(agentId, event);
|
|
158
|
+
await this.dispatch(agentId, renderEventInput(event), {
|
|
159
|
+
trigger: { kind: "event", detail: `${event.type} ${event.threadId}` },
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
this.log.error("event dispatch failed", { agentId, error: err.message });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
async shutdown() {
|
|
167
|
+
this.abort.abort();
|
|
168
|
+
for (const managed of this.managed.values()) {
|
|
169
|
+
managed.scheduler?.stop();
|
|
170
|
+
for (const connector of managed.connectors) {
|
|
171
|
+
await connector.shutdown?.().catch(() => undefined);
|
|
172
|
+
}
|
|
173
|
+
await managed.seen.persist().catch(() => undefined);
|
|
174
|
+
}
|
|
175
|
+
this.log.info("orchestrator stopped");
|
|
176
|
+
}
|
|
177
|
+
agentIds() {
|
|
178
|
+
return [...this.managed.keys()];
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=orchestrator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orchestrator.js","sourceRoot":"","sources":["../../src/orchestrator/orchestrator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Schedule } from "../agent/config.js";
|
|
2
|
+
/**
|
|
3
|
+
* Fires an agent's scheduled inputs. Two forms:
|
|
4
|
+
* - { every: "15m" } — fixed interval from start
|
|
5
|
+
* - { dailyAt: "09:00" } — local wall-clock time, once a day
|
|
6
|
+
*
|
|
7
|
+
* The clock is injectable so tests can drive it deterministically.
|
|
8
|
+
*/
|
|
9
|
+
export interface SchedulerClock {
|
|
10
|
+
now(): number;
|
|
11
|
+
setTimeout(fn: () => void, ms: number): NodeJS.Timeout | number;
|
|
12
|
+
clearTimeout(handle: NodeJS.Timeout | number): void;
|
|
13
|
+
}
|
|
14
|
+
export declare function parseDuration(spec: string): number;
|
|
15
|
+
/** Milliseconds from `now` until the next local occurrence of HH:MM. */
|
|
16
|
+
export declare function msUntilDailyAt(timeSpec: string, now: number): number;
|
|
17
|
+
export declare function msUntilNext(schedule: Schedule, now: number): number;
|
|
18
|
+
/**
|
|
19
|
+
* Runs one agent's schedules until stopped. Fires are delivered through
|
|
20
|
+
* `onFire`; overlapping work is the dispatcher's problem (the orchestrator
|
|
21
|
+
* serializes per-agent runs).
|
|
22
|
+
*/
|
|
23
|
+
export declare class Scheduler {
|
|
24
|
+
private readonly schedules;
|
|
25
|
+
private readonly onFire;
|
|
26
|
+
private readonly clock;
|
|
27
|
+
private readonly handles;
|
|
28
|
+
private stopped;
|
|
29
|
+
constructor(schedules: Schedule[], onFire: (schedule: Schedule) => void, clock?: SchedulerClock);
|
|
30
|
+
start(): void;
|
|
31
|
+
private arm;
|
|
32
|
+
stop(): void;
|
|
33
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const realClock = {
|
|
2
|
+
now: () => Date.now(),
|
|
3
|
+
setTimeout: (fn, ms) => setTimeout(fn, ms),
|
|
4
|
+
clearTimeout: (handle) => clearTimeout(handle),
|
|
5
|
+
};
|
|
6
|
+
export function parseDuration(spec) {
|
|
7
|
+
const match = /^(\d+)(s|m|h|d)$/.exec(spec);
|
|
8
|
+
if (!match)
|
|
9
|
+
throw new Error(`invalid duration "${spec}" (use e.g. "30s", "15m", "2h", "1d")`);
|
|
10
|
+
const value = Number(match[1]);
|
|
11
|
+
const unit = match[2];
|
|
12
|
+
return value * { s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 }[unit];
|
|
13
|
+
}
|
|
14
|
+
/** Milliseconds from `now` until the next local occurrence of HH:MM. */
|
|
15
|
+
export function msUntilDailyAt(timeSpec, now) {
|
|
16
|
+
const [hours, minutes] = timeSpec.split(":").map(Number);
|
|
17
|
+
const next = new Date(now);
|
|
18
|
+
next.setHours(hours, minutes, 0, 0);
|
|
19
|
+
if (next.getTime() <= now)
|
|
20
|
+
next.setDate(next.getDate() + 1);
|
|
21
|
+
return next.getTime() - now;
|
|
22
|
+
}
|
|
23
|
+
export function msUntilNext(schedule, now) {
|
|
24
|
+
if (schedule.every)
|
|
25
|
+
return parseDuration(schedule.every);
|
|
26
|
+
return msUntilDailyAt(schedule.dailyAt, now);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Runs one agent's schedules until stopped. Fires are delivered through
|
|
30
|
+
* `onFire`; overlapping work is the dispatcher's problem (the orchestrator
|
|
31
|
+
* serializes per-agent runs).
|
|
32
|
+
*/
|
|
33
|
+
export class Scheduler {
|
|
34
|
+
schedules;
|
|
35
|
+
onFire;
|
|
36
|
+
clock;
|
|
37
|
+
handles = [];
|
|
38
|
+
stopped = false;
|
|
39
|
+
constructor(schedules, onFire, clock = realClock) {
|
|
40
|
+
this.schedules = schedules;
|
|
41
|
+
this.onFire = onFire;
|
|
42
|
+
this.clock = clock;
|
|
43
|
+
}
|
|
44
|
+
start() {
|
|
45
|
+
for (const schedule of this.schedules)
|
|
46
|
+
this.arm(schedule);
|
|
47
|
+
}
|
|
48
|
+
arm(schedule) {
|
|
49
|
+
if (this.stopped)
|
|
50
|
+
return;
|
|
51
|
+
const delay = msUntilNext(schedule, this.clock.now());
|
|
52
|
+
const handle = this.clock.setTimeout(() => {
|
|
53
|
+
if (this.stopped)
|
|
54
|
+
return;
|
|
55
|
+
this.onFire(schedule);
|
|
56
|
+
this.arm(schedule); // chain the next occurrence
|
|
57
|
+
}, delay);
|
|
58
|
+
this.handles.push(handle);
|
|
59
|
+
}
|
|
60
|
+
stop() {
|
|
61
|
+
this.stopped = true;
|
|
62
|
+
for (const handle of this.handles)
|
|
63
|
+
this.clock.clearTimeout(handle);
|
|
64
|
+
this.handles.length = 0;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=scheduler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../../src/orchestrator/scheduler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persisted at-least-once dedup for inbound events. Connectors may redeliver
|
|
3
|
+
* (Slack retries unacked envelopes, polls overlap, processes restart); the
|
|
4
|
+
* orchestrator drops anything whose event id it has already dispatched.
|
|
5
|
+
* Keeps a bounded FIFO of recent keys per agent, persisted to disk.
|
|
6
|
+
*/
|
|
7
|
+
export declare class SeenEvents {
|
|
8
|
+
private readonly capacity;
|
|
9
|
+
private readonly path;
|
|
10
|
+
private keys;
|
|
11
|
+
private readonly index;
|
|
12
|
+
private loaded;
|
|
13
|
+
private persistTimer?;
|
|
14
|
+
constructor(dataDir: string, agentId: string, capacity?: number);
|
|
15
|
+
private load;
|
|
16
|
+
/** Returns true the first time a key is seen; false on duplicates. */
|
|
17
|
+
markSeen(key: string): Promise<boolean>;
|
|
18
|
+
/** Debounced write so event bursts don't thrash the disk. */
|
|
19
|
+
private schedulePersist;
|
|
20
|
+
persist(): Promise<void>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
/**
|
|
5
|
+
* Persisted at-least-once dedup for inbound events. Connectors may redeliver
|
|
6
|
+
* (Slack retries unacked envelopes, polls overlap, processes restart); the
|
|
7
|
+
* orchestrator drops anything whose event id it has already dispatched.
|
|
8
|
+
* Keeps a bounded FIFO of recent keys per agent, persisted to disk.
|
|
9
|
+
*/
|
|
10
|
+
export class SeenEvents {
|
|
11
|
+
capacity;
|
|
12
|
+
path;
|
|
13
|
+
keys = [];
|
|
14
|
+
index = new Set();
|
|
15
|
+
loaded = false;
|
|
16
|
+
persistTimer;
|
|
17
|
+
constructor(dataDir, agentId, capacity = 500) {
|
|
18
|
+
this.capacity = capacity;
|
|
19
|
+
this.path = join(dataDir, "agents", agentId, "seen-events.json");
|
|
20
|
+
}
|
|
21
|
+
async load() {
|
|
22
|
+
if (this.loaded)
|
|
23
|
+
return;
|
|
24
|
+
this.loaded = true;
|
|
25
|
+
try {
|
|
26
|
+
const stored = JSON.parse(await readFile(this.path, "utf8"));
|
|
27
|
+
this.keys = stored.slice(-this.capacity);
|
|
28
|
+
for (const key of this.keys)
|
|
29
|
+
this.index.add(key);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// missing or corrupt file — start fresh
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** Returns true the first time a key is seen; false on duplicates. */
|
|
36
|
+
async markSeen(key) {
|
|
37
|
+
await this.load();
|
|
38
|
+
if (this.index.has(key))
|
|
39
|
+
return false;
|
|
40
|
+
this.keys.push(key);
|
|
41
|
+
this.index.add(key);
|
|
42
|
+
while (this.keys.length > this.capacity) {
|
|
43
|
+
const evicted = this.keys.shift();
|
|
44
|
+
if (evicted)
|
|
45
|
+
this.index.delete(evicted);
|
|
46
|
+
}
|
|
47
|
+
this.schedulePersist();
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
/** Debounced write so event bursts don't thrash the disk. */
|
|
51
|
+
schedulePersist() {
|
|
52
|
+
if (this.persistTimer)
|
|
53
|
+
return;
|
|
54
|
+
this.persistTimer = setTimeout(() => {
|
|
55
|
+
this.persistTimer = undefined;
|
|
56
|
+
void this.persist();
|
|
57
|
+
}, 250);
|
|
58
|
+
this.persistTimer.unref?.();
|
|
59
|
+
}
|
|
60
|
+
async persist() {
|
|
61
|
+
if (this.persistTimer) {
|
|
62
|
+
clearTimeout(this.persistTimer);
|
|
63
|
+
this.persistTimer = undefined;
|
|
64
|
+
}
|
|
65
|
+
await mkdir(dirname(this.path), { recursive: true });
|
|
66
|
+
const tmp = `${this.path}.${randomUUID().slice(0, 8)}.tmp`;
|
|
67
|
+
await writeFile(tmp, JSON.stringify(this.keys), "utf8");
|
|
68
|
+
await rename(tmp, this.path);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=seen-events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seen-events.js","sourceRoot":"","sources":["../../src/orchestrator/seen-events.ts"],"names":[],"mappings":"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"}
|