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
@@ -24,24 +24,13 @@ extension _ChatBlocEvents on ChatBloc {
case AgUiEventType.runFinished:
_trackChatCompleted();
_clearRunMetrics();
emit(
_resetRunState().copyWith(items: _removeToolCallItems(state.items)),
);
emit(_resetRunState());
case AgUiEventType.runError:
final errorEvent = event as RunErrorEvent;
_clearRunMetrics();
final isCanceledByUser = errorEvent.code == 'RUN_CANCELED';
emit(
_resetRunState(
error: isCanceledByUser ? null : errorEvent.message,
).copyWith(
items: _markActiveToolCallsFailed(
state.items,
reason: isCanceledByUser
? L10n.current.chatRunCanceled
: L10n.current.chatRunFailed,
),
),
_resetRunState(error: isCanceledByUser ? null : errorEvent.message),
);
case AgUiEventType.stepStarted:
_handleStepStarted(event as StepStartedEvent);
@@ -49,12 +38,6 @@ extension _ChatBlocEvents on ChatBloc {
_handleStepFinished(event as StepFinishedEvent);
case AgUiEventType.textMessageEnd:
_handleTextMessageEnd(event as TextMessageEndEvent);
case AgUiEventType.toolCallStart:
_handleToolCallStart(event as ToolCallStartEvent);
case AgUiEventType.toolCallArgs:
_handleToolCallArgs(event as ToolCallArgsEvent);
case AgUiEventType.toolCallEnd:
_handleToolCallEnd(event as ToolCallEndEvent);
case AgUiEventType.toolCallResult:
_handleToolCallResult(event as ToolCallResultEvent);
case AgUiEventType.unknown:
@@ -84,12 +67,13 @@ extension _ChatBlocEvents on ChatBloc {
state.items,
event.messageId,
event.answer,
event.suggestedActions,
timestamp,
);
emit(
state.copyWith(
items: _removeToolCallItems(items),
items: items,
currentMessageId: null,
isWaitingFirstToken: false,
isStreaming: false,
@@ -101,6 +85,7 @@ extension _ChatBlocEvents on ChatBloc {
List<ChatListItem> items,
String messageId,
String content,
List<String> suggestedActions,
DateTime timestamp,
) {
final result = List<ChatListItem>.from(items);
@@ -110,7 +95,11 @@ extension _ChatBlocEvents on ChatBloc {
if (index >= 0) {
final existing = result[index] as TextMessageItem;
result[index] = existing.copyWith(content: content, isStreaming: false);
result[index] = existing.copyWith(
content: content,
isStreaming: false,
suggestedActions: suggestedActions,
);
return result;
}
@@ -121,73 +110,18 @@ extension _ChatBlocEvents on ChatBloc {
timestamp: timestamp,
sender: MessageSender.ai,
isStreaming: false,
suggestedActions: suggestedActions,
),
);
return result;
}
void _handleToolCallStart(ToolCallStartEvent event) {
final exists = state.items.any(
(item) => item is ToolCallItem && item.id == event.toolCallId,
);
if (exists) {
return;
}
emit(
state.copyWith(
items: [
...state.items,
ToolCallItem(
id: event.toolCallId,
callId: event.toolCallId,
toolName: event.toolCallName,
args: const {},
status: ToolCallStatus.pending,
timestamp: DateTime.now(),
sender: MessageSender.ai,
),
],
),
);
}
void _handleToolCallArgs(ToolCallArgsEvent event) {
emit(
state.copyWith(
items: state.items.map((item) {
if (item is ToolCallItem && item.id == event.toolCallId) {
return item.copyWith(args: event.args);
}
return item;
}).toList(),
),
);
}
void _handleToolCallEnd(ToolCallEndEvent event) {
emit(
state.copyWith(
items: state.items.map((item) {
if (item is ToolCallItem && item.id == event.toolCallId) {
return item.copyWith(status: ToolCallStatus.executing);
}
return item;
}).toList(),
),
);
}
void _handleToolCallResult(ToolCallResultEvent event) {
if (_shouldRefreshCalendarForTool(event)) {
unawaited(_refreshCalendarAfterToolMutation());
}
final timestamp = DateTime.now();
final items = state.items.map((item) {
if (item is ToolCallItem && item.id == event.toolCallId) {
return item.copyWith(status: ToolCallStatus.completed);
}
return item;
}).toList();
final items = List<ChatListItem>.from(state.items);
final uiSchema = event.uiSchema;
if (uiSchema != null) {
@@ -219,30 +153,12 @@ extension _ChatBlocEvents on ChatBloc {
items.add(uiItem);
}
List<ChatListItem> _removeToolCallItems(List<ChatListItem> items) {
return items.where((item) => item is! ToolCallItem).toList();
}
List<ChatListItem> _markActiveToolCallsFailed(
List<ChatListItem> items, {
required String reason,
}) {
return items.map((item) {
if (item is! ToolCallItem ||
item.status == ToolCallStatus.error ||
item.status == ToolCallStatus.completed) {
return item;
}
return item.copyWith(status: ToolCallStatus.error, errorMessage: reason);
}).toList();
}
List<ChatListItem> _convertHistoryMessages(List<HistoryMessage> messages) {
final converted = <ChatListItem>[];
for (final msg in messages) {
final normalizedRole = msg.role.toLowerCase();
final isUser = normalizedRole == 'user';
final isTool = normalizedRole == 'tool' || normalizedRole == 'tools';
final isTool = normalizedRole == 'tool';
final sender = isUser ? MessageSender.user : MessageSender.ai;
final attachments = msg.attachments
.map(
@@ -262,11 +178,12 @@ extension _ChatBlocEvents on ChatBloc {
sender: sender,
isLocalEcho: false,
attachments: attachments,
suggestedActions: msg.suggestedActions,
),
);
}
if (!isTool && msg.uiSchema != null) {
if (isTool && msg.uiSchema != null) {
converted.add(
ToolResultItem(
id: '${msg.id}-ui',