@contextableai/clawg-ui 0.2.1 → 0.2.3

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,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.3 (2026-02-06)
4
+
5
+ ### Fixed
6
+ - Append `\n\n` paragraph joiner to streamed text deltas so chunks render with proper spacing
7
+ - Include `runId` in all `TEXT_MESSAGE_START`, `TEXT_MESSAGE_CONTENT`, and `TEXT_MESSAGE_END` events for AG-UI protocol compliance
8
+
9
+ ### Changed
10
+ - Set channel defaults to `blockStreaming: true` and `chunkMode: "newline"` for correct paragraph-based streaming out of the box
11
+ - Clean up multi-run logic for tool-call-then-text flows (single run per request)
12
+
13
+ ## 0.2.2 (2026-02-05)
14
+
15
+ ### Fixed
16
+ - Include `messageId` in `TOOL_CALL_RESULT` events as required by AG-UI client v0.0.43 Zod schema
17
+
18
+ ### Added
19
+ - Debug logging throughout tool call flow for easier troubleshooting
20
+
3
21
  ## 0.2.1 (2026-02-05)
4
22
 
5
23
  ### Fixed
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.1",
3
+ "version": "0.2.3",
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",
@@ -39,7 +39,11 @@
39
39
  "docsPath": "/channels/clawg-ui",
40
40
  "docsLabel": "clawg-ui",
41
41
  "blurb": "AG-UI protocol endpoint for CopilotKit and HttpAgent clients.",
42
- "order": 90
42
+ "order": 90,
43
+ "defaults": {
44
+ "blockStreaming": true,
45
+ "chunkMode": "newline"
46
+ }
43
47
  },
44
48
  "install": {
45
49
  "npmSpec": "@contextableai/clawg-ui",
@@ -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
  {
@@ -368,8 +368,9 @@ export function createAguiHttpHandler(api: OpenClawPluginApi) {
368
368
  res.flushHeaders?.();
369
369
 
370
370
  let closed = false;
371
- const messageId = `msg-${randomUUID()}`;
371
+ let currentMessageId = `msg-${randomUUID()}`;
372
372
  let messageStarted = false;
373
+ let currentRunId = runId;
373
374
 
374
375
  const writeEvent = (event: { type: EventType } & Record<string, unknown>) => {
375
376
  if (closed) {
@@ -408,7 +409,7 @@ export function createAguiHttpHandler(api: OpenClawPluginApi) {
408
409
  }
409
410
 
410
411
  // Register SSE writer so before/after_tool_call hooks can emit AG-UI events
411
- setWriter(sessionKey, writeEvent);
412
+ setWriter(sessionKey, writeEvent, currentMessageId);
412
413
  const storePath = runtime.channel.session.resolveStorePath(cfg.session?.store, {
413
414
  agentId: route.agentId,
414
415
  });
@@ -473,18 +474,23 @@ export function createAguiHttpHandler(api: OpenClawPluginApi) {
473
474
  if (!text) {
474
475
  return false;
475
476
  }
477
+
476
478
  if (!messageStarted) {
477
479
  messageStarted = true;
478
480
  writeEvent({
479
481
  type: EventType.TEXT_MESSAGE_START,
480
- messageId,
482
+ messageId: currentMessageId,
483
+ runId: currentRunId,
481
484
  role: "assistant",
482
485
  });
483
486
  }
487
+
488
+ // Join chunks with \n\n (breakPreference: paragraph uses double-newline joiner)
484
489
  writeEvent({
485
490
  type: EventType.TEXT_MESSAGE_CONTENT,
486
- messageId,
487
- delta: text,
491
+ messageId: currentMessageId,
492
+ runId: currentRunId,
493
+ delta: text + "\n\n",
488
494
  });
489
495
  return true;
490
496
  },
@@ -493,32 +499,37 @@ export function createAguiHttpHandler(api: OpenClawPluginApi) {
493
499
  return false;
494
500
  }
495
501
  const text = wasClientToolCalled(sessionKey) ? "" : payload.text?.trim();
502
+
496
503
  if (text) {
497
504
  if (!messageStarted) {
498
505
  messageStarted = true;
499
506
  writeEvent({
500
507
  type: EventType.TEXT_MESSAGE_START,
501
- messageId,
508
+ messageId: currentMessageId,
509
+ runId: currentRunId,
502
510
  role: "assistant",
503
511
  });
504
512
  }
513
+ // Join chunks with \n\n (breakPreference: paragraph uses double-newline joiner)
505
514
  writeEvent({
506
515
  type: EventType.TEXT_MESSAGE_CONTENT,
507
- messageId,
508
- delta: text,
516
+ messageId: currentMessageId,
517
+ runId: currentRunId,
518
+ delta: text + "\n\n",
509
519
  });
510
520
  }
511
521
  // End the message and run
512
522
  if (messageStarted) {
513
523
  writeEvent({
514
524
  type: EventType.TEXT_MESSAGE_END,
515
- messageId,
525
+ messageId: currentMessageId,
526
+ runId: currentRunId,
516
527
  });
517
528
  }
518
529
  writeEvent({
519
530
  type: EventType.RUN_FINISHED,
520
531
  threadId,
521
- runId,
532
+ runId: currentRunId,
522
533
  });
523
534
  closed = true;
524
535
  res.end();
@@ -550,13 +561,14 @@ export function createAguiHttpHandler(api: OpenClawPluginApi) {
550
561
  if (messageStarted) {
551
562
  writeEvent({
552
563
  type: EventType.TEXT_MESSAGE_END,
553
- messageId,
564
+ messageId: currentMessageId,
565
+ runId: currentRunId,
554
566
  });
555
567
  }
556
568
  writeEvent({
557
569
  type: EventType.RUN_FINISHED,
558
570
  threadId,
559
- runId,
571
+ runId: currentRunId,
560
572
  });
561
573
  closed = true;
562
574
  res.end();
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,18 @@ 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
  }
132
+