@kafca/agentdock 0.1.44 → 0.1.46

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 (126) hide show
  1. package/README.md +76 -11
  2. package/bin/agentdock.mjs +5 -5
  3. package/dist/renderer/assets/{Badge-Qk9qHovH.js → Badge-BIesaDYG.js} +1 -1
  4. package/dist/renderer/assets/Chat-CpataoDh.js +2 -0
  5. package/dist/renderer/assets/{ChatMarkdown-BKdW8OON.js → ChatMarkdown-_Mdmltgv.js} +1 -1
  6. package/dist/renderer/assets/Config-Pe5-zKZk.js +21 -0
  7. package/dist/renderer/assets/CronList-BnaOSPT8.js +1 -0
  8. package/dist/renderer/assets/Dashboard-D05l8GKE.js +26 -0
  9. package/dist/renderer/assets/{EmptyState-kD9yAwtw.js → EmptyState-BDBavDll.js} +1 -1
  10. package/dist/renderer/assets/{KnowledgeDetail-BAMCLrjz.js → KnowledgeDetail-DvR798l_.js} +2 -2
  11. package/dist/renderer/assets/KnowledgeHome-r2ZuvU8-.js +11 -0
  12. package/dist/renderer/assets/Logs-BaljtmAV.js +1 -0
  13. package/dist/renderer/assets/{Modal-DwY4lecS.js → Modal-CXhD_OWR.js} +1 -1
  14. package/dist/renderer/assets/MonitorList-D7tyipbx.js +1 -0
  15. package/dist/renderer/assets/{Page-CnB9ePsb.js → Page-KUuFYgjg.js} +1 -1
  16. package/dist/renderer/assets/ProjectDetail-Ddq94-yu.js +11 -0
  17. package/dist/renderer/assets/ProjectList-CbdHd5Ev.js +1 -0
  18. package/dist/renderer/assets/{Select-zfoksLGB.js → Select-Djc8BXMC.js} +1 -1
  19. package/dist/renderer/assets/SessionChat-DOy0iKQR.js +1 -0
  20. package/dist/renderer/assets/SessionList-DHgl1s1D.js +1 -0
  21. package/dist/renderer/assets/ThreadChat-I7dYibDc.js +23 -0
  22. package/dist/renderer/assets/Workspace-DTocqY-F.js +19 -0
  23. package/dist/renderer/assets/{arrow-left-CNEAuElg.js → arrow-left-CXyya3hP.js} +1 -1
  24. package/dist/renderer/assets/{book-open-BNJMhNI-.js → book-open-9n4Kxc7E.js} +1 -1
  25. package/dist/renderer/assets/bot-C9HBk5XH.js +6 -0
  26. package/dist/renderer/assets/{check-BnclClRU.js → check-CutSg9a5.js} +1 -1
  27. package/dist/renderer/assets/{chevron-down-Bh53e0O-.js → chevron-down-DrydR4i3.js} +1 -1
  28. package/dist/renderer/assets/{circle-ClZTWCWO.js → circle-BEjtahw6.js} +1 -1
  29. package/dist/renderer/assets/desktop-DATgk_ka.js +1 -0
  30. package/dist/renderer/assets/{desktop-C6q6wBae.js → desktop-JQkhNOF4.js} +5 -5
  31. package/dist/renderer/assets/en-CmAr_0e6.js +1 -0
  32. package/dist/renderer/assets/es-Cs9x52ca.js +1 -0
  33. package/dist/renderer/assets/{funnel-_5hsz0xI.js → funnel-BhqZpS4f.js} +1 -1
  34. package/dist/renderer/assets/{heart-u4Q1aOqj.js → heart-Dp8Vs-7_.js} +1 -1
  35. package/dist/renderer/assets/{index-BNFx4uYv.js → index-BKpv0P0s.js} +2 -2
  36. package/dist/renderer/assets/index-CO4fGqmv.css +11 -0
  37. package/dist/renderer/assets/{index-B36eowj-.js → index-cI3pWBnN.js} +36 -36
  38. package/dist/renderer/assets/ja-DtvtBRTW.js +1 -0
  39. package/dist/renderer/assets/{pencil-DLh9QNmT.js → pencil-ywcJzeOo.js} +1 -1
  40. package/dist/renderer/assets/{play-BDTk9Scm.js → play-AtN492Cx.js} +1 -1
  41. package/dist/renderer/assets/{plus-BSc8POsz.js → plus-BEj14BBV.js} +1 -1
  42. package/dist/renderer/assets/projects-Clar8lgb.js +1 -0
  43. package/dist/renderer/assets/save-D6ov4CNv.js +11 -0
  44. package/dist/renderer/assets/{search-CniPk7Ey.js → search-C3bvw7AR.js} +1 -1
  45. package/dist/renderer/assets/{server-GsXyQAZm.js → server-C1Uyedyx.js} +1 -1
  46. package/dist/renderer/assets/{session-utils-BDxzgTBC.js → session-utils-DpRwJ6Nh.js} +1 -1
  47. package/dist/renderer/assets/{sessions-CLcKA1Tt.js → sessions-BWmAppwK.js} +1 -1
  48. package/dist/renderer/assets/{shield-check-4i0E3hmf.js → shield-check-CVx1mEs2.js} +1 -1
  49. package/dist/renderer/assets/{trash-2-DhtOCsO5.js → trash-2-o-heXOgs.js} +1 -1
  50. package/dist/renderer/assets/user-dPOzUqIc.js +6 -0
  51. package/dist/renderer/assets/{wifi-off-BHPzPY_B.js → wifi-off-Fz5c3_x_.js} +1 -1
  52. package/dist/renderer/assets/zh-CcJ9vBZj.js +1 -0
  53. package/dist/renderer/assets/zh-TW-elI72AC1.js +1 -0
  54. package/dist/renderer/index.html +2 -2
  55. package/dist-electron/electron/main.js +26 -1
  56. package/dist-electron/packages/core-sdk/src/index.js +28 -0
  57. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-backend.js +111 -62
  58. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-session-coordinator.js +80 -7
  59. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-transport.js +78 -5
  60. package/dist-electron/services/local-ai-core/src/acp/store/external-store.js +98 -0
  61. package/dist-electron/services/local-ai-core/src/acp/store/local-core-acp-store.js +33 -0
  62. package/dist-electron/services/local-ai-core/src/acp/store/model-provider-store.js +88 -0
  63. package/dist-electron/services/local-ai-core/src/acp/store/schema.js +43 -0
  64. package/dist-electron/services/local-ai-core/src/agents/pi/launch.js +2 -119
  65. package/dist-electron/services/local-ai-core/src/agents/pi/provider-materializer.js +148 -0
  66. package/dist-electron/services/local-ai-core/src/channel/lark/inbound.js +55 -1
  67. package/dist-electron/services/local-ai-core/src/channel/lark/local-core-lark-gateway.js +65 -10
  68. package/dist-electron/services/local-ai-core/src/channel/shared/session-command-runtime.js +4 -2
  69. package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js +139 -29
  70. package/dist-electron/services/local-ai-core/src/execution/agent-execution-backend.js +56 -0
  71. package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +18 -57
  72. package/dist-electron/services/local-ai-core/src/kernel/local-core-errors.js +255 -0
  73. package/dist-electron/services/local-ai-core/src/plugins/builtin/catalog.js +78 -0
  74. package/dist-electron/services/local-ai-core/src/router/workspace-route-config.js +3 -1
  75. package/dist-electron/services/local-ai-core/src/router/workspace-router.js +64 -9
  76. package/dist-electron/services/local-ai-core/src/runtime/agent-runtime-detector.js +5 -2
  77. package/dist-electron/services/local-ai-core/src/runtime/config-migration.js +152 -0
  78. package/dist-electron/services/local-ai-core/src/runtime/deployment-diagnostics.js +175 -0
  79. package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +330 -3
  80. package/dist-electron/services/local-ai-core/src/runtime/local-core-runtime-state.js +11 -2
  81. package/dist-electron/services/local-ai-core/src/runtime/provider-config-migration.js +32 -0
  82. package/dist-electron/services/local-ai-core/src/runtime/runtime-detection-service.js +76 -8
  83. package/dist-electron/services/local-ai-core/src/runtime/server-routes.js +62 -0
  84. package/dist-electron/services/local-ai-core/src/runtime/server.js +708 -4
  85. package/dist-electron/services/local-ai-core/src/runtime/standalone.js +13 -3
  86. package/dist-electron/services/local-ai-core/src/sandbox/opensandbox-client.js +113 -0
  87. package/dist-electron/services/local-ai-core/src/sandbox/sandbox-config.js +273 -0
  88. package/dist-electron/services/local-ai-core/src/sandbox/sandbox-manager.js +215 -0
  89. package/dist-electron/services/local-ai-core/src/sandbox/sandbox-stdio-proxy.js +188 -0
  90. package/dist-electron/services/local-ai-core/src/thread/slash-command-registry.js +3 -0
  91. package/dist-electron/services/local-ai-core/src/thread/thread-command-service.js +32 -0
  92. package/dist-electron/services/local-ai-core/src/thread/thread-slash-command-dispatcher.js +71 -0
  93. package/dist-electron/shared/desktop.js +85 -1
  94. package/dist-electron/tests/contracts/architecture-docs.test.js +83 -0
  95. package/dist-electron/tests/electron/plugin-kernel.test.js +225 -18
  96. package/dist-electron/tests/electron/runtime-detection-service.test.js +57 -5
  97. package/dist-electron/tests/electron/sandbox-config.test.js +541 -0
  98. package/dist-electron/tests/electron/workspace-task-store.test.js +257 -0
  99. package/dist-electron/tests/integration/lark-gateway.test.js +139 -0
  100. package/dist-electron/tests/integration/local-core-refactor.test.js +183 -20
  101. package/dist-electron/tests/integration/local-core-routes.test.js +34 -0
  102. package/package.json +2 -3
  103. package/dist/renderer/assets/Chat-BK-fm0Xr.js +0 -2
  104. package/dist/renderer/assets/Config-B93CQGvY.js +0 -16
  105. package/dist/renderer/assets/CronList-Z3TvSRKP.js +0 -1
  106. package/dist/renderer/assets/Dashboard-Ch3QkWwf.js +0 -26
  107. package/dist/renderer/assets/KnowledgeHome-ChvjOfrc.js +0 -11
  108. package/dist/renderer/assets/Logs-Cv0nth3j.js +0 -1
  109. package/dist/renderer/assets/MonitorList-JEvMoWUm.js +0 -1
  110. package/dist/renderer/assets/ProjectDetail-DJa3sMmU.js +0 -11
  111. package/dist/renderer/assets/ProjectList-D6CXumR_.js +0 -1
  112. package/dist/renderer/assets/SessionChat-DUTh4WoM.js +0 -1
  113. package/dist/renderer/assets/SessionList-D9o-lUst.js +0 -1
  114. package/dist/renderer/assets/ThreadChat-BYXlT-Kc.js +0 -23
  115. package/dist/renderer/assets/Workspace-BrhSwRQ-.js +0 -14
  116. package/dist/renderer/assets/desktop-C5vBT0yM.js +0 -1
  117. package/dist/renderer/assets/en-D2SkCrhG.js +0 -1
  118. package/dist/renderer/assets/es-C8UBPhxM.js +0 -1
  119. package/dist/renderer/assets/index-CaQZSRbg.css +0 -11
  120. package/dist/renderer/assets/ja-ChF4vbBL.js +0 -1
  121. package/dist/renderer/assets/plug-BG0JHW6B.js +0 -6
  122. package/dist/renderer/assets/projects-DwfZwxDW.js +0 -1
  123. package/dist/renderer/assets/save-D5AO_G6a.js +0 -6
  124. package/dist/renderer/assets/user-BtC_pVXV.js +0 -11
  125. package/dist/renderer/assets/zh-DIcdHycM.js +0 -1
  126. package/dist/renderer/assets/zh-TW-BtRwreY_.js +0 -1
@@ -10,6 +10,7 @@ const node_path_1 = require("node:path");
10
10
  const node_os_1 = require("node:os");
11
11
  const local_core_acp_store_js_1 = require("../../services/local-ai-core/src/acp/local-core-acp-store.js");
12
12
  const local_core_acp_session_coordinator_js_1 = require("../../services/local-ai-core/src/acp/local-core-acp-session-coordinator.js");
13
+ const local_core_acp_transport_js_1 = require("../../services/local-ai-core/src/acp/local-core-acp-transport.js");
13
14
  const local_core_acp_turn_coordinator_js_1 = require("../../services/local-ai-core/src/acp/local-core-acp-turn-coordinator.js");
14
15
  const scheduler_service_js_1 = require("../../services/local-ai-core/src/scheduler/scheduler-service.js");
15
16
  const lark_schedule_adapter_js_1 = require("../../services/local-ai-core/src/scheduler/lark-schedule-adapter.js");
@@ -40,6 +41,141 @@ const local_core_controller_js_1 = require("../../services/local-ai-core/src/run
40
41
  (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
41
42
  }
42
43
  });
44
+ (0, node_test_1.default)('model providers persist independently from workspace config', () => {
45
+ const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'model-provider-store-'));
46
+ try {
47
+ const store = new local_core_acp_store_js_1.LocalCoreAcpStore(userDataPath);
48
+ const provider = store.upsertModelProvider({
49
+ name: 'deepseek',
50
+ api_key: 'secret',
51
+ base_url: 'https://api.deepseek.com',
52
+ model: 'deepseek-v4-flash',
53
+ env: { CUSTOM_PROVIDER_ENV: '1' },
54
+ });
55
+ strict_1.default.equal(provider.id, 'deepseek');
56
+ strict_1.default.equal(provider.api_key, 'secret');
57
+ strict_1.default.equal(store.listModelProviders().length, 1);
58
+ const reopened = new local_core_acp_store_js_1.LocalCoreAcpStore(userDataPath);
59
+ strict_1.default.equal(reopened.getModelProvider('deepseek')?.model, 'deepseek-v4-flash');
60
+ reopened.close();
61
+ store.close();
62
+ }
63
+ finally {
64
+ (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
65
+ }
66
+ });
67
+ (0, node_test_1.default)('external project and thread mappings persist with isolated workspace paths', () => {
68
+ const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'external-store-'));
69
+ try {
70
+ const store = new local_core_acp_store_js_1.LocalCoreAcpStore(userDataPath);
71
+ const project = store.upsertExternalProject({
72
+ userId: 'user-a',
73
+ externalProjectId: 'project-a',
74
+ workspaceId: 'external-user-a-project-a',
75
+ workspacePath: '/data/users/user-a/projects/project-a',
76
+ displayName: 'Project A',
77
+ agentType: 'pi',
78
+ providerId: 'deepseek',
79
+ metadata: { source: 'external' },
80
+ createdAt: '2026-05-15T00:00:00.000Z',
81
+ updatedAt: '2026-05-15T00:00:00.000Z',
82
+ });
83
+ const thread = store.createThread(project.workspaceId, 'Thread A', 'pi');
84
+ store.upsertExternalThread({
85
+ userId: project.userId,
86
+ externalProjectId: project.externalProjectId,
87
+ externalThreadId: 'thread-a',
88
+ workspaceId: project.workspaceId,
89
+ threadId: thread.id,
90
+ workspacePath: '/data/users/user-a/projects/project-a/threads/thread-a/workspace',
91
+ metadata: { channel: 'api' },
92
+ createdAt: '2026-05-15T00:00:00.000Z',
93
+ updatedAt: '2026-05-15T00:00:00.000Z',
94
+ });
95
+ store.close();
96
+ const reopened = new local_core_acp_store_js_1.LocalCoreAcpStore(userDataPath);
97
+ strict_1.default.equal(reopened.getExternalProject('user-a', 'project-a')?.workspacePath, '/data/users/user-a/projects/project-a');
98
+ strict_1.default.equal(reopened.getExternalThreadByThreadId(thread.id)?.workspacePath, '/data/users/user-a/projects/project-a/threads/thread-a/workspace');
99
+ reopened.close();
100
+ }
101
+ finally {
102
+ (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
103
+ }
104
+ });
105
+ (0, node_test_1.default)('controller migrates embedded project providers into shared provider store', async () => {
106
+ const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'model-provider-migration-'));
107
+ try {
108
+ const runtime = (0, bootstrap_js_1.bootstrapLocalCoreRuntime)({
109
+ userDataPath,
110
+ enableKnowledge: false,
111
+ log: () => { },
112
+ });
113
+ const controller = new local_core_controller_js_1.LocalCoreController(userDataPath, runtime);
114
+ await controller.saveStructuredConfigFile({
115
+ projects: [{
116
+ name: 'workspace-a',
117
+ agent: {
118
+ type: 'pi',
119
+ options: { work_dir: '/tmp/workspace-a' },
120
+ providers: [{
121
+ name: 'deepseek-v4-flash',
122
+ api_key: 'secret',
123
+ base_url: 'https://api.deepseek.com',
124
+ model: 'deepseek-v4-flash',
125
+ }],
126
+ },
127
+ platforms: [],
128
+ }],
129
+ });
130
+ const config = await controller.readConfigFile();
131
+ strict_1.default.equal(config.parsed?.projects?.[0]?.agent.options?.provider_id, 'deepseek');
132
+ strict_1.default.equal(config.parsed?.projects?.[0]?.agent.providers, undefined);
133
+ const providers = await controller.listModelProviders();
134
+ strict_1.default.equal(providers.providers[0]?.id, 'deepseek');
135
+ strict_1.default.equal(providers.providers[0]?.name, 'deepseek');
136
+ await controller.close();
137
+ }
138
+ finally {
139
+ (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
140
+ }
141
+ });
142
+ (0, node_test_1.default)('workspace router resolves projects that select a shared provider', async () => {
143
+ const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'workspace-provider-ref-'));
144
+ try {
145
+ const runtime = (0, bootstrap_js_1.bootstrapLocalCoreRuntime)({
146
+ userDataPath,
147
+ enableKnowledge: false,
148
+ log: () => { },
149
+ });
150
+ const controller = new local_core_controller_js_1.LocalCoreController(userDataPath, runtime);
151
+ const provider = await controller.createModelProvider({
152
+ name: 'deepseek',
153
+ api_key: 'secret',
154
+ base_url: 'https://api.deepseek.com',
155
+ model: 'deepseek-v4-flash',
156
+ });
157
+ await controller.saveStructuredConfigFile({
158
+ projects: [{
159
+ name: 'workspace-a',
160
+ agent: {
161
+ type: 'pi',
162
+ options: {
163
+ work_dir: '/tmp/workspace-a',
164
+ provider_id: provider.id,
165
+ },
166
+ },
167
+ platforms: [],
168
+ }],
169
+ });
170
+ const workspaces = await controller.listWorkspaces();
171
+ strict_1.default.equal(workspaces[0]?.id, 'workspace-a');
172
+ strict_1.default.equal(workspaces[0]?.agentType, 'pi');
173
+ await controller.close();
174
+ }
175
+ finally {
176
+ (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
177
+ }
178
+ });
43
179
  (0, node_test_1.default)('scheduler create resolves a Lark delivery route without binding the job to a thread', async () => {
44
180
  const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'scheduler-binding-route-'));
45
181
  try {
@@ -443,6 +579,50 @@ const local_core_controller_js_1 = require("../../services/local-ai-core/src/run
443
579
  (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
444
580
  }
445
581
  });
582
+ (0, node_test_1.default)('ACP runtime PATH includes service-safe user bin directories', () => {
583
+ const previousHome = process.env.HOME;
584
+ process.env.HOME = '/home/agentdock-test';
585
+ try {
586
+ const path = (0, local_core_acp_session_coordinator_js_1.buildAgentPath)('/usr/bin:/bin', '/opt/agentdock/bin');
587
+ strict_1.default.equal(path, '/opt/agentdock/bin:/home/agentdock-test/.local/bin:/home/agentdock-test/bin:/usr/bin:/bin');
588
+ }
589
+ finally {
590
+ if (previousHome === undefined) {
591
+ delete process.env.HOME;
592
+ }
593
+ else {
594
+ process.env.HOME = previousHome;
595
+ }
596
+ }
597
+ });
598
+ (0, node_test_1.default)('ACP transport reports missing agent commands without an unhandled process error', async () => {
599
+ const closed = new Promise((resolve) => {
600
+ const transport = new local_core_acp_transport_js_1.LocalCoreAcpTransport({
601
+ log: () => { },
602
+ onAgentRequest: () => { },
603
+ onAgentNotification: () => { },
604
+ onSessionClosed: (_session, error) => {
605
+ resolve(error);
606
+ },
607
+ });
608
+ transport.spawnSession({
609
+ threadId: 'thread-missing-command',
610
+ bridgeSessionKey: 'session:missing-command',
611
+ config: {
612
+ workspaceId: 'workspace-a',
613
+ agentType: 'hermes',
614
+ command: 'agentdock-command-that-does-not-exist',
615
+ args: ['acp'],
616
+ env: {},
617
+ workDir: (0, node_os_1.tmpdir)(),
618
+ model: '',
619
+ },
620
+ runtimeEnv: {},
621
+ });
622
+ });
623
+ const error = await closed;
624
+ strict_1.default.equal(error.message, 'ACP agent command not found: agentdock-command-that-does-not-exist');
625
+ });
446
626
  (0, node_test_1.default)('ACP scheduled session can override permission mode without changing thread mode', async () => {
447
627
  const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'scheduler-permission-mode-'));
448
628
  try {
@@ -515,6 +695,83 @@ const local_core_controller_js_1 = require("../../services/local-ai-core/src/run
515
695
  (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
516
696
  }
517
697
  });
698
+ (0, node_test_1.default)('ACP sandbox sessions use container workspace cwd', async () => {
699
+ const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'sandbox-session-cwd-'));
700
+ try {
701
+ const store = new local_core_acp_store_js_1.LocalCoreAcpStore(userDataPath);
702
+ const thread = store.createThread('workspace-a', 'Thread');
703
+ let sessionNewParams;
704
+ const coordinator = new local_core_acp_session_coordinator_js_1.LocalCoreAcpSessionCoordinator({
705
+ store,
706
+ transport: {
707
+ spawnSession(input) {
708
+ return {
709
+ threadId: input.threadId,
710
+ bridgeSessionKey: input.bridgeSessionKey,
711
+ closed: false,
712
+ sessionId: '',
713
+ supportsLoad: false,
714
+ pendingPermissionByRun: new Map(),
715
+ schedulerJobCreatedByRun: new Map(),
716
+ launchPermissionMode: '',
717
+ };
718
+ },
719
+ initializeSession: async () => { },
720
+ request: async (_session, method, params) => {
721
+ if (method === 'session/new') {
722
+ sessionNewParams = params;
723
+ return { sessionId: 'session-1' };
724
+ }
725
+ return {};
726
+ },
727
+ closeSession: () => { },
728
+ closeSessionWithError: () => { },
729
+ sendRaw: () => true,
730
+ },
731
+ runThreadMap: new Map(),
732
+ emitBridge: () => { },
733
+ });
734
+ await coordinator.ensureSession(thread.id, 'session:thread-1', {
735
+ workspaceId: 'workspace-a',
736
+ agentType: 'pi',
737
+ command: process.execPath,
738
+ args: ['/host/sandbox-stdio-proxy.js'],
739
+ env: {},
740
+ workDir: '/host/workspace-a',
741
+ model: '',
742
+ sandbox: {
743
+ enabled: true,
744
+ provider: 'opensandbox',
745
+ transport: 'http-ndjson',
746
+ serverUrl: 'http://127.0.0.1:8080',
747
+ apiKeyEnv: 'OPEN_SANDBOX_API_KEY',
748
+ image: 'agentdock/pi-acp:local',
749
+ acpPort: 8080,
750
+ entrypoint: ['node', '/opt/agentdock/acp-bridge.mjs'],
751
+ timeoutSeconds: 600,
752
+ lifecycle: 'per_thread',
753
+ idleSeconds: 900,
754
+ warmPoolSize: 0,
755
+ cpu: '1000m',
756
+ memory: '1Gi',
757
+ userId: 'local',
758
+ projectId: 'workspace-a',
759
+ stateScope: 'project',
760
+ workspaceHostPath: '/host/workspace-a',
761
+ workspaceMountPath: '/workspace',
762
+ stateHostPath: '/host/state',
763
+ stateMountPath: '/agent-state',
764
+ runtimeCommand: '/usr/local/bin/pi-acp',
765
+ runtimeArgs: [],
766
+ runtimeEnv: {},
767
+ },
768
+ });
769
+ strict_1.default.equal(sessionNewParams?.cwd, '/workspace');
770
+ }
771
+ finally {
772
+ (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
773
+ }
774
+ });
518
775
  (0, node_test_1.default)('ACP permission requests honor scheduled session permission override', () => {
519
776
  const sentPayloads = [];
520
777
  let approvalCreated = false;
@@ -687,6 +687,65 @@ function findLarkPostMdText(content) {
687
687
  strict_1.default.deepEqual(patchedCards.map((card) => card.text), []);
688
688
  strict_1.default.ok(!createdCards.some((card) => /处理中|正在思考/.test(card.text)));
689
689
  });
690
+ (0, node_test_1.default)('lark gateway records structured error state when startup fails', async () => {
691
+ const emittedEvents = [];
692
+ const gateway = new local_core_lark_gateway_js_1.LocalCoreLarkGateway({
693
+ store: {
694
+ expirePendingPairings: () => { },
695
+ listPendingPairings: () => [],
696
+ listAuthorizedUsers: () => [],
697
+ },
698
+ readConfig: async () => null,
699
+ getWorkspaceRouter: () => ({}),
700
+ eventBus: {
701
+ emit: (event) => emittedEvents.push(event),
702
+ on: () => () => { },
703
+ },
704
+ });
705
+ gateway.larkModulePromise = Promise.resolve({
706
+ AppType: { SelfBuild: 'SelfBuild' },
707
+ Domain: { Feishu: 'Feishu' },
708
+ LoggerLevel: { info: 'info' },
709
+ Client: class {
710
+ request() {
711
+ return Promise.resolve({ bot: { open_id: 'bot-open-id' } });
712
+ }
713
+ },
714
+ EventDispatcher: class {
715
+ register() { }
716
+ },
717
+ WSClient: class {
718
+ start() {
719
+ throw new Error('invalid app credentials');
720
+ }
721
+ on() { }
722
+ },
723
+ });
724
+ await gateway.startWorkspace({
725
+ workspaceId: 'workspace-1',
726
+ instanceId: 'default',
727
+ displayName: 'Workspace 1',
728
+ platformKey: 'lark',
729
+ appId: 'cli_a1',
730
+ appSecret: 'secret',
731
+ encryptKey: '',
732
+ verificationToken: '',
733
+ autoApprove: false,
734
+ cardActionsEnabled: true,
735
+ groupReplyAll: false,
736
+ enabled: true,
737
+ brand: 'lark',
738
+ project: { name: 'workspace-1' },
739
+ });
740
+ const status = gateway.getStatus('workspace-1');
741
+ strict_1.default.equal(status.status, 'error');
742
+ strict_1.default.equal(status.connected, false);
743
+ strict_1.default.equal(status.lastErrorInfo?.code, 'channel_auth_failed');
744
+ strict_1.default.equal(status.lastErrorInfo?.suggestedAction, 'Check app credentials and restart the Lark gateway.');
745
+ strict_1.default.match(status.lastError || '', /invalid app credentials/);
746
+ strict_1.default.ok(status.lastErrorAt);
747
+ strict_1.default.equal(emittedEvents.some((event) => event.type === 'localcore.error' && event.payload?.scope === 'channel.lark'), true);
748
+ });
690
749
  (0, node_test_1.default)('lark permission requests render as clickable card buttons', async () => {
691
750
  const createdCards = [];
692
751
  const patchedCards = [];
@@ -1424,6 +1483,86 @@ function findLarkPostMdText(content) {
1424
1483
  });
1425
1484
  strict_1.default.equal(sentMessages.length, 0);
1426
1485
  });
1486
+ (0, node_test_1.default)('lark post messages from numbered lists are delivered as text', async () => {
1487
+ const sentMessages = [];
1488
+ const gateway = new local_core_lark_gateway_js_1.LocalCoreLarkGateway({
1489
+ store: {
1490
+ expirePendingPairings: () => { },
1491
+ getAuthorizedUser: () => ({
1492
+ id: 'auth-1',
1493
+ workspace_id: 'project-1',
1494
+ platform: 'lark:lark-1',
1495
+ platform_user_id: 'user-1',
1496
+ chat_id: 'chat-1',
1497
+ display_name: 'User',
1498
+ thread_id: 'thread-1',
1499
+ }),
1500
+ getPlatformThreadBinding: () => ({
1501
+ workspace_id: 'project-1',
1502
+ platform: 'lark:lark-1',
1503
+ chat_id: 'chat-1',
1504
+ platform_user_id: 'user-1',
1505
+ thread_id: 'thread-1',
1506
+ last_platform_message_id: null,
1507
+ }),
1508
+ getLatestRunForThread: () => null,
1509
+ clearPlatformThreadMessageId: () => { },
1510
+ },
1511
+ readConfig: async () => ({ projects: [] }),
1512
+ getWorkspaceRouter: () => ({
1513
+ getThreadSessionKey: (threadId) => `session:${threadId}`,
1514
+ sendThreadMessage: async (threadId, content) => {
1515
+ sentMessages.push({ threadId, content });
1516
+ return { runId: 'run-1' };
1517
+ },
1518
+ }),
1519
+ eventBus: { emit: () => { }, on: () => () => { } },
1520
+ });
1521
+ const internals = gateway;
1522
+ internals.runtime.set('project-1::lark-1', {
1523
+ workspaceId: 'project-1',
1524
+ instanceId: 'lark-1',
1525
+ platformKey: 'lark:lark-1',
1526
+ enabled: true,
1527
+ status: 'running',
1528
+ connected: true,
1529
+ appId: 'app-1',
1530
+ client: {
1531
+ im: {
1532
+ messageReaction: {
1533
+ create: async () => ({ data: { reaction_id: 'reaction-1' } }),
1534
+ },
1535
+ },
1536
+ },
1537
+ });
1538
+ await internals.handleMessageEvent('project-1', 'lark-1', 'lark:lark-1', {
1539
+ event: {
1540
+ sender: {
1541
+ sender_id: { user_id: 'user-1' },
1542
+ },
1543
+ message: {
1544
+ message_id: 'msg-post-numbered-list',
1545
+ message_type: 'post',
1546
+ chat_id: 'chat-1',
1547
+ chat_type: 'p2p',
1548
+ content: JSON.stringify({
1549
+ title: '',
1550
+ content: [
1551
+ [
1552
+ { tag: 'text', text: '1. hi' },
1553
+ ],
1554
+ [
1555
+ { tag: 'text', text: '2. hello' },
1556
+ ],
1557
+ ],
1558
+ }),
1559
+ },
1560
+ },
1561
+ });
1562
+ strict_1.default.equal(sentMessages.length, 1);
1563
+ strict_1.default.equal(sentMessages[0]?.threadId, 'thread-1');
1564
+ strict_1.default.match(sentMessages[0]?.content, /\[User Message\]\n1\. hi\n2\. hello\n\[\/User Message\]/);
1565
+ });
1427
1566
  (0, node_test_1.default)('lark inbound messages create a chat binding when an authorized user has an old direct thread', async () => {
1428
1567
  const bindings = [];
1429
1568
  const createdThreads = [];