@openviking/openclaw-plugin 2026.5.10-dev.3 → 2026.5.21

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.
@@ -1,98 +1,3 @@
1
- import { Socket } from "node:net";
2
-
3
- type ChildLike = {
4
- killed?: boolean;
5
- exitCode?: number | null;
6
- signalCode?: string | null;
7
- off?: (event: string, listener: (...args: any[]) => void) => void;
8
- once: (event: string, listener: (...args: any[]) => void) => void;
9
- };
10
-
11
- export function waitForHealth(baseUrl: string, timeoutMs: number, intervalMs: number): Promise<void> {
12
- const deadline = Date.now() + timeoutMs;
13
- return new Promise((resolve, reject) => {
14
- const tick = () => {
15
- if (Date.now() > deadline) {
16
- reject(new Error(`OpenViking health check timeout at ${baseUrl}`));
17
- return;
18
- }
19
- fetch(`${baseUrl}/health`)
20
- .then((r) => r.json())
21
- .then((body: { status?: string }) => {
22
- if (body?.status === "ok") {
23
- resolve();
24
- return;
25
- }
26
- setTimeout(tick, intervalMs);
27
- })
28
- .catch(() => setTimeout(tick, intervalMs));
29
- };
30
- tick();
31
- });
32
- }
33
-
34
- export function waitForHealthOrExit(
35
- baseUrl: string,
36
- timeoutMs: number,
37
- intervalMs: number,
38
- child: ChildLike,
39
- ): Promise<void> {
40
- const exited =
41
- child.killed || child.exitCode !== null || child.signalCode !== null;
42
- if (exited) {
43
- return Promise.reject(
44
- new Error(
45
- `OpenViking subprocess exited before health check ` +
46
- `(code=${child.exitCode}, signal=${child.signalCode})`,
47
- ),
48
- );
49
- }
50
-
51
- return new Promise((resolve, reject) => {
52
- let settled = false;
53
-
54
- const cleanup = () => {
55
- child.off?.("error", onError);
56
- child.off?.("exit", onExit);
57
- };
58
-
59
- const finishResolve = () => {
60
- if (settled) {
61
- return;
62
- }
63
- settled = true;
64
- cleanup();
65
- resolve();
66
- };
67
-
68
- const finishReject = (err: unknown) => {
69
- if (settled) {
70
- return;
71
- }
72
- settled = true;
73
- cleanup();
74
- reject(err instanceof Error ? err : new Error(String(err)));
75
- };
76
-
77
- const onError = (err: Error) => {
78
- finishReject(err);
79
- };
80
-
81
- const onExit = (code: number | null, signal: string | null) => {
82
- finishReject(
83
- new Error(
84
- `OpenViking subprocess exited before health check ` +
85
- `(code=${code}, signal=${signal})`,
86
- ),
87
- );
88
- };
89
-
90
- child.once("error", onError);
91
- child.once("exit", onExit);
92
- waitForHealth(baseUrl, timeoutMs, intervalMs).then(finishResolve, finishReject);
93
- });
94
- }
95
-
96
1
  export function withTimeout<T>(promise: Promise<T>, timeoutMs: number, timeoutMessage: string): Promise<T> {
97
2
  return new Promise((resolve, reject) => {
98
3
  const timer = setTimeout(() => reject(new Error(timeoutMessage)), timeoutMs);
@@ -109,30 +14,6 @@ export function withTimeout<T>(promise: Promise<T>, timeoutMs: number, timeoutMe
109
14
  });
110
15
  }
111
16
 
112
- export function quickTcpProbe(host: string, port: number, timeoutMs: number): Promise<boolean> {
113
- return new Promise((resolve) => {
114
- const socket = new Socket();
115
- let done = false;
116
- const finish = (ok: boolean) => {
117
- if (done) {
118
- return;
119
- }
120
- done = true;
121
- socket.destroy();
122
- resolve(ok);
123
- };
124
- socket.setTimeout(timeoutMs);
125
- socket.once("connect", () => finish(true));
126
- socket.once("timeout", () => finish(false));
127
- socket.once("error", () => finish(false));
128
- try {
129
- socket.connect(port, host);
130
- } catch {
131
- finish(false);
132
- }
133
- });
134
- }
135
-
136
17
  export async function quickHealthCheck(baseUrl: string, timeoutMs: number): Promise<boolean> {
137
18
  const controller = new AbortController();
138
19
  const timer = setTimeout(() => controller.abort(), timeoutMs);
@@ -162,8 +43,3 @@ export async function quickRecallPrecheck(
162
43
  }
163
44
  return { ok: false, reason: "health check failed" };
164
45
  }
165
-
166
- export interface ProcessLogger {
167
- info?: (msg: string) => void;
168
- warn?: (msg: string) => void;
169
- }
package/runtime-utils.ts CHANGED
@@ -1,7 +1,4 @@
1
1
  const _env = globalThis["process"];
2
- export function sysEnv(): NodeJS.ProcessEnv {
3
- return _env.env;
4
- }
5
2
 
6
3
  export function getEnv(key: string): string | undefined {
7
4
  return _env.env[key];
@@ -13,7 +13,7 @@ description: >
13
13
  The user does NOT need to know any CLI commands — the agent runs everything and only asks for a few values.
14
14
  This skill assumes the OpenViking server is already running. If the server is not ready, the skill
15
15
  tells the user to contact their admin or set it up via the OpenViking docs — it does NOT install the server.
16
- version: 2.0.0
16
+ version: 2.0.1
17
17
  metadata:
18
18
  openclaw:
19
19
  requires:
@@ -231,7 +231,7 @@ Tell the user:
231
231
  Run:
232
232
 
233
233
  ```bash
234
- openclaw plugins install @openclaw/openviking
234
+ openclaw plugins install clawhub:@openviking/openclaw-plugin
235
235
  ```
236
236
 
237
237
  Trigger fallback to Path B only if the output contains any of these strings:
@@ -245,7 +245,7 @@ Trigger fallback to Path B only if the output contains any of these strings:
245
245
  Before falling back, also try the explicit registry prefix once:
246
246
 
247
247
  ```bash
248
- openclaw plugins install clawhub:@openclaw/openviking
248
+ openclaw plugins install clawhub:@openviking/openclaw-plugin
249
249
  ```
250
250
 
251
251
  If the install **succeeds**, jump to STEP 7.
@@ -256,8 +256,8 @@ If both attempts fail with one of the fallback-eligible errors above, go to Path
256
256
 
257
257
  Tell the user:
258
258
 
259
- > (CN) ClawHub 现在好像被限流、不可用,或者当前账号不能安装。我改用备用路径,直接从 GitHub 下载插件源码安装。
260
- > (EN) ClawHub looks rate-limited, unavailable, or blocked for this account. I'll use the backup path and install the plugin source directly from GitHub.
259
+ > (CN) ClawHub 现在好像被限流、不可用,或者当前账号不能安装。我改用备用路径,通过 npm 下载并部署插件包。
260
+ > (EN) ClawHub looks rate-limited, unavailable, or blocked for this account. I'll use the backup path and install the plugin package from npm.
261
261
 
262
262
  Run the installer with `npx` (no global install needed):
263
263
 
@@ -273,11 +273,11 @@ Build the flag list according to what the user gave you:
273
273
  - `--account-id` / `--user-id` only if the root-key path requires them.
274
274
 
275
275
  `ov-install` will, in one shot:
276
- 1. Download plugin sources from `raw.githubusercontent.com` into the OpenClaw `extensions/` dir.
277
- 2. Run `npm install --omit=dev` inside the staging dir.
276
+ 1. Download the `@openviking/openclaw-plugin` package from npm into a temporary staging dir.
277
+ 2. Copy the package into the OpenClaw `extensions/` dir and install plugin dependencies.
278
278
  3. Register the plugin in `openclaw.json` (via `openclaw plugins enable` or direct write).
279
- 4. Run `openclaw openviking setup --json --base-url … [--api-key …] --force-slot` for the user.
280
- 5. Fall back to direct config write if `setup --json` fails.
279
+ 4. Run `openclaw openviking setup --json --base-url … [--api-key …]` for the user.
280
+ 5. Return a non-zero exit if setup needs explicit `--allow-offline` or `--force-slot` consent.
281
281
 
282
282
  This means **STEP 7 is effectively done by `ov-install`**. After `ov-install` exits 0, jump straight to **STEP 9** (gateway restart) and **STEP 10** (verify).
283
283
 
package/text-utils.ts CHANGED
@@ -27,8 +27,6 @@ const SUBAGENT_CONTEXT_RE = /^\s*\[Subagent Context\]/i;
27
27
  const MEMORY_INTENT_RE = /记住|记下|remember|save|store|偏好|preference|规则|rule|事实|fact/i;
28
28
  const QUESTION_CUE_RE =
29
29
  /[??]|\b(?:what|when|where|who|why|how|which|can|could|would|did|does|is|are)\b|^(?:请问|能否|可否|怎么|如何|什么时候|谁|什么|哪|是否)/i;
30
- export const CAPTURE_LIMIT = 3;
31
-
32
30
  function resolveCaptureMinLength(text: string): number {
33
31
  return CJK_CHAR_REGEX.test(text) ? 4 : 10;
34
32
  }
@@ -274,21 +272,6 @@ export function extractTextsFromUserMessages(messages: unknown[]): string[] {
274
272
  return texts;
275
273
  }
276
274
 
277
- function formatToolUseBlock(b: Record<string, unknown>): string {
278
- const name = typeof b.name === "string" ? b.name : "unknown";
279
- let inputStr = "";
280
- if (b.input !== undefined && b.input !== null) {
281
- try {
282
- inputStr = typeof b.input === "string" ? b.input : JSON.stringify(b.input);
283
- } catch {
284
- inputStr = String(b.input);
285
- }
286
- }
287
- return inputStr
288
- ? `[toolUse: ${name}]\n${inputStr}`
289
- : `[toolUse: ${name}]`;
290
- }
291
-
292
275
  function formatToolResultContent(content: unknown): string {
293
276
  if (typeof content === "string") return content.trim();
294
277
  if (Array.isArray(content)) {
@@ -311,39 +294,6 @@ function formatToolResultContent(content: unknown): string {
311
294
  return "";
312
295
  }
313
296
 
314
- /**
315
- * Extract text from a single message without a `[role]:` prefix.
316
- * Used by afterTurn to send messages with their actual role.
317
- */
318
- export function extractSingleMessageText(msg: unknown): string {
319
- if (!msg || typeof msg !== "object") return "";
320
- const m = msg as Record<string, unknown>;
321
- const role = m.role as string;
322
- if (!role || role === "system") return "";
323
-
324
- if (role === "toolResult") {
325
- const toolName = typeof m.toolName === "string" ? m.toolName : "tool";
326
- const resultText = formatToolResultContent(m.content);
327
- return resultText ? `[${toolName} result]: ${resultText}` : "";
328
- }
329
-
330
- const content = m.content;
331
- if (typeof content === "string") return content.trim();
332
- if (Array.isArray(content)) {
333
- const parts: string[] = [];
334
- for (const block of content) {
335
- const b = block as Record<string, unknown>;
336
- if (b?.type === "text" && typeof b.text === "string") {
337
- parts.push((b.text as string).trim());
338
- } else if (b?.type === "toolUse") {
339
- parts.push(formatToolUseBlock(b));
340
- }
341
- }
342
- return parts.join("\n");
343
- }
344
- return "";
345
- }
346
-
347
297
  /**
348
298
  * 提取消息中的一个 part 的文本内容,并清理时间戳等噪音
349
299
  */
@@ -367,7 +317,7 @@ function extractPartText(content: unknown): string {
367
317
  /**
368
318
  * 结构化消息类型 - 用于 afterTurn 发送到 OpenViking
369
319
  */
370
- export type ExtractedMessage = {
320
+ type ExtractedMessage = {
371
321
  role: "user" | "assistant";
372
322
  parts: Array<{
373
323
  type: "text";
@@ -459,7 +409,6 @@ export function extractNewTurnMessages(
459
409
  }
460
410
 
461
411
  // user/assistant -> type: "text"
462
- // 统一 role 为 user
463
412
  const content = msg.content;
464
413
  const text = extractPartText(content);
465
414
 
@@ -477,6 +426,40 @@ export function extractNewTurnMessages(
477
426
  }],
478
427
  });
479
428
  }
429
+ } else {
430
+ /// 如果原始消息有 toolCall,提取所有工具名并生成占位符
431
+ if (role === "assistant" && Array.isArray(content)) {
432
+ const toolNames: string[] = [];
433
+
434
+ // 收集所有 toolCall 的 tool_name
435
+ for (const block of content) {
436
+ const b = block as Record<string, unknown>;
437
+ const blockType = b?.type as string;
438
+ if (
439
+ blockType === "toolCall" ||
440
+ blockType === "toolUse" ||
441
+ blockType === "tool_use" ||
442
+ blockType === "tool_call"
443
+ ) {
444
+ const name = b?.name as string || b?.toolName as string;
445
+ if (name && typeof name === "string" && name.trim()) {
446
+ toolNames.push(name.trim());
447
+ }
448
+ }
449
+ }
450
+
451
+ // 只有找到 toolCall 时才添加占位符
452
+ if (toolNames.length > 0) {
453
+ const toolNamesStr = toolNames.join(", ");
454
+ result.push({
455
+ role: "assistant",
456
+ parts: [{
457
+ type: "text",
458
+ text: `[tool: ${toolNamesStr}]`,
459
+ }],
460
+ });
461
+ }
462
+ }
480
463
  }
481
464
  }
482
465