talon-agent 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +137 -0
- package/bin/talon.js +5 -0
- package/package.json +86 -0
- package/prompts/base.md +13 -0
- package/prompts/custom.md.example +22 -0
- package/prompts/dream.md +41 -0
- package/prompts/identity.md +45 -0
- package/prompts/teams.md +52 -0
- package/prompts/telegram.md +89 -0
- package/prompts/terminal.md +13 -0
- package/src/__tests__/chat-id.test.ts +91 -0
- package/src/__tests__/chat-settings.test.ts +337 -0
- package/src/__tests__/config.test.ts +546 -0
- package/src/__tests__/cron-store.test.ts +440 -0
- package/src/__tests__/daily-log.test.ts +146 -0
- package/src/__tests__/dispatcher.test.ts +383 -0
- package/src/__tests__/errors.test.ts +240 -0
- package/src/__tests__/fuzz.test.ts +302 -0
- package/src/__tests__/gateway-actions.test.ts +1453 -0
- package/src/__tests__/gateway-context.test.ts +102 -0
- package/src/__tests__/gateway-http.test.ts +245 -0
- package/src/__tests__/handlers.test.ts +351 -0
- package/src/__tests__/history-persistence.test.ts +172 -0
- package/src/__tests__/history.test.ts +659 -0
- package/src/__tests__/integration.test.ts +189 -0
- package/src/__tests__/log.test.ts +110 -0
- package/src/__tests__/media-index.test.ts +277 -0
- package/src/__tests__/plugin.test.ts +317 -0
- package/src/__tests__/prompt-builder.test.ts +71 -0
- package/src/__tests__/sessions.test.ts +594 -0
- package/src/__tests__/teams-frontend.test.ts +239 -0
- package/src/__tests__/telegram.test.ts +177 -0
- package/src/__tests__/terminal-commands.test.ts +367 -0
- package/src/__tests__/terminal-frontend.test.ts +141 -0
- package/src/__tests__/terminal-renderer.test.ts +278 -0
- package/src/__tests__/watchdog.test.ts +287 -0
- package/src/__tests__/workspace.test.ts +184 -0
- package/src/backend/claude-sdk/index.ts +438 -0
- package/src/backend/claude-sdk/tools.ts +605 -0
- package/src/backend/opencode/index.ts +252 -0
- package/src/bootstrap.ts +134 -0
- package/src/cli.ts +611 -0
- package/src/core/cron.ts +148 -0
- package/src/core/dispatcher.ts +126 -0
- package/src/core/dream.ts +295 -0
- package/src/core/errors.ts +206 -0
- package/src/core/gateway-actions.ts +267 -0
- package/src/core/gateway.ts +258 -0
- package/src/core/plugin.ts +432 -0
- package/src/core/prompt-builder.ts +43 -0
- package/src/core/pulse.ts +175 -0
- package/src/core/types.ts +85 -0
- package/src/frontend/teams/actions.ts +101 -0
- package/src/frontend/teams/formatting.ts +220 -0
- package/src/frontend/teams/graph.ts +297 -0
- package/src/frontend/teams/index.ts +308 -0
- package/src/frontend/teams/proxy-fetch.ts +28 -0
- package/src/frontend/teams/tools.ts +177 -0
- package/src/frontend/telegram/actions.ts +437 -0
- package/src/frontend/telegram/admin.ts +178 -0
- package/src/frontend/telegram/callbacks.ts +251 -0
- package/src/frontend/telegram/commands.ts +543 -0
- package/src/frontend/telegram/formatting.ts +101 -0
- package/src/frontend/telegram/handlers.ts +1008 -0
- package/src/frontend/telegram/helpers.ts +105 -0
- package/src/frontend/telegram/index.ts +130 -0
- package/src/frontend/telegram/middleware.ts +177 -0
- package/src/frontend/telegram/userbot.ts +546 -0
- package/src/frontend/terminal/commands.ts +303 -0
- package/src/frontend/terminal/index.ts +282 -0
- package/src/frontend/terminal/input.ts +297 -0
- package/src/frontend/terminal/renderer.ts +248 -0
- package/src/index.ts +144 -0
- package/src/login.ts +89 -0
- package/src/storage/chat-settings.ts +218 -0
- package/src/storage/cron-store.ts +165 -0
- package/src/storage/daily-log.ts +97 -0
- package/src/storage/history.ts +278 -0
- package/src/storage/media-index.ts +116 -0
- package/src/storage/sessions.ts +328 -0
- package/src/util/chat-id.ts +21 -0
- package/src/util/config.ts +244 -0
- package/src/util/log.ts +122 -0
- package/src/util/paths.ts +80 -0
- package/src/util/time.ts +86 -0
- package/src/util/trace.ts +35 -0
- package/src/util/watchdog.ts +108 -0
- package/src/util/workspace.ts +208 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
|
|
3
|
+
vi.mock("../util/log.js", () => ({
|
|
4
|
+
log: vi.fn(),
|
|
5
|
+
logError: vi.fn(),
|
|
6
|
+
logWarn: vi.fn(),
|
|
7
|
+
logDebug: vi.fn(),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
const existsSyncMock = vi.fn(() => false);
|
|
11
|
+
const readFileSyncMock = vi.fn(() => "{}");
|
|
12
|
+
const mkdirSyncMock = vi.fn();
|
|
13
|
+
|
|
14
|
+
vi.mock("node:fs", () => ({
|
|
15
|
+
existsSync: existsSyncMock,
|
|
16
|
+
readFileSync: readFileSyncMock,
|
|
17
|
+
mkdirSync: mkdirSyncMock,
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
const writeFileSyncMock = vi.fn();
|
|
21
|
+
|
|
22
|
+
vi.mock("write-file-atomic", () => ({
|
|
23
|
+
default: { sync: (...args: unknown[]) => writeFileSyncMock(...args) },
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
const {
|
|
27
|
+
loadHistory,
|
|
28
|
+
flushHistory,
|
|
29
|
+
pushMessage,
|
|
30
|
+
getRecentHistory,
|
|
31
|
+
} = await import("../storage/history.js");
|
|
32
|
+
|
|
33
|
+
describe("history persistence", () => {
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
vi.clearAllMocks();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe("loadHistory", () => {
|
|
39
|
+
it("loads history from a JSON file when it exists", () => {
|
|
40
|
+
const data: Record<string, Array<{ msgId: number; senderId: number; senderName: string; text: string; timestamp: number }>> = {
|
|
41
|
+
"chat-1": [
|
|
42
|
+
{ msgId: 1, senderId: 100, senderName: "Alice", text: "hello", timestamp: 1000 },
|
|
43
|
+
{ msgId: 2, senderId: 200, senderName: "Bob", text: "hi", timestamp: 2000 },
|
|
44
|
+
],
|
|
45
|
+
};
|
|
46
|
+
existsSyncMock.mockReturnValue(true);
|
|
47
|
+
readFileSyncMock.mockReturnValue(JSON.stringify(data));
|
|
48
|
+
|
|
49
|
+
loadHistory();
|
|
50
|
+
|
|
51
|
+
const history = getRecentHistory("chat-1");
|
|
52
|
+
expect(history).toHaveLength(2);
|
|
53
|
+
expect(history[0].senderName).toBe("Alice");
|
|
54
|
+
expect(history[1].senderName).toBe("Bob");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("trims loaded history to MAX_HISTORY_PER_CHAT (500)", () => {
|
|
58
|
+
const msgs = Array.from({ length: 600 }, (_, i) => ({
|
|
59
|
+
msgId: i,
|
|
60
|
+
senderId: 1,
|
|
61
|
+
senderName: "User",
|
|
62
|
+
text: `msg ${i}`,
|
|
63
|
+
timestamp: i * 1000,
|
|
64
|
+
}));
|
|
65
|
+
existsSyncMock.mockReturnValue(true);
|
|
66
|
+
readFileSyncMock.mockReturnValue(JSON.stringify({ "trim-chat": msgs }));
|
|
67
|
+
|
|
68
|
+
loadHistory();
|
|
69
|
+
|
|
70
|
+
const history = getRecentHistory("trim-chat", 1000);
|
|
71
|
+
expect(history).toHaveLength(500);
|
|
72
|
+
// Should keep the last 500 messages (100-599)
|
|
73
|
+
expect(history[0].msgId).toBe(100);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("does nothing when store file does not exist", () => {
|
|
77
|
+
existsSyncMock.mockReturnValue(false);
|
|
78
|
+
// Should not throw
|
|
79
|
+
expect(() => loadHistory()).not.toThrow();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("handles JSON parse errors gracefully (starts fresh)", () => {
|
|
83
|
+
existsSyncMock.mockReturnValue(true);
|
|
84
|
+
readFileSyncMock.mockReturnValue("not valid json{{{");
|
|
85
|
+
|
|
86
|
+
// Should not throw
|
|
87
|
+
expect(() => loadHistory()).not.toThrow();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("loads multiple chats", () => {
|
|
91
|
+
const data = {
|
|
92
|
+
"chat-a": [{ msgId: 1, senderId: 1, senderName: "A", text: "a", timestamp: 1 }],
|
|
93
|
+
"chat-b": [{ msgId: 2, senderId: 2, senderName: "B", text: "b", timestamp: 2 }],
|
|
94
|
+
"chat-c": [{ msgId: 3, senderId: 3, senderName: "C", text: "c", timestamp: 3 }],
|
|
95
|
+
};
|
|
96
|
+
existsSyncMock.mockReturnValue(true);
|
|
97
|
+
readFileSyncMock.mockReturnValue(JSON.stringify(data));
|
|
98
|
+
|
|
99
|
+
loadHistory();
|
|
100
|
+
|
|
101
|
+
expect(getRecentHistory("chat-a")).toHaveLength(1);
|
|
102
|
+
expect(getRecentHistory("chat-b")).toHaveLength(1);
|
|
103
|
+
expect(getRecentHistory("chat-c")).toHaveLength(1);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("flushHistory", () => {
|
|
108
|
+
it("writes history to disk", () => {
|
|
109
|
+
const id = `flush-test-${Date.now()}`;
|
|
110
|
+
pushMessage(id, {
|
|
111
|
+
msgId: 1,
|
|
112
|
+
senderId: 1,
|
|
113
|
+
senderName: "TestUser",
|
|
114
|
+
text: "flush test",
|
|
115
|
+
timestamp: Date.now(),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Make existsSync return true for the workspace dir check
|
|
119
|
+
existsSyncMock.mockReturnValue(true);
|
|
120
|
+
|
|
121
|
+
flushHistory();
|
|
122
|
+
|
|
123
|
+
expect(writeFileSyncMock).toHaveBeenCalled();
|
|
124
|
+
// Last write call is the actual data (earlier calls may be .bak backups)
|
|
125
|
+
const lastCall = writeFileSyncMock.mock.calls[writeFileSyncMock.mock.calls.length - 1];
|
|
126
|
+
const writtenData = lastCall[1] as string;
|
|
127
|
+
const parsed = JSON.parse(writtenData.trim());
|
|
128
|
+
expect(parsed[id]).toBeDefined();
|
|
129
|
+
expect(parsed[id][0].text).toBe("flush test");
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("creates workspace directory if it does not exist", () => {
|
|
133
|
+
const id = `flush-mkdir-${Date.now()}`;
|
|
134
|
+
pushMessage(id, {
|
|
135
|
+
msgId: 1,
|
|
136
|
+
senderId: 1,
|
|
137
|
+
senderName: "TestUser",
|
|
138
|
+
text: "test",
|
|
139
|
+
timestamp: Date.now(),
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// First call (dir check) returns false, triggering mkdirSync
|
|
143
|
+
existsSyncMock.mockReturnValue(false);
|
|
144
|
+
|
|
145
|
+
flushHistory();
|
|
146
|
+
|
|
147
|
+
expect(mkdirSyncMock).toHaveBeenCalledWith(
|
|
148
|
+
expect.any(String),
|
|
149
|
+
{ recursive: true },
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("handles write errors gracefully", () => {
|
|
154
|
+
const id = `flush-err-${Date.now()}`;
|
|
155
|
+
pushMessage(id, {
|
|
156
|
+
msgId: 1,
|
|
157
|
+
senderId: 1,
|
|
158
|
+
senderName: "TestUser",
|
|
159
|
+
text: "test",
|
|
160
|
+
timestamp: Date.now(),
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
existsSyncMock.mockReturnValue(true);
|
|
164
|
+
writeFileSyncMock.mockImplementationOnce(() => {
|
|
165
|
+
throw new Error("disk full");
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Should not throw
|
|
169
|
+
expect(() => flushHistory()).not.toThrow();
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
});
|