@ynhcj/xiaoyi-channel 0.0.211-beta → 0.0.212-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/src/bot.js +15 -4
- package/dist/src/tools/hmos-cli.d.ts +7 -1
- package/dist/src/tools/hmos-cli.js +25 -29
- package/dist/src/tools/invoke.js +20 -12
- package/dist/src/websocket.js +1 -0
- package/package.json +1 -1
package/dist/src/bot.js
CHANGED
|
@@ -597,13 +597,24 @@ async function dispatchSteerWhenReady(params) {
|
|
|
597
597
|
: "";
|
|
598
598
|
const steerMessage = `${steerText}${fileHint}`;
|
|
599
599
|
log.log(`[STEER-QUEUE] Injecting steer message directly into active run`);
|
|
600
|
-
const
|
|
601
|
-
|
|
602
|
-
|
|
600
|
+
const STEER_RETRY_MAX = 3;
|
|
601
|
+
const STEER_RETRY_DELAY_MS = 500;
|
|
602
|
+
let injected = false;
|
|
603
|
+
for (let attempt = 0; attempt < STEER_RETRY_MAX; attempt++) {
|
|
604
|
+
injected = queueAgentHarnessMessage(sessionId, steerMessage, {
|
|
605
|
+
steeringMode: "all",
|
|
606
|
+
});
|
|
607
|
+
if (injected)
|
|
608
|
+
break;
|
|
609
|
+
if (attempt < STEER_RETRY_MAX - 1 && hasActiveTask(sessionId)) {
|
|
610
|
+
log.log(`[STEER-QUEUE] Retry ${attempt + 1}/${STEER_RETRY_MAX}: run not accepting, waiting ${STEER_RETRY_DELAY_MS}ms`);
|
|
611
|
+
await new Promise(r => setTimeout(r, STEER_RETRY_DELAY_MS));
|
|
612
|
+
}
|
|
613
|
+
}
|
|
603
614
|
if (injected) {
|
|
604
615
|
log.log(`[STEER-QUEUE] Steer message injected successfully`);
|
|
605
616
|
}
|
|
606
617
|
else {
|
|
607
|
-
log.log(`[STEER-QUEUE] Steer injection failed — run may not be accepting messages`);
|
|
618
|
+
log.log(`[STEER-QUEUE] Steer injection failed after ${STEER_RETRY_MAX} attempts — run may not be accepting messages`);
|
|
608
619
|
}
|
|
609
620
|
}
|
|
@@ -38,6 +38,11 @@ export interface CLICache {
|
|
|
38
38
|
getCLIs(skillName: string): CLICacheEntry[] | null;
|
|
39
39
|
getCLI(skillName: string, cliName: string): CLICacheEntry | null;
|
|
40
40
|
hasCLI(skillName: string, cliName: string): boolean;
|
|
41
|
+
/** Search all cached skills for a CLI whose name matches the command prefix. */
|
|
42
|
+
findCLIByCommand(command: string): {
|
|
43
|
+
entry: CLICacheEntry;
|
|
44
|
+
skillName: string;
|
|
45
|
+
} | null;
|
|
41
46
|
refresh(): Promise<void>;
|
|
42
47
|
getRootDir(): string;
|
|
43
48
|
}
|
|
@@ -90,7 +95,8 @@ interface HookResult {
|
|
|
90
95
|
* before_tool_call hook for CLI exec interception.
|
|
91
96
|
*
|
|
92
97
|
* Only handles built-in `exec` calls whose command prefix matches a CLI
|
|
93
|
-
*
|
|
98
|
+
* from any skill's metadata.clis + available_clis.json (searched across
|
|
99
|
+
* all cached skills, not just the current session's skill).
|
|
94
100
|
* Non-matching commands return undefined (noop) so native exec handles them.
|
|
95
101
|
*/
|
|
96
102
|
export declare function cliBeforeToolCallHandler(event: BeforeToolCallEvent, ctx: HookContext): Promise<HookResult | undefined>;
|
|
@@ -216,6 +216,23 @@ function wrapState(state) {
|
|
|
216
216
|
return entries.find(e => e.cliName === cliName) ?? null;
|
|
217
217
|
},
|
|
218
218
|
hasCLI(skillName, cliName) { return this.getCLI(skillName, cliName) !== null; },
|
|
219
|
+
findCLIByCommand(command) {
|
|
220
|
+
maybeRefresh(state);
|
|
221
|
+
const all = [];
|
|
222
|
+
for (const [skillName, entries] of state.skillClis) {
|
|
223
|
+
for (const entry of entries) {
|
|
224
|
+
all.push({ entry, skillName });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Longest CLI name first so "foo bar" matches before "foo"
|
|
228
|
+
all.sort((a, b) => b.entry.cliName.length - a.entry.cliName.length);
|
|
229
|
+
for (const item of all) {
|
|
230
|
+
if (command === item.entry.cliName || command.startsWith(item.entry.cliName + " ")) {
|
|
231
|
+
return item;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return null;
|
|
235
|
+
},
|
|
219
236
|
async refresh() {
|
|
220
237
|
state.skillClis = scanCLIDirectories(state.rootDir);
|
|
221
238
|
state.lastScanMs = Date.now();
|
|
@@ -470,7 +487,8 @@ export { parseSkillName as deriveSkillName };
|
|
|
470
487
|
* before_tool_call hook for CLI exec interception.
|
|
471
488
|
*
|
|
472
489
|
* Only handles built-in `exec` calls whose command prefix matches a CLI
|
|
473
|
-
*
|
|
490
|
+
* from any skill's metadata.clis + available_clis.json (searched across
|
|
491
|
+
* all cached skills, not just the current session's skill).
|
|
474
492
|
* Non-matching commands return undefined (noop) so native exec handles them.
|
|
475
493
|
*/
|
|
476
494
|
export async function cliBeforeToolCallHandler(event, ctx) {
|
|
@@ -481,37 +499,15 @@ export async function cliBeforeToolCallHandler(event, ctx) {
|
|
|
481
499
|
return undefined;
|
|
482
500
|
const t0 = performance.now();
|
|
483
501
|
const command = rawCommand.trim();
|
|
484
|
-
//
|
|
485
|
-
const workspaceDir = ctx.workspaceDir;
|
|
486
|
-
if (!workspaceDir) {
|
|
487
|
-
logger.log(`[CLI-HOOK] no workspaceDir, noop (${(performance.now() - t0).toFixed(1)}ms)`);
|
|
488
|
-
return undefined;
|
|
489
|
-
}
|
|
490
|
-
const skillName = parseSkillName(workspaceDir);
|
|
491
|
-
if (!skillName) {
|
|
492
|
-
logger.log(`[CLI-HOOK] no skill name in ${workspaceDir}, noop (${(performance.now() - t0).toFixed(1)}ms)`);
|
|
493
|
-
return undefined;
|
|
494
|
-
}
|
|
502
|
+
// Search all cached skills for a CLI matching the command prefix
|
|
495
503
|
const cache = getCLICache();
|
|
496
|
-
const
|
|
497
|
-
if (!
|
|
498
|
-
logger.log(`[CLI-HOOK]
|
|
499
|
-
return undefined;
|
|
500
|
-
}
|
|
501
|
-
// Match longest CLI name first
|
|
502
|
-
const sorted = [...skillCLIs].sort((a, b) => b.cliName.length - a.cliName.length);
|
|
503
|
-
let matchedCLI = null;
|
|
504
|
-
for (const entry of sorted) {
|
|
505
|
-
if (command === entry.cliName || command.startsWith(entry.cliName + " ")) {
|
|
506
|
-
matchedCLI = entry;
|
|
507
|
-
break;
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
if (!matchedCLI) {
|
|
511
|
-
logger.log(`[CLI-HOOK] No CLI match for command prefix, noop (${(performance.now() - t0).toFixed(1)}ms)`);
|
|
504
|
+
const match = cache.findCLIByCommand(command);
|
|
505
|
+
if (!match) {
|
|
506
|
+
logger.log(`[CLI-HOOK] No CLI match for '${command}', noop (${(performance.now() - t0).toFixed(1)}ms)`);
|
|
512
507
|
return undefined;
|
|
513
508
|
}
|
|
514
|
-
|
|
509
|
+
const { entry: matchedCLI, skillName } = match;
|
|
510
|
+
logger.log(`[CLI-HOOK] Matched CLI '${matchedCLI.cliName}' in skill '${skillName}' (${(performance.now() - t0).toFixed(1)}ms)`);
|
|
515
511
|
// Parse and validate
|
|
516
512
|
let parsed;
|
|
517
513
|
try {
|
package/dist/src/tools/invoke.js
CHANGED
|
@@ -47,6 +47,13 @@ const CLIENT_ERROR_CODES = new Set([
|
|
|
47
47
|
"DEVICE_TOOL_BLOCKED",
|
|
48
48
|
"UNKNOWN",
|
|
49
49
|
]);
|
|
50
|
+
/** Parse the &-separated taskId into sessionId (part 0) and interactionId (part 1). */
|
|
51
|
+
function parseTaskId(taskId) {
|
|
52
|
+
const parts = taskId.split("&");
|
|
53
|
+
const taskSessionId = parts[0] ?? taskId;
|
|
54
|
+
const interactionId = parseInt(parts[1] ?? "1", 10) || 1;
|
|
55
|
+
return { taskSessionId, interactionId };
|
|
56
|
+
}
|
|
50
57
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
51
58
|
// 2. Errors
|
|
52
59
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -127,7 +134,7 @@ const REFRESH_INTERVAL_MS = 30_000;
|
|
|
127
134
|
const REQUIRED_FIELDS = ["schemaVersion", "bundleName", "toolName", "pluginType", "description", "arguments"];
|
|
128
135
|
const CORE_FIELDS = ["bundleName", "toolName", "toolType", "pluginType", "protocol", "description", "arguments", "deviceCommand"];
|
|
129
136
|
const VALID_PLUGIN_TYPES = new Set(["Cloud", "Device", "MCP"]);
|
|
130
|
-
const VALID_PROTOCOLS = new Set(["REST", "SSE", "Websocket"]);
|
|
137
|
+
const VALID_PROTOCOLS = new Set(["REST", "SSE", "Websocket", "WebSocket"]);
|
|
131
138
|
const FILENAME_PATTERN = /^(.+)__(.+)\.json$/;
|
|
132
139
|
const _g = globalThis;
|
|
133
140
|
const CACHE_SLOT = "__xyInvokeToolCache";
|
|
@@ -526,13 +533,14 @@ function loadCloudConfig() {
|
|
|
526
533
|
return { serviceUrl: env["SERVICE_URL"], apiKey: env["PERSONAL-API-KEY"], uid: env["PERSONAL-UID"] };
|
|
527
534
|
}
|
|
528
535
|
function buildCloudRequest(p) {
|
|
536
|
+
const { taskSessionId, interactionId } = parseTaskId(p.taskId);
|
|
529
537
|
return {
|
|
530
538
|
version: "1.0",
|
|
531
539
|
session: {
|
|
532
540
|
isNew: "true",
|
|
533
|
-
sessionId:
|
|
534
|
-
interactionId
|
|
535
|
-
conversationId:
|
|
541
|
+
sessionId: taskSessionId,
|
|
542
|
+
interactionId,
|
|
543
|
+
conversationId: p.sessionId, // ctx.sessionId
|
|
536
544
|
agentLoginSessionId1: p.agentId,
|
|
537
545
|
},
|
|
538
546
|
endpoint: {
|
|
@@ -564,11 +572,11 @@ function buildCloudRequest(p) {
|
|
|
564
572
|
},
|
|
565
573
|
};
|
|
566
574
|
}
|
|
567
|
-
function buildHeaders(config, skillName, protocol) {
|
|
575
|
+
function buildHeaders(config, skillName, protocol, taskId) {
|
|
568
576
|
return {
|
|
569
577
|
"content-type": "application/json",
|
|
570
578
|
accept: protocol === "REST" ? "application/json" : "text/event-stream",
|
|
571
|
-
"x-hag-trace-id":
|
|
579
|
+
"x-hag-trace-id": taskId,
|
|
572
580
|
"x-uid": config.uid,
|
|
573
581
|
"x-api-key": config.apiKey,
|
|
574
582
|
"x-request-from": "openclaw",
|
|
@@ -582,10 +590,10 @@ function buildHeaders(config, skillName, protocol) {
|
|
|
582
590
|
// - REST: single JSON frame
|
|
583
591
|
// - SSE/Websocket: multiple SSE frames; only the final frame is used (§4.7)
|
|
584
592
|
async function executePluginExecutor(config, requestBody, headers, toolName, bundleName, protocol) {
|
|
585
|
-
//
|
|
586
|
-
const wsBaseUrl = config.serviceUrl.replace(/^
|
|
593
|
+
// Map http→ws, https→wss
|
|
594
|
+
const wsBaseUrl = config.serviceUrl.replace(/^http(s)?:\/\//i, "ws$1://");
|
|
587
595
|
const url = `${wsBaseUrl}${UNIFIED_API_SUFFIX}`;
|
|
588
|
-
const isStreaming = protocol === "SSE" || protocol === "Websocket";
|
|
596
|
+
const isStreaming = protocol === "SSE" || protocol === "Websocket" || protocol === "WebSocket";
|
|
589
597
|
logger.log(`[INVOKE-CLOUD] calling PluginExecutor via WebSocket`, { url, toolName, bundleName, protocol });
|
|
590
598
|
const urlObj = new URL(url);
|
|
591
599
|
const isWssWithIP = urlObj.protocol === "wss:" && /^(\d{1,3}\.){3}\d{1,3}$/.test(urlObj.hostname);
|
|
@@ -857,14 +865,14 @@ async function executeCloudTool(params) {
|
|
|
857
865
|
toolName, bundleName, skillName, protocol,
|
|
858
866
|
businessKeysCount: Object.keys(businessParams).length,
|
|
859
867
|
});
|
|
860
|
-
if (protocol !== "REST" && protocol !== "SSE" && protocol !== "Websocket") {
|
|
868
|
+
if (protocol !== "REST" && protocol !== "SSE" && protocol !== "Websocket" && protocol !== "WebSocket") {
|
|
861
869
|
logger.warn("[INVOKE-CLOUD] unknown protocol", { toolName, bundleName, protocol });
|
|
862
870
|
throw new InvokeError("UNSUPPORTED_PROTOCOL", `Unknown protocol: ${protocol}`);
|
|
863
871
|
}
|
|
864
872
|
// Per invoke.md §4.3: REST, SSE, and Websocket all use the same endpoint.
|
|
865
873
|
// Only the accept header and response handling differ.
|
|
866
874
|
const config = loadCloudConfig();
|
|
867
|
-
return executePluginExecutor(config, buildCloudRequest(params), buildHeaders(config, skillName, protocol), toolName, bundleName, protocol);
|
|
875
|
+
return executePluginExecutor(config, buildCloudRequest(params), buildHeaders(config, skillName, protocol, params.taskId), toolName, bundleName, protocol);
|
|
868
876
|
}
|
|
869
877
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
870
878
|
// 7. Device executor
|
|
@@ -1162,7 +1170,7 @@ export const invokeTool = {
|
|
|
1162
1170
|
logger.log(`[INVOKE] dispatching to ${pluginType} executor`, { toolCallId, toolName, bundleName, pluginType });
|
|
1163
1171
|
try {
|
|
1164
1172
|
if (pluginType === "Cloud" || pluginType === "MCP") {
|
|
1165
|
-
const result = await executeCloudTool({ definition, businessParams, skillName, sessionId: ctx?.sessionId ?? "", agentId: ctx?.agentId ?? "" });
|
|
1173
|
+
const result = await executeCloudTool({ definition, businessParams, skillName, sessionId: ctx?.sessionId ?? "", agentId: ctx?.agentId ?? "", taskId: ctx?.taskId ?? toolCallId });
|
|
1166
1174
|
logger.log("[INVOKE] cloud execution succeeded", {
|
|
1167
1175
|
toolCallId, toolName, bundleName, pluginType,
|
|
1168
1176
|
resultLength: result.content[0]?.text?.length ?? 0,
|
package/dist/src/websocket.js
CHANGED
|
@@ -512,6 +512,7 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
512
512
|
? logger.withContext(sessionId, taskId)
|
|
513
513
|
: { log: (msg, ...args) => logger.log(msg, ...args) };
|
|
514
514
|
log.log(`[WS-RECV] Raw message frame, size: ${messageStr.length} characters`);
|
|
515
|
+
log.log(`[WS-RECV] Full message body: ${messageStr}`);
|
|
515
516
|
// Handle direct cross-task requests (top-level networkId)
|
|
516
517
|
const directRunCrossTaskRequest = this.toRunCrossTaskA2ARequest(parsed);
|
|
517
518
|
if (directRunCrossTaskRequest) {
|