talon-agent 1.0.0 → 1.2.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 (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1 -0
  3. package/package.json +15 -11
  4. package/prompts/dream.md +7 -3
  5. package/prompts/heartbeat.md +30 -0
  6. package/prompts/identity.md +1 -0
  7. package/prompts/teams.md +3 -0
  8. package/prompts/telegram.md +1 -0
  9. package/src/__tests__/chat-settings.test.ts +108 -2
  10. package/src/__tests__/cleanup-registry.test.ts +58 -0
  11. package/src/__tests__/config.test.ts +118 -52
  12. package/src/__tests__/cron-store-extended.test.ts +661 -0
  13. package/src/__tests__/cron-store.test.ts +145 -11
  14. package/src/__tests__/daily-log.test.ts +224 -13
  15. package/src/__tests__/dispatcher.test.ts +424 -23
  16. package/src/__tests__/dream.test.ts +1028 -0
  17. package/src/__tests__/errors-extended.test.ts +428 -0
  18. package/src/__tests__/errors.test.ts +95 -3
  19. package/src/__tests__/fuzz.test.ts +87 -15
  20. package/src/__tests__/gateway-actions.test.ts +1174 -433
  21. package/src/__tests__/gateway-http.test.ts +210 -19
  22. package/src/__tests__/gateway-retry.test.ts +359 -0
  23. package/src/__tests__/gateway-withRetry-extended.test.ts +343 -0
  24. package/src/__tests__/graph.test.ts +830 -0
  25. package/src/__tests__/handlers-stream.test.ts +208 -0
  26. package/src/__tests__/handlers.test.ts +2539 -70
  27. package/src/__tests__/heartbeat.test.ts +364 -0
  28. package/src/__tests__/history-extended.test.ts +775 -0
  29. package/src/__tests__/history-persistence.test.ts +74 -19
  30. package/src/__tests__/history.test.ts +113 -79
  31. package/src/__tests__/integration.test.ts +43 -8
  32. package/src/__tests__/log-init.test.ts +129 -0
  33. package/src/__tests__/log.test.ts +23 -5
  34. package/src/__tests__/media-index.test.ts +317 -35
  35. package/src/__tests__/plugin.test.ts +314 -0
  36. package/src/__tests__/prompt-builder-extended.test.ts +296 -0
  37. package/src/__tests__/prompt-builder.test.ts +44 -9
  38. package/src/__tests__/sessions.test.ts +258 -4
  39. package/src/__tests__/storage-save-errors.test.ts +342 -0
  40. package/src/__tests__/teams-frontend.test.ts +526 -31
  41. package/src/__tests__/telegram-formatting.test.ts +82 -0
  42. package/src/__tests__/terminal-commands.test.ts +208 -1
  43. package/src/__tests__/terminal-renderer.test.ts +223 -0
  44. package/src/__tests__/time.test.ts +107 -0
  45. package/src/__tests__/workspace-migrate.test.ts +256 -0
  46. package/src/__tests__/workspace.test.ts +63 -1
  47. package/src/backend/claude-sdk/tools.ts +64 -18
  48. package/src/bootstrap.ts +14 -14
  49. package/src/cli.ts +440 -125
  50. package/src/core/cron.ts +20 -5
  51. package/src/core/dispatcher.ts +27 -9
  52. package/src/core/dream.ts +79 -24
  53. package/src/core/errors.ts +12 -2
  54. package/src/core/gateway-actions.ts +182 -46
  55. package/src/core/gateway.ts +93 -41
  56. package/src/core/heartbeat.ts +515 -0
  57. package/src/core/plugin.ts +1 -1
  58. package/src/core/prompt-builder.ts +1 -4
  59. package/src/core/pulse.ts +4 -3
  60. package/src/frontend/teams/actions.ts +3 -1
  61. package/src/frontend/teams/formatting.ts +47 -8
  62. package/src/frontend/teams/graph.ts +35 -11
  63. package/src/frontend/teams/index.ts +155 -57
  64. package/src/frontend/teams/tools.ts +4 -6
  65. package/src/frontend/telegram/actions.ts +358 -82
  66. package/src/frontend/telegram/admin.ts +162 -72
  67. package/src/frontend/telegram/callbacks.ts +16 -10
  68. package/src/frontend/telegram/commands.ts +37 -21
  69. package/src/frontend/telegram/formatting.ts +2 -4
  70. package/src/frontend/telegram/handlers.ts +262 -66
  71. package/src/frontend/telegram/index.ts +39 -14
  72. package/src/frontend/telegram/middleware.ts +14 -4
  73. package/src/frontend/telegram/userbot.ts +16 -4
  74. package/src/frontend/terminal/renderer.ts +1 -4
  75. package/src/index.ts +28 -4
  76. package/src/storage/chat-settings.ts +32 -9
  77. package/src/storage/cron-store.ts +53 -11
  78. package/src/storage/daily-log.ts +72 -19
  79. package/src/storage/history.ts +39 -21
  80. package/src/storage/media-index.ts +37 -12
  81. package/src/storage/sessions.ts +3 -2
  82. package/src/util/cleanup-registry.ts +34 -0
  83. package/src/util/config.ts +85 -23
  84. package/src/util/log.ts +47 -17
  85. package/src/util/paths.ts +10 -0
  86. package/src/util/time.ts +29 -6
  87. package/src/util/watchdog.ts +5 -1
  88. package/src/util/workspace.ts +51 -10
@@ -51,6 +51,11 @@ const { handleSharedAction } = await import("../core/gateway-actions.js");
51
51
  const { resolveModelName } = await import("../storage/chat-settings.js");
52
52
  const { Cron } = await import("croner");
53
53
 
54
+ // ── Configuration ───────────────────────────────────────────────────────────
55
+
56
+ const NUM_RUNS = Number(process.env.FAST_CHECK_NUM_RUNS) || 100;
57
+ const fcParams = { numRuns: NUM_RUNS };
58
+
54
59
  // ── Fuzz tests ───────────────────────────────────────────────────────────────
55
60
 
56
61
  describe("fuzz: classify()", () => {
@@ -63,6 +68,7 @@ describe("fuzz: classify()", () => {
63
68
  expect(typeof result.retryable).toBe("boolean");
64
69
  expect(typeof result.message).toBe("string");
65
70
  }),
71
+ fcParams,
66
72
  );
67
73
  });
68
74
 
@@ -73,6 +79,7 @@ describe("fuzz: classify()", () => {
73
79
  expect(result).toBeInstanceOf(TalonError);
74
80
  expect(result.reason).toBeTruthy();
75
81
  }),
82
+ fcParams,
76
83
  );
77
84
  });
78
85
 
@@ -93,6 +100,7 @@ describe("fuzz: classify()", () => {
93
100
  expect(result.reason).toBeTruthy();
94
101
  },
95
102
  ),
103
+ fcParams,
96
104
  );
97
105
  });
98
106
 
@@ -115,6 +123,7 @@ describe("fuzz: classify()", () => {
115
123
  const result = classify(input);
116
124
  expect(validReasons.has(result.reason)).toBe(true);
117
125
  }),
126
+ fcParams,
118
127
  );
119
128
  });
120
129
 
@@ -126,18 +135,25 @@ describe("fuzz: classify()", () => {
126
135
  expect(result.retryAfterMs).toBeGreaterThanOrEqual(0);
127
136
  }
128
137
  }),
138
+ fcParams,
129
139
  );
130
140
  });
131
141
  });
132
142
 
133
143
  // Real cron validator (the import is mocked, so we use Cron directly)
134
- function realValidateCron(expr: string, timezone?: string): { valid: boolean; error?: string } {
144
+ function realValidateCron(
145
+ expr: string,
146
+ timezone?: string,
147
+ ): { valid: boolean; error?: string } {
135
148
  try {
136
149
  const cron = new Cron(expr, { timezone: timezone ?? undefined });
137
150
  cron.nextRun();
138
151
  return { valid: true };
139
152
  } catch (err) {
140
- return { valid: false, error: err instanceof Error ? err.message : String(err) };
153
+ return {
154
+ valid: false,
155
+ error: err instanceof Error ? err.message : String(err),
156
+ };
141
157
  }
142
158
  }
143
159
 
@@ -151,6 +167,7 @@ describe("fuzz: validateCronExpression()", () => {
151
167
  expect(typeof result.error).toBe("string");
152
168
  }
153
169
  }),
170
+ fcParams,
154
171
  );
155
172
  });
156
173
 
@@ -160,6 +177,7 @@ describe("fuzz: validateCronExpression()", () => {
160
177
  const result = realValidateCron(expr, tz);
161
178
  expect(typeof result.valid).toBe("boolean");
162
179
  }),
180
+ fcParams,
163
181
  );
164
182
  });
165
183
  });
@@ -170,15 +188,24 @@ describe("fuzz: handleSharedAction() — unknown actions", () => {
170
188
  fc.asyncProperty(fc.string(), async (actionName) => {
171
189
  // Skip known action names to test only the default/unknown path
172
190
  const knownActions = new Set([
173
- "read_history", "search_history", "get_user_messages",
174
- "list_known_users", "list_media", "web_search", "fetch_url",
175
- "create_cron_job", "list_cron_jobs", "edit_cron_job", "delete_cron_job",
191
+ "read_history",
192
+ "search_history",
193
+ "get_user_messages",
194
+ "list_known_users",
195
+ "list_media",
196
+ "web_search",
197
+ "fetch_url",
198
+ "create_cron_job",
199
+ "list_cron_jobs",
200
+ "edit_cron_job",
201
+ "delete_cron_job",
176
202
  ]);
177
203
  if (knownActions.has(actionName)) return;
178
204
 
179
205
  const result = await handleSharedAction({ action: actionName }, 123);
180
206
  expect(result).toBeNull();
181
207
  }),
208
+ fcParams,
182
209
  );
183
210
  });
184
211
 
@@ -192,9 +219,17 @@ describe("fuzz: handleSharedAction() — unknown actions", () => {
192
219
  }),
193
220
  async (body) => {
194
221
  const knownActions = new Set([
195
- "read_history", "search_history", "get_user_messages",
196
- "list_known_users", "list_media", "web_search", "fetch_url",
197
- "create_cron_job", "list_cron_jobs", "edit_cron_job", "delete_cron_job",
222
+ "read_history",
223
+ "search_history",
224
+ "get_user_messages",
225
+ "list_known_users",
226
+ "list_media",
227
+ "web_search",
228
+ "fetch_url",
229
+ "create_cron_job",
230
+ "list_cron_jobs",
231
+ "edit_cron_job",
232
+ "delete_cron_job",
198
233
  ]);
199
234
  if (knownActions.has(body.action)) return;
200
235
 
@@ -202,6 +237,7 @@ describe("fuzz: handleSharedAction() — unknown actions", () => {
202
237
  expect(result).toBeNull();
203
238
  },
204
239
  ),
240
+ fcParams,
205
241
  );
206
242
  });
207
243
  });
@@ -216,7 +252,10 @@ describe("fuzz: fetch_url URL validation", () => {
216
252
 
217
253
  fc.assert(
218
254
  fc.asyncProperty(fc.string(), async (url) => {
219
- const result = await handleSharedAction({ action: "fetch_url", url }, 123);
255
+ const result = await handleSharedAction(
256
+ { action: "fetch_url", url },
257
+ 123,
258
+ );
220
259
  // Should either return an error result or handle gracefully
221
260
  expect(result).not.toBeUndefined();
222
261
  if (result !== null) {
@@ -226,6 +265,7 @@ describe("fuzz: fetch_url URL validation", () => {
226
265
  }
227
266
  }
228
267
  }),
268
+ fcParams,
229
269
  );
230
270
 
231
271
  globalThis.fetch = originalFetch;
@@ -234,11 +274,22 @@ describe("fuzz: fetch_url URL validation", () => {
234
274
  it("rejects all non-http/https protocols", () => {
235
275
  fc.assert(
236
276
  fc.asyncProperty(
237
- fc.constantFrom("ftp", "file", "javascript", "data", "ws", "wss", "ssh"),
277
+ fc.constantFrom(
278
+ "ftp",
279
+ "file",
280
+ "javascript",
281
+ "data",
282
+ "ws",
283
+ "wss",
284
+ "ssh",
285
+ ),
238
286
  fc.webUrl(),
239
287
  async (protocol, path) => {
240
288
  const url = `${protocol}://${path.replace(/^https?:\/\//, "")}`;
241
- const result = await handleSharedAction({ action: "fetch_url", url }, 123);
289
+ const result = await handleSharedAction(
290
+ { action: "fetch_url", url },
291
+ 123,
292
+ );
242
293
  if (result !== null) {
243
294
  // Should not be ok for non-http protocols
244
295
  // (some invalid URLs will get "Invalid URL" error at parse time)
@@ -246,6 +297,7 @@ describe("fuzz: fetch_url URL validation", () => {
246
297
  }
247
298
  },
248
299
  ),
300
+ fcParams,
249
301
  );
250
302
  });
251
303
  });
@@ -257,6 +309,7 @@ describe("fuzz: resolveModelName()", () => {
257
309
  const result = resolveModelName(input);
258
310
  expect(typeof result).toBe("string");
259
311
  }),
312
+ fcParams,
260
313
  );
261
314
  });
262
315
 
@@ -266,11 +319,22 @@ describe("fuzz: resolveModelName()", () => {
266
319
  const result = resolveModelName(input);
267
320
  expect(result).toBe(result.trim());
268
321
  }),
322
+ fcParams,
269
323
  );
270
324
  });
271
325
 
272
326
  it("known aliases always resolve to claude model names", () => {
273
- const aliases = ["sonnet", "opus", "haiku", "sonnet-4.6", "opus-4.6", "haiku-4.5", "sonnet-4-6", "opus-4-6", "haiku-4-5"];
327
+ const aliases = [
328
+ "sonnet",
329
+ "opus",
330
+ "haiku",
331
+ "sonnet-4.6",
332
+ "opus-4.6",
333
+ "haiku-4.5",
334
+ "sonnet-4-6",
335
+ "opus-4-6",
336
+ "haiku-4-5",
337
+ ];
274
338
  fc.assert(
275
339
  fc.property(
276
340
  fc.constantFrom(...aliases),
@@ -280,6 +344,7 @@ describe("fuzz: resolveModelName()", () => {
280
344
  expect(result).toMatch(/^claude-/);
281
345
  },
282
346
  ),
347
+ fcParams,
283
348
  );
284
349
  });
285
350
 
@@ -288,15 +353,22 @@ describe("fuzz: resolveModelName()", () => {
288
353
  fc.property(fc.string(), (input) => {
289
354
  const trimmed = input.trim().toLowerCase();
290
355
  const knownAliases = [
291
- "sonnet", "opus", "haiku",
292
- "sonnet-4.6", "opus-4.6", "haiku-4.5",
293
- "sonnet-4-6", "opus-4-6", "haiku-4-5",
356
+ "sonnet",
357
+ "opus",
358
+ "haiku",
359
+ "sonnet-4.6",
360
+ "opus-4.6",
361
+ "haiku-4.5",
362
+ "sonnet-4-6",
363
+ "opus-4-6",
364
+ "haiku-4-5",
294
365
  ];
295
366
  if (knownAliases.includes(trimmed)) return;
296
367
 
297
368
  const result = resolveModelName(input);
298
369
  expect(result).toBe(input.trim());
299
370
  }),
371
+ fcParams,
300
372
  );
301
373
  });
302
374
  });