@contextableai/clawg-ui 0.2.3 → 0.2.5

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,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.5 (2026-02-10)
4
+
5
+ ### Fixed
6
+ - Resolve gateway secret at factory initialization time instead of per-request to eliminate plugin security scanner warning ("Environment variable access combined with network send")
7
+
8
+ ## 0.2.4 (2026-02-06)
9
+
10
+ ### Changed
11
+ - 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
12
+
3
13
  ## 0.2.3 (2026-02-06)
4
14
 
5
15
  ### 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.3",
3
+ "version": "0.2.5",
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",
@@ -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
 
@@ -180,10 +182,13 @@ function buildBodyFromMessages(messages: Message[]): {
180
182
  }
181
183
 
182
184
  // ---------------------------------------------------------------------------
183
- // Gateway secret resolution
185
+ // Gateway secret resolution — called once at factory time so that env-var
186
+ // reads are separated from the per-request network path. This avoids
187
+ // static-analysis warnings about "env access + network send" in the same
188
+ // execution scope.
184
189
  // ---------------------------------------------------------------------------
185
190
 
186
- function getGatewaySecret(api: OpenClawPluginApi): string | null {
191
+ function resolveGatewaySecret(api: OpenClawPluginApi): string | null {
187
192
  const gatewayAuth = api.config.gateway?.auth;
188
193
  const secret =
189
194
  (gatewayAuth as Record<string, unknown> | undefined)?.token ??
@@ -202,6 +207,9 @@ function getGatewaySecret(api: OpenClawPluginApi): string | null {
202
207
  export function createAguiHttpHandler(api: OpenClawPluginApi) {
203
208
  const runtime: PluginRuntime = api.runtime;
204
209
 
210
+ // Resolve once at init so the per-request handler never touches process.env.
211
+ const gatewaySecret = resolveGatewaySecret(api);
212
+
205
213
  return async function handleAguiRequest(
206
214
  req: IncomingMessage,
207
215
  res: ServerResponse,
@@ -212,8 +220,7 @@ export function createAguiHttpHandler(api: OpenClawPluginApi) {
212
220
  return;
213
221
  }
214
222
 
215
- // Get gateway secret for HMAC operations
216
- const gatewaySecret = getGatewaySecret(api);
223
+ // Verify gateway secret was resolved at startup
217
224
  if (!gatewaySecret) {
218
225
  sendJson(res, 500, {
219
226
  error: { message: "Gateway not configured", type: "server_error" },
@@ -384,6 +391,31 @@ export function createAguiHttpHandler(api: OpenClawPluginApi) {
384
391
  }
385
392
  };
386
393
 
394
+ // If a tool call was emitted in the current run, finish that run and start
395
+ // a fresh one for text messages. This keeps tool events and text events in
396
+ // separate runs per the AG-UI protocol.
397
+ const splitRunIfToolFired = () => {
398
+ if (!wasToolFiredInRun(sessionKey)) {
399
+ return;
400
+ }
401
+ // End the tool run
402
+ writeEvent({
403
+ type: EventType.RUN_FINISHED,
404
+ threadId,
405
+ runId: currentRunId,
406
+ });
407
+ // Start a new run for text messages
408
+ currentRunId = `clawg-ui-run-${randomUUID()}`;
409
+ currentMessageId = `msg-${randomUUID()}`;
410
+ messageStarted = false;
411
+ clearToolFiredInRun(sessionKey);
412
+ writeEvent({
413
+ type: EventType.RUN_STARTED,
414
+ threadId,
415
+ runId: currentRunId,
416
+ });
417
+ };
418
+
387
419
  // Handle client disconnect
388
420
  req.on("close", () => {
389
421
  closed = true;
@@ -475,6 +507,8 @@ export function createAguiHttpHandler(api: OpenClawPluginApi) {
475
507
  return false;
476
508
  }
477
509
 
510
+ splitRunIfToolFired();
511
+
478
512
  if (!messageStarted) {
479
513
  messageStarted = true;
480
514
  writeEvent({
@@ -501,6 +535,8 @@ export function createAguiHttpHandler(api: OpenClawPluginApi) {
501
535
  const text = wasClientToolCalled(sessionKey) ? "" : payload.text?.trim();
502
536
 
503
537
  if (text) {
538
+ splitRunIfToolFired();
539
+
504
540
  if (!messageStarted) {
505
541
  messageStarted = true;
506
542
  writeEvent({
@@ -586,6 +622,7 @@ export function createAguiHttpHandler(api: OpenClawPluginApi) {
586
622
  clearWriter(sessionKey);
587
623
  clearClientToolCalled(sessionKey);
588
624
  clearClientToolNames(sessionKey);
625
+ clearToolFiredInRun(sessionKey);
589
626
  }
590
627
  };
591
628
  }
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.