@contextableai/clawg-ui 0.2.2 → 0.2.4

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,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.4 (2026-02-06)
4
+
5
+ ### Changed
6
+ - Separate tool call events and text message events into distinct AG-UI runs — when text follows a tool call, the tool run is finished and a new run (with a unique runId) is started for the text messages
7
+
8
+ ## 0.2.3 (2026-02-06)
9
+
10
+ ### Fixed
11
+ - Append `\n\n` paragraph joiner to streamed text deltas so chunks render with proper spacing
12
+ - Include `runId` in all `TEXT_MESSAGE_START`, `TEXT_MESSAGE_CONTENT`, and `TEXT_MESSAGE_END` events for AG-UI protocol compliance
13
+
14
+ ### Changed
15
+ - Set channel defaults to `blockStreaming: true` and `chunkMode: "newline"` for correct paragraph-based streaming out of the box
16
+ - Clean up multi-run logic for tool-call-then-text flows (single run per request)
17
+
3
18
  ## 0.2.2 (2026-02-05)
4
19
 
5
20
  ### Fixed
package/index.ts CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  popToolCallId,
14
14
  isClientTool,
15
15
  setClientToolCalled,
16
+ setToolFiredInRun,
16
17
  } from "./src/tool-store.js";
17
18
 
18
19
  const plugin = {
@@ -50,6 +51,7 @@ const plugin = {
50
51
  toolCallId,
51
52
  toolCallName: event.toolName,
52
53
  });
54
+ setToolFiredInRun(sk);
53
55
  if (event.params && Object.keys(event.params).length > 0) {
54
56
  console.log(`[clawg-ui] before_tool_call: emitting TOOL_CALL_ARGS, params=${JSON.stringify(event.params)}`);
55
57
  writer({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contextableai/clawg-ui",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
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",
@@ -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",
@@ -12,6 +12,8 @@ import {
12
12
  wasClientToolCalled,
13
13
  clearClientToolCalled,
14
14
  clearClientToolNames,
15
+ wasToolFiredInRun,
16
+ clearToolFiredInRun,
15
17
  } from "./tool-store.js";
16
18
  import { aguiChannelPlugin } from "./channel.js";
17
19
 
@@ -368,8 +370,9 @@ export function createAguiHttpHandler(api: OpenClawPluginApi) {
368
370
  res.flushHeaders?.();
369
371
 
370
372
  let closed = false;
371
- const messageId = `msg-${randomUUID()}`;
373
+ let currentMessageId = `msg-${randomUUID()}`;
372
374
  let messageStarted = false;
375
+ let currentRunId = runId;
373
376
 
374
377
  const writeEvent = (event: { type: EventType } & Record<string, unknown>) => {
375
378
  if (closed) {
@@ -383,6 +386,31 @@ export function createAguiHttpHandler(api: OpenClawPluginApi) {
383
386
  }
384
387
  };
385
388
 
389
+ // If a tool call was emitted in the current run, finish that run and start
390
+ // a fresh one for text messages. This keeps tool events and text events in
391
+ // separate runs per the AG-UI protocol.
392
+ const splitRunIfToolFired = () => {
393
+ if (!wasToolFiredInRun(sessionKey)) {
394
+ return;
395
+ }
396
+ // End the tool run
397
+ writeEvent({
398
+ type: EventType.RUN_FINISHED,
399
+ threadId,
400
+ runId: currentRunId,
401
+ });
402
+ // Start a new run for text messages
403
+ currentRunId = `clawg-ui-run-${randomUUID()}`;
404
+ currentMessageId = `msg-${randomUUID()}`;
405
+ messageStarted = false;
406
+ clearToolFiredInRun(sessionKey);
407
+ writeEvent({
408
+ type: EventType.RUN_STARTED,
409
+ threadId,
410
+ runId: currentRunId,
411
+ });
412
+ };
413
+
386
414
  // Handle client disconnect
387
415
  req.on("close", () => {
388
416
  closed = true;
@@ -408,7 +436,7 @@ export function createAguiHttpHandler(api: OpenClawPluginApi) {
408
436
  }
409
437
 
410
438
  // Register SSE writer so before/after_tool_call hooks can emit AG-UI events
411
- setWriter(sessionKey, writeEvent, messageId);
439
+ setWriter(sessionKey, writeEvent, currentMessageId);
412
440
  const storePath = runtime.channel.session.resolveStorePath(cfg.session?.store, {
413
441
  agentId: route.agentId,
414
442
  });
@@ -473,18 +501,25 @@ export function createAguiHttpHandler(api: OpenClawPluginApi) {
473
501
  if (!text) {
474
502
  return false;
475
503
  }
504
+
505
+ splitRunIfToolFired();
506
+
476
507
  if (!messageStarted) {
477
508
  messageStarted = true;
478
509
  writeEvent({
479
510
  type: EventType.TEXT_MESSAGE_START,
480
- messageId,
511
+ messageId: currentMessageId,
512
+ runId: currentRunId,
481
513
  role: "assistant",
482
514
  });
483
515
  }
516
+
517
+ // Join chunks with \n\n (breakPreference: paragraph uses double-newline joiner)
484
518
  writeEvent({
485
519
  type: EventType.TEXT_MESSAGE_CONTENT,
486
- messageId,
487
- delta: text,
520
+ messageId: currentMessageId,
521
+ runId: currentRunId,
522
+ delta: text + "\n\n",
488
523
  });
489
524
  return true;
490
525
  },
@@ -493,32 +528,39 @@ export function createAguiHttpHandler(api: OpenClawPluginApi) {
493
528
  return false;
494
529
  }
495
530
  const text = wasClientToolCalled(sessionKey) ? "" : payload.text?.trim();
531
+
496
532
  if (text) {
533
+ splitRunIfToolFired();
534
+
497
535
  if (!messageStarted) {
498
536
  messageStarted = true;
499
537
  writeEvent({
500
538
  type: EventType.TEXT_MESSAGE_START,
501
- messageId,
539
+ messageId: currentMessageId,
540
+ runId: currentRunId,
502
541
  role: "assistant",
503
542
  });
504
543
  }
544
+ // Join chunks with \n\n (breakPreference: paragraph uses double-newline joiner)
505
545
  writeEvent({
506
546
  type: EventType.TEXT_MESSAGE_CONTENT,
507
- messageId,
508
- delta: text,
547
+ messageId: currentMessageId,
548
+ runId: currentRunId,
549
+ delta: text + "\n\n",
509
550
  });
510
551
  }
511
552
  // End the message and run
512
553
  if (messageStarted) {
513
554
  writeEvent({
514
555
  type: EventType.TEXT_MESSAGE_END,
515
- messageId,
556
+ messageId: currentMessageId,
557
+ runId: currentRunId,
516
558
  });
517
559
  }
518
560
  writeEvent({
519
561
  type: EventType.RUN_FINISHED,
520
562
  threadId,
521
- runId,
563
+ runId: currentRunId,
522
564
  });
523
565
  closed = true;
524
566
  res.end();
@@ -550,13 +592,14 @@ export function createAguiHttpHandler(api: OpenClawPluginApi) {
550
592
  if (messageStarted) {
551
593
  writeEvent({
552
594
  type: EventType.TEXT_MESSAGE_END,
553
- messageId,
595
+ messageId: currentMessageId,
596
+ runId: currentRunId,
554
597
  });
555
598
  }
556
599
  writeEvent({
557
600
  type: EventType.RUN_FINISHED,
558
601
  threadId,
559
- runId,
602
+ runId: currentRunId,
560
603
  });
561
604
  closed = true;
562
605
  res.end();
@@ -574,6 +617,7 @@ export function createAguiHttpHandler(api: OpenClawPluginApi) {
574
617
  clearWriter(sessionKey);
575
618
  clearClientToolCalled(sessionKey);
576
619
  clearClientToolNames(sessionKey);
620
+ clearToolFiredInRun(sessionKey);
577
621
  }
578
622
  };
579
623
  }
package/src/tool-store.ts CHANGED
@@ -108,6 +108,26 @@ export function clearClientToolNames(sessionKey: string): void {
108
108
  clientToolNames.delete(sessionKey);
109
109
  }
110
110
 
111
+ // --- Tool-fired-in-run flag ---
112
+ // Tracks whether any tool call (server or client) was emitted in the current
113
+ // run. When a text message is about to be emitted and this flag is set, the
114
+ // http-handler splits into a new run so tool events and text events live in
115
+ // separate runs (per AG-UI protocol best practice).
116
+
117
+ const toolFiredInRunFlags = new Map<string, boolean>();
118
+
119
+ export function setToolFiredInRun(sessionKey: string): void {
120
+ toolFiredInRunFlags.set(sessionKey, true);
121
+ }
122
+
123
+ export function wasToolFiredInRun(sessionKey: string): boolean {
124
+ return toolFiredInRunFlags.get(sessionKey) ?? false;
125
+ }
126
+
127
+ export function clearToolFiredInRun(sessionKey: string): void {
128
+ toolFiredInRunFlags.delete(sessionKey);
129
+ }
130
+
111
131
  // --- Client-tool-called flag ---
112
132
  // Set when a client tool is invoked during a run so the dispatcher can
113
133
  // suppress text output and end the run after the tool call events.
@@ -129,3 +149,4 @@ export function clearClientToolCalled(sessionKey: string): void {
129
149
  console.log(`[clawg-ui] clearClientToolCalled: sessionKey=${sessionKey}`);
130
150
  clientToolCalledFlags.delete(sessionKey);
131
151
  }
152
+