@kafca/agentdock 0.1.24 → 0.1.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +2 -0
  2. package/dist/renderer/assets/{Badge-CXcUMqb-.js → Badge-Bn2h-Qkf.js} +1 -1
  3. package/dist/renderer/assets/{Chat-BRxdPzi-.js → Chat-Cgo9aneg.js} +1 -1
  4. package/dist/renderer/assets/{ChatMarkdown-sWGBIfIb.js → ChatMarkdown-CYzHfSiR.js} +1 -1
  5. package/dist/renderer/assets/{Config-DqxuxIGg.js → Config-DljB2-eP.js} +1 -1
  6. package/dist/renderer/assets/{CronList-OgHxbti5.js → CronList-DeVbiY9a.js} +1 -1
  7. package/dist/renderer/assets/{Dashboard-DmklXiH1.js → Dashboard-fSqx5sm8.js} +2 -2
  8. package/dist/renderer/assets/{EmptyState-BvDmi8dq.js → EmptyState-DgqKDwlq.js} +1 -1
  9. package/dist/renderer/assets/{KnowledgeDetail-Bj59Hdpo.js → KnowledgeDetail-u8s2SmPV.js} +1 -1
  10. package/dist/renderer/assets/{KnowledgeHome-CigQ-nSo.js → KnowledgeHome-BdQw8nbY.js} +1 -1
  11. package/dist/renderer/assets/{Logs-Bfc7gBpY.js → Logs-DSbndMpy.js} +1 -1
  12. package/dist/renderer/assets/{Modal-CfmiFknz.js → Modal-0d0EmAuK.js} +1 -1
  13. package/dist/renderer/assets/{Page-BxtLEw_b.js → Page-CVbOH_Kh.js} +1 -1
  14. package/dist/renderer/assets/{ProjectDetail-CcUphxGm.js → ProjectDetail-C91tF7uA.js} +1 -1
  15. package/dist/renderer/assets/{ProjectList-CVkvDIfy.js → ProjectList-D78Pc-BQ.js} +1 -1
  16. package/dist/renderer/assets/{Select-DgQMZ0MD.js → Select-BbKzAIuO.js} +1 -1
  17. package/dist/renderer/assets/{SessionChat-CRmGKfBx.js → SessionChat-BERvL5e4.js} +1 -1
  18. package/dist/renderer/assets/{SessionList-D0E8XbYw.js → SessionList-Bomz335M.js} +1 -1
  19. package/dist/renderer/assets/ThreadChat-CLr3amU2.js +23 -0
  20. package/dist/renderer/assets/{Workspace-Q4g4rlRT.js → Workspace-In91z4W2.js} +1 -1
  21. package/dist/renderer/assets/{arrow-left-BySN_zhF.js → arrow-left-FBnVKKBl.js} +1 -1
  22. package/dist/renderer/assets/{book-open-BGlrnrTc.js → book-open-4_CuP6Sa.js} +1 -1
  23. package/dist/renderer/assets/{check-BiJogEa5.js → check-vsoVjvac.js} +1 -1
  24. package/dist/renderer/assets/{chevron-down-D2fHRJv6.js → chevron-down-DFpmFTJW.js} +1 -1
  25. package/dist/renderer/assets/{circle-CIcjixmI.js → circle-vRULegER.js} +1 -1
  26. package/dist/renderer/assets/{desktop-DAXY9Bxv.js → desktop-BkMW-h5p.js} +2 -2
  27. package/dist/renderer/assets/{desktop-DyA_NYew.js → desktop-ul0Tpzyc.js} +1 -1
  28. package/dist/renderer/assets/{funnel-BUzwb-id.js → funnel-Gu0E_hJ_.js} +1 -1
  29. package/dist/renderer/assets/{heart-DZEQ1z_V.js → heart-DsdofOHf.js} +1 -1
  30. package/dist/renderer/assets/{index-CB_KyLOr.js → index-BD3aCyBa.js} +3 -3
  31. package/dist/renderer/assets/{index-DgsIo4vJ.js → index-pJUFobU7.js} +1 -1
  32. package/dist/renderer/assets/{pencil-DGL4agcl.js → pencil-DbKmV8nD.js} +1 -1
  33. package/dist/renderer/assets/{play-DzasCLM4.js → play-BDMsRxSm.js} +1 -1
  34. package/dist/renderer/assets/{plug-dubNMe_m.js → plug-t2dMl78-.js} +1 -1
  35. package/dist/renderer/assets/{plus-DN4kML4k.js → plus-BUb6zOAe.js} +1 -1
  36. package/dist/renderer/assets/projects-4F6D_Q8t.js +1 -0
  37. package/dist/renderer/assets/{save-CNIc9tjl.js → save-C-_5rQdv.js} +1 -1
  38. package/dist/renderer/assets/{search-C0EGbF3I.js → search-R3ekHFPM.js} +1 -1
  39. package/dist/renderer/assets/{server-BMxQ4zmc.js → server-BTuvHf5n.js} +1 -1
  40. package/dist/renderer/assets/{session-utils-Bf5O-iMi.js → session-utils-ClX6VPFG.js} +1 -1
  41. package/dist/renderer/assets/{sessions-BMSs6rEI.js → sessions-AFKIC-ub.js} +1 -1
  42. package/dist/renderer/assets/{shield-check-i3Tf1TUz.js → shield-check-CqMW8CHe.js} +1 -1
  43. package/dist/renderer/assets/{trash-2-SKrcp18f.js → trash-2-BNLcHyUw.js} +1 -1
  44. package/dist/renderer/assets/{user-cNUZddjZ.js → user-BCCN8FYK.js} +1 -1
  45. package/dist/renderer/assets/{wifi-off-Dyb84ANR.js → wifi-off-BsNsx3oX.js} +1 -1
  46. package/dist/renderer/index.html +1 -1
  47. package/dist-electron/electron/agent-runtime-detector.test.js +64 -0
  48. package/dist-electron/electron/architecture-docs.test.js +56 -0
  49. package/dist-electron/electron/channel-content.test.js +8 -0
  50. package/dist-electron/electron/lac-cli.test.js +248 -0
  51. package/dist-electron/electron/local-core-refactor.test.js +666 -3
  52. package/dist-electron/electron/plugin-kernel.test.js +108 -0
  53. package/dist-electron/electron/renderer-packaging.test.js +77 -0
  54. package/dist-electron/electron/workspace-task-store.test.js +57 -0
  55. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-backend.js +67 -6
  56. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-permission-lifecycle.js +76 -0
  57. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-progress.js +201 -0
  58. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-session-coordinator.js +29 -0
  59. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-store.js +30 -18
  60. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-turn-coordinator.js +112 -210
  61. package/dist-electron/services/local-ai-core/src/acp/local-core-slash-commands.js +60 -0
  62. package/dist-electron/services/local-ai-core/src/channel/lark/local-core-lark-gateway.js +15 -3
  63. package/dist-electron/services/local-ai-core/src/channel/lark/runtime-state.js +11 -0
  64. package/dist-electron/services/local-ai-core/src/channel/shared/file-utils.js +13 -1
  65. package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js +8 -1
  66. package/dist-electron/services/local-ai-core/src/cli/lac.js +3 -1
  67. package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +1 -0
  68. package/dist-electron/services/local-ai-core/src/plugins/builtin/agent-localcore-acp-plugin.js +9 -0
  69. package/dist-electron/services/local-ai-core/src/router/workspace-route-config.js +221 -10
  70. package/dist-electron/services/local-ai-core/src/runtime/agent-runtime-detector.js +39 -9
  71. package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +1 -0
  72. package/dist-electron/services/local-ai-core/src/runtime/runtime-detection-service.js +1 -0
  73. package/dist-electron/services/local-ai-core/src/runtime/server-routes.js +439 -0
  74. package/dist-electron/services/local-ai-core/src/runtime/server.js +208 -303
  75. package/dist-electron/shared/desktop.js +7 -3
  76. package/dist-electron/src/pages/Threads/thread-chat-message-blocks.js +187 -0
  77. package/dist-electron/src/pages/Threads/thread-chat-model.js +1 -0
  78. package/dist-electron/src/pages/Threads/thread-chat-page-state.js +96 -0
  79. package/dist-electron/src/pages/Threads/thread-chat-permission.js +18 -0
  80. package/dist-electron/src/pages/Threads/thread-chat-permission.test.js +277 -0
  81. package/package.json +4 -2
  82. package/dist/renderer/assets/ThreadChat-DUgqZSBB.js +0 -23
  83. package/dist/renderer/assets/projects-migPtfRe.js +0 -1
@@ -10,15 +10,215 @@ const node_os_1 = require("node:os");
10
10
  const node_path_1 = require("node:path");
11
11
  const node_stream_1 = require("node:stream");
12
12
  const local_core_acp_response_processor_js_1 = require("../services/local-ai-core/src/acp/local-core-acp-response-processor.js");
13
+ const local_core_slash_commands_js_1 = require("../services/local-ai-core/src/acp/local-core-slash-commands.js");
13
14
  const scheduled_conversation_executor_js_1 = require("../services/local-ai-core/src/scheduler/scheduled-conversation-executor.js");
14
15
  const scheduler_run_lifecycle_js_1 = require("../services/local-ai-core/src/scheduler/scheduler-run-lifecycle.js");
15
16
  const lark_execution_policies_js_1 = require("../services/local-ai-core/src/scheduler/lark-execution-policies.js");
16
17
  const local_schedule_adapter_js_1 = require("../services/local-ai-core/src/scheduler/local-schedule-adapter.js");
17
18
  const local_core_weixin_gateway_js_1 = require("../services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js");
18
19
  const local_core_lark_gateway_js_1 = require("../services/local-ai-core/src/channel/lark/local-core-lark-gateway.js");
20
+ const runtime_state_js_1 = require("../services/local-ai-core/src/channel/lark/runtime-state.js");
19
21
  const local_core_acp_turn_coordinator_js_1 = require("../services/local-ai-core/src/acp/local-core-acp-turn-coordinator.js");
20
22
  const local_core_acp_store_js_1 = require("../services/local-ai-core/src/acp/local-core-acp-store.js");
23
+ const local_core_acp_progress_js_1 = require("../services/local-ai-core/src/acp/local-core-acp-progress.js");
24
+ const local_core_acp_permission_lifecycle_js_1 = require("../services/local-ai-core/src/acp/local-core-acp-permission-lifecycle.js");
21
25
  const workspace_acp_permissions_js_1 = require("../services/local-ai-core/src/acp/workspace-acp-permissions.js");
26
+ const server_routes_js_1 = require("../services/local-ai-core/src/runtime/server-routes.js");
27
+ (0, node_test_1.default)('local core route parser separates runtime refresh and runtime detail routes', () => {
28
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/runtimes'), { name: 'runtimes.list' });
29
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('POST', '/api/local/v1/runtimes/refresh'), { name: 'runtimes.refresh' });
30
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/runtimes/codex'), {
31
+ name: 'runtimes.detail',
32
+ runtimeId: 'codex',
33
+ });
34
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('POST', '/api/local/v1/runtimes/codex/refresh'), {
35
+ name: 'runtimes.refresh-one',
36
+ runtimeId: 'codex',
37
+ });
38
+ });
39
+ (0, node_test_1.default)('local core route parser keeps scheduler job get, runs, and run distinct', () => {
40
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/scheduler/jobs/job-abc'), {
41
+ name: 'scheduler.job.get',
42
+ jobId: 'job-abc',
43
+ });
44
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/scheduler/jobs/job-abc/runs'), {
45
+ name: 'scheduler.job.runs',
46
+ jobId: 'job-abc',
47
+ });
48
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('POST', '/api/local/v1/scheduler/jobs/job-abc/run'), {
49
+ name: 'scheduler.job.run',
50
+ jobId: 'job-abc',
51
+ });
52
+ strict_1.default.equal((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/scheduler/jobs/job-abc/run'), null);
53
+ });
54
+ (0, node_test_1.default)('local core route parser keeps thread actions separate from generic thread routes', () => {
55
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/threads'), { name: 'threads.list' });
56
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('POST', '/api/local/v1/threads'), { name: 'threads.create' });
57
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/threads/workspace%2Fa%3A%3Athread%2F1'), {
58
+ name: 'thread.get',
59
+ threadId: 'workspace/a::thread/1',
60
+ });
61
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('PATCH', '/api/local/v1/threads/thread-1/knowledge-bases'), {
62
+ name: 'thread.update-knowledge-bases',
63
+ threadId: 'thread-1',
64
+ });
65
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('POST', '/api/local/v1/threads/thread-1/messages'), {
66
+ name: 'thread.messages.send',
67
+ threadId: 'thread-1',
68
+ });
69
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('POST', '/api/local/v1/threads/thread-1/actions'), {
70
+ name: 'thread.actions.send',
71
+ threadId: 'thread-1',
72
+ });
73
+ strict_1.default.equal((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/threads/thread-1/messages'), null);
74
+ });
75
+ (0, node_test_1.default)('local core route parser only accepts run interrupt action with POST', () => {
76
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('POST', '/api/local/v1/runs/run-1/interrupt'), {
77
+ name: 'run.interrupt',
78
+ runId: 'run-1',
79
+ });
80
+ strict_1.default.equal((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/runs/run-1/interrupt'), null);
81
+ });
82
+ (0, node_test_1.default)('local core route parser keeps workspace state routes bounded to one id segment', () => {
83
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/workspaces'), { name: 'workspaces.list' });
84
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('POST', '/api/local/v1/workspaces/workspace%2Fone/streaming-probe'), {
85
+ name: 'workspace.streaming-probe',
86
+ workspaceId: 'workspace/one',
87
+ });
88
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('POST', '/api/local/v1/workspace-registry'), { name: 'workspace-registry.create' });
89
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('PATCH', '/api/local/v1/workspace-registry/workspace%2Fone'), {
90
+ name: 'workspace-registry.update',
91
+ workspaceId: 'workspace/one',
92
+ });
93
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/workspace-security/workspace%2Fone'), {
94
+ name: 'workspace-security.get',
95
+ workspaceId: 'workspace/one',
96
+ });
97
+ strict_1.default.equal((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/workspace-registry/workspace-1/extra'), null);
98
+ strict_1.default.equal((0, server_routes_js_1.parseLocalAiCoreRoute)('POST', '/api/local/v1/workspace-security/workspace-1'), null);
99
+ strict_1.default.equal((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/workspaces/workspace-1/streaming-probe'), null);
100
+ });
101
+ (0, node_test_1.default)('local core route parser keeps approval resolution separate from approval detail', () => {
102
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/approvals'), { name: 'approvals.list' });
103
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('POST', '/api/local/v1/approvals'), { name: 'approvals.create' });
104
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/approvals/approval-1'), {
105
+ name: 'approval.get',
106
+ approvalId: 'approval-1',
107
+ });
108
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('POST', '/api/local/v1/approvals/approval-1/resolve'), {
109
+ name: 'approval.resolve',
110
+ approvalId: 'approval-1',
111
+ });
112
+ strict_1.default.equal((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/approvals/approval-1/resolve'), null);
113
+ });
114
+ (0, node_test_1.default)('local core route parser keeps task collection and task detail routes distinct', () => {
115
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/audit-events'), { name: 'audit-events.list' });
116
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('POST', '/api/local/v1/security/command-risk'), { name: 'security.command-risk.classify' });
117
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/tasks'), { name: 'tasks.list' });
118
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('POST', '/api/local/v1/tasks'), { name: 'tasks.create' });
119
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('PATCH', '/api/local/v1/tasks/task%2Fone'), {
120
+ name: 'task.update',
121
+ taskId: 'task/one',
122
+ });
123
+ strict_1.default.equal((0, server_routes_js_1.parseLocalAiCoreRoute)('DELETE', '/api/local/v1/tasks/task-1'), null);
124
+ });
125
+ (0, node_test_1.default)('local core route parser keeps knowledge collection and folder routes distinct', () => {
126
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/knowledge/sources'), { name: 'knowledge.sources.list' });
127
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/knowledge/config'), { name: 'knowledge.config.read' });
128
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('POST', '/api/local/v1/knowledge/config'), { name: 'knowledge.config.update' });
129
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/knowledge/folders'), { name: 'knowledge.folders.list' });
130
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('POST', '/api/local/v1/knowledge/folders'), { name: 'knowledge.folders.create' });
131
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('PATCH', '/api/local/v1/knowledge/folders/folder%2Fone'), {
132
+ name: 'knowledge.folder.update',
133
+ folderId: 'folder/one',
134
+ });
135
+ strict_1.default.equal((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/knowledge/folders/folder-1'), null);
136
+ });
137
+ (0, node_test_1.default)('local core route parser keeps knowledge base files and search routes distinct', () => {
138
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/knowledge/bases'), { name: 'knowledge.bases.list' });
139
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('POST', '/api/local/v1/knowledge/bases'), { name: 'knowledge.bases.create' });
140
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/knowledge/bases/base%2Fone'), {
141
+ name: 'knowledge.base.get',
142
+ knowledgeBaseId: 'base/one',
143
+ });
144
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/knowledge/bases/base%2Fone/files'), {
145
+ name: 'knowledge.base.files.list',
146
+ knowledgeBaseId: 'base/one',
147
+ });
148
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('POST', '/api/local/v1/knowledge/bases/base%2Fone/search'), {
149
+ name: 'knowledge.base.search',
150
+ knowledgeBaseId: 'base/one',
151
+ });
152
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('DELETE', '/api/local/v1/knowledge/bases/base%2Fone/files/file%2Fone'), {
153
+ name: 'knowledge.base.file.delete',
154
+ knowledgeBaseId: 'base/one',
155
+ fileId: 'file/one',
156
+ });
157
+ strict_1.default.equal((0, server_routes_js_1.parseLocalAiCoreRoute)('PATCH', '/api/local/v1/knowledge/bases/base-1/files'), null);
158
+ });
159
+ (0, node_test_1.default)('local core route parser recognizes capability, plugin, and event routes', () => {
160
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/capabilities'), { name: 'capabilities.read' });
161
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/capabilities/snapshot'), { name: 'capabilities.snapshot' });
162
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/plugins/diagnostics'), { name: 'plugins.diagnostics' });
163
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/events'), { name: 'events.stream' });
164
+ strict_1.default.equal((0, server_routes_js_1.parseLocalAiCoreRoute)('POST', '/api/local/v1/events'), null);
165
+ });
166
+ (0, node_test_1.default)('local core route parser keeps platform read routes distinct', () => {
167
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/platforms/lark'), {
168
+ name: 'platform.gateways.list',
169
+ platform: 'lark',
170
+ });
171
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/platforms/lark/pairings'), {
172
+ name: 'platform.pairings.list',
173
+ platform: 'lark',
174
+ });
175
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/platforms/lark/users'), {
176
+ name: 'platform.users.list',
177
+ platform: 'lark',
178
+ });
179
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/platforms/lark/workspace%2Fone'), {
180
+ name: 'platform.gateway.get',
181
+ platform: 'lark',
182
+ workspaceId: 'workspace/one',
183
+ });
184
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/platforms/lark/workspace%2Fone/qrcode/status'), {
185
+ name: 'platform.qrcode.status',
186
+ platform: 'lark',
187
+ workspaceId: 'workspace/one',
188
+ });
189
+ strict_1.default.equal((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/platforms/lark/workspace-1/qrcode'), null);
190
+ });
191
+ (0, node_test_1.default)('local core route parser keeps platform write routes distinct', () => {
192
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('POST', '/api/local/v1/platforms/lark/pairings/approve'), {
193
+ name: 'platform.pairing.approve',
194
+ platform: 'lark',
195
+ });
196
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('POST', '/api/local/v1/platforms/lark/pairings/reject'), {
197
+ name: 'platform.pairing.reject',
198
+ platform: 'lark',
199
+ });
200
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('POST', '/api/local/v1/platforms/lark/workspace%2Fone/test'), {
201
+ name: 'platform.gateway.test',
202
+ platform: 'lark',
203
+ workspaceId: 'workspace/one',
204
+ });
205
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('POST', '/api/local/v1/platforms/lark/workspace%2Fone/files'), {
206
+ name: 'platform.file.send',
207
+ platform: 'lark',
208
+ workspaceId: 'workspace/one',
209
+ });
210
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('POST', '/api/local/v1/platforms/lark/workspace%2Fone/messages'), {
211
+ name: 'platform.message.send',
212
+ platform: 'lark',
213
+ workspaceId: 'workspace/one',
214
+ });
215
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('POST', '/api/local/v1/platforms/lark/workspace%2Fone/qrcode'), {
216
+ name: 'platform.qrcode.create',
217
+ platform: 'lark',
218
+ workspaceId: 'workspace/one',
219
+ });
220
+ strict_1.default.equal((0, server_routes_js_1.parseLocalAiCoreRoute)('POST', '/api/local/v1/platforms/lark/pairings/qrcode'), null);
221
+ });
22
222
  (0, node_test_1.default)('ACP tool call update is emitted with its pending tool name', () => {
23
223
  const appended = [];
24
224
  const emitted = [];
@@ -95,6 +295,271 @@ const workspace_acp_permissions_js_1 = require("../services/local-ai-core/src/ac
95
295
  kind: 'allow',
96
296
  }), 'allow');
97
297
  });
298
+ (0, node_test_1.default)('ACP permission lifecycle parses actionable options and fallback prompt content', () => {
299
+ const options = (0, local_core_acp_permission_lifecycle_js_1.parsePermissionOptions)([
300
+ { optionId: 'approve-once', name: 'Allow once', kind: 'allow' },
301
+ { optionId: 'approve-always', name: 'Always allow', kind: 'allow' },
302
+ { optionId: '', name: 'missing id', kind: 'reject' },
303
+ ]);
304
+ strict_1.default.deepEqual(options, [
305
+ {
306
+ optionId: 'approve-once',
307
+ name: 'Allow once',
308
+ kind: 'allow',
309
+ normalizedAction: 'allow',
310
+ },
311
+ {
312
+ optionId: 'approve-always',
313
+ name: 'Always allow',
314
+ kind: 'allow',
315
+ normalizedAction: 'allow all',
316
+ },
317
+ ]);
318
+ strict_1.default.deepEqual((0, local_core_acp_permission_lifecycle_js_1.createRunningPermissionRequest)({
319
+ requestId: 42,
320
+ toolTitle: 'Terminal: lac scheduler add --cron "* * * * *"',
321
+ options,
322
+ approvalId: 'approval-1',
323
+ }), {
324
+ requestId: 42,
325
+ approvalId: 'approval-1',
326
+ toolTitle: 'Terminal: lac scheduler add --cron "* * * * *"',
327
+ isSchedulerAdd: true,
328
+ options,
329
+ });
330
+ strict_1.default.deepEqual((0, local_core_acp_permission_lifecycle_js_1.createPermissionApprovalInput)({
331
+ threadId: 'thread-1',
332
+ runId: 'run-1',
333
+ toolTitle: 'Terminal: npm test',
334
+ options,
335
+ }), {
336
+ threadId: 'thread-1',
337
+ runId: 'run-1',
338
+ title: 'Approve Terminal: npm test',
339
+ description: 'Terminal: npm test',
340
+ command: 'Terminal: npm test',
341
+ options,
342
+ });
343
+ strict_1.default.equal((0, local_core_acp_permission_lifecycle_js_1.isSchedulerAddCommand)('Terminal: lac scheduler add --cron "* * * * *"'), true);
344
+ strict_1.default.match((0, local_core_acp_permission_lifecycle_js_1.createPermissionPrompt)('Terminal: npm test'), /Terminal: npm test/);
345
+ strict_1.default.match((0, local_core_acp_permission_lifecycle_js_1.createPermissionPrompt)('Terminal: npm test'), /allow all \/ allow \/ deny/);
346
+ });
347
+ (0, node_test_1.default)('ACP permission lifecycle writes pending permission state and tool detail together', () => {
348
+ const permissionRequest = (0, local_core_acp_permission_lifecycle_js_1.createRunningPermissionRequest)({
349
+ requestId: 42,
350
+ toolTitle: 'Terminal: npm test',
351
+ options: [],
352
+ approvalId: 'approval-1',
353
+ });
354
+ const toolCall = {
355
+ key: 'call-1',
356
+ title: 'Terminal',
357
+ messageId: 'run-1-tool-1',
358
+ sequence: 1,
359
+ emitted: false,
360
+ };
361
+ const currentTurn = {
362
+ runId: 'run-1',
363
+ replyCtx: 'run-1',
364
+ previewHandle: 'preview-1',
365
+ thoughtPreviewHandle: 'thought-preview-1',
366
+ thoughtMessageId: 'run-1-thought',
367
+ assistantText: '',
368
+ thoughtText: '',
369
+ typingStarted: true,
370
+ previewStarted: false,
371
+ thoughtPreviewStarted: false,
372
+ permission: null,
373
+ pendingToolCalls: { 'call-1': toolCall },
374
+ pendingToolCallOrder: ['call-1'],
375
+ activeToolCallKey: 'call-1',
376
+ };
377
+ const session = {
378
+ currentTurn,
379
+ pendingPermissionByRun: new Map(),
380
+ };
381
+ const synced = [];
382
+ (0, local_core_acp_permission_lifecycle_js_1.applyPendingPermissionRequest)({
383
+ session,
384
+ runId: 'run-1',
385
+ permissionRequest,
386
+ resolveFallbackToolCall: () => toolCall,
387
+ syncLegacyPendingToolCall: (_turn, nextToolCall) => synced.push(nextToolCall?.detail || ''),
388
+ });
389
+ strict_1.default.equal(session.pendingPermissionByRun.get('run-1'), permissionRequest);
390
+ strict_1.default.equal(currentTurn.permission, permissionRequest);
391
+ strict_1.default.equal(currentTurn.pendingToolCallDetail, 'Terminal: npm test');
392
+ strict_1.default.equal(toolCall.detail, 'Terminal: npm test');
393
+ strict_1.default.deepEqual(synced, ['Terminal: npm test']);
394
+ });
395
+ (0, node_test_1.default)('ACP pending permission is projected into refreshed thread detail payloads', () => {
396
+ const coordinator = new local_core_acp_turn_coordinator_js_1.LocalCoreAcpTurnCoordinator({
397
+ emitBridge: () => { },
398
+ appendMessage: () => { },
399
+ updateRunStatus: () => { },
400
+ sendRaw: () => true,
401
+ });
402
+ const permissionRequest = (0, local_core_acp_permission_lifecycle_js_1.createRunningPermissionRequest)({
403
+ requestId: 42,
404
+ toolTitle: 'Terminal: npm test',
405
+ options: (0, local_core_acp_permission_lifecycle_js_1.parsePermissionOptions)([
406
+ { optionId: 'approve-once', name: 'Allow once', kind: 'allow' },
407
+ { optionId: 'reject', name: 'Deny', kind: 'reject' },
408
+ ]),
409
+ approvalId: 'approval-1',
410
+ });
411
+ const session = {
412
+ currentRunId: 'run-1',
413
+ pendingPermissionByRun: new Map([['run-1', permissionRequest]]),
414
+ };
415
+ const detail = {
416
+ messages: [
417
+ {
418
+ id: 'permission-message',
419
+ role: 'assistant',
420
+ content: '等待工具确认',
421
+ },
422
+ ],
423
+ };
424
+ const pending = coordinator.getPendingPermissionRequest(session, detail);
425
+ strict_1.default.ok(pending);
426
+ strict_1.default.equal(pending.id, 'permission-message');
427
+ strict_1.default.equal(pending.content, '等待工具确认');
428
+ strict_1.default.equal(pending.actionReplyCtx, 'run-1');
429
+ strict_1.default.equal(pending.actionMode, 'permission');
430
+ strict_1.default.equal(pending.actionInteractive, true);
431
+ strict_1.default.deepEqual(pending.actions.flat().map((action) => action.data), ['allow', 'deny']);
432
+ });
433
+ (0, node_test_1.default)('ACP progress projection extracts tool output and formats durable progress content', () => {
434
+ strict_1.default.equal((0, local_core_acp_progress_js_1.extractToolCallKey)({ tool_call_id: ' call-a ' }), 'call-a');
435
+ strict_1.default.equal((0, local_core_acp_progress_js_1.extractToolCallKey)({ invocationId: 42 }), '42');
436
+ strict_1.default.equal((0, local_core_acp_progress_js_1.extractToolCallKey)({ id: ' ' }), '');
437
+ strict_1.default.equal((0, local_core_acp_progress_js_1.extractToolUpdateContent)([
438
+ { type: 'content', content: { type: 'text', text: 'first line' } },
439
+ { type: 'content', content: { type: 'image', text: 'ignored' } },
440
+ { type: 'content', content: { type: 'text', text: 'second line' } },
441
+ ]), 'first line\nsecond line');
442
+ strict_1.default.equal((0, local_core_acp_progress_js_1.formatToolProgressMessage)({
443
+ toolName: 'Terminal',
444
+ title: 'npm test',
445
+ status: 'completed',
446
+ content: 'ok',
447
+ }), '🔧 Terminal: npm test - completed - ok');
448
+ strict_1.default.equal((0, local_core_acp_progress_js_1.resolveToolUpdateDisplayTitle)({
449
+ title: 'Tool update',
450
+ status: 'completed',
451
+ priorDetail: 'npm test',
452
+ }), 'npm test');
453
+ strict_1.default.equal((0, local_core_acp_progress_js_1.isEmptyRunningToolUpdate)({
454
+ title: 'Tool update',
455
+ status: 'running',
456
+ content: '',
457
+ }), true);
458
+ });
459
+ (0, node_test_1.default)('ACP progress projection applies assistant and thought chunks with bridge metadata', () => {
460
+ const currentTurn = {
461
+ previewHandle: 'preview-1',
462
+ thoughtPreviewHandle: 'thought-preview-1',
463
+ thoughtMessageId: 'run-1-thought',
464
+ assistantText: '',
465
+ thoughtText: '',
466
+ previewStarted: false,
467
+ thoughtPreviewStarted: false,
468
+ };
469
+ strict_1.default.deepEqual((0, local_core_acp_progress_js_1.applyAssistantMessageChunk)(currentTurn, 'hello'), {
470
+ bridgeType: 'preview_start',
471
+ previewHandle: 'preview-1',
472
+ content: 'hello',
473
+ });
474
+ strict_1.default.deepEqual((0, local_core_acp_progress_js_1.applyAssistantMessageChunk)(currentTurn, ' world'), {
475
+ bridgeType: 'update_message',
476
+ previewHandle: 'preview-1',
477
+ content: 'hello world',
478
+ });
479
+ strict_1.default.deepEqual((0, local_core_acp_progress_js_1.applyThoughtChunk)(currentTurn, '先理解'), {
480
+ bridgeType: 'preview_start',
481
+ previewHandle: 'thought-preview-1',
482
+ messageId: 'run-1-thought',
483
+ content: '💭 先理解',
484
+ });
485
+ strict_1.default.deepEqual((0, local_core_acp_progress_js_1.applyThoughtChunk)(currentTurn, ',再修改'), {
486
+ bridgeType: 'update_message',
487
+ previewHandle: 'thought-preview-1',
488
+ messageId: 'run-1-thought',
489
+ content: '💭 先理解,再修改',
490
+ });
491
+ });
492
+ (0, node_test_1.default)('ACP thought chunks merge provider snapshots without duplicating text', () => {
493
+ const currentTurn = {
494
+ thoughtPreviewHandle: 'thought-preview-1',
495
+ thoughtMessageId: 'run-1-thought',
496
+ thoughtText: '',
497
+ thoughtPreviewStarted: false,
498
+ };
499
+ strict_1.default.deepEqual((0, local_core_acp_progress_js_1.applyThoughtChunk)(currentTurn, 'The user wants to see their desktop files.'), {
500
+ bridgeType: 'preview_start',
501
+ previewHandle: 'thought-preview-1',
502
+ messageId: 'run-1-thought',
503
+ content: '💭 The user wants to see their desktop files.',
504
+ });
505
+ strict_1.default.deepEqual((0, local_core_acp_progress_js_1.applyThoughtChunk)(currentTurn, 'The user wants to see their desktop files. Let me show them.'), {
506
+ bridgeType: 'update_message',
507
+ previewHandle: 'thought-preview-1',
508
+ messageId: 'run-1-thought',
509
+ content: '💭 The user wants to see their desktop files. Let me show them.',
510
+ });
511
+ strict_1.default.equal(currentTurn.thoughtText, 'The user wants to see their desktop files. Let me show them.');
512
+ });
513
+ (0, node_test_1.default)('ACP progress projection registers pending tool calls in order', () => {
514
+ const currentTurn = {
515
+ toolCallSequence: 0,
516
+ pendingToolCalls: {},
517
+ pendingToolCallOrder: [],
518
+ };
519
+ strict_1.default.deepEqual((0, local_core_acp_progress_js_1.registerPendingToolCall)({
520
+ currentTurn,
521
+ runId: 'run-1',
522
+ update: { id: 'call-a', title: 'Terminal', rawInput: { command: 'npm test' } },
523
+ }), {
524
+ key: 'call-a',
525
+ title: 'Terminal',
526
+ messageId: 'run-1-tool-1',
527
+ input: { command: 'npm test' },
528
+ sequence: 1,
529
+ emitted: false,
530
+ });
531
+ strict_1.default.deepEqual((0, local_core_acp_progress_js_1.registerPendingToolCall)({
532
+ currentTurn,
533
+ runId: 'run-1',
534
+ update: { title: 'Read' },
535
+ }), {
536
+ key: 'sequence:2',
537
+ title: 'Read',
538
+ messageId: 'run-1-tool-2',
539
+ sequence: 2,
540
+ emitted: false,
541
+ });
542
+ strict_1.default.deepEqual(currentTurn.pendingToolCallOrder, ['call-a', 'sequence:2']);
543
+ strict_1.default.equal(currentTurn.activeToolCallKey, 'sequence:2');
544
+ strict_1.default.deepEqual((0, local_core_acp_progress_js_1.getToolCallsInOrder)(currentTurn).map((toolCall) => toolCall.key), ['call-a', 'sequence:2']);
545
+ strict_1.default.equal((0, local_core_acp_progress_js_1.resolveFallbackToolCall)(currentTurn)?.key, 'sequence:2');
546
+ strict_1.default.equal((0, local_core_acp_progress_js_1.resolveToolCallForUpdate)(currentTurn, { id: 'call-a' })?.key, 'call-a');
547
+ strict_1.default.equal(currentTurn.activeToolCallKey, 'call-a');
548
+ (0, local_core_acp_progress_js_1.syncLegacyPendingToolCall)(currentTurn, currentTurn.pendingToolCalls['call-a']);
549
+ strict_1.default.equal(currentTurn.pendingToolCallTitle, 'Terminal');
550
+ strict_1.default.equal(currentTurn.pendingToolCallId, 'run-1-tool-1');
551
+ (0, local_core_acp_progress_js_1.deletePendingToolCall)(currentTurn, 'call-a');
552
+ strict_1.default.deepEqual(currentTurn.pendingToolCallOrder, ['sequence:2']);
553
+ strict_1.default.equal(currentTurn.activeToolCallKey, undefined);
554
+ });
555
+ (0, node_test_1.default)('ACP progress projection ignores empty plan entries', () => {
556
+ strict_1.default.equal((0, local_core_acp_progress_js_1.formatPlanProgress)([
557
+ { content: '检查消息流' },
558
+ { content: ' ' },
559
+ { content: '修复持久化' },
560
+ ]), '💭 检查消息流 | 修复持久化');
561
+ strict_1.default.equal((0, local_core_acp_progress_js_1.formatPlanProgress)([{ content: '' }]), '');
562
+ });
98
563
  (0, node_test_1.default)('ACP tool call running and completed updates share one message id', () => {
99
564
  const upserted = [];
100
565
  const emitted = [];
@@ -172,7 +637,7 @@ const workspace_acp_permissions_js_1 = require("../services/local-ai-core/src/ac
172
637
  const emitted = [];
173
638
  const coordinator = new local_core_acp_turn_coordinator_js_1.LocalCoreAcpTurnCoordinator({
174
639
  appendMessage: () => strict_1.default.fail('tool updates should be upserted'),
175
- upsertMessage: (_threadId, id, _role, content, kind) => upserted.push({ id, content, kind }),
640
+ upsertMessage: (_threadId, id, _role, content, kind, toolCall) => upserted.push({ id, content, kind, toolCall }),
176
641
  emitBridge: (event) => emitted.push(event),
177
642
  updateRunStatus: () => { },
178
643
  sendRaw: () => true,
@@ -200,6 +665,7 @@ const workspace_acp_permissions_js_1 = require("../services/local-ai-core/src/ac
200
665
  sessionUpdate: 'tool_call',
201
666
  id: 'call-a',
202
667
  title: 'Terminal',
668
+ rawInput: { command: 'npm test' },
203
669
  },
204
670
  },
205
671
  });
@@ -250,6 +716,74 @@ const workspace_acp_permissions_js_1 = require("../services/local-ai-core/src/ac
250
716
  strict_1.default.equal(upserted[1]?.id, 'run-1-tool-1');
251
717
  strict_1.default.equal(upserted[1]?.content, '🔧 Terminal: completed - terminal output');
252
718
  strict_1.default.deepEqual(emitted.map((event) => event.messageId), ['run-1-tool-2', 'run-1-tool-1']);
719
+ strict_1.default.deepEqual(upserted[1]?.toolCall, {
720
+ id: 'call-a',
721
+ name: 'Terminal',
722
+ status: 'completed',
723
+ input: { command: 'npm test' },
724
+ output: 'terminal output',
725
+ detail: undefined,
726
+ label: '工具结果',
727
+ });
728
+ strict_1.default.deepEqual(emitted[1]?.toolCall, upserted[1]?.toolCall);
729
+ });
730
+ (0, node_test_1.default)('ACP tool call update backfills rawInput from pi ACP updates', () => {
731
+ const upserted = [];
732
+ const coordinator = new local_core_acp_turn_coordinator_js_1.LocalCoreAcpTurnCoordinator({
733
+ appendMessage: () => strict_1.default.fail('tool updates should be upserted'),
734
+ upsertMessage: (_threadId, id, _role, content, _kind, toolCall) => upserted.push({ id, content, toolCall }),
735
+ emitBridge: () => { },
736
+ updateRunStatus: () => { },
737
+ sendRaw: () => true,
738
+ });
739
+ const session = {
740
+ threadId: 'thread-1',
741
+ bridgeSessionKey: 'session:thread-1',
742
+ currentRunId: 'run-1',
743
+ currentTurn: {
744
+ runId: 'run-1',
745
+ replyCtx: 'run-1',
746
+ previewHandle: 'preview-1',
747
+ assistantText: '',
748
+ typingStarted: true,
749
+ previewStarted: false,
750
+ permission: null,
751
+ },
752
+ loadReplayMode: false,
753
+ schedulerJobCreatedByRun: new Map(),
754
+ };
755
+ coordinator.handleAgentNotification(session, {
756
+ method: 'session/update',
757
+ params: {
758
+ update: {
759
+ sessionUpdate: 'tool_call',
760
+ toolCallId: 'call-a',
761
+ title: 'bash',
762
+ },
763
+ },
764
+ });
765
+ coordinator.handleAgentNotification(session, {
766
+ method: 'session/update',
767
+ params: {
768
+ update: {
769
+ sessionUpdate: 'tool_call_update',
770
+ toolCallId: 'call-a',
771
+ status: 'completed',
772
+ rawInput: { command: 'ls -la ~/Desktop' },
773
+ content: [
774
+ {
775
+ type: 'content',
776
+ content: {
777
+ type: 'text',
778
+ text: 'total 32',
779
+ },
780
+ },
781
+ ],
782
+ },
783
+ },
784
+ });
785
+ strict_1.default.equal(upserted[0]?.toolCall?.name, 'bash');
786
+ strict_1.default.deepEqual(upserted[0]?.toolCall?.input, { command: 'ls -la ~/Desktop' });
253
787
  });
254
788
  (0, node_test_1.default)('ACP permission tool parameters are preserved in completed tool cards', () => {
255
789
  const upserted = [];
@@ -378,6 +912,61 @@ const workspace_acp_permissions_js_1 = require("../services/local-ai-core/src/ac
378
912
  ]]);
379
913
  strict_1.default.equal(session.pendingPermissionByRun.get('run-1')?.options[1]?.optionId, 'approve-always');
380
914
  });
915
+ (0, node_test_1.default)('ACP yolo mode auto-selects permission requests without rendering cards', () => {
916
+ const emitted = [];
917
+ const appended = [];
918
+ const rawPayloads = [];
919
+ const coordinator = new local_core_acp_turn_coordinator_js_1.LocalCoreAcpTurnCoordinator({
920
+ appendMessage: (_threadId, _role, content) => appended.push(content),
921
+ emitBridge: (event) => emitted.push(event),
922
+ updateRunStatus: () => { },
923
+ getThreadAgentMode: () => 'bypassPermissions',
924
+ sendRaw: (_session, payload) => {
925
+ rawPayloads.push(payload);
926
+ return true;
927
+ },
928
+ });
929
+ const session = {
930
+ threadId: 'thread-1',
931
+ bridgeSessionKey: 'session:thread-1',
932
+ currentRunId: 'run-1',
933
+ currentTurn: {
934
+ runId: 'run-1',
935
+ replyCtx: 'run-1',
936
+ previewHandle: 'preview-1',
937
+ assistantText: '',
938
+ typingStarted: true,
939
+ previewStarted: false,
940
+ permission: null,
941
+ },
942
+ pendingPermissionByRun: new Map(),
943
+ loadReplayMode: false,
944
+ schedulerJobCreatedByRun: new Map(),
945
+ };
946
+ coordinator.handleAgentRequest(session, {
947
+ method: 'session/request_permission',
948
+ id: 42,
949
+ params: {
950
+ toolCall: {
951
+ title: 'Terminal',
952
+ parameters: {
953
+ command: 'system_profiler SPHardwareDataType',
954
+ },
955
+ },
956
+ options: [
957
+ { optionId: 'approve-once', name: 'Allow once', kind: 'allow' },
958
+ { optionId: 'approve-always', name: 'Always allow', kind: 'allow_all' },
959
+ { optionId: 'reject', name: 'Reject', kind: 'reject' },
960
+ ],
961
+ },
962
+ });
963
+ strict_1.default.equal(rawPayloads[0]?.id, 42);
964
+ strict_1.default.equal(rawPayloads[0]?.result?.outcome?.outcome, 'selected');
965
+ strict_1.default.equal(rawPayloads[0]?.result?.outcome?.optionId, 'approve-always');
966
+ strict_1.default.equal(session.pendingPermissionByRun.size, 0);
967
+ strict_1.default.deepEqual(appended, []);
968
+ strict_1.default.deepEqual(emitted, []);
969
+ });
381
970
  (0, node_test_1.default)('ACP bare tool call is flushed before assistant text', () => {
382
971
  const appended = [];
383
972
  const emitted = [];
@@ -559,6 +1148,34 @@ const workspace_acp_permissions_js_1 = require("../services/local-ai-core/src/ac
559
1148
  (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
560
1149
  }
561
1150
  });
1151
+ (0, node_test_1.default)('ACP store preserves structured tool call progress metadata', () => {
1152
+ const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-tool-store-'));
1153
+ const store = new local_core_acp_store_js_1.LocalCoreAcpStore(userDataPath);
1154
+ try {
1155
+ const thread = store.createThread('project-1', 'Thread');
1156
+ store.upsertMessage(thread.id, 'run-1-tool-1', 'assistant', '🔧 bash: completed - total 32', 'progress', {
1157
+ id: 'call-1',
1158
+ name: 'bash',
1159
+ status: 'completed',
1160
+ input: { command: 'ls -la ~/Desktop' },
1161
+ detail: 'ls -la ~/Desktop',
1162
+ output: 'total 32',
1163
+ label: '工具结果',
1164
+ });
1165
+ const detail = store.getThread(thread.id, []);
1166
+ strict_1.default.equal(detail.messages.length, 1);
1167
+ strict_1.default.equal(detail.messages[0]?.id, 'run-1-tool-1');
1168
+ strict_1.default.equal(detail.messages[0]?.toolCall?.name, 'bash');
1169
+ strict_1.default.equal(detail.messages[0]?.toolCall?.status, 'completed');
1170
+ strict_1.default.deepEqual(detail.messages[0]?.toolCall?.input, { command: 'ls -la ~/Desktop' });
1171
+ strict_1.default.equal(detail.messages[0]?.toolCall?.detail, 'ls -la ~/Desktop');
1172
+ strict_1.default.equal(detail.messages[0]?.toolCall?.output, 'total 32');
1173
+ }
1174
+ finally {
1175
+ store.close();
1176
+ (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
1177
+ }
1178
+ });
562
1179
  (0, node_test_1.default)('lark bridge keeps thought preview and final answer in separate messages', async () => {
563
1180
  const createdCards = [];
564
1181
  const patchedCards = [];
@@ -721,7 +1338,7 @@ const workspace_acp_permissions_js_1 = require("../services/local-ai-core/src/ac
721
1338
  strict_1.default.equal(patchedCards.length, 0);
722
1339
  strict_1.default.deepEqual(storedMessageIds, ['lark-msg-1', 'lark-msg-2']);
723
1340
  });
724
- (0, node_test_1.default)('lark bridge does not leave typing placeholders or throttle thought updates', async () => {
1341
+ (0, node_test_1.default)('lark bridge does not leave typing placeholders and coalesces thought updates', async () => {
725
1342
  const createdCards = [];
726
1343
  const patchedCards = [];
727
1344
  const client = {
@@ -811,7 +1428,6 @@ const workspace_acp_permissions_js_1 = require("../services/local-ai-core/src/ac
811
1428
  strict_1.default.deepEqual(createdCards.map((card) => card.text), ['💭 The user']);
812
1429
  strict_1.default.deepEqual(patchedCards.map((card) => card.text), [
813
1430
  '💭 The user sent a short casual message.',
814
- '💭 The user sent a short casual message. I should reply briefly.',
815
1431
  ]);
816
1432
  strict_1.default.ok(!createdCards.some((card) => /处理中|正在思考/.test(card.text)));
817
1433
  });
@@ -1668,6 +2284,7 @@ const workspace_acp_permissions_js_1 = require("../services/local-ai-core/src/ac
1668
2284
  const users = new Map();
1669
2285
  const threadBindings = new Map();
1670
2286
  const createdThreads = [];
2287
+ const updatedModes = [];
1671
2288
  const sentCards = [];
1672
2289
  const platformKey = 'lark:lark-hot';
1673
2290
  const bindingKey = `${platformKey}:chat-1:user-1`;
@@ -1683,6 +2300,8 @@ const workspace_acp_permissions_js_1 = require("../services/local-ai-core/src/ac
1683
2300
  },
1684
2301
  getPlatformThreadBinding: () => threadBindings.get(bindingKey),
1685
2302
  upsertPlatformThreadBinding: (binding) => threadBindings.set(bindingKey, binding),
2303
+ getThreadRow: (threadId) => ({ id: threadId, agent_mode: threadId === 'thread-1' ? 'bypassPermissions' : 'default' }),
2304
+ updateThreadAgentMode: (threadId, mode) => updatedModes.push({ threadId, mode }),
1686
2305
  getLatestRunForThread: () => null,
1687
2306
  clearPlatformThreadMessageId: () => { },
1688
2307
  },
@@ -1736,8 +2355,29 @@ const workspace_acp_permissions_js_1 = require("../services/local-ai-core/src/ac
1736
2355
  });
1737
2356
  strict_1.default.equal(users.get(`${platformKey}:user-1`)?.thread_id, 'thread-2');
1738
2357
  strict_1.default.deepEqual(createdThreads.map((item) => item.split(':')[0]), ['thread-1', 'thread-2']);
2358
+ strict_1.default.deepEqual(updatedModes, [{ threadId: 'thread-2', mode: 'bypassPermissions' }]);
1739
2359
  strict_1.default.match(sentCards[0]?.elements?.[0]?.content || '', /已开始新会话/);
1740
2360
  });
2361
+ (0, node_test_1.default)('lark rendering suppresses noisy pending tool progress cards', () => {
2362
+ const turn = (0, runtime_state_js_1.createLarkTurnState)('session-1');
2363
+ const rendered = (0, runtime_state_js_1.renderLarkBridgeEventMessage)(turn, {
2364
+ type: 'update_message',
2365
+ sessionKey: 'session-1',
2366
+ replyCtx: 'run-1',
2367
+ previewHandle: 'tool-1',
2368
+ content: '🔧 bash: Tool update - pending',
2369
+ });
2370
+ strict_1.default.equal(rendered.text, '');
2371
+ strict_1.default.equal(rendered.key, 'noop');
2372
+ const completed = (0, runtime_state_js_1.renderLarkBridgeEventMessage)(turn, {
2373
+ type: 'update_message',
2374
+ sessionKey: 'session-1',
2375
+ replyCtx: 'run-1',
2376
+ previewHandle: 'tool-1',
2377
+ content: '🔧 bash: Tool update - completed - verbose output',
2378
+ });
2379
+ strict_1.default.equal(completed.text, '🔧 bash: Tool update - completed');
2380
+ });
1741
2381
  (0, node_test_1.default)('ACP skips empty generic running tool updates', () => {
1742
2382
  const appended = [];
1743
2383
  const emitted = [];
@@ -1863,6 +2503,29 @@ const workspace_acp_permissions_js_1 = require("../services/local-ai-core/src/ac
1863
2503
  strict_1.default.equal(processed.displayContent.trim(), '已为你创建。');
1864
2504
  strict_1.default.match(processed.systemResponses[0] || '', /已创建定时任务/);
1865
2505
  });
2506
+ (0, node_test_1.default)('slash mode commands normalize yolo aliases and expose current help', () => {
2507
+ strict_1.default.deepEqual((0, local_core_slash_commands_js_1.parseSlashCommand)('/mode yolo'), { name: 'mode', args: ['yolo'] });
2508
+ strict_1.default.equal((0, local_core_slash_commands_js_1.normalizeAgentMode)('yolo'), 'bypassPermissions');
2509
+ strict_1.default.equal((0, local_core_slash_commands_js_1.normalizeAgentMode)('accept-edits'), 'acceptEdits');
2510
+ strict_1.default.equal((0, local_core_slash_commands_js_1.formatAgentMode)('bypassPermissions'), 'yolo');
2511
+ strict_1.default.match((0, local_core_slash_commands_js_1.modeHelpText)('bypassPermissions'), /当前模式:yolo/);
2512
+ });
2513
+ (0, node_test_1.default)('thread agent mode persists with thread state', () => {
2514
+ const dir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-mode-'));
2515
+ try {
2516
+ const store = new local_core_acp_store_js_1.LocalCoreAcpStore(dir);
2517
+ const thread = store.createThread('workspace-a', 'Mode test', 'claudecode');
2518
+ strict_1.default.equal(store.getThreadRow(thread.id)?.agent_mode, 'default');
2519
+ const inheritedThread = store.createThread('workspace-a', 'Inherited mode test', 'claudecode', 'bypassPermissions');
2520
+ strict_1.default.equal(store.getThreadRow(inheritedThread.id)?.agent_mode, 'bypassPermissions');
2521
+ store.updateThreadAgentMode(thread.id, 'bypassPermissions');
2522
+ strict_1.default.equal(store.getThreadRow(thread.id)?.agent_mode, 'bypassPermissions');
2523
+ store.close();
2524
+ }
2525
+ finally {
2526
+ (0, node_fs_1.rmSync)(dir, { recursive: true, force: true });
2527
+ }
2528
+ });
1866
2529
  (0, node_test_1.default)('scheduled conversation executor uses execution policy hooks around a thread run', async () => {
1867
2530
  const calls = [];
1868
2531
  const job = {