feat: add tool status management and localization for skill installation
- Updated chat message types to include tool statuses. - Enhanced localization files for English, Thai, and Chinese to support new tool status messages. - Modified HomePage and SkillsPage components to handle tool statuses in chat messages. - Implemented tool status merging and updating logic in the chat store. - Added handling for tool status events in the gateway event processing. - Created tests for chat message rendering with tool statuses and skill installation shortcuts. - Improved gateway event dispatching for tool lifecycle events.
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { EventEmitter } from 'node:events';
|
||||
import { createServer } from 'node:net';
|
||||
import { join } from 'node:path';
|
||||
@@ -63,6 +62,7 @@ import {
|
||||
import { GatewayStateController, type GatewayRuntimeStatus } from './state';
|
||||
import { connectGatewaySocket, waitForGatewayReady } from './ws-client';
|
||||
import { dispatchGatewayRpcMethod } from './rpc-dispatch';
|
||||
import { createRandomId } from './random-id';
|
||||
|
||||
type RuntimeChangeBroadcast = {
|
||||
topics: RuntimeRefreshTopic[];
|
||||
@@ -95,6 +95,11 @@ type GatewayNotificationEvent =
|
||||
params?: unknown;
|
||||
};
|
||||
|
||||
type GatewayToolStatusNotification = {
|
||||
status: 'running' | 'completed' | 'error';
|
||||
payload?: unknown;
|
||||
};
|
||||
|
||||
export interface GatewayManagerEvents {
|
||||
status: (status: GatewayRuntimeStatus) => void;
|
||||
message: (message: unknown) => void;
|
||||
@@ -104,6 +109,7 @@ export interface GatewayManagerEvents {
|
||||
'channel:status': (data: { channelId: string; status: string }) => void;
|
||||
'chat:message': (data: { message: unknown }) => void;
|
||||
'gateway:ready': (payload: unknown) => void;
|
||||
'tool:status': (payload: GatewayToolStatusNotification) => void;
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
@@ -243,6 +249,78 @@ function extractTextFromGatewayPayload(payload: Record<string, unknown>): string
|
||||
return '';
|
||||
}
|
||||
|
||||
function getRecordStringField(record: Record<string, unknown>, ...keys: string[]): string | undefined {
|
||||
for (const key of keys) {
|
||||
const value = record[key];
|
||||
if (typeof value === 'string' && value.trim()) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getRecordNumberField(record: Record<string, unknown>, ...keys: string[]): number | undefined {
|
||||
for (const key of keys) {
|
||||
const value = record[key];
|
||||
if (typeof value === 'number' && Number.isFinite(value)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function normalizeGatewayToolStatus(value: unknown): 'running' | 'completed' | 'error' {
|
||||
if (typeof value !== 'string') {
|
||||
return 'completed';
|
||||
}
|
||||
|
||||
switch (value.trim().toLowerCase()) {
|
||||
case 'running':
|
||||
case 'completed':
|
||||
case 'error':
|
||||
return value.trim().toLowerCase() as 'running' | 'completed' | 'error';
|
||||
case 'failed':
|
||||
return 'error';
|
||||
default:
|
||||
return 'completed';
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeToolStatusEvent(value: GatewayToolStatusNotification): GatewayEvent | null {
|
||||
const payload = isRecord(value.payload) ? value.payload : {};
|
||||
const sessionKey = getRecordStringField(payload, 'sessionKey', 'session_key');
|
||||
const runId = getRecordStringField(payload, 'runId', 'run_id');
|
||||
const toolName = getRecordStringField(payload, 'toolName', 'tool_name', 'name');
|
||||
|
||||
if (!sessionKey || !runId || !toolName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const toolCallId = getRecordStringField(payload, 'toolCallId', 'tool_call_id', 'id');
|
||||
const summary = getRecordStringField(payload, 'summary', 'message');
|
||||
const durationMs = getRecordNumberField(payload, 'durationMs', 'duration_ms');
|
||||
const errorMessage = getRecordStringField(payload, 'error');
|
||||
const status = errorMessage
|
||||
? 'error'
|
||||
: normalizeGatewayToolStatus(payload.status ?? value.status);
|
||||
|
||||
return {
|
||||
type: 'tool:status',
|
||||
sessionKey: normalizeAgentSessionKey(sessionKey),
|
||||
runId,
|
||||
toolCallId,
|
||||
toolName,
|
||||
status,
|
||||
updatedAt: normalizeTimestamp(payload.timestamp) ?? Date.now(),
|
||||
summary: errorMessage ?? summary,
|
||||
durationMs,
|
||||
input: payload.input ?? payload.arguments ?? payload.args,
|
||||
result: payload.result,
|
||||
};
|
||||
}
|
||||
|
||||
function buildGatewayRpcError(error: unknown, fallback: string): Error {
|
||||
if (typeof error === 'string' && error.trim()) {
|
||||
return new Error(error);
|
||||
@@ -292,7 +370,7 @@ export class GatewayManager extends EventEmitter {
|
||||
private readonly processOwner = new OpenClawProcessOwner();
|
||||
private readonly pendingRequests = new Map<string, PendingGatewayRequest>();
|
||||
private readonly deltaSnapshots = new Map<string, string>();
|
||||
private gatewayToken = randomUUID();
|
||||
private gatewayToken = createRandomId();
|
||||
private socket: WebSocket | null = null;
|
||||
private child: GatewayProcessHandle | null = null;
|
||||
private port: number | null = null;
|
||||
@@ -343,6 +421,7 @@ export class GatewayManager extends EventEmitter {
|
||||
override on(eventName: 'channel:status', listener: GatewayManagerEvents['channel:status']): this;
|
||||
override on(eventName: 'chat:message', listener: GatewayManagerEvents['chat:message']): this;
|
||||
override on(eventName: 'gateway:ready', listener: GatewayManagerEvents['gateway:ready']): this;
|
||||
override on(eventName: 'tool:status', listener: GatewayManagerEvents['tool:status']): this;
|
||||
override on(eventName: string | symbol, listener: (...args: any[]) => void): this;
|
||||
override on(eventName: string | symbol, listener: (...args: any[]) => void): this {
|
||||
return super.on(eventName, listener);
|
||||
@@ -356,6 +435,7 @@ export class GatewayManager extends EventEmitter {
|
||||
override once(eventName: 'channel:status', listener: GatewayManagerEvents['channel:status']): this;
|
||||
override once(eventName: 'chat:message', listener: GatewayManagerEvents['chat:message']): this;
|
||||
override once(eventName: 'gateway:ready', listener: GatewayManagerEvents['gateway:ready']): this;
|
||||
override once(eventName: 'tool:status', listener: GatewayManagerEvents['tool:status']): this;
|
||||
override once(eventName: string | symbol, listener: (...args: any[]) => void): this;
|
||||
override once(eventName: string | symbol, listener: (...args: any[]) => void): this {
|
||||
return super.once(eventName, listener);
|
||||
@@ -369,6 +449,7 @@ export class GatewayManager extends EventEmitter {
|
||||
override off(eventName: 'channel:status', listener: GatewayManagerEvents['channel:status']): this;
|
||||
override off(eventName: 'chat:message', listener: GatewayManagerEvents['chat:message']): this;
|
||||
override off(eventName: 'gateway:ready', listener: GatewayManagerEvents['gateway:ready']): this;
|
||||
override off(eventName: 'tool:status', listener: GatewayManagerEvents['tool:status']): this;
|
||||
override off(eventName: string | symbol, listener: (...args: any[]) => void): this;
|
||||
override off(eventName: string | symbol, listener: (...args: any[]) => void): this {
|
||||
return super.off(eventName, listener);
|
||||
@@ -414,6 +495,13 @@ export class GatewayManager extends EventEmitter {
|
||||
}
|
||||
});
|
||||
|
||||
this.on('tool:status', (payload) => {
|
||||
const event = normalizeToolStatusEvent(payload);
|
||||
if (event) {
|
||||
this.broadcast(event);
|
||||
}
|
||||
});
|
||||
|
||||
this.on('error', (error) => {
|
||||
logManager.debug('GatewayManager emitted error event', error);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user