talon-agent 1.2.0 → 1.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/package.json +7 -6
- package/prompts/dream.md +6 -2
- package/prompts/mempalace.md +57 -0
- package/src/__tests__/compose-tools.test.ts +216 -0
- package/src/__tests__/cron-store-extended.test.ts +1 -1
- package/src/__tests__/dream.test.ts +118 -1
- package/src/__tests__/fuzz.test.ts +1 -3
- package/src/__tests__/gateway-actions.test.ts +1 -423
- package/src/__tests__/gateway-retry.test.ts +0 -4
- package/src/__tests__/handlers.test.ts +0 -4
- package/src/__tests__/heartbeat.test.ts +3 -0
- package/src/__tests__/mempalace-plugin.test.ts +295 -0
- package/src/__tests__/plugin.test.ts +169 -0
- package/src/__tests__/storage-save-errors.test.ts +1 -1
- package/src/__tests__/time.test.ts +1 -1
- package/src/__tests__/watchdog.test.ts +1 -3
- package/src/__tests__/workspace.test.ts +0 -1
- package/src/backend/claude-sdk/index.ts +39 -54
- package/src/backend/opencode/index.ts +5 -20
- package/src/bootstrap.ts +140 -11
- package/src/core/dream.ts +40 -6
- package/src/core/gateway-actions.ts +0 -87
- package/src/core/plugin.ts +103 -16
- package/src/core/tools/bridge.ts +40 -0
- package/src/core/tools/chat.ts +52 -0
- package/src/core/tools/history.ts +80 -0
- package/src/core/tools/index.ts +82 -0
- package/src/core/tools/mcp-server.ts +64 -0
- package/src/core/tools/media.ts +23 -0
- package/src/core/tools/members.ts +46 -0
- package/src/core/tools/messaging.ts +300 -0
- package/src/core/tools/scheduling.ts +89 -0
- package/src/core/tools/stickers.ts +143 -0
- package/src/core/tools/types.ts +60 -0
- package/src/core/tools/web.ts +26 -0
- package/src/frontend/telegram/actions.ts +10 -1
- package/src/frontend/telegram/handlers.ts +5 -17
- package/src/plugins/github/index.ts +106 -0
- package/src/plugins/mempalace/index.ts +147 -0
- package/src/plugins/playwright/index.ts +82 -0
- package/src/storage/sessions.ts +0 -10
- package/src/util/config.ts +31 -1
- package/src/util/log.ts +4 -1
- package/src/util/paths.ts +9 -0
- package/src/backend/claude-sdk/tools.ts +0 -651
- package/src/frontend/teams/tools.ts +0 -175
|
@@ -125,9 +125,6 @@ describe("gateway shared actions", () => {
|
|
|
125
125
|
originalFetch = globalThis.fetch;
|
|
126
126
|
originalEnv = { ...process.env };
|
|
127
127
|
vi.clearAllMocks();
|
|
128
|
-
// Reset env vars used by web_search
|
|
129
|
-
delete process.env.TALON_BRAVE_API_KEY;
|
|
130
|
-
delete process.env.TALON_SEARXNG_URL;
|
|
131
128
|
});
|
|
132
129
|
|
|
133
130
|
afterEach(() => {
|
|
@@ -276,377 +273,6 @@ describe("gateway shared actions", () => {
|
|
|
276
273
|
});
|
|
277
274
|
});
|
|
278
275
|
|
|
279
|
-
// ════════════════════════════════════════════════════════════════════════
|
|
280
|
-
// web_search
|
|
281
|
-
// ════════════════════════════════════════════════════════════════════════
|
|
282
|
-
|
|
283
|
-
describe("web_search", () => {
|
|
284
|
-
it("returns error for missing query", async () => {
|
|
285
|
-
const result = await handleSharedAction({ action: "web_search" }, 123);
|
|
286
|
-
expect(result).toEqual({ ok: false, error: "Missing query" });
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
it("returns error for empty query string", async () => {
|
|
290
|
-
const result = await handleSharedAction(
|
|
291
|
-
{ action: "web_search", query: "" },
|
|
292
|
-
123,
|
|
293
|
-
);
|
|
294
|
-
expect(result).toEqual({ ok: false, error: "Missing query" });
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
it("uses Brave API when key is configured", async () => {
|
|
298
|
-
process.env.TALON_BRAVE_API_KEY = "test-brave-key";
|
|
299
|
-
const mockFetch = vi.fn().mockResolvedValueOnce(
|
|
300
|
-
mockResponse({
|
|
301
|
-
ok: true,
|
|
302
|
-
contentType: "application/json",
|
|
303
|
-
json: {
|
|
304
|
-
web: {
|
|
305
|
-
results: [
|
|
306
|
-
{
|
|
307
|
-
title: "Result 1",
|
|
308
|
-
url: "https://example.com/1",
|
|
309
|
-
description: "Description 1",
|
|
310
|
-
},
|
|
311
|
-
{
|
|
312
|
-
title: "Result 2",
|
|
313
|
-
url: "https://example.com/2",
|
|
314
|
-
description: "Description 2",
|
|
315
|
-
},
|
|
316
|
-
],
|
|
317
|
-
},
|
|
318
|
-
},
|
|
319
|
-
}),
|
|
320
|
-
);
|
|
321
|
-
vi.stubGlobal("fetch", mockFetch);
|
|
322
|
-
|
|
323
|
-
const result = await handleSharedAction(
|
|
324
|
-
{ action: "web_search", query: "test query" },
|
|
325
|
-
123,
|
|
326
|
-
);
|
|
327
|
-
|
|
328
|
-
expect(result?.ok).toBe(true);
|
|
329
|
-
expect(result?.text).toContain("via Brave");
|
|
330
|
-
expect(result?.text).toContain("Result 1");
|
|
331
|
-
expect(result?.text).toContain("https://example.com/1");
|
|
332
|
-
expect(result?.text).toContain("Description 1");
|
|
333
|
-
expect(result?.text).toContain("Result 2");
|
|
334
|
-
|
|
335
|
-
// Verify Brave API was called correctly
|
|
336
|
-
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
337
|
-
const [url, opts] = mockFetch.mock.calls[0];
|
|
338
|
-
expect(url).toContain("api.search.brave.com");
|
|
339
|
-
expect(url).toContain("q=test%20query");
|
|
340
|
-
expect(url).toContain("count=5");
|
|
341
|
-
expect(opts.headers["X-Subscription-Token"]).toBe("test-brave-key");
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
it("respects custom limit for Brave API", async () => {
|
|
345
|
-
process.env.TALON_BRAVE_API_KEY = "test-brave-key";
|
|
346
|
-
const mockFetch = vi.fn().mockResolvedValueOnce(
|
|
347
|
-
mockResponse({
|
|
348
|
-
ok: true,
|
|
349
|
-
json: {
|
|
350
|
-
web: {
|
|
351
|
-
results: [{ title: "R", url: "https://r.com", description: "d" }],
|
|
352
|
-
},
|
|
353
|
-
},
|
|
354
|
-
}),
|
|
355
|
-
);
|
|
356
|
-
vi.stubGlobal("fetch", mockFetch);
|
|
357
|
-
|
|
358
|
-
await handleSharedAction(
|
|
359
|
-
{ action: "web_search", query: "test", limit: 8 },
|
|
360
|
-
123,
|
|
361
|
-
);
|
|
362
|
-
const [url] = mockFetch.mock.calls[0];
|
|
363
|
-
expect(url).toContain("count=8");
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
it("clamps search limit to 10", async () => {
|
|
367
|
-
process.env.TALON_BRAVE_API_KEY = "test-brave-key";
|
|
368
|
-
const mockFetch = vi.fn().mockResolvedValueOnce(
|
|
369
|
-
mockResponse({
|
|
370
|
-
ok: true,
|
|
371
|
-
json: {
|
|
372
|
-
web: {
|
|
373
|
-
results: [{ title: "R", url: "https://r.com", description: "d" }],
|
|
374
|
-
},
|
|
375
|
-
},
|
|
376
|
-
}),
|
|
377
|
-
);
|
|
378
|
-
vi.stubGlobal("fetch", mockFetch);
|
|
379
|
-
|
|
380
|
-
await handleSharedAction(
|
|
381
|
-
{ action: "web_search", query: "test", limit: 50 },
|
|
382
|
-
123,
|
|
383
|
-
);
|
|
384
|
-
const [url] = mockFetch.mock.calls[0];
|
|
385
|
-
expect(url).toContain("count=10");
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
it("falls back to SearXNG when Brave returns non-ok", async () => {
|
|
389
|
-
process.env.TALON_BRAVE_API_KEY = "test-brave-key";
|
|
390
|
-
const mockFetch = vi
|
|
391
|
-
.fn()
|
|
392
|
-
.mockResolvedValueOnce(mockResponse({ ok: false, status: 429 })) // Brave fails
|
|
393
|
-
.mockResolvedValueOnce(
|
|
394
|
-
mockResponse({
|
|
395
|
-
ok: true,
|
|
396
|
-
json: {
|
|
397
|
-
results: [
|
|
398
|
-
{
|
|
399
|
-
title: "SearX Result",
|
|
400
|
-
url: "https://searx.example.com",
|
|
401
|
-
content: "SearX snippet",
|
|
402
|
-
},
|
|
403
|
-
],
|
|
404
|
-
},
|
|
405
|
-
}),
|
|
406
|
-
);
|
|
407
|
-
vi.stubGlobal("fetch", mockFetch);
|
|
408
|
-
|
|
409
|
-
const result = await handleSharedAction(
|
|
410
|
-
{ action: "web_search", query: "fallback test" },
|
|
411
|
-
123,
|
|
412
|
-
);
|
|
413
|
-
|
|
414
|
-
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
415
|
-
expect(result?.ok).toBe(true);
|
|
416
|
-
expect(result?.text).toContain("via SearXNG");
|
|
417
|
-
expect(result?.text).toContain("SearX Result");
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
it("falls back to SearXNG when Brave throws an error", async () => {
|
|
421
|
-
process.env.TALON_BRAVE_API_KEY = "test-brave-key";
|
|
422
|
-
const mockFetch = vi
|
|
423
|
-
.fn()
|
|
424
|
-
.mockRejectedValueOnce(new Error("network error")) // Brave throws
|
|
425
|
-
.mockResolvedValueOnce(
|
|
426
|
-
mockResponse({
|
|
427
|
-
ok: true,
|
|
428
|
-
json: {
|
|
429
|
-
results: [
|
|
430
|
-
{
|
|
431
|
-
title: "Fallback",
|
|
432
|
-
url: "https://fb.com",
|
|
433
|
-
content: "snippet",
|
|
434
|
-
},
|
|
435
|
-
],
|
|
436
|
-
},
|
|
437
|
-
}),
|
|
438
|
-
);
|
|
439
|
-
vi.stubGlobal("fetch", mockFetch);
|
|
440
|
-
|
|
441
|
-
const result = await handleSharedAction(
|
|
442
|
-
{ action: "web_search", query: "test" },
|
|
443
|
-
123,
|
|
444
|
-
);
|
|
445
|
-
|
|
446
|
-
expect(result?.ok).toBe(true);
|
|
447
|
-
expect(result?.text).toContain("via SearXNG");
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
it("uses SearXNG directly when no Brave key", async () => {
|
|
451
|
-
// No TALON_BRAVE_API_KEY set
|
|
452
|
-
const mockFetch = vi.fn().mockResolvedValueOnce(
|
|
453
|
-
mockResponse({
|
|
454
|
-
ok: true,
|
|
455
|
-
json: {
|
|
456
|
-
results: [
|
|
457
|
-
{
|
|
458
|
-
title: "Direct SearX",
|
|
459
|
-
url: "https://searx.com/r",
|
|
460
|
-
content: "content here",
|
|
461
|
-
},
|
|
462
|
-
],
|
|
463
|
-
},
|
|
464
|
-
}),
|
|
465
|
-
);
|
|
466
|
-
vi.stubGlobal("fetch", mockFetch);
|
|
467
|
-
|
|
468
|
-
const result = await handleSharedAction(
|
|
469
|
-
{ action: "web_search", query: "direct" },
|
|
470
|
-
123,
|
|
471
|
-
);
|
|
472
|
-
|
|
473
|
-
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
474
|
-
const [url] = mockFetch.mock.calls[0];
|
|
475
|
-
expect(url).toContain("localhost:8080");
|
|
476
|
-
expect(url).toContain("format=json");
|
|
477
|
-
expect(result?.ok).toBe(true);
|
|
478
|
-
expect(result?.text).toContain("via SearXNG");
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
it("uses custom SearXNG URL from env", async () => {
|
|
482
|
-
process.env.TALON_SEARXNG_URL = "http://my-searx:9090";
|
|
483
|
-
const mockFetch = vi.fn().mockResolvedValueOnce(
|
|
484
|
-
mockResponse({
|
|
485
|
-
ok: true,
|
|
486
|
-
json: {
|
|
487
|
-
results: [{ title: "T", url: "https://t.com", content: "c" }],
|
|
488
|
-
},
|
|
489
|
-
}),
|
|
490
|
-
);
|
|
491
|
-
vi.stubGlobal("fetch", mockFetch);
|
|
492
|
-
|
|
493
|
-
await handleSharedAction({ action: "web_search", query: "custom" }, 123);
|
|
494
|
-
|
|
495
|
-
const [url] = mockFetch.mock.calls[0];
|
|
496
|
-
expect(url).toContain("my-searx:9090");
|
|
497
|
-
});
|
|
498
|
-
|
|
499
|
-
it("returns 'no results' when both providers fail", async () => {
|
|
500
|
-
process.env.TALON_BRAVE_API_KEY = "test-key";
|
|
501
|
-
const mockFetch = vi
|
|
502
|
-
.fn()
|
|
503
|
-
.mockRejectedValueOnce(new Error("brave fail"))
|
|
504
|
-
.mockRejectedValueOnce(new Error("searx fail"));
|
|
505
|
-
vi.stubGlobal("fetch", mockFetch);
|
|
506
|
-
|
|
507
|
-
const result = await handleSharedAction(
|
|
508
|
-
{ action: "web_search", query: "nothing" },
|
|
509
|
-
123,
|
|
510
|
-
);
|
|
511
|
-
|
|
512
|
-
expect(result?.ok).toBe(true);
|
|
513
|
-
expect(result?.text).toBe('No results for "nothing".');
|
|
514
|
-
});
|
|
515
|
-
|
|
516
|
-
it("returns 'no results' when both return non-ok", async () => {
|
|
517
|
-
process.env.TALON_BRAVE_API_KEY = "test-key";
|
|
518
|
-
const mockFetch = vi
|
|
519
|
-
.fn()
|
|
520
|
-
.mockResolvedValueOnce(mockResponse({ ok: false, status: 500 }))
|
|
521
|
-
.mockResolvedValueOnce(mockResponse({ ok: false, status: 503 }));
|
|
522
|
-
vi.stubGlobal("fetch", mockFetch);
|
|
523
|
-
|
|
524
|
-
const result = await handleSharedAction(
|
|
525
|
-
{ action: "web_search", query: "failing" },
|
|
526
|
-
123,
|
|
527
|
-
);
|
|
528
|
-
|
|
529
|
-
expect(result?.ok).toBe(true);
|
|
530
|
-
expect(result?.text).toBe('No results for "failing".');
|
|
531
|
-
});
|
|
532
|
-
|
|
533
|
-
it("returns 'no results' when Brave returns empty results array", async () => {
|
|
534
|
-
process.env.TALON_BRAVE_API_KEY = "test-key";
|
|
535
|
-
const mockFetch = vi
|
|
536
|
-
.fn()
|
|
537
|
-
.mockResolvedValueOnce(
|
|
538
|
-
mockResponse({ ok: true, json: { web: { results: [] } } }),
|
|
539
|
-
)
|
|
540
|
-
.mockResolvedValueOnce(
|
|
541
|
-
mockResponse({ ok: true, json: { results: [] } }),
|
|
542
|
-
);
|
|
543
|
-
vi.stubGlobal("fetch", mockFetch);
|
|
544
|
-
|
|
545
|
-
const result = await handleSharedAction(
|
|
546
|
-
{ action: "web_search", query: "empty" },
|
|
547
|
-
123,
|
|
548
|
-
);
|
|
549
|
-
|
|
550
|
-
expect(result?.ok).toBe(true);
|
|
551
|
-
expect(result?.text).toBe('No results for "empty".');
|
|
552
|
-
});
|
|
553
|
-
|
|
554
|
-
it("handles Brave response with missing web field", async () => {
|
|
555
|
-
process.env.TALON_BRAVE_API_KEY = "test-key";
|
|
556
|
-
const mockFetch = vi
|
|
557
|
-
.fn()
|
|
558
|
-
.mockResolvedValueOnce(mockResponse({ ok: true, json: {} })) // no web field
|
|
559
|
-
.mockResolvedValueOnce(
|
|
560
|
-
mockResponse({
|
|
561
|
-
ok: true,
|
|
562
|
-
json: {
|
|
563
|
-
results: [
|
|
564
|
-
{ title: "FallbackR", url: "https://f.com", content: "fb" },
|
|
565
|
-
],
|
|
566
|
-
},
|
|
567
|
-
}),
|
|
568
|
-
);
|
|
569
|
-
vi.stubGlobal("fetch", mockFetch);
|
|
570
|
-
|
|
571
|
-
const result = await handleSharedAction(
|
|
572
|
-
{ action: "web_search", query: "test" },
|
|
573
|
-
123,
|
|
574
|
-
);
|
|
575
|
-
expect(result?.text).toContain("via SearXNG");
|
|
576
|
-
});
|
|
577
|
-
|
|
578
|
-
it("truncates long snippets to 200 chars", async () => {
|
|
579
|
-
process.env.TALON_BRAVE_API_KEY = "test-key";
|
|
580
|
-
const longDesc = "A".repeat(500);
|
|
581
|
-
const mockFetch = vi.fn().mockResolvedValueOnce(
|
|
582
|
-
mockResponse({
|
|
583
|
-
ok: true,
|
|
584
|
-
json: {
|
|
585
|
-
web: {
|
|
586
|
-
results: [
|
|
587
|
-
{ title: "Long", url: "https://l.com", description: longDesc },
|
|
588
|
-
],
|
|
589
|
-
},
|
|
590
|
-
},
|
|
591
|
-
}),
|
|
592
|
-
);
|
|
593
|
-
vi.stubGlobal("fetch", mockFetch);
|
|
594
|
-
|
|
595
|
-
const result = await handleSharedAction(
|
|
596
|
-
{ action: "web_search", query: "long" },
|
|
597
|
-
123,
|
|
598
|
-
);
|
|
599
|
-
// The snippet should be sliced to 200 chars
|
|
600
|
-
expect(result?.text).not.toContain("A".repeat(201));
|
|
601
|
-
expect(result?.text).toContain("A".repeat(200));
|
|
602
|
-
});
|
|
603
|
-
|
|
604
|
-
it("handles missing description in Brave results", async () => {
|
|
605
|
-
process.env.TALON_BRAVE_API_KEY = "test-key";
|
|
606
|
-
const mockFetch = vi.fn().mockResolvedValueOnce(
|
|
607
|
-
mockResponse({
|
|
608
|
-
ok: true,
|
|
609
|
-
json: {
|
|
610
|
-
web: { results: [{ title: "NoDesc", url: "https://nd.com" }] },
|
|
611
|
-
},
|
|
612
|
-
}),
|
|
613
|
-
);
|
|
614
|
-
vi.stubGlobal("fetch", mockFetch);
|
|
615
|
-
|
|
616
|
-
const result = await handleSharedAction(
|
|
617
|
-
{ action: "web_search", query: "nodesc" },
|
|
618
|
-
123,
|
|
619
|
-
);
|
|
620
|
-
expect(result?.ok).toBe(true);
|
|
621
|
-
expect(result?.text).toContain("NoDesc");
|
|
622
|
-
});
|
|
623
|
-
|
|
624
|
-
it("slices SearXNG results to limit", async () => {
|
|
625
|
-
const mockFetch = vi.fn().mockResolvedValueOnce(
|
|
626
|
-
mockResponse({
|
|
627
|
-
ok: true,
|
|
628
|
-
json: {
|
|
629
|
-
results: Array.from({ length: 20 }, (_, i) => ({
|
|
630
|
-
title: `R${i}`,
|
|
631
|
-
url: `https://r${i}.com`,
|
|
632
|
-
content: `c${i}`,
|
|
633
|
-
})),
|
|
634
|
-
},
|
|
635
|
-
}),
|
|
636
|
-
);
|
|
637
|
-
vi.stubGlobal("fetch", mockFetch);
|
|
638
|
-
|
|
639
|
-
const result = await handleSharedAction(
|
|
640
|
-
{ action: "web_search", query: "many", limit: 3 },
|
|
641
|
-
123,
|
|
642
|
-
);
|
|
643
|
-
// Should only contain 3 results (numbered 1-3)
|
|
644
|
-
expect(result?.text).toContain("1. R0");
|
|
645
|
-
expect(result?.text).toContain("3. R2");
|
|
646
|
-
expect(result?.text).not.toContain("4. R3");
|
|
647
|
-
});
|
|
648
|
-
});
|
|
649
|
-
|
|
650
276
|
// ════════════════════════════════════════════════════════════════════════
|
|
651
277
|
// fetch_url
|
|
652
278
|
// ════════════════════════════════════════════════════════════════════════
|
|
@@ -2082,14 +1708,13 @@ describe("gateway shared actions", () => {
|
|
|
2082
1708
|
});
|
|
2083
1709
|
});
|
|
2084
1710
|
|
|
2085
|
-
// ── Additional branch coverage for fetch_url
|
|
1711
|
+
// ── Additional branch coverage for fetch_url ──────────────────────────────
|
|
2086
1712
|
|
|
2087
1713
|
describe("gateway-actions — additional branch coverage", () => {
|
|
2088
1714
|
let originalFetch: typeof globalThis.fetch;
|
|
2089
1715
|
|
|
2090
1716
|
beforeEach(() => {
|
|
2091
1717
|
originalFetch = globalThis.fetch;
|
|
2092
|
-
delete process.env.TALON_BRAVE_API_KEY;
|
|
2093
1718
|
});
|
|
2094
1719
|
|
|
2095
1720
|
afterEach(() => {
|
|
@@ -2144,51 +1769,4 @@ describe("gateway-actions — additional branch coverage", () => {
|
|
|
2144
1769
|
// Should succeed (downloaded as bin), covering ct ?? "" right side and ct.split("/")[1] ?? "file" right side
|
|
2145
1770
|
expect(result?.ok).toBe(true);
|
|
2146
1771
|
});
|
|
2147
|
-
|
|
2148
|
-
it("handles search result with missing content field (line 113 r.content ?? '' branch)", async () => {
|
|
2149
|
-
const mockFetch = vi.fn().mockResolvedValueOnce({
|
|
2150
|
-
ok: true,
|
|
2151
|
-
status: 200,
|
|
2152
|
-
headers: new Headers(),
|
|
2153
|
-
json: async () => ({
|
|
2154
|
-
results: [
|
|
2155
|
-
{ title: "Result", url: "https://example.com", content: undefined },
|
|
2156
|
-
],
|
|
2157
|
-
}),
|
|
2158
|
-
text: async () => "",
|
|
2159
|
-
arrayBuffer: async () => new ArrayBuffer(0),
|
|
2160
|
-
} as unknown as Response);
|
|
2161
|
-
vi.stubGlobal("fetch", mockFetch);
|
|
2162
|
-
process.env.TALON_SEARXNG_URL = "http://localhost:8080";
|
|
2163
|
-
|
|
2164
|
-
const result = await handleSharedAction(
|
|
2165
|
-
{ action: "web_search", query: "test" },
|
|
2166
|
-
123,
|
|
2167
|
-
);
|
|
2168
|
-
expect(result?.ok).toBe(true);
|
|
2169
|
-
// snippet should be "" (from ?? "")
|
|
2170
|
-
expect(result?.text).toBeDefined();
|
|
2171
|
-
});
|
|
2172
|
-
|
|
2173
|
-
it("handles search response with no results array (line 113 data.results ?? [] branch)", async () => {
|
|
2174
|
-
const mockFetch = vi.fn().mockResolvedValueOnce({
|
|
2175
|
-
ok: true,
|
|
2176
|
-
status: 200,
|
|
2177
|
-
headers: new Headers(),
|
|
2178
|
-
json: async () => ({
|
|
2179
|
-
/* no results property */
|
|
2180
|
-
}),
|
|
2181
|
-
text: async () => "",
|
|
2182
|
-
arrayBuffer: async () => new ArrayBuffer(0),
|
|
2183
|
-
} as unknown as Response);
|
|
2184
|
-
vi.stubGlobal("fetch", mockFetch);
|
|
2185
|
-
process.env.TALON_SEARXNG_URL = "http://localhost:8080";
|
|
2186
|
-
|
|
2187
|
-
const result = await handleSharedAction(
|
|
2188
|
-
{ action: "web_search", query: "empty" },
|
|
2189
|
-
123,
|
|
2190
|
-
);
|
|
2191
|
-
expect(result?.ok).toBe(true);
|
|
2192
|
-
expect(result?.text).toContain("No results for");
|
|
2193
|
-
});
|
|
2194
1772
|
});
|
|
@@ -244,10 +244,8 @@ describe("withRetry", () => {
|
|
|
244
244
|
|
|
245
245
|
describe("exhausting all retries", () => {
|
|
246
246
|
it("throws the last error after 3 total attempts for retryable errors", async () => {
|
|
247
|
-
let calls = 0;
|
|
248
247
|
const networkErr = talonErr("network");
|
|
249
248
|
const fn = vi.fn(async () => {
|
|
250
|
-
calls++;
|
|
251
249
|
throw networkErr;
|
|
252
250
|
});
|
|
253
251
|
|
|
@@ -257,9 +255,7 @@ describe("withRetry", () => {
|
|
|
257
255
|
});
|
|
258
256
|
|
|
259
257
|
it("throws the last error after 3 total attempts for overloaded errors", async () => {
|
|
260
|
-
let calls = 0;
|
|
261
258
|
const fn = vi.fn(async () => {
|
|
262
|
-
calls++;
|
|
263
259
|
throw talonErr("overloaded");
|
|
264
260
|
});
|
|
265
261
|
|
|
@@ -533,8 +533,6 @@ describe("handleTextMessage — integration via mock Context", () => {
|
|
|
533
533
|
});
|
|
534
534
|
|
|
535
535
|
describe("handlePhotoMessage — downloads and enqueues photo", () => {
|
|
536
|
-
let restoreFetch: () => void;
|
|
537
|
-
|
|
538
536
|
beforeEach(() => {
|
|
539
537
|
// Mock bot.api.getFile for file download
|
|
540
538
|
mockBot.api.getFile = vi.fn(async () => ({ file_path: "photos/test.jpg" }));
|
|
@@ -547,7 +545,6 @@ describe("handlePhotoMessage — downloads and enqueues photo", () => {
|
|
|
547
545
|
headers: { get: (_name: string) => null },
|
|
548
546
|
arrayBuffer: async () => jpegBuf.buffer,
|
|
549
547
|
}));
|
|
550
|
-
restoreFetch = () => {};
|
|
551
548
|
vi.stubGlobal("fetch", mockFetch);
|
|
552
549
|
|
|
553
550
|
executeMock.mockResolvedValue({
|
|
@@ -795,7 +792,6 @@ describe("rate limiting — isUserRateLimited via handleTextMessage", () => {
|
|
|
795
792
|
}
|
|
796
793
|
|
|
797
794
|
// 16th message should be rate limited (return early without enqueuing)
|
|
798
|
-
const before = executeMock.mock.calls.length;
|
|
799
795
|
await handleTextMessage(makeCtx(15), mockBot, mockConfig);
|
|
800
796
|
|
|
801
797
|
// Wait to confirm no debounce fires for the 16th chat
|
|
@@ -197,6 +197,7 @@ describe("forceHeartbeat", () => {
|
|
|
197
197
|
|
|
198
198
|
// Make agent throw
|
|
199
199
|
queryMock.mockImplementationOnce(async function* () {
|
|
200
|
+
yield; // satisfy require-yield
|
|
200
201
|
throw new Error("Agent exploded");
|
|
201
202
|
});
|
|
202
203
|
|
|
@@ -217,6 +218,7 @@ describe("forceHeartbeat", () => {
|
|
|
217
218
|
existsSyncMock.mockReturnValue(false);
|
|
218
219
|
|
|
219
220
|
queryMock.mockImplementationOnce(async function* () {
|
|
221
|
+
yield; // satisfy require-yield
|
|
220
222
|
throw new Error("Boom");
|
|
221
223
|
});
|
|
222
224
|
|
|
@@ -340,6 +342,7 @@ describe("awaitCurrentRun", () => {
|
|
|
340
342
|
});
|
|
341
343
|
|
|
342
344
|
queryMock.mockImplementationOnce(async function* () {
|
|
345
|
+
yield; // satisfy require-yield
|
|
343
346
|
await agentPromise;
|
|
344
347
|
});
|
|
345
348
|
|