@contextableai/clawg-ui 0.2.0 → 0.2.2
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/CHANGELOG.md +14 -1
- package/index.ts +25 -5
- package/package.json +2 -2
- package/src/client-tools.ts +9 -0
- package/src/http-handler.ts +12 -1
- package/src/tool-store.ts +31 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.2 (2026-02-05)
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- Include `messageId` in `TOOL_CALL_RESULT` events as required by AG-UI client v0.0.43 Zod schema
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- Debug logging throughout tool call flow for easier troubleshooting
|
|
10
|
+
|
|
11
|
+
## 0.2.1 (2026-02-05)
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- Return HTTP 429 `rate_limit` error when max pending pairing requests (3) is reached, instead of returning an empty pairing code
|
|
15
|
+
|
|
3
16
|
## 0.2.0 (2026-02-04)
|
|
4
17
|
|
|
5
18
|
### Added
|
|
@@ -13,7 +26,7 @@
|
|
|
13
26
|
|
|
14
27
|
### Security
|
|
15
28
|
- Device tokens are HMAC-signed and do not expose the gateway's master secret
|
|
16
|
-
- Pending pairing requests expire after
|
|
29
|
+
- Pending pairing requests expire after 1 hour (max 3 per channel)
|
|
17
30
|
- Each device requires explicit approval by the gateway owner
|
|
18
31
|
|
|
19
32
|
## 0.1.1 (2026-02-03)
|
package/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { createAguiHttpHandler } from "./src/http-handler.js";
|
|
|
8
8
|
import { clawgUiToolFactory } from "./src/client-tools.js";
|
|
9
9
|
import {
|
|
10
10
|
getWriter,
|
|
11
|
+
getMessageId,
|
|
11
12
|
pushToolCallId,
|
|
12
13
|
popToolCallId,
|
|
13
14
|
isClientTool,
|
|
@@ -32,16 +33,25 @@ const plugin = {
|
|
|
32
33
|
// For server tools: TOOL_CALL_END is emitted later by tool_result_persist.
|
|
33
34
|
api.on("before_tool_call", (event, ctx) => {
|
|
34
35
|
const sk = ctx.sessionKey;
|
|
35
|
-
|
|
36
|
+
console.log(`[clawg-ui] before_tool_call: tool=${event.toolName}, sessionKey=${sk ?? "none"}, hasParams=${!!(event.params && Object.keys(event.params).length > 0)}, params=${JSON.stringify(event.params ?? {})}`);
|
|
37
|
+
if (!sk) {
|
|
38
|
+
console.log(`[clawg-ui] before_tool_call: skipping, no sessionKey`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
36
41
|
const writer = getWriter(sk);
|
|
37
|
-
if (!writer)
|
|
42
|
+
if (!writer) {
|
|
43
|
+
console.log(`[clawg-ui] before_tool_call: skipping, no writer for sessionKey=${sk}`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
38
46
|
const toolCallId = `tool-${randomUUID()}`;
|
|
47
|
+
console.log(`[clawg-ui] before_tool_call: emitting TOOL_CALL_START, toolCallId=${toolCallId}`);
|
|
39
48
|
writer({
|
|
40
49
|
type: EventType.TOOL_CALL_START,
|
|
41
50
|
toolCallId,
|
|
42
51
|
toolCallName: event.toolName,
|
|
43
52
|
});
|
|
44
53
|
if (event.params && Object.keys(event.params).length > 0) {
|
|
54
|
+
console.log(`[clawg-ui] before_tool_call: emitting TOOL_CALL_ARGS, params=${JSON.stringify(event.params)}`);
|
|
45
55
|
writer({
|
|
46
56
|
type: EventType.TOOL_CALL_ARGS,
|
|
47
57
|
toolCallId,
|
|
@@ -52,6 +62,7 @@ const plugin = {
|
|
|
52
62
|
if (isClientTool(sk, event.toolName)) {
|
|
53
63
|
// Client tool: emit TOOL_CALL_END now. The run will finish and the
|
|
54
64
|
// client initiates a new run with the tool result.
|
|
65
|
+
console.log(`[clawg-ui] before_tool_call: client tool detected, emitting TOOL_CALL_END immediately`);
|
|
55
66
|
writer({
|
|
56
67
|
type: EventType.TOOL_CALL_END,
|
|
57
68
|
toolCallId,
|
|
@@ -60,21 +71,30 @@ const plugin = {
|
|
|
60
71
|
} else {
|
|
61
72
|
// Server tool: push ID so tool_result_persist can emit
|
|
62
73
|
// TOOL_CALL_RESULT + TOOL_CALL_END after execute() completes.
|
|
74
|
+
console.log(`[clawg-ui] before_tool_call: server tool, pushing toolCallId to stack`);
|
|
63
75
|
pushToolCallId(sk, toolCallId);
|
|
64
76
|
}
|
|
65
77
|
});
|
|
66
78
|
|
|
67
79
|
// Emit TOOL_CALL_RESULT + TOOL_CALL_END for server-side tools only.
|
|
68
80
|
// Client tools already emitted TOOL_CALL_END in before_tool_call.
|
|
69
|
-
api.on("tool_result_persist", (
|
|
81
|
+
api.on("tool_result_persist", (event, ctx) => {
|
|
70
82
|
const sk = ctx.sessionKey;
|
|
71
|
-
|
|
83
|
+
console.log(`[clawg-ui] tool_result_persist: sessionKey=${sk ?? "none"}, event=${JSON.stringify(event)}`);
|
|
84
|
+
if (!sk) {
|
|
85
|
+
console.log(`[clawg-ui] tool_result_persist: skipping, no sessionKey`);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
72
88
|
const writer = getWriter(sk);
|
|
73
89
|
const toolCallId = popToolCallId(sk);
|
|
74
|
-
|
|
90
|
+
const messageId = getMessageId(sk);
|
|
91
|
+
console.log(`[clawg-ui] tool_result_persist: writer=${writer ? "present" : "missing"}, toolCallId=${toolCallId ?? "none"}, messageId=${messageId ?? "none"}`);
|
|
92
|
+
if (writer && toolCallId && messageId) {
|
|
93
|
+
console.log(`[clawg-ui] tool_result_persist: emitting TOOL_CALL_RESULT and TOOL_CALL_END`);
|
|
75
94
|
writer({
|
|
76
95
|
type: EventType.TOOL_CALL_RESULT,
|
|
77
96
|
toolCallId,
|
|
97
|
+
messageId,
|
|
78
98
|
content: "",
|
|
79
99
|
});
|
|
80
100
|
writer({
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contextableai/clawg-ui",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "AG-UI protocol channel plugin for OpenClaw — connect CopilotKit and AG-UI clients to your OpenClaw gateway",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "https://github.com/contextable/clawg-ui"
|
|
9
|
+
"url": "git+https://github.com/contextable/clawg-ui.git"
|
|
10
10
|
},
|
|
11
11
|
"keywords": [
|
|
12
12
|
"openclaw",
|
package/src/client-tools.ts
CHANGED
|
@@ -10,13 +10,21 @@ import { popTools } from "./tool-store.js";
|
|
|
10
10
|
*/
|
|
11
11
|
export function clawgUiToolFactory(ctx: { sessionKey?: string }) {
|
|
12
12
|
const sessionKey = ctx.sessionKey;
|
|
13
|
+
console.log(`[clawg-ui] clawgUiToolFactory: sessionKey=${sessionKey ?? "none"}`);
|
|
13
14
|
if (!sessionKey) {
|
|
15
|
+
console.log(`[clawg-ui] clawgUiToolFactory: returning null, no sessionKey`);
|
|
14
16
|
return null;
|
|
15
17
|
}
|
|
16
18
|
const clientTools = popTools(sessionKey);
|
|
19
|
+
console.log(`[clawg-ui] clawgUiToolFactory: popped ${clientTools.length} client tools`);
|
|
17
20
|
if (clientTools.length === 0) {
|
|
21
|
+
console.log(`[clawg-ui] clawgUiToolFactory: returning null, no client tools`);
|
|
18
22
|
return null;
|
|
19
23
|
}
|
|
24
|
+
console.log(`[clawg-ui] clawgUiToolFactory: creating ${clientTools.length} agent tools`);
|
|
25
|
+
for (const t of clientTools) {
|
|
26
|
+
console.log(`[clawg-ui] creating tool: name=${t.name}, description=${t.description ?? "(none)"}, hasParams=${!!t.parameters}, params=${JSON.stringify(t.parameters ?? {})}`);
|
|
27
|
+
}
|
|
20
28
|
return clientTools.map((t) => ({
|
|
21
29
|
name: t.name,
|
|
22
30
|
label: t.name,
|
|
@@ -28,6 +36,7 @@ export function clawgUiToolFactory(ctx: { sessionKey?: string }) {
|
|
|
28
36
|
// The run ends, and the client initiates a new run with the tool result.
|
|
29
37
|
// Return args so the agent loop can continue (the dispatcher will
|
|
30
38
|
// suppress any text output after a client tool call).
|
|
39
|
+
console.log(`[clawg-ui] client tool execute: name=${t.name}, args=${JSON.stringify(args)}`);
|
|
31
40
|
return {
|
|
32
41
|
content: [
|
|
33
42
|
{
|
package/src/http-handler.ts
CHANGED
|
@@ -240,6 +240,17 @@ export function createAguiHttpHandler(api: OpenClawPluginApi) {
|
|
|
240
240
|
pairingAdapter: aguiChannelPlugin.pairing,
|
|
241
241
|
});
|
|
242
242
|
|
|
243
|
+
// Rate limit reached - max pending requests exceeded
|
|
244
|
+
if (!pairingCode) {
|
|
245
|
+
sendJson(res, 429, {
|
|
246
|
+
error: {
|
|
247
|
+
type: "rate_limit",
|
|
248
|
+
message: "Too many pending pairing requests. Please wait for existing requests to expire (10 minutes) or ask the owner to approve/reject them.",
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
243
254
|
// Generate signed device token
|
|
244
255
|
const deviceToken = createDeviceToken(gatewaySecret, deviceId);
|
|
245
256
|
|
|
@@ -397,7 +408,7 @@ export function createAguiHttpHandler(api: OpenClawPluginApi) {
|
|
|
397
408
|
}
|
|
398
409
|
|
|
399
410
|
// Register SSE writer so before/after_tool_call hooks can emit AG-UI events
|
|
400
|
-
setWriter(sessionKey, writeEvent);
|
|
411
|
+
setWriter(sessionKey, writeEvent, messageId);
|
|
401
412
|
const storePath = runtime.channel.session.resolveStorePath(cfg.session?.store, {
|
|
402
413
|
agentId: route.agentId,
|
|
403
414
|
});
|
package/src/tool-store.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Tool } from "@ag-ui/core";
|
|
2
2
|
|
|
3
|
-
export type EventWriter = (event: Record<string, unknown>) => void;
|
|
3
|
+
export type EventWriter = (event: { type: string } & Record<string, unknown>) => void;
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Per-session store for:
|
|
@@ -15,27 +15,44 @@ const writerStore = new Map<string, EventWriter>();
|
|
|
15
15
|
// --- Client tools (for the plugin tool factory) ---
|
|
16
16
|
|
|
17
17
|
export function stashTools(sessionKey: string, tools: Tool[]): void {
|
|
18
|
+
console.log(`[clawg-ui] stashTools: sessionKey=${sessionKey}, toolCount=${tools.length}`);
|
|
19
|
+
for (const t of tools) {
|
|
20
|
+
console.log(`[clawg-ui] tool: name=${t.name}, description=${t.description ?? "(none)"}, hasParams=${!!t.parameters}, params=${JSON.stringify(t.parameters ?? {})}`);
|
|
21
|
+
}
|
|
18
22
|
toolStore.set(sessionKey, tools);
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
export function popTools(sessionKey: string): Tool[] {
|
|
22
26
|
const tools = toolStore.get(sessionKey) ?? [];
|
|
27
|
+
console.log(`[clawg-ui] popTools: sessionKey=${sessionKey}, tools=${tools.length}`);
|
|
23
28
|
toolStore.delete(sessionKey);
|
|
24
29
|
return tools;
|
|
25
30
|
}
|
|
26
31
|
|
|
27
32
|
// --- SSE event writer (for before/after_tool_call hooks) ---
|
|
28
33
|
|
|
29
|
-
|
|
34
|
+
const messageIdStore = new Map<string, string>();
|
|
35
|
+
|
|
36
|
+
export function setWriter(
|
|
37
|
+
sessionKey: string,
|
|
38
|
+
writer: EventWriter,
|
|
39
|
+
messageId: string,
|
|
40
|
+
): void {
|
|
30
41
|
writerStore.set(sessionKey, writer);
|
|
42
|
+
messageIdStore.set(sessionKey, messageId);
|
|
31
43
|
}
|
|
32
44
|
|
|
33
45
|
export function getWriter(sessionKey: string): EventWriter | undefined {
|
|
34
46
|
return writerStore.get(sessionKey);
|
|
35
47
|
}
|
|
36
48
|
|
|
49
|
+
export function getMessageId(sessionKey: string): string | undefined {
|
|
50
|
+
return messageIdStore.get(sessionKey);
|
|
51
|
+
}
|
|
52
|
+
|
|
37
53
|
export function clearWriter(sessionKey: string): void {
|
|
38
54
|
writerStore.delete(sessionKey);
|
|
55
|
+
messageIdStore.delete(sessionKey);
|
|
39
56
|
}
|
|
40
57
|
|
|
41
58
|
// --- Pending toolCallId stack (before_tool_call pushes, tool_result_persist pops) ---
|
|
@@ -51,11 +68,13 @@ export function pushToolCallId(sessionKey: string, toolCallId: string): void {
|
|
|
51
68
|
pendingStacks.set(sessionKey, stack);
|
|
52
69
|
}
|
|
53
70
|
stack.push(toolCallId);
|
|
71
|
+
console.log(`[clawg-ui] pushToolCallId: sessionKey=${sessionKey}, toolCallId=${toolCallId}, stackSize=${stack.length}`);
|
|
54
72
|
}
|
|
55
73
|
|
|
56
74
|
export function popToolCallId(sessionKey: string): string | undefined {
|
|
57
75
|
const stack = pendingStacks.get(sessionKey);
|
|
58
76
|
const id = stack?.pop();
|
|
77
|
+
console.log(`[clawg-ui] popToolCallId: sessionKey=${sessionKey}, toolCallId=${id ?? "none"}, stackSize=${stack?.length ?? 0}`);
|
|
59
78
|
if (stack && stack.length === 0) {
|
|
60
79
|
pendingStacks.delete(sessionKey);
|
|
61
80
|
}
|
|
@@ -71,6 +90,7 @@ export function markClientToolNames(
|
|
|
71
90
|
sessionKey: string,
|
|
72
91
|
names: string[],
|
|
73
92
|
): void {
|
|
93
|
+
console.log(`[clawg-ui] markClientToolNames: sessionKey=${sessionKey}, names=${names.join(", ")}`);
|
|
74
94
|
clientToolNames.set(sessionKey, new Set(names));
|
|
75
95
|
}
|
|
76
96
|
|
|
@@ -78,10 +98,13 @@ export function isClientTool(
|
|
|
78
98
|
sessionKey: string,
|
|
79
99
|
toolName: string,
|
|
80
100
|
): boolean {
|
|
81
|
-
|
|
101
|
+
const result = clientToolNames.get(sessionKey)?.has(toolName) ?? false;
|
|
102
|
+
console.log(`[clawg-ui] isClientTool: sessionKey=${sessionKey}, toolName=${toolName}, result=${result}`);
|
|
103
|
+
return result;
|
|
82
104
|
}
|
|
83
105
|
|
|
84
106
|
export function clearClientToolNames(sessionKey: string): void {
|
|
107
|
+
console.log(`[clawg-ui] clearClientToolNames: sessionKey=${sessionKey}`);
|
|
85
108
|
clientToolNames.delete(sessionKey);
|
|
86
109
|
}
|
|
87
110
|
|
|
@@ -92,13 +115,17 @@ export function clearClientToolNames(sessionKey: string): void {
|
|
|
92
115
|
const clientToolCalledFlags = new Map<string, boolean>();
|
|
93
116
|
|
|
94
117
|
export function setClientToolCalled(sessionKey: string): void {
|
|
118
|
+
console.log(`[clawg-ui] setClientToolCalled: sessionKey=${sessionKey}`);
|
|
95
119
|
clientToolCalledFlags.set(sessionKey, true);
|
|
96
120
|
}
|
|
97
121
|
|
|
98
122
|
export function wasClientToolCalled(sessionKey: string): boolean {
|
|
99
|
-
|
|
123
|
+
const result = clientToolCalledFlags.get(sessionKey) ?? false;
|
|
124
|
+
console.log(`[clawg-ui] wasClientToolCalled: sessionKey=${sessionKey}, result=${result}`);
|
|
125
|
+
return result;
|
|
100
126
|
}
|
|
101
127
|
|
|
102
128
|
export function clearClientToolCalled(sessionKey: string): void {
|
|
129
|
+
console.log(`[clawg-ui] clearClientToolCalled: sessionKey=${sessionKey}`);
|
|
103
130
|
clientToolCalledFlags.delete(sessionKey);
|
|
104
131
|
}
|