@circuitwall/jarela 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.
Files changed (96) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +2 -2
  3. package/.next/standalone/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  5. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  6. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  7. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  14. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  15. package/.next/standalone/.next/server/app/_not-found.rsc +1 -1
  16. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  18. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  20. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  21. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  22. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/chats/route.js +3 -3
  23. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/chats/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/lookup/route.js +3 -3
  25. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/lookup/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/pair/route.js +3 -3
  27. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/pair/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/route.js +3 -3
  29. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/status/route.js +3 -3
  31. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/status/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/v1/builtin-tools/route.js +218 -7
  33. package/.next/standalone/.next/server/app/api/v1/builtin-tools/route.js.map +1 -1
  34. package/.next/standalone/.next/server/app/api/v1/events/route.js +3 -3
  35. package/.next/standalone/.next/server/app/api/v1/events/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/v1/extension/agents/route.js +8 -1
  37. package/.next/standalone/.next/server/app/api/v1/extension/agents/route.js.map +1 -1
  38. package/.next/standalone/.next/server/app/api/v1/extension/fill/route.js +8 -1
  39. package/.next/standalone/.next/server/app/api/v1/extension/fill/route.js.map +1 -1
  40. package/.next/standalone/.next/server/app/api/v1/extension/refine/route.js +8 -1
  41. package/.next/standalone/.next/server/app/api/v1/extension/refine/route.js.map +1 -1
  42. package/.next/standalone/.next/server/app/api/v1/extension/turn/route.js +8 -1
  43. package/.next/standalone/.next/server/app/api/v1/extension/turn/route.js.map +1 -1
  44. package/.next/standalone/.next/server/app/api/v1/extensions/route.js +2 -2
  45. package/.next/standalone/.next/server/app/api/v1/extensions/tools/[name]/secrets/route.js +2 -2
  46. package/.next/standalone/.next/server/app/api/v1/page-capture/route.js +37 -3
  47. package/.next/standalone/.next/server/app/api/v1/page-capture/route.js.map +1 -1
  48. package/.next/standalone/.next/server/app/api/v1/tools/route.js +2 -2
  49. package/.next/standalone/.next/server/app/page.js +10 -18
  50. package/.next/standalone/.next/server/app/page.js.map +1 -1
  51. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  52. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  53. package/.next/standalone/.next/server/chunks/210.js +1 -1
  54. package/.next/standalone/.next/server/chunks/239.js +5335 -5230
  55. package/.next/standalone/.next/server/chunks/239.js.map +1 -1
  56. package/.next/standalone/.next/server/chunks/{1683.js → 241.js} +210 -36
  57. package/.next/standalone/.next/server/chunks/241.js.map +1 -0
  58. package/.next/standalone/.next/server/chunks/{8135.js → 2539.js} +218 -36
  59. package/.next/standalone/.next/server/chunks/2539.js.map +1 -0
  60. package/.next/standalone/.next/server/chunks/4631.js +218 -7
  61. package/.next/standalone/.next/server/chunks/4631.js.map +1 -1
  62. package/.next/standalone/.next/server/chunks/8866.js +13389 -13073
  63. package/.next/standalone/.next/server/chunks/8866.js.map +1 -1
  64. package/.next/standalone/.next/server/chunks/9032.js +1 -1
  65. package/.next/standalone/.next/server/chunks/9032.js.map +1 -1
  66. package/.next/standalone/.next/server/middleware-build-manifest.js +2 -2
  67. package/.next/standalone/.next/server/pages/404.html +1 -1
  68. package/.next/standalone/.next/server/pages/500.html +1 -1
  69. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  70. package/.next/standalone/.next/static/chunks/app/{page-62e0d5f2404b403b.js → page-74846c864241b96d.js} +11 -19
  71. package/.next/standalone/.next/static/chunks/app/page-74846c864241b96d.js.map +1 -0
  72. package/.next/standalone/package.json +2 -1
  73. package/CHANGELOG.md +98 -0
  74. package/README.md +51 -26
  75. package/components/chat/InputBar.tsx +10 -1
  76. package/components/ui/BootScreen.tsx +0 -10
  77. package/lib/agents/agent-turn.ts +9 -0
  78. package/lib/agents/prepare/request.ts +9 -0
  79. package/lib/agents/run-thread.ts +9 -1
  80. package/lib/api/extension-turn.ts +7 -0
  81. package/lib/api/page-capture.test.ts +58 -0
  82. package/lib/api/page-capture.ts +31 -1
  83. package/lib/bridges/attachment-store.test.ts +440 -0
  84. package/lib/bridges/attachment-store.ts +184 -0
  85. package/lib/bridges/whatsapp.ts +50 -32
  86. package/lib/tools/async-results-tool.ts +114 -0
  87. package/lib/tools/async-results.test.ts +481 -0
  88. package/lib/tools/async-results.ts +165 -0
  89. package/lib/tools/builtins.ts +1 -0
  90. package/lib/tools/wallclock.ts +114 -8
  91. package/package.json +2 -1
  92. package/.next/standalone/.next/server/chunks/1683.js.map +0 -1
  93. package/.next/standalone/.next/server/chunks/8135.js.map +0 -1
  94. package/.next/standalone/.next/static/chunks/app/page-62e0d5f2404b403b.js.map +0 -1
  95. /package/.next/standalone/.next/static/{2xWP8843jbntFGKLnHK6R → AV5AO0yTRABo-NgwxhDe7}/_buildManifest.js +0 -0
  96. /package/.next/standalone/.next/static/{2xWP8843jbntFGKLnHK6R → AV5AO0yTRABo-NgwxhDe7}/_ssgManifest.js +0 -0
@@ -27,6 +27,7 @@ import { createRequire } from "node:module";
27
27
  import { ensureBridgeAuthDir, findRoute, removeBridgeAuthDir } from "@/lib/stores/bridges";
28
28
  import type { BridgeAdapter, ChatInfo, InboundHandler, StatusHandler, InboundMessage, StatusUpdate } from "./types";
29
29
  import type { ContentPart } from "@/lib/tools/types";
30
+ import { saveBridgeAttachment, shouldInline } from "./attachment-store";
30
31
 
31
32
  // Baileys + qrcode are dev-time-installed peer libs. We never import their
32
33
  // types directly — both modules are loaded via dynamic `import()` inside
@@ -597,14 +598,44 @@ export class WhatsAppBridgeAdapter implements BridgeAdapter {
597
598
  }
598
599
  };
599
600
 
601
+ // Spill helper: persist any buffer we can't (or shouldn't) inline,
602
+ // and append a text pointer the agent can act on with file_read.
603
+ // Returns true if the buffer was spilled so callers can decide
604
+ // whether to also push an inline ContentPart.
605
+ const messageId = (rawMessage as { key?: { id?: string } })?.key?.id ?? null;
606
+ const spill = async (
607
+ buf: Buffer,
608
+ filename: string,
609
+ mime: string,
610
+ label: string,
611
+ ): Promise<void> => {
612
+ try {
613
+ const saved = await saveBridgeAttachment({
614
+ bridge_id: this.bridge_id,
615
+ filename,
616
+ media_type: mime,
617
+ message_id: messageId,
618
+ buffer: buf,
619
+ });
620
+ const sizeKb = Math.max(1, Math.round(saved.size / 1024));
621
+ text = (text ? text + "\n" : "")
622
+ + `[Attached ${label}: ${filename} (${mime}, ${sizeKb} KB) saved locally at ${saved.abs_path}. `
623
+ + `Use file_read on that path to inspect the contents.]`;
624
+ } catch (err) {
625
+ const m = err instanceof Error ? err.message : String(err);
626
+ console.warn(`[bridge ${this.bridge_id}] failed to spill ${label} from ${remote_jid}: ${m}`);
627
+ }
628
+ };
629
+
600
630
  if (inner.imageMessage) {
601
631
  const buf = await download("image");
602
632
  if (buf) {
603
- attachments.push({
604
- type: "image",
605
- media_type: sanitizeMediaType(inner.imageMessage.mimetype, "image", "image/jpeg"),
606
- data: buf.toString("base64"),
607
- });
633
+ const mime = sanitizeMediaType(inner.imageMessage.mimetype, "image", "image/jpeg");
634
+ if (shouldInline(mime, buf.length)) {
635
+ attachments.push({ type: "image", media_type: mime, data: buf.toString("base64") });
636
+ } else {
637
+ await spill(buf, `image-${messageId ?? Date.now()}.${mime.split("/")[1] ?? "bin"}`, mime, "image");
638
+ }
608
639
  }
609
640
  }
610
641
 
@@ -615,11 +646,12 @@ export class WhatsAppBridgeAdapter implements BridgeAdapter {
615
646
  // models actually describe them. Animated stickers go through as
616
647
  // their raw webp; providers that can't decode animated webp will
617
648
  // typically render the first frame.
618
- attachments.push({
619
- type: "image",
620
- media_type: sanitizeMediaType(inner.stickerMessage.mimetype, "image", "image/webp"),
621
- data: buf.toString("base64"),
622
- });
649
+ const mime = sanitizeMediaType(inner.stickerMessage.mimetype, "image", "image/webp");
650
+ if (shouldInline(mime, buf.length)) {
651
+ attachments.push({ type: "image", media_type: mime, data: buf.toString("base64") });
652
+ } else {
653
+ await spill(buf, `sticker-${messageId ?? Date.now()}.webp`, mime, "sticker");
654
+ }
623
655
  }
624
656
  }
625
657
 
@@ -627,24 +659,18 @@ export class WhatsAppBridgeAdapter implements BridgeAdapter {
627
659
  const isVoice = !!inner.audioMessage.ptt;
628
660
  const buf = await download(isVoice ? "voice" : "audio");
629
661
  if (buf) {
630
- attachments.push({
631
- type: "file",
632
- name: isVoice ? "voice-note" : "audio",
633
- media_type: sanitizeMediaType(inner.audioMessage.mimetype, "audio", "audio/ogg"),
634
- data: buf.toString("base64"),
635
- });
662
+ const mime = sanitizeMediaType(inner.audioMessage.mimetype, "audio", "audio/ogg");
663
+ const ext = mime.split("/")[1]?.replace(/^x-/, "") ?? "ogg";
664
+ await spill(buf, `${isVoice ? "voice-note" : "audio"}-${messageId ?? Date.now()}.${ext}`, mime, isVoice ? "voice note" : "audio");
636
665
  }
637
666
  }
638
667
 
639
668
  if (inner.videoMessage) {
640
669
  const buf = await download("video");
641
670
  if (buf) {
642
- attachments.push({
643
- type: "file",
644
- name: "video",
645
- media_type: sanitizeMediaType(inner.videoMessage.mimetype, "video", "video/mp4"),
646
- data: buf.toString("base64"),
647
- });
671
+ const mime = sanitizeMediaType(inner.videoMessage.mimetype, "video", "video/mp4");
672
+ const ext = mime.split("/")[1] ?? "mp4";
673
+ await spill(buf, `video-${messageId ?? Date.now()}.${ext}`, mime, "video");
648
674
  }
649
675
  }
650
676
 
@@ -656,16 +682,8 @@ export class WhatsAppBridgeAdapter implements BridgeAdapter {
656
682
  "document",
657
683
  "application/octet-stream",
658
684
  );
659
- // The LLM layer (lib/agents/llm.ts) inlines text/* and
660
- // application/json file parts directly as their `data` string. For
661
- // those we must store the decoded UTF-8 contents, not base64.
662
- const inlineAsText = mime.startsWith("text/") || mime === "application/json";
663
- attachments.push({
664
- type: "file",
665
- name: inner.documentMessage.fileName || inner.documentMessage.title || "document",
666
- media_type: mime,
667
- data: inlineAsText ? buf.toString("utf8") : buf.toString("base64"),
668
- });
685
+ const filename = inner.documentMessage.fileName || inner.documentMessage.title || `document-${messageId ?? Date.now()}`;
686
+ await spill(buf, filename, mime, "document");
669
687
  }
670
688
  }
671
689
 
@@ -0,0 +1,114 @@
1
+ // Built-in tools that pair with the wallclock wrapper's `async_run` mode.
2
+ //
3
+ // `async_run: true` on any tool returns immediately with a key; the
4
+ // agent later calls these tools to retrieve the result.
5
+
6
+ import { tool } from "@langchain/core/tools";
7
+ import { z } from "zod";
8
+ import { registerTools } from "./registry";
9
+ import {
10
+ consumeAsyncResult,
11
+ getAsyncResult,
12
+ listAsyncResults,
13
+ type AsyncResultRecord,
14
+ } from "./async-results";
15
+
16
+ function serialize(rec: AsyncResultRecord, includeResult: boolean): Record<string, unknown> {
17
+ const out: Record<string, unknown> = {
18
+ key: rec.key,
19
+ tool: rec.tool,
20
+ status: rec.status,
21
+ started_at: rec.started_at,
22
+ finished_at: rec.finished_at,
23
+ elapsed_ms: (rec.finished_at ?? Date.now()) - rec.started_at,
24
+ };
25
+ if (includeResult && rec.status === "done") out.result = rec.result;
26
+ if (includeResult && rec.status === "error") out.error = rec.error;
27
+ return out;
28
+ }
29
+
30
+ async function waitForFinish(key: string, waitMs: number): Promise<AsyncResultRecord | null> {
31
+ const deadline = Date.now() + Math.max(0, waitMs);
32
+ // 50ms poll — cheap on a Map.get, and bounded by waitMs.
33
+ while (Date.now() < deadline) {
34
+ const rec = getAsyncResult(key);
35
+ if (!rec) return null;
36
+ if (rec.status !== "pending") return rec;
37
+ await new Promise((r) => setTimeout(r, 50));
38
+ }
39
+ return getAsyncResult(key);
40
+ }
41
+
42
+ export const toolResultGetTool = tool(
43
+ async ({ key, wait_ms, consume }) => {
44
+ let rec: AsyncResultRecord | null = getAsyncResult(key);
45
+ if (!rec) {
46
+ return JSON.stringify({
47
+ ok: false,
48
+ status: "unknown",
49
+ key,
50
+ error: "no async result for that key (it may have expired or never existed)",
51
+ });
52
+ }
53
+ if (rec.status === "pending" && typeof wait_ms === "number" && wait_ms > 0) {
54
+ rec = (await waitForFinish(key, wait_ms)) ?? rec;
55
+ }
56
+ if (!rec) {
57
+ return JSON.stringify({ ok: false, status: "unknown", key });
58
+ }
59
+ const finished = rec.status !== "pending";
60
+ if (consume && finished) {
61
+ consumeAsyncResult(key);
62
+ }
63
+ return JSON.stringify({ ok: true, ...serialize(rec, /* includeResult */ true) });
64
+ },
65
+ {
66
+ name: "tool_result_get",
67
+ description:
68
+ "Retrieve the result of a previously async-fired tool call by its key. " +
69
+ "Pass `wait_ms` to short-poll up to that long for a pending call to finish. " +
70
+ "Pass `consume: true` to delete the entry after reading a finished result. " +
71
+ "Status will be 'pending' (still running), 'done' (success — `result` populated), " +
72
+ "'error' (failed — `error` populated), or 'unknown' (no such key).",
73
+ schema: z.object({
74
+ key: z.string().describe("The key returned by the original async tool call."),
75
+ wait_ms: z
76
+ .number()
77
+ .int()
78
+ .min(0)
79
+ .max(60_000)
80
+ .optional()
81
+ .describe("Optional short-poll budget (0–60000 ms). Returns as soon as the call finishes or the budget elapses."),
82
+ consume: z
83
+ .boolean()
84
+ .optional()
85
+ .describe("If true and the call has finished, delete the entry after returning it. Default false."),
86
+ }),
87
+ },
88
+ );
89
+
90
+ export const toolResultListTool = tool(
91
+ async ({ status }) => {
92
+ let recs = listAsyncResults();
93
+ if (status) recs = recs.filter((r) => r.status === status);
94
+ return JSON.stringify({
95
+ ok: true,
96
+ count: recs.length,
97
+ // Lightweight summary only — full result/error stays behind tool_result_get.
98
+ results: recs.map((r) => serialize(r, /* includeResult */ false)),
99
+ });
100
+ },
101
+ {
102
+ name: "tool_result_list",
103
+ description:
104
+ "List currently tracked async tool results (newest first). Useful when you've forgotten " +
105
+ "a key or want a quick status check across pending background calls. Optional `status` " +
106
+ "filter narrows to 'pending' / 'done' / 'error'.",
107
+ schema: z.object({
108
+ status: z.enum(["pending", "done", "error"]).optional()
109
+ .describe("Optional status filter."),
110
+ }),
111
+ },
112
+ );
113
+
114
+ registerTools("Agent", "read", [toolResultGetTool, toolResultListTool]);