@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 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 10 minutes (max 3 per channel)
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
- if (!sk) return;
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) return;
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", (_event, ctx) => {
81
+ api.on("tool_result_persist", (event, ctx) => {
70
82
  const sk = ctx.sessionKey;
71
- if (!sk) return;
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
- if (writer && toolCallId) {
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.0",
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",
@@ -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
  {
@@ -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
- export function setWriter(sessionKey: string, writer: EventWriter): void {
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
- return clientToolNames.get(sessionKey)?.has(toolName) ?? false;
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
- return clientToolCalledFlags.get(sessionKey) ?? false;
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
  }