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:
qzl
2026-04-24 13:24:13 +08:00
parent ab526af2c4
commit d060962a5f
62 changed files with 4802 additions and 805 deletions
@@ -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 {