refactor: clean CLI taxonomy — canonical subcommands, merged memory.update, no aliases

- calendar: split write → create/read/update/delete/share
- contacts: rename lookup → read
- memory: merge write+forget → update (unified action field in operations)
- Remove all alias/normalization logic from adapter and handlers
- Update tool_postprocessor ui_hints builders to canonical keys
- Remove frontend legacy TOOL_CALL_START/ARGS/END events and ToolCallItem
- Update SKILL.md files and protocol docs
- Update tests and settings screens
This commit is contained in:
qzl
2026-04-23 12:12:41 +08:00
parent 91077a933d
commit 19e273a9e6
48 changed files with 1578 additions and 811 deletions
+23 -56
View File
@@ -5,9 +5,6 @@ class AgUiEventTypeWire {
static const stepStarted = 'STEP_STARTED';
static const stepFinished = 'STEP_FINISHED';
static const textMessageEnd = 'TEXT_MESSAGE_END';
static const toolCallStart = 'TOOL_CALL_START';
static const toolCallArgs = 'TOOL_CALL_ARGS';
static const toolCallEnd = 'TOOL_CALL_END';
static const toolCallResult = 'TOOL_CALL_RESULT';
}
@@ -18,9 +15,6 @@ enum AgUiEventType {
stepStarted,
stepFinished,
textMessageEnd,
toolCallStart,
toolCallArgs,
toolCallEnd,
toolCallResult,
unknown,
}
@@ -32,9 +26,6 @@ const _wireToTypeMap = {
AgUiEventTypeWire.stepStarted: AgUiEventType.stepStarted,
AgUiEventTypeWire.stepFinished: AgUiEventType.stepFinished,
AgUiEventTypeWire.textMessageEnd: AgUiEventType.textMessageEnd,
AgUiEventTypeWire.toolCallStart: AgUiEventType.toolCallStart,
AgUiEventTypeWire.toolCallArgs: AgUiEventType.toolCallArgs,
AgUiEventTypeWire.toolCallEnd: AgUiEventType.toolCallEnd,
AgUiEventTypeWire.toolCallResult: AgUiEventType.toolCallResult,
};
@@ -45,9 +36,6 @@ const _typeToWireMap = {
AgUiEventType.stepStarted: AgUiEventTypeWire.stepStarted,
AgUiEventType.stepFinished: AgUiEventTypeWire.stepFinished,
AgUiEventType.textMessageEnd: AgUiEventTypeWire.textMessageEnd,
AgUiEventType.toolCallStart: AgUiEventTypeWire.toolCallStart,
AgUiEventType.toolCallArgs: AgUiEventTypeWire.toolCallArgs,
AgUiEventType.toolCallEnd: AgUiEventTypeWire.toolCallEnd,
AgUiEventType.toolCallResult: AgUiEventTypeWire.toolCallResult,
AgUiEventType.unknown: '',
};
@@ -74,9 +62,6 @@ abstract class AgUiEvent {
AgUiEventType.stepStarted => StepStartedEvent.fromJson(json),
AgUiEventType.stepFinished => StepFinishedEvent.fromJson(json),
AgUiEventType.textMessageEnd => TextMessageEndEvent.fromJson(json),
AgUiEventType.toolCallStart => ToolCallStartEvent.fromJson(json),
AgUiEventType.toolCallArgs => ToolCallArgsEvent.fromJson(json),
AgUiEventType.toolCallEnd => ToolCallEndEvent.fromJson(json),
AgUiEventType.toolCallResult => ToolCallResultEvent.fromJson(json),
AgUiEventType.unknown => UnknownAgUiEvent(rawJson: json),
};
@@ -157,12 +142,14 @@ class TextMessageEndEvent extends AgUiEvent {
required this.answer,
required this.role,
required this.status,
this.suggestedActions = const <String>[],
}) : super(type: AgUiEventType.textMessageEnd);
final String messageId;
final String answer;
final String role;
final String status;
final List<String> suggestedActions;
factory TextMessageEndEvent.fromJson(Map<String, dynamic> json) =>
TextMessageEndEvent(
@@ -170,53 +157,17 @@ class TextMessageEndEvent extends AgUiEvent {
answer: _asString(json['answer']),
role: _asString(json['role'], fallback: 'assistant'),
status: _asString(json['status'], fallback: 'success'),
suggestedActions: _asStringList(json['suggested_actions']),
);
}
class ToolCallStartEvent extends AgUiEvent {
ToolCallStartEvent({required this.toolCallId, required this.toolCallName})
: super(type: AgUiEventType.toolCallStart);
final String toolCallId;
final String toolCallName;
factory ToolCallStartEvent.fromJson(Map<String, dynamic> json) =>
ToolCallStartEvent(
toolCallId: _asString(json['toolCallId']),
toolCallName: _asString(json['toolCallName']),
);
}
class ToolCallArgsEvent extends AgUiEvent {
ToolCallArgsEvent({required this.toolCallId, required this.args})
: super(type: AgUiEventType.toolCallArgs);
final String toolCallId;
final Map<String, dynamic> args;
factory ToolCallArgsEvent.fromJson(Map<String, dynamic> json) =>
ToolCallArgsEvent(
toolCallId: _asString(json['toolCallId']),
args: _asMap(json['args']) ?? const {},
);
}
class ToolCallEndEvent extends AgUiEvent {
ToolCallEndEvent({required this.toolCallId})
: super(type: AgUiEventType.toolCallEnd);
final String toolCallId;
factory ToolCallEndEvent.fromJson(Map<String, dynamic> json) =>
ToolCallEndEvent(toolCallId: _asString(json['toolCallId']));
}
class ToolCallResultEvent extends AgUiEvent {
ToolCallResultEvent({
required this.messageId,
required this.toolCallId,
required this.toolName,
required this.resultSummary,
this.toolCallArgs,
this.result,
required this.status,
required this.uiSchema,
}) : super(type: AgUiEventType.toolCallResult);
@@ -224,7 +175,8 @@ class ToolCallResultEvent extends AgUiEvent {
final String messageId;
final String toolCallId;
final String toolName;
final String resultSummary;
final Map<String, dynamic>? toolCallArgs;
final Object? result;
final String status;
final Map<String, dynamic>? uiSchema;
@@ -233,7 +185,8 @@ class ToolCallResultEvent extends AgUiEvent {
messageId: _asString(json['messageId']),
toolCallId: _asString(json['tool_call_id']),
toolName: _asString(json['tool_name']),
resultSummary: _asString(json['result']),
toolCallArgs: _asMap(json['tool_call_args']),
result: json['result'],
status: _asString(json['status'], fallback: 'success'),
uiSchema: _asMap(json['ui_schema']),
);
@@ -280,6 +233,7 @@ class HistoryMessage {
required this.content,
required this.timestamp,
this.attachments = const <HistoryAttachment>[],
this.suggestedActions = const <String>[],
this.uiSchema,
});
@@ -289,6 +243,7 @@ class HistoryMessage {
final String content;
final DateTime timestamp;
final List<HistoryAttachment> attachments;
final List<String> suggestedActions;
final Map<String, dynamic>? uiSchema;
factory HistoryMessage.fromJson(Map<String, dynamic> json) => HistoryMessage(
@@ -298,6 +253,7 @@ class HistoryMessage {
content: _asString(json['content']),
timestamp: _parseTimestamp(_asString(json['timestamp'])),
attachments: _parseHistoryAttachments(json['attachments']),
suggestedActions: _asStringList(json['suggestedActions']),
uiSchema: _asMap(json['ui_schema']),
);
}
@@ -374,3 +330,14 @@ List<HistoryAttachment> _parseHistoryAttachments(Object? value) {
)
.toList();
}
List<String> _asStringList(Object? value) {
if (value is! List) {
return const <String>[];
}
return value
.whereType<String>()
.map((item) => item.trim())
.where((item) => item.isNotEmpty)
.toList();
}
@@ -91,6 +91,7 @@ class ChatHistoryRepository extends CachedRepository<HistorySnapshot> {
},
)
.toList(growable: false),
'suggestedActions': message.suggestedActions,
'ui_schema': message.uiSchema,
};
}
+5 -51
View File
@@ -1,9 +1,7 @@
enum ChatItemType { message, toolCall, toolResult }
enum ChatItemType { message, toolResult }
enum MessageSender { user, ai }
enum ToolCallStatus { pending, executing, completed, error }
abstract class ChatListItem {
String get id;
DateTime get timestamp;
@@ -22,6 +20,7 @@ class TextMessageItem extends ChatListItem {
final bool isStreaming;
final bool isLocalEcho;
final List<Map<String, dynamic>> attachments;
final List<String> suggestedActions;
TextMessageItem({
required this.id,
@@ -31,6 +30,7 @@ class TextMessageItem extends ChatListItem {
this.isStreaming = false,
this.isLocalEcho = false,
this.attachments = const [],
this.suggestedActions = const [],
});
@override
@@ -44,6 +44,7 @@ class TextMessageItem extends ChatListItem {
bool? isStreaming,
bool? isLocalEcho,
List<Map<String, dynamic>>? attachments,
List<String>? suggestedActions,
}) => TextMessageItem(
id: id ?? this.id,
content: content ?? this.content,
@@ -52,54 +53,7 @@ class TextMessageItem extends ChatListItem {
isStreaming: isStreaming ?? this.isStreaming,
isLocalEcho: isLocalEcho ?? this.isLocalEcho,
attachments: attachments ?? this.attachments,
);
}
class ToolCallItem extends ChatListItem {
@override
final String id;
final String callId;
final String toolName;
final Map<String, dynamic> args;
final ToolCallStatus status;
final String? errorMessage;
@override
final DateTime timestamp;
@override
final MessageSender sender;
ToolCallItem({
required this.id,
required this.callId,
required this.toolName,
required this.args,
required this.status,
this.errorMessage,
required this.timestamp,
required this.sender,
});
@override
ChatItemType get type => ChatItemType.toolCall;
ToolCallItem copyWith({
String? id,
String? callId,
String? toolName,
Map<String, dynamic>? args,
ToolCallStatus? status,
String? errorMessage,
DateTime? timestamp,
MessageSender? sender,
}) => ToolCallItem(
id: id ?? this.id,
callId: callId ?? this.callId,
toolName: toolName ?? this.toolName,
args: args ?? this.args,
status: status ?? this.status,
errorMessage: errorMessage ?? this.errorMessage,
timestamp: timestamp ?? this.timestamp,
sender: sender ?? this.sender,
suggestedActions: suggestedActions ?? this.suggestedActions,
);
}
+13 -20
View File
@@ -1,21 +1,13 @@
import '../l10n/l10n.dart';
const Map<String, String> _toolNameAliases = {
'calendar_read': 'calendar.read',
'calendar_write': 'calendar.write',
'calendar_share': 'calendar.share',
'user_lookup': 'user.lookup',
'memory_write': 'memory.write',
'memory_forget': 'memory.forget',
};
const List<String> automationToolOptions = [
'calendar.create',
'calendar.read',
'calendar.write',
'calendar.update',
'calendar.delete',
'calendar.share',
'user.lookup',
'memory.write',
'memory.forget',
'contacts.read',
'memory.update',
];
String localizeToolName(String rawName) {
@@ -23,20 +15,21 @@ String localizeToolName(String rawName) {
if (normalized.isEmpty) {
return rawName;
}
final canonical = _toolNameAliases[normalized] ?? normalized;
switch (canonical) {
switch (normalized) {
case 'calendar.create':
return L10n.current.toolCalendarWrite;
case 'calendar.read':
return L10n.current.toolCalendarRead;
case 'calendar.write':
case 'calendar.update':
return L10n.current.toolCalendarWrite;
case 'calendar.delete':
return L10n.current.toolCalendarWrite;
case 'calendar.share':
return L10n.current.toolCalendarShare;
case 'user.lookup':
case 'contacts.read':
return L10n.current.toolUserLookup;
case 'memory.write':
case 'memory.update':
return L10n.current.toolMemoryWrite;
case 'memory.forget':
return L10n.current.toolMemoryForget;
default:
return rawName;
}