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.
Files changed (89) hide show
  1. package/README.md +137 -0
  2. package/bin/talon.js +5 -0
  3. package/package.json +86 -0
  4. package/prompts/base.md +13 -0
  5. package/prompts/custom.md.example +22 -0
  6. package/prompts/dream.md +41 -0
  7. package/prompts/identity.md +45 -0
  8. package/prompts/teams.md +52 -0
  9. package/prompts/telegram.md +89 -0
  10. package/prompts/terminal.md +13 -0
  11. package/src/__tests__/chat-id.test.ts +91 -0
  12. package/src/__tests__/chat-settings.test.ts +337 -0
  13. package/src/__tests__/config.test.ts +546 -0
  14. package/src/__tests__/cron-store.test.ts +440 -0
  15. package/src/__tests__/daily-log.test.ts +146 -0
  16. package/src/__tests__/dispatcher.test.ts +383 -0
  17. package/src/__tests__/errors.test.ts +240 -0
  18. package/src/__tests__/fuzz.test.ts +302 -0
  19. package/src/__tests__/gateway-actions.test.ts +1453 -0
  20. package/src/__tests__/gateway-context.test.ts +102 -0
  21. package/src/__tests__/gateway-http.test.ts +245 -0
  22. package/src/__tests__/handlers.test.ts +351 -0
  23. package/src/__tests__/history-persistence.test.ts +172 -0
  24. package/src/__tests__/history.test.ts +659 -0
  25. package/src/__tests__/integration.test.ts +189 -0
  26. package/src/__tests__/log.test.ts +110 -0
  27. package/src/__tests__/media-index.test.ts +277 -0
  28. package/src/__tests__/plugin.test.ts +317 -0
  29. package/src/__tests__/prompt-builder.test.ts +71 -0
  30. package/src/__tests__/sessions.test.ts +594 -0
  31. package/src/__tests__/teams-frontend.test.ts +239 -0
  32. package/src/__tests__/telegram.test.ts +177 -0
  33. package/src/__tests__/terminal-commands.test.ts +367 -0
  34. package/src/__tests__/terminal-frontend.test.ts +141 -0
  35. package/src/__tests__/terminal-renderer.test.ts +278 -0
  36. package/src/__tests__/watchdog.test.ts +287 -0
  37. package/src/__tests__/workspace.test.ts +184 -0
  38. package/src/backend/claude-sdk/index.ts +438 -0
  39. package/src/backend/claude-sdk/tools.ts +605 -0
  40. package/src/backend/opencode/index.ts +252 -0
  41. package/src/bootstrap.ts +134 -0
  42. package/src/cli.ts +611 -0
  43. package/src/core/cron.ts +148 -0
  44. package/src/core/dispatcher.ts +126 -0
  45. package/src/core/dream.ts +295 -0
  46. package/src/core/errors.ts +206 -0
  47. package/src/core/gateway-actions.ts +267 -0
  48. package/src/core/gateway.ts +258 -0
  49. package/src/core/plugin.ts +432 -0
  50. package/src/core/prompt-builder.ts +43 -0
  51. package/src/core/pulse.ts +175 -0
  52. package/src/core/types.ts +85 -0
  53. package/src/frontend/teams/actions.ts +101 -0
  54. package/src/frontend/teams/formatting.ts +220 -0
  55. package/src/frontend/teams/graph.ts +297 -0
  56. package/src/frontend/teams/index.ts +308 -0
  57. package/src/frontend/teams/proxy-fetch.ts +28 -0
  58. package/src/frontend/teams/tools.ts +177 -0
  59. package/src/frontend/telegram/actions.ts +437 -0
  60. package/src/frontend/telegram/admin.ts +178 -0
  61. package/src/frontend/telegram/callbacks.ts +251 -0
  62. package/src/frontend/telegram/commands.ts +543 -0
  63. package/src/frontend/telegram/formatting.ts +101 -0
  64. package/src/frontend/telegram/handlers.ts +1008 -0
  65. package/src/frontend/telegram/helpers.ts +105 -0
  66. package/src/frontend/telegram/index.ts +130 -0
  67. package/src/frontend/telegram/middleware.ts +177 -0
  68. package/src/frontend/telegram/userbot.ts +546 -0
  69. package/src/frontend/terminal/commands.ts +303 -0
  70. package/src/frontend/terminal/index.ts +282 -0
  71. package/src/frontend/terminal/input.ts +297 -0
  72. package/src/frontend/terminal/renderer.ts +248 -0
  73. package/src/index.ts +144 -0
  74. package/src/login.ts +89 -0
  75. package/src/storage/chat-settings.ts +218 -0
  76. package/src/storage/cron-store.ts +165 -0
  77. package/src/storage/daily-log.ts +97 -0
  78. package/src/storage/history.ts +278 -0
  79. package/src/storage/media-index.ts +116 -0
  80. package/src/storage/sessions.ts +328 -0
  81. package/src/util/chat-id.ts +21 -0
  82. package/src/util/config.ts +244 -0
  83. package/src/util/log.ts +122 -0
  84. package/src/util/paths.ts +80 -0
  85. package/src/util/time.ts +86 -0
  86. package/src/util/trace.ts +35 -0
  87. package/src/util/watchdog.ts +108 -0
  88. package/src/util/workspace.ts +208 -0
  89. 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
+ });