@zeulewan/glueclaw-provider 1.2.3 → 1.4.0

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/README.md CHANGED
@@ -50,6 +50,18 @@ The only way this breaks is if Anthropic changes how `--system-prompt` or `--out
50
50
 
51
51
  Switch in TUI: `/model glueclaw/glueclaw-opus`
52
52
 
53
+ ## Configuration
54
+
55
+ | Env var | Default | Description |
56
+ | ----------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
57
+ | `GLUECLAW_REQUEST_TIMEOUT_MS` | `120000` | Maximum time (in milliseconds) to wait for the Claude CLI subprocess to complete a single request before it is terminated. Increase if long-running tool calls or extensive reasoning trip the default 120s limit. Invalid or non-positive values fall back to the default. |
58
+
59
+ Example (10 minute timeout):
60
+
61
+ ```bash
62
+ export GLUECLAW_REQUEST_TIMEOUT_MS=600000
63
+ ```
64
+
53
65
  ## Notes
54
66
 
55
67
  - Tested with Telegram and OpenClaw TUI
package/index.ts CHANGED
@@ -1,9 +1,11 @@
1
+ import { basename } from "node:path";
1
2
  import {
2
3
  definePluginEntry,
3
4
  type OpenClawPluginApi,
4
5
  } from "openclaw/plugin-sdk/plugin-entry";
5
6
  import { createClaudeCliStreamFn } from "./src/stream.js";
6
7
  import { MODEL_CATALOG } from "./src/catalog.js";
8
+ import { resolveSessionKey } from "./src/session-key.js";
7
9
 
8
10
  const PROVIDER_ID = "glueclaw";
9
11
  const PROVIDER_LABEL = "GlueClaw";
@@ -18,6 +20,17 @@ const MODEL_MAP: Readonly<Record<string, string>> = {
18
20
  "glueclaw-haiku": "claude-haiku-4-5",
19
21
  };
20
22
 
23
+ const DEFAULT_REQUEST_TIMEOUT_MS = 120_000;
24
+
25
+ function resolveRequestTimeoutMs(): number {
26
+ const raw = process.env.GLUECLAW_REQUEST_TIMEOUT_MS;
27
+ if (raw === undefined || raw === "") return DEFAULT_REQUEST_TIMEOUT_MS;
28
+ const parsed = Number(raw);
29
+ if (!Number.isFinite(parsed) || parsed <= 0)
30
+ return DEFAULT_REQUEST_TIMEOUT_MS;
31
+ return parsed;
32
+ }
33
+
21
34
  export default definePluginEntry({
22
35
  register(api: OpenClawPluginApi): void {
23
36
  const authProfile = () =>
@@ -69,11 +82,19 @@ export default definePluginEntry({
69
82
  },
70
83
  }),
71
84
  },
72
- createStreamFn: (ctx: { modelId: string; agentDir?: string }) => {
85
+ createStreamFn: (ctx: {
86
+ modelId: string;
87
+ agentDir?: string;
88
+ sessionId?: string;
89
+ sessionKey?: string;
90
+ }) => {
73
91
  const realModel = MODEL_MAP[ctx.modelId] ?? ctx.modelId;
92
+ const agentId = ctx.agentDir ? basename(ctx.agentDir) : undefined;
74
93
  return createClaudeCliStreamFn({
75
- sessionKey: ctx.agentDir ?? "default",
94
+ sessionKey: resolveSessionKey(ctx),
95
+ agentId,
76
96
  modelOverride: realModel,
97
+ requestTimeoutMs: resolveRequestTimeoutMs(),
77
98
  });
78
99
  },
79
100
  resolveSyntheticAuth: () => ({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeulewan/glueclaw-provider",
3
- "version": "1.2.3",
3
+ "version": "1.4.0",
4
4
  "description": "GlueClaw - Claude CLI subprocess provider for Max subscription",
5
5
  "type": "module",
6
6
  "engines": {
package/src/stream.ts CHANGED
@@ -170,6 +170,7 @@ function evictSessions(): void {
170
170
  export function createClaudeCliStreamFn(opts: {
171
171
  claudeBin?: string;
172
172
  sessionKey?: string;
173
+ agentId?: string;
173
174
  modelOverride?: string;
174
175
  requestTimeoutMs?: number;
175
176
  }): StreamFn {
@@ -194,14 +195,17 @@ export function createClaudeCliStreamFn(opts: {
194
195
  "--verbose",
195
196
  "--include-partial-messages",
196
197
  ];
197
- // Resume session for multi-turn conversation memory
198
+ // Resume session for multi-turn conversation memory.
199
+ // Always re-inject the system prompt — on resumptions the CLI would
200
+ // otherwise stick to whatever identity was used on the first turn,
201
+ // leaving no way for callers to reinforce or correct an agent's
202
+ // identity across turns.
198
203
  const sessionKey = `glueclaw:${opts.sessionKey ?? "default"}`;
199
204
  const existingSessionId = sessionMap.get(sessionKey);
200
205
  if (existingSessionId) {
201
206
  args.push("--resume", existingSessionId);
202
- } else {
203
- if (cleanPrompt) args.push("--system-prompt", cleanPrompt);
204
207
  }
208
+ if (cleanPrompt) args.push("--system-prompt", cleanPrompt);
205
209
  if (resolvedModel) args.push("--model", resolvedModel);
206
210
 
207
211
  // Debug: log args for resume troubleshooting
@@ -233,7 +237,7 @@ export function createClaudeCliStreamFn(opts: {
233
237
  args.push("--strict-mcp-config", "--mcp-config", mcp.path);
234
238
  env.OPENCLAW_MCP_TOKEN = loopback.token;
235
239
  env.OPENCLAW_MCP_SESSION_KEY = opts.sessionKey ?? "";
236
- env.OPENCLAW_MCP_AGENT_ID = "main";
240
+ env.OPENCLAW_MCP_AGENT_ID = opts.agentId ?? "main";
237
241
  env.OPENCLAW_MCP_ACCOUNT_ID = "";
238
242
  env.OPENCLAW_MCP_MESSAGE_CHANNEL = "";
239
243
  }
@@ -356,14 +360,46 @@ export function createClaudeCliStreamFn(opts: {
356
360
  continue;
357
361
  }
358
362
 
359
- // Complete assistant message - only use if we didn't get streaming deltas
363
+ // Assistant message may contain tool_use and/or text content blocks
360
364
  if (type === "assistant") {
361
- if (!started) {
362
- const content = data.message?.content;
363
- if (content) {
365
+ const content = data.message?.content;
366
+ if (content) {
367
+ // Emit tool call events for any tool_use blocks
368
+ for (const block of content) {
369
+ if (block.type === "tool_use") {
370
+ const b = block as {
371
+ type: string;
372
+ id: string;
373
+ name: string;
374
+ input: Record<string, unknown>;
375
+ };
376
+ startStream();
377
+ const toolCall = {
378
+ type: "toolCall" as const,
379
+ id: b.id,
380
+ name: b.name,
381
+ arguments: (b.input ?? {}) as Record<string, any>,
382
+ };
383
+ stream.push({
384
+ type: "toolcall_start",
385
+ contentIndex: 0,
386
+ toolName: b.name,
387
+ partial: buildMsg(info, text, buildUsage()),
388
+ } as any);
389
+ stream.push({
390
+ type: "toolcall_end",
391
+ contentIndex: 0,
392
+ toolCall,
393
+ partial: buildMsg(info, text, buildUsage()),
394
+ });
395
+ }
396
+ }
397
+
398
+ // Handle text blocks (only if we haven't streamed via deltas)
399
+ if (!streamed) {
364
400
  const textBlocks = content
365
- .filter((b) => b.type === "text" && b.text)
366
- .map((b) => b.text ?? "");
401
+ .filter((b: any) => b.type === "text" && b.text)
402
+ .map((b: any) => b.text ?? "");
367
403
  if (textBlocks.length > 0) {
368
404
  const fullText = textBlocks.join("\n");
369
405
  startStream();