feat(agent): redesign project_cli with module/method/input protocol
- Replace command/subcommand/args with module/method/input envelope - Calendar handler uses discriminated union (mode) for read operations - Strict Pydantic models with extra='forbid' for all calendar methods - Worker max_iters=7, router prompt simplified (removed project_cli_defaults) - Skill index cards + per-action files for progressive disclosure - Frontend/AG-UI aligned to module/method dispatch - Protocol docs updated to module/method/input contract WIP: action cards need envelope fix, 2 tests need update, memory handler needs Pydantic models.
This commit is contained in:
@@ -263,10 +263,10 @@ class ChatBloc extends Cubit<ChatState> implements ChatOrchestrator {
|
||||
if (args == null) {
|
||||
return false;
|
||||
}
|
||||
final command = (args['command'] as String?)?.trim().toLowerCase();
|
||||
final subcommand = (args['subcommand'] as String?)?.trim().toLowerCase();
|
||||
const mutationSubcommands = {'create', 'update', 'delete'};
|
||||
if (command != 'calendar' || !mutationSubcommands.contains(subcommand)) {
|
||||
final skill = (args['skill'] as String?)?.trim().toLowerCase();
|
||||
final action = (args['action'] as String?)?.trim().toLowerCase();
|
||||
const mutationActions = {'create_event', 'update_event', 'delete_event'};
|
||||
if (skill != 'calendar' || !mutationActions.contains(action)) {
|
||||
return false;
|
||||
}
|
||||
return status == 'success' || status == 'partial';
|
||||
|
||||
@@ -262,35 +262,15 @@ class HomeChatItemRenderer {
|
||||
ToolResultItem item,
|
||||
) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final rootNode = item.uiSchema['root'];
|
||||
final appearance = rootNode is Map<String, dynamic>
|
||||
? rootNode['appearance'] as String?
|
||||
: null;
|
||||
final needsOuterCard = appearance == null || appearance == 'plain';
|
||||
final schemaContent = UiSchemaRenderer(
|
||||
context,
|
||||
colorScheme,
|
||||
).renderSchema(item.uiSchema);
|
||||
final wrappedContent = needsOuterCard
|
||||
? Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(AppSpacing.md),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainerLow.withValues(alpha: 0.65),
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
border: Border.all(
|
||||
color: colorScheme.outlineVariant.withValues(alpha: 0.25),
|
||||
),
|
||||
),
|
||||
child: schemaContent,
|
||||
)
|
||||
: schemaContent;
|
||||
final schemaContent = UiSchemaRenderer(context, colorScheme).renderSchema(
|
||||
item.uiSchema,
|
||||
);
|
||||
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FractionallySizedBox(
|
||||
widthFactor: _toolResultWidthFactor,
|
||||
child: wrappedContent,
|
||||
child: schemaContent,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -234,7 +234,7 @@ void main() {
|
||||
});
|
||||
|
||||
test(
|
||||
'tool calendar_create success triggers calendar refresh callback',
|
||||
'calendar mutation tool result triggers calendar refresh callback',
|
||||
() async {
|
||||
final service = _FakeAgUiService();
|
||||
var refreshCalls = 0;
|
||||
@@ -251,7 +251,10 @@ void main() {
|
||||
messageId: 'msg-1',
|
||||
toolCallId: 'call-1',
|
||||
toolName: 'project_cli',
|
||||
toolCallArgs: const {'command': 'calendar', 'subcommand': 'create'},
|
||||
toolCallArgs: const {
|
||||
'skill': 'calendar',
|
||||
'action': 'create_event',
|
||||
},
|
||||
result: const {'ok': true},
|
||||
status: 'success',
|
||||
uiSchema: null,
|
||||
@@ -264,6 +267,36 @@ void main() {
|
||||
},
|
||||
);
|
||||
|
||||
test('calendar read tool result does not trigger calendar refresh callback', () async {
|
||||
final service = _FakeAgUiService();
|
||||
var refreshCalls = 0;
|
||||
final bloc = ChatBloc(
|
||||
service: service,
|
||||
chatApi: _NoopChatApi(),
|
||||
onCalendarMutated: () async {
|
||||
refreshCalls += 1;
|
||||
},
|
||||
);
|
||||
|
||||
service.emitEventForTest(
|
||||
ToolCallResultEvent(
|
||||
messageId: 'msg-1',
|
||||
toolCallId: 'call-1',
|
||||
toolName: 'project_cli',
|
||||
toolCallArgs: const {
|
||||
'skill': 'calendar',
|
||||
'action': 'list_day',
|
||||
},
|
||||
result: const {'ok': true},
|
||||
status: 'success',
|
||||
uiSchema: null,
|
||||
),
|
||||
);
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
expect(refreshCalls, 0);
|
||||
});
|
||||
|
||||
test(
|
||||
'sendMessage recovers from premature SSE close with polled history',
|
||||
() async {
|
||||
|
||||
Reference in New Issue
Block a user