@ynhcj/xiaoyi-channel 0.0.207-beta → 0.0.209-beta
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/dist/index.d.ts +3 -8
- package/dist/index.js +5 -1
- package/dist/src/bot.js +16 -77
- package/dist/src/cspl/config.js +3 -0
- package/dist/src/reply-dispatcher.js +4 -3
- package/dist/src/skill-retriever/config.js +2 -0
- package/dist/src/skill-retriever/hooks.js +1 -0
- package/dist/src/skill-retriever/tool-search.d.ts +3 -0
- package/dist/src/skill-retriever/tool-search.js +27 -11
- package/dist/src/skill-retriever/types.d.ts +1 -0
- package/dist/src/tools/device-tool-map.js +0 -6
- package/dist/src/tools/hmos-cli.d.ts +103 -0
- package/dist/src/tools/hmos-cli.js +561 -0
- package/dist/src/tools/invoke.d.ts +48 -0
- package/dist/src/tools/invoke.js +1201 -0
- package/dist/src/websocket.js +10 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
description: string;
|
|
5
|
-
configSchema: import("openclaw/plugin-sdk").OpenClawPluginConfigSchema;
|
|
6
|
-
register: NonNullable<import("openclaw/plugin-sdk/core").OpenClawPluginDefinition["register"]>;
|
|
7
|
-
} & Pick<import("openclaw/plugin-sdk/core").OpenClawPluginDefinition, "kind" | "reload" | "nodeHostCommands" | "securityAuditCollectors">;
|
|
8
|
-
export default _default;
|
|
1
|
+
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
|
2
|
+
declare const pluginEntry: ReturnType<typeof definePluginEntry>;
|
|
3
|
+
export default pluginEntry;
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { getAllPushIds } from "./src/utils/pushid-manager.js";
|
|
|
10
10
|
import { registerSelfEvolutionToolResultNudge } from "./src/self-evolution-tool-result-nudge.js";
|
|
11
11
|
import { createBeforePromptBuildHandler } from "./src/skill-retriever/hooks.js";
|
|
12
12
|
import { normalizeToolRetrieverConfig } from "./src/skill-retriever/config.js";
|
|
13
|
+
import { registerCLIHook } from "./src/tools/hmos-cli.js";
|
|
13
14
|
/**
|
|
14
15
|
* Register the cron detection hook.
|
|
15
16
|
*
|
|
@@ -181,7 +182,7 @@ function registerFullHooks(api) {
|
|
|
181
182
|
api.on("before_prompt_build", beforePromptBuildHandler);
|
|
182
183
|
registerSelfEvolutionToolResultNudge(api);
|
|
183
184
|
}
|
|
184
|
-
|
|
185
|
+
const pluginEntry = definePluginEntry({
|
|
185
186
|
id: "xiaoyi-channel",
|
|
186
187
|
name: "Xiaoyi Channel",
|
|
187
188
|
description: "Xiaoyi channel plugin - Xiaoyi A2A protocol integration",
|
|
@@ -208,6 +209,9 @@ export default definePluginEntry({
|
|
|
208
209
|
registerSentinelHook(api);
|
|
209
210
|
// Cron detection hook: marks toolCallIds from cron sessions
|
|
210
211
|
registerCronDetectionHook(api);
|
|
212
|
+
// CLI exec hook: intercepts built-in exec for HarmonyOS CLI skill tools
|
|
213
|
+
registerCLIHook(api);
|
|
211
214
|
}
|
|
212
215
|
},
|
|
213
216
|
});
|
|
217
|
+
export default pluginEntry;
|
package/dist/src/bot.js
CHANGED
|
@@ -7,6 +7,7 @@ import { downloadFilesFromParts } from "./file-download.js";
|
|
|
7
7
|
import { resolveXYConfig } from "./config.js";
|
|
8
8
|
import { sendStatusUpdate, sendClearContextResponse, sendTasksCancelResponse, sendA2AResponse } from "./formatter.js";
|
|
9
9
|
import { appendSelfEvolutionKeywordNudge, shouldNudgeForSelfEvolutionKeyword, } from "./self-evolution-keyword.js";
|
|
10
|
+
import { queueAgentHarnessMessage } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
10
11
|
import { runWithSessionContext } from "./tools/session-manager.js";
|
|
11
12
|
import { configManager } from "./utils/config-manager.js";
|
|
12
13
|
import { addPushId } from "./utils/pushid-manager.js";
|
|
@@ -552,7 +553,7 @@ function enqueueSteer(params) {
|
|
|
552
553
|
return next;
|
|
553
554
|
}
|
|
554
555
|
async function dispatchSteerWhenReady(params) {
|
|
555
|
-
const { sessionId,
|
|
556
|
+
const { sessionId, steerText } = params;
|
|
556
557
|
const log = logger.withContext(sessionId, params.parsed.taskId);
|
|
557
558
|
// 1. 等待第一条消息开始 streaming
|
|
558
559
|
// signal 可能尚未创建(第一条消息还在文件下载等耗时操作中),
|
|
@@ -578,7 +579,7 @@ async function dispatchSteerWhenReady(params) {
|
|
|
578
579
|
}
|
|
579
580
|
else {
|
|
580
581
|
// 轮询超时且 hasActiveTask 仍为 true——说明第一条消息可能卡在异常路径,
|
|
581
|
-
// 没有创建 signal
|
|
582
|
+
// 没有创建 signal。此时放弃,避免并发碰撞。
|
|
582
583
|
log.log(`[STEER-QUEUE] Signal never appeared after polling, skip steer to avoid collision`);
|
|
583
584
|
return;
|
|
584
585
|
}
|
|
@@ -587,84 +588,22 @@ async function dispatchSteerWhenReady(params) {
|
|
|
587
588
|
log.log(`[STEER-QUEUE] First message completed, skip steer`);
|
|
588
589
|
return;
|
|
589
590
|
}
|
|
590
|
-
// 3.
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
// 如果有文件附件,把路径拼到 steer 文本末尾,让模型通过工具读取
|
|
591
|
+
// 3. 直接注入到活跃的 Pi run 中,不创建独立 dispatcher。
|
|
592
|
+
// 模型回复通过第一条消息的 dispatcher 的 onPartialReply 流式发出,
|
|
593
|
+
// 使用 registerTaskId + updateFallbackTaskId 已同步的最新 taskId。
|
|
594
594
|
const mediaPaths = params.mediaPayload?.MediaPaths;
|
|
595
595
|
const fileHint = mediaPaths && mediaPaths.length > 0
|
|
596
596
|
? `\n【用户上传附件】:${JSON.stringify(mediaPaths)}`
|
|
597
597
|
: "";
|
|
598
|
-
const
|
|
599
|
-
|
|
600
|
-
const
|
|
601
|
-
|
|
602
|
-
channel: "xiaoyi-channel",
|
|
603
|
-
from: speaker,
|
|
604
|
-
timestamp: new Date(),
|
|
605
|
-
envelope: envelopeOptions,
|
|
606
|
-
body: messageBody,
|
|
607
|
-
});
|
|
608
|
-
const ctxPayload = core.channel.reply.finalizeInboundContext({
|
|
609
|
-
Body: body,
|
|
610
|
-
RawBody: steerCommand,
|
|
611
|
-
CommandBody: steerCommand,
|
|
612
|
-
From: sessionId,
|
|
613
|
-
To: sessionId,
|
|
614
|
-
SessionKey: params.route.sessionKey,
|
|
615
|
-
AccountId: params.route.accountId,
|
|
616
|
-
ChatType: "direct",
|
|
617
|
-
GroupSubject: undefined,
|
|
618
|
-
SenderName: sessionId,
|
|
619
|
-
SenderId: sessionId,
|
|
620
|
-
Provider: "xiaoyi-channel",
|
|
621
|
-
Surface: "xiaoyi-channel",
|
|
622
|
-
MessageSid: `xiaoyi_${params.parsed.taskId}_${params.deviceType}`,
|
|
623
|
-
Timestamp: Date.now(),
|
|
624
|
-
WasMentioned: false,
|
|
625
|
-
CommandAuthorized: true,
|
|
626
|
-
OriginatingChannel: "xiaoyi-channel",
|
|
627
|
-
OriginatingTo: sessionId,
|
|
628
|
-
ReplyToBody: undefined,
|
|
629
|
-
...params.mediaPayload,
|
|
630
|
-
});
|
|
631
|
-
const steerState = { steered: true };
|
|
632
|
-
const { dispatcher, replyOptions } = createXYReplyDispatcher({
|
|
633
|
-
cfg: params.cfg,
|
|
634
|
-
runtime: params.runtime,
|
|
635
|
-
sessionId,
|
|
636
|
-
taskId: params.parsed.taskId,
|
|
637
|
-
messageId: params.parsed.messageId,
|
|
638
|
-
accountId: params.route.accountId,
|
|
639
|
-
steerState,
|
|
640
|
-
});
|
|
641
|
-
const sessionContext = {
|
|
642
|
-
config: resolveXYConfig(params.cfg),
|
|
643
|
-
sessionId,
|
|
644
|
-
taskId: params.parsed.taskId,
|
|
645
|
-
messageId: params.parsed.messageId,
|
|
646
|
-
agentId: params.route.accountId,
|
|
647
|
-
deviceType: params.deviceType,
|
|
648
|
-
};
|
|
649
|
-
log.log(`[STEER-QUEUE] Dispatching steer`);
|
|
650
|
-
await core.channel.reply.withReplyDispatcher({
|
|
651
|
-
dispatcher,
|
|
652
|
-
onSettled: () => {
|
|
653
|
-
log.log(`[STEER-QUEUE] Steer dispatch settled`);
|
|
654
|
-
},
|
|
655
|
-
run: () => {
|
|
656
|
-
return runWithSessionContext(sessionContext, async () => {
|
|
657
|
-
log.log(`[ALS-PROOF] bot entered steer dispatch scope sessionId=${sessionContext.sessionId} taskId=${sessionContext.taskId} isSteer=true`);
|
|
658
|
-
const result = await core.channel.reply.dispatchReplyFromConfig({
|
|
659
|
-
ctx: ctxPayload,
|
|
660
|
-
cfg: params.cfg,
|
|
661
|
-
dispatcher,
|
|
662
|
-
replyOptions,
|
|
663
|
-
});
|
|
664
|
-
log.log(`[STEER-QUEUE] dispatch result: ${JSON.stringify(result)}`);
|
|
665
|
-
return result;
|
|
666
|
-
});
|
|
667
|
-
},
|
|
598
|
+
const steerMessage = `${steerText}${fileHint}`;
|
|
599
|
+
log.log(`[STEER-QUEUE] Injecting steer message directly into active run`);
|
|
600
|
+
const injected = queueAgentHarnessMessage(sessionId, steerMessage, {
|
|
601
|
+
steeringMode: "all",
|
|
668
602
|
});
|
|
669
|
-
|
|
603
|
+
if (injected) {
|
|
604
|
+
log.log(`[STEER-QUEUE] Steer message injected successfully`);
|
|
605
|
+
}
|
|
606
|
+
else {
|
|
607
|
+
log.log(`[STEER-QUEUE] Steer injection failed — run may not be accepting messages`);
|
|
608
|
+
}
|
|
670
609
|
}
|
package/dist/src/cspl/config.js
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import fs from 'fs';
|
|
5
5
|
import path from 'path';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
6
9
|
import { CONFIG_FILE_NAME, ENV_FILE_PATH, REQUIRED_ENV_VARS } from './constants.js';
|
|
7
10
|
import { logger } from '../utils/logger.js';
|
|
8
11
|
let cachedConfig = null;
|
|
@@ -430,15 +430,16 @@ export function createXYReplyDispatcher(params) {
|
|
|
430
430
|
}
|
|
431
431
|
else {
|
|
432
432
|
// 新文本不以已累积内容为前缀(如工具调用后模型重新开始生成),
|
|
433
|
-
//
|
|
433
|
+
// 重置 accumulatedText 为当前文本,后续基于此新前缀做去重
|
|
434
434
|
const wasFirstRound = accumulatedText.length === 0;
|
|
435
|
-
accumulatedText = "";
|
|
436
435
|
// 新一轮输出前加换行分隔(第一轮除外)
|
|
437
436
|
if (sendText.length > 0 && !wasFirstRound) {
|
|
438
437
|
sendText = "\n" + sendText;
|
|
439
438
|
}
|
|
440
439
|
}
|
|
441
|
-
|
|
440
|
+
// 始终追踪模型的原始输出文本,避免注入的 "\n" 前缀污染 accumulatedText
|
|
441
|
+
// 导致下一轮 startsWith 永久匹配失败
|
|
442
|
+
accumulatedText = text;
|
|
442
443
|
hasSentResponse = true;
|
|
443
444
|
if (sendText.length > 0) {
|
|
444
445
|
await sendA2AResponse({
|
|
@@ -4,6 +4,7 @@ const DEFAULT_CONFIG = {
|
|
|
4
4
|
includeUninstalledOnly: true,
|
|
5
5
|
envFilePath: "~/.openclaw/.xiaoyienv",
|
|
6
6
|
timeoutMs: 1000,
|
|
7
|
+
excludedSkills: ["content-compliance", "plugin-audit"],
|
|
7
8
|
};
|
|
8
9
|
export function normalizeToolRetrieverConfig(raw) {
|
|
9
10
|
if (!raw || typeof raw !== "object") {
|
|
@@ -19,5 +20,6 @@ export function normalizeToolRetrieverConfig(raw) {
|
|
|
19
20
|
apiKey: cfg.apiKey,
|
|
20
21
|
uid: cfg.uid,
|
|
21
22
|
timeoutMs: cfg.timeoutMs ?? DEFAULT_CONFIG.timeoutMs,
|
|
23
|
+
excludedSkills: cfg.excludedSkills ?? DEFAULT_CONFIG.excludedSkills,
|
|
22
24
|
};
|
|
23
25
|
}
|
|
@@ -59,6 +59,7 @@ export function createBeforePromptBuildHandler(config) {
|
|
|
59
59
|
apiKey: config.apiKey,
|
|
60
60
|
uid: config.uid,
|
|
61
61
|
timeoutMs: config.timeoutMs,
|
|
62
|
+
configExcludedSkills: config.excludedSkills,
|
|
62
63
|
});
|
|
63
64
|
if (!searchResult || searchResult.tools.length === 0) {
|
|
64
65
|
return undefined;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { EnvConfig, ToolSearchResult } from "./types.js";
|
|
2
2
|
export declare function extractUserQuery(fullPrompt: string): string;
|
|
3
3
|
export declare function readEnvFile(filePath: string): EnvConfig;
|
|
4
|
+
export declare function getSkillsInDirectory(dirPath: string): string[];
|
|
4
5
|
export declare function getInstalledSkills(): string[];
|
|
6
|
+
export declare function buildExcludedSkillIds(excludedSkills?: string[]): Set<string>;
|
|
5
7
|
export interface SearchToolsOptions {
|
|
6
8
|
query: string;
|
|
7
9
|
maxTools?: number;
|
|
@@ -11,6 +13,7 @@ export interface SearchToolsOptions {
|
|
|
11
13
|
apiKey?: string;
|
|
12
14
|
uid?: string;
|
|
13
15
|
timeoutMs?: number;
|
|
16
|
+
configExcludedSkills?: string[];
|
|
14
17
|
}
|
|
15
18
|
export declare function searchTools(options: SearchToolsOptions): Promise<ToolSearchResult | null>;
|
|
16
19
|
export declare function formatToolsForContext(result: ToolSearchResult, includeInstallUrl?: boolean): string;
|
|
@@ -48,24 +48,37 @@ export function readEnvFile(filePath) {
|
|
|
48
48
|
}
|
|
49
49
|
return envDict;
|
|
50
50
|
}
|
|
51
|
-
export function
|
|
52
|
-
const
|
|
53
|
-
const
|
|
51
|
+
export function getSkillsInDirectory(dirPath) {
|
|
52
|
+
const expandedDir = expandPath(dirPath);
|
|
53
|
+
const skills = [];
|
|
54
54
|
try {
|
|
55
|
-
if (fs.existsSync(
|
|
56
|
-
const entries = fs.readdirSync(
|
|
55
|
+
if (fs.existsSync(expandedDir) && fs.statSync(expandedDir).isDirectory()) {
|
|
56
|
+
const entries = fs.readdirSync(expandedDir);
|
|
57
57
|
for (const entry of entries) {
|
|
58
|
-
const entryPath = path.join(
|
|
58
|
+
const entryPath = path.join(expandedDir, entry);
|
|
59
59
|
if (fs.statSync(entryPath).isDirectory()) {
|
|
60
|
-
|
|
60
|
+
skills.push(entry);
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
catch {
|
|
66
|
-
//
|
|
66
|
+
// 目录不存在或读取错误 - 返回空列表
|
|
67
|
+
}
|
|
68
|
+
return skills;
|
|
69
|
+
}
|
|
70
|
+
export function getInstalledSkills() {
|
|
71
|
+
return getSkillsInDirectory("~/.openclaw/workspace/skills");
|
|
72
|
+
}
|
|
73
|
+
export function buildExcludedSkillIds(excludedSkills) {
|
|
74
|
+
const coreSkills = getSkillsInDirectory("~/core_skills");
|
|
75
|
+
const excluded = new Set(coreSkills);
|
|
76
|
+
if (excludedSkills) {
|
|
77
|
+
for (const skillId of excludedSkills) {
|
|
78
|
+
excluded.add(skillId);
|
|
79
|
+
}
|
|
67
80
|
}
|
|
68
|
-
return
|
|
81
|
+
return excluded;
|
|
69
82
|
}
|
|
70
83
|
function formatSkillData(rawSkills, installedSkills) {
|
|
71
84
|
const formattedSkills = [];
|
|
@@ -83,7 +96,7 @@ function formatSkillData(rawSkills, installedSkills) {
|
|
|
83
96
|
return formattedSkills;
|
|
84
97
|
}
|
|
85
98
|
export async function searchTools(options) {
|
|
86
|
-
const { query, maxTools = 5, includeUninstalledOnly = true, envFilePath = "~/.openclaw/.xiaoyienv", serviceUrl: configServiceUrl, apiKey: configApiKey, uid: configUid, timeoutMs = 1000, } = options;
|
|
99
|
+
const { query, maxTools = 5, includeUninstalledOnly = true, envFilePath = "~/.openclaw/.xiaoyienv", serviceUrl: configServiceUrl, apiKey: configApiKey, uid: configUid, timeoutMs = 1000, configExcludedSkills, } = options;
|
|
87
100
|
const envConfig = readEnvFile(envFilePath);
|
|
88
101
|
const hasRequiredConfig = !!envConfig.SERVICE_URL && !!envConfig.PERSONAL_API_KEY && !!envConfig.PERSONAL_UID;
|
|
89
102
|
const serviceUrl = configServiceUrl ?? envConfig.SERVICE_URL;
|
|
@@ -123,7 +136,10 @@ export async function searchTools(options) {
|
|
|
123
136
|
const rawSkills = responseData.content.skills;
|
|
124
137
|
const installedSkills = getInstalledSkills();
|
|
125
138
|
const formattedData = formatSkillData(rawSkills, installedSkills);
|
|
126
|
-
const
|
|
139
|
+
const excludedSKills = buildExcludedSkillIds(configExcludedSkills);
|
|
140
|
+
const candidateTools = formattedData
|
|
141
|
+
.filter((skills) => !excludedSKills.has(skills.skillId))
|
|
142
|
+
.filter((tool) => (tool.rrfScore ?? 0) >= 0.016);
|
|
127
143
|
logger.log(`${PLUGIN_LOG_PREFIX} [DEBUG] Candidates with rrfScore >= 0.016: ${candidateTools.length}, details: ${candidateTools.map((t) => `${t.skillId}(rrfScore=${t.rrfScore}, status=${t.status})`).join(", ")}`);
|
|
128
144
|
const hasInstalledInCandidates = candidateTools.some((tool) => tool.status === "已安装");
|
|
129
145
|
if (hasInstalledInCandidates) {
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { SessionContext } from "./session-manager.js";
|
|
2
|
+
export interface CLIDefinition {
|
|
3
|
+
name: string;
|
|
4
|
+
version?: string;
|
|
5
|
+
description: string;
|
|
6
|
+
executeSide?: "device" | "cloud" | "local";
|
|
7
|
+
requirePermissions?: string[];
|
|
8
|
+
inputSchema?: CLIInputSchema;
|
|
9
|
+
outputSchema?: CLIOutputSchema;
|
|
10
|
+
}
|
|
11
|
+
export interface CLIInputSchema {
|
|
12
|
+
properties?: Record<string, CLIPropertyDef>;
|
|
13
|
+
required?: string[];
|
|
14
|
+
}
|
|
15
|
+
export interface CLIPropertyDef {
|
|
16
|
+
type: "string" | "number" | "boolean" | "array";
|
|
17
|
+
description?: string;
|
|
18
|
+
default?: unknown;
|
|
19
|
+
}
|
|
20
|
+
export interface CLIOutputSchema {
|
|
21
|
+
type: "object";
|
|
22
|
+
properties: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
interface CLICacheEntry {
|
|
25
|
+
cliName: string;
|
|
26
|
+
definition: CLIDefinition;
|
|
27
|
+
filePath: string;
|
|
28
|
+
mtimeMs: number;
|
|
29
|
+
}
|
|
30
|
+
export interface ParsedCLIArgs {
|
|
31
|
+
toolName: string;
|
|
32
|
+
subcommand: string;
|
|
33
|
+
args: Record<string, unknown>;
|
|
34
|
+
}
|
|
35
|
+
declare function parseSkillName(skillDir: string): string | null;
|
|
36
|
+
export declare function extractCLINames(content: string): string[] | null;
|
|
37
|
+
export interface CLICache {
|
|
38
|
+
getCLIs(skillName: string): CLICacheEntry[] | null;
|
|
39
|
+
getCLI(skillName: string, cliName: string): CLICacheEntry | null;
|
|
40
|
+
hasCLI(skillName: string, cliName: string): boolean;
|
|
41
|
+
refresh(): Promise<void>;
|
|
42
|
+
getRootDir(): string;
|
|
43
|
+
}
|
|
44
|
+
export declare function getCLICache(rootDir?: string): CLICache;
|
|
45
|
+
/**
|
|
46
|
+
* Parse and validate a command string against a CLI definition.
|
|
47
|
+
* Returns typed ParsedCLIArgs ready for ExecuteCLI assembly.
|
|
48
|
+
*/
|
|
49
|
+
export declare function parseAndValidate(command: string, cliDef: CLIDefinition): ParsedCLIArgs;
|
|
50
|
+
export interface CLIExecuteResult {
|
|
51
|
+
content: Array<{
|
|
52
|
+
type: "text";
|
|
53
|
+
text: string;
|
|
54
|
+
}>;
|
|
55
|
+
details: unknown;
|
|
56
|
+
isError?: boolean;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Execute a CLI command via WebSocket:
|
|
60
|
+
* 1. Acquire CLI serial lock (per session)
|
|
61
|
+
* 2. Send FunctionExecute/ExecuteCLI
|
|
62
|
+
* 3. Wait for FunctionExecute/ExecuteCLIRsp (via cli-response event)
|
|
63
|
+
* 4. Return result or error
|
|
64
|
+
*/
|
|
65
|
+
export declare function executeCLI(parsed: ParsedCLIArgs, sessionCtx: SessionContext, toolCallId: string): Promise<CLIExecuteResult>;
|
|
66
|
+
export declare function cliErrorToResult(err: unknown): CLIExecuteResult;
|
|
67
|
+
export { parseSkillName as deriveSkillName };
|
|
68
|
+
interface BeforeToolCallEvent {
|
|
69
|
+
toolName: string;
|
|
70
|
+
params: Record<string, unknown>;
|
|
71
|
+
toolCallId: string;
|
|
72
|
+
runId?: string;
|
|
73
|
+
}
|
|
74
|
+
interface HookContext {
|
|
75
|
+
sessionKey?: string;
|
|
76
|
+
sessionId?: string;
|
|
77
|
+
workspaceDir?: string;
|
|
78
|
+
agentId?: string;
|
|
79
|
+
}
|
|
80
|
+
interface HookResult {
|
|
81
|
+
noop?: boolean;
|
|
82
|
+
block?: boolean;
|
|
83
|
+
blockReason?: string;
|
|
84
|
+
requireApproval?: {
|
|
85
|
+
title: string;
|
|
86
|
+
description: string;
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* before_tool_call hook for CLI exec interception.
|
|
91
|
+
*
|
|
92
|
+
* Only handles built-in `exec` calls whose command prefix matches a CLI
|
|
93
|
+
* declared in the current skill's metadata.clis + available_clis.json.
|
|
94
|
+
* Non-matching commands return undefined (noop) so native exec handles them.
|
|
95
|
+
*/
|
|
96
|
+
export declare function cliBeforeToolCallHandler(event: BeforeToolCallEvent, ctx: HookContext): Promise<HookResult | undefined>;
|
|
97
|
+
/**
|
|
98
|
+
* Register the CLI hook on the OpenClaw plugin API.
|
|
99
|
+
* Call during `registrationMode === "full"` in the plugin entry point.
|
|
100
|
+
*/
|
|
101
|
+
export declare function registerCLIHook(api: {
|
|
102
|
+
on: (event: string, handler: (...args: any[]) => any) => void;
|
|
103
|
+
}): void;
|