@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.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +2 -2
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/chats/route.js +3 -3
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/chats/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/lookup/route.js +3 -3
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/lookup/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/pair/route.js +3 -3
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/pair/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/route.js +3 -3
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/status/route.js +3 -3
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/builtin-tools/route.js +218 -7
- package/.next/standalone/.next/server/app/api/v1/builtin-tools/route.js.map +1 -1
- package/.next/standalone/.next/server/app/api/v1/events/route.js +3 -3
- package/.next/standalone/.next/server/app/api/v1/events/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/extension/agents/route.js +8 -1
- package/.next/standalone/.next/server/app/api/v1/extension/agents/route.js.map +1 -1
- package/.next/standalone/.next/server/app/api/v1/extension/fill/route.js +8 -1
- package/.next/standalone/.next/server/app/api/v1/extension/fill/route.js.map +1 -1
- package/.next/standalone/.next/server/app/api/v1/extension/refine/route.js +8 -1
- package/.next/standalone/.next/server/app/api/v1/extension/refine/route.js.map +1 -1
- package/.next/standalone/.next/server/app/api/v1/extension/turn/route.js +8 -1
- package/.next/standalone/.next/server/app/api/v1/extension/turn/route.js.map +1 -1
- package/.next/standalone/.next/server/app/api/v1/extensions/route.js +2 -2
- package/.next/standalone/.next/server/app/api/v1/extensions/tools/[name]/secrets/route.js +2 -2
- package/.next/standalone/.next/server/app/api/v1/page-capture/route.js +37 -3
- package/.next/standalone/.next/server/app/api/v1/page-capture/route.js.map +1 -1
- package/.next/standalone/.next/server/app/api/v1/tools/route.js +2 -2
- package/.next/standalone/.next/server/app/page.js +10 -18
- package/.next/standalone/.next/server/app/page.js.map +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/210.js +1 -1
- package/.next/standalone/.next/server/chunks/239.js +5335 -5230
- package/.next/standalone/.next/server/chunks/239.js.map +1 -1
- package/.next/standalone/.next/server/chunks/{1683.js → 241.js} +210 -36
- package/.next/standalone/.next/server/chunks/241.js.map +1 -0
- package/.next/standalone/.next/server/chunks/{8135.js → 2539.js} +218 -36
- package/.next/standalone/.next/server/chunks/2539.js.map +1 -0
- package/.next/standalone/.next/server/chunks/4631.js +218 -7
- package/.next/standalone/.next/server/chunks/4631.js.map +1 -1
- package/.next/standalone/.next/server/chunks/8866.js +13389 -13073
- package/.next/standalone/.next/server/chunks/8866.js.map +1 -1
- package/.next/standalone/.next/server/chunks/9032.js +1 -1
- package/.next/standalone/.next/server/chunks/9032.js.map +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +2 -2
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/app/{page-62e0d5f2404b403b.js → page-74846c864241b96d.js} +11 -19
- package/.next/standalone/.next/static/chunks/app/page-74846c864241b96d.js.map +1 -0
- package/.next/standalone/package.json +2 -1
- package/CHANGELOG.md +98 -0
- package/README.md +51 -26
- package/components/chat/InputBar.tsx +10 -1
- package/components/ui/BootScreen.tsx +0 -10
- package/lib/agents/agent-turn.ts +9 -0
- package/lib/agents/prepare/request.ts +9 -0
- package/lib/agents/run-thread.ts +9 -1
- package/lib/api/extension-turn.ts +7 -0
- package/lib/api/page-capture.test.ts +58 -0
- package/lib/api/page-capture.ts +31 -1
- package/lib/bridges/attachment-store.test.ts +440 -0
- package/lib/bridges/attachment-store.ts +184 -0
- package/lib/bridges/whatsapp.ts +50 -32
- package/lib/tools/async-results-tool.ts +114 -0
- package/lib/tools/async-results.test.ts +481 -0
- package/lib/tools/async-results.ts +165 -0
- package/lib/tools/builtins.ts +1 -0
- package/lib/tools/wallclock.ts +114 -8
- package/package.json +2 -1
- package/.next/standalone/.next/server/chunks/1683.js.map +0 -1
- package/.next/standalone/.next/server/chunks/8135.js.map +0 -1
- package/.next/standalone/.next/static/chunks/app/page-62e0d5f2404b403b.js.map +0 -1
- /package/.next/standalone/.next/static/{2xWP8843jbntFGKLnHK6R → AV5AO0yTRABo-NgwxhDe7}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{2xWP8843jbntFGKLnHK6R → AV5AO0yTRABo-NgwxhDe7}/_ssgManifest.js +0 -0
package/lib/bridges/whatsapp.ts
CHANGED
|
@@ -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
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
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
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
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
|
-
|
|
631
|
-
|
|
632
|
-
|
|
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
|
-
|
|
643
|
-
|
|
644
|
-
|
|
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
|
-
|
|
660
|
-
|
|
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]);
|