@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
@@ -83,6 +83,49 @@ function plugin(id, dependsOn = []) {
83
83
  },
84
84
  };
85
85
  }
86
+ async function invokeServer(server, method, url, inputBody) {
87
+ const requestBody = inputBody === undefined ? '' : JSON.stringify(inputBody);
88
+ const req = Object.assign(new node_events_1.EventEmitter(), {
89
+ method,
90
+ url,
91
+ headers: {},
92
+ async *[Symbol.asyncIterator]() {
93
+ if (requestBody) {
94
+ yield Buffer.from(requestBody);
95
+ }
96
+ },
97
+ });
98
+ let body = '';
99
+ const headers = new Map();
100
+ const res = Object.assign(new node_events_1.EventEmitter(), {
101
+ statusCode: 200,
102
+ setHeader(name, value) {
103
+ headers.set(name, value);
104
+ },
105
+ writeHead(statusCode, nextHeaders) {
106
+ this.statusCode = statusCode;
107
+ for (const [name, value] of Object.entries(nextHeaders)) {
108
+ headers.set(name, value);
109
+ }
110
+ },
111
+ write(chunk) {
112
+ body += chunk;
113
+ return true;
114
+ },
115
+ end(chunk) {
116
+ if (chunk) {
117
+ body += chunk;
118
+ }
119
+ res.emit('finish');
120
+ },
121
+ });
122
+ await server.handleRequest(req, res);
123
+ return {
124
+ statusCode: res.statusCode,
125
+ headers,
126
+ body: body ? JSON.parse(body) : null,
127
+ };
128
+ }
86
129
  (0, node_test_1.default)('bootstrapLocalCoreKernel exposes the static built-in capability snapshot', () => {
87
130
  const kernel = (0, bootstrap_js_1.bootstrapLocalCoreKernel)();
88
131
  strict_1.default.deepEqual(kernel.getCapabilitySnapshot(), {
@@ -464,6 +507,45 @@ model = "deepseek-v4-flash"
464
507
  (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
465
508
  }
466
509
  });
510
+ (0, node_test_1.default)('pi agent runtime normalizes DeepSeek provider when provider name is the model id', async () => {
511
+ const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-kernel-'));
512
+ try {
513
+ const runtime = (0, bootstrap_js_2.bootstrapLocalCoreRuntime)({
514
+ userDataPath,
515
+ enableKnowledge: false,
516
+ });
517
+ await runtime.state.saveRawConfigFile(`
518
+ [[projects]]
519
+ name = "deepseek-model-name-workspace"
520
+
521
+ [projects.agent]
522
+ type = "pi"
523
+
524
+ [[projects.agent.providers]]
525
+ name = "deepseek-v4-flash"
526
+ api_key = "test-deepseek-key"
527
+ base_url = "https://api.deepseek.com"
528
+ model = "deepseek-v4-flash"
529
+ `);
530
+ const configState = await runtime.state.readConfigFile();
531
+ const project = configState.parsed?.projects?.find((entry) => entry.name === 'deepseek-model-name-workspace');
532
+ const piRuntime = runtime.agentRuntimes.find((entry) => entry.agentType === 'pi');
533
+ const route = project ? piRuntime?.createRoute(configState, project) : null;
534
+ const piAgentDir = route?.config.env.PI_CODING_AGENT_DIR || '';
535
+ strict_1.default.equal(route?.config.env.DEEPSEEK_API_KEY, 'test-deepseek-key');
536
+ strict_1.default.deepEqual(JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(piAgentDir, 'auth.json'), 'utf8')), {
537
+ deepseek: { type: 'api_key', key: 'test-deepseek-key' },
538
+ });
539
+ strict_1.default.deepEqual(JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(piAgentDir, 'settings.json'), 'utf8')), {
540
+ defaultProvider: 'deepseek',
541
+ defaultModel: 'deepseek-v4-flash',
542
+ quietStartup: true,
543
+ });
544
+ }
545
+ finally {
546
+ (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
547
+ }
548
+ });
467
549
  (0, node_test_1.default)('runtime bootstrap knowledge capabilities come from the selected provider plugin', () => {
468
550
  const enabledUserDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-kernel-'));
469
551
  const disabledUserDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-kernel-'));
@@ -870,24 +952,149 @@ model = "deepseek-v4-flash"
870
952
  },
871
953
  });
872
954
  const server = new server_js_1.LocalAiCoreServer(bindings, { port: 0 });
873
- await server.start();
874
- try {
875
- const address = server.server.address();
876
- const baseUrl = `http://127.0.0.1:${address.port}/api/local/v1`;
877
- const qrResponse = await fetch(`${baseUrl}/platforms/slack/workspace-1/qrcode?instance_id=bot-1`, { method: 'POST' });
878
- const qrBody = await qrResponse.json();
879
- strict_1.default.equal(qrBody.data.ticket, 'ticket-1');
880
- const statusResponse = await fetch(`${baseUrl}/platforms/slack/workspace-1/qrcode/status?ticket=ticket-1&instance_id=bot-1`);
881
- const statusBody = await statusResponse.json();
882
- strict_1.default.equal(statusBody.data.status, 'signed');
883
- strict_1.default.deepEqual(calls, [
884
- 'get:slack:workspace-1:bot-1',
885
- 'check:slack:workspace-1:ticket-1:bot-1',
886
- ]);
887
- }
888
- finally {
889
- await server.stop();
890
- }
955
+ const qrResponse = await invokeServer(server, 'POST', '/api/local/v1/platforms/slack/workspace-1/qrcode?instance_id=bot-1');
956
+ strict_1.default.equal(qrResponse.body.data.ticket, 'ticket-1');
957
+ const statusResponse = await invokeServer(server, 'GET', '/api/local/v1/platforms/slack/workspace-1/qrcode/status?ticket=ticket-1&instance_id=bot-1');
958
+ strict_1.default.equal(statusResponse.body.data.status, 'signed');
959
+ strict_1.default.deepEqual(calls, [
960
+ 'get:slack:workspace-1:bot-1',
961
+ 'check:slack:workspace-1:ticket-1:bot-1',
962
+ ]);
963
+ });
964
+ (0, node_test_1.default)('server unknown routes return structured error responses', async () => {
965
+ const bindings = new node_events_1.EventEmitter();
966
+ const server = new server_js_1.LocalAiCoreServer(bindings, { port: 0 });
967
+ const response = await invokeServer(server, 'GET', '/api/local/v1/not-found');
968
+ strict_1.default.equal(response.statusCode, 404);
969
+ strict_1.default.equal(response.body.ok, false);
970
+ strict_1.default.equal(response.body.code, 'internal_error');
971
+ strict_1.default.match(response.body.error, /Unknown route/);
972
+ strict_1.default.equal(response.body.errorInfo?.severity, 'error');
973
+ });
974
+ (0, node_test_1.default)('server diagnostics routes dispatch through generic bindings', async () => {
975
+ const calls = [];
976
+ const bindings = Object.assign(new node_events_1.EventEmitter(), {
977
+ listDiagnosticErrors: async () => {
978
+ calls.push('errors');
979
+ return [{
980
+ key: 'channel.weixin:channel_session_expired:workspace-1',
981
+ count: 2,
982
+ firstSeenAt: '2026-05-12T00:00:00.000Z',
983
+ lastSeenAt: '2026-05-12T00:05:00.000Z',
984
+ errorInfo: {
985
+ code: 'channel_session_expired',
986
+ message: 'WeChat login expired.',
987
+ userMessage: 'Channel login has expired.',
988
+ severity: 'error',
989
+ retryable: false,
990
+ suggestedAction: 'Reconnect the channel.',
991
+ },
992
+ }];
993
+ },
994
+ runDiagnosticsDoctor: async () => {
995
+ calls.push('doctor');
996
+ return {
997
+ status: 'warn',
998
+ checkedAt: '2026-05-12T00:00:00.000Z',
999
+ checks: [
1000
+ { id: 'config', label: 'Configuration', status: 'pass', summary: 'ok' },
1001
+ ],
1002
+ };
1003
+ },
1004
+ runDeploymentDiagnostics: async () => {
1005
+ calls.push('deployment');
1006
+ return {
1007
+ status: 'pass',
1008
+ checkedAt: '2026-05-14T00:00:00.000Z',
1009
+ checks: [
1010
+ { id: 'deployment.profile', label: 'Deployment profile', status: 'pass', summary: 'ok' },
1011
+ ],
1012
+ };
1013
+ },
1014
+ });
1015
+ const server = new server_js_1.LocalAiCoreServer(bindings, { port: 0 });
1016
+ const errorsResponse = await invokeServer(server, 'GET', '/api/local/v1/diagnostics/errors');
1017
+ const doctorResponse = await invokeServer(server, 'POST', '/api/local/v1/diagnostics/doctor');
1018
+ const deploymentResponse = await invokeServer(server, 'POST', '/api/local/v1/diagnostics/deployment');
1019
+ strict_1.default.equal(errorsResponse.body.data.errors[0].errorInfo.code, 'channel_session_expired');
1020
+ strict_1.default.equal(doctorResponse.body.data.status, 'warn');
1021
+ strict_1.default.equal(deploymentResponse.body.data.status, 'pass');
1022
+ strict_1.default.deepEqual(calls, ['errors', 'doctor', 'deployment']);
1023
+ });
1024
+ (0, node_test_1.default)('server OpenAI-compatible chat route maps metadata to sandbox yolo external run', async () => {
1025
+ let externalInput;
1026
+ const bindings = Object.assign(new node_events_1.EventEmitter(), {
1027
+ createExternalRun: async (input) => {
1028
+ externalInput = input;
1029
+ return {
1030
+ project: {},
1031
+ thread: {},
1032
+ workspace_id: 'external-user-project',
1033
+ thread_id: 'thread-1',
1034
+ run_id: 'run-1',
1035
+ task_id: 'task-1',
1036
+ events_url: '/api/local/v1/external/runs/run-1/events',
1037
+ };
1038
+ },
1039
+ getExternalRunSnapshot: async () => ({
1040
+ runId: 'run-1',
1041
+ task: {
1042
+ taskId: 'task-1',
1043
+ workspaceId: 'external-user-project',
1044
+ runtimeId: 'pi',
1045
+ threadId: 'thread-1',
1046
+ runId: 'run-1',
1047
+ title: 'Task',
1048
+ status: 'completed',
1049
+ createdAt: '2026-05-15T00:00:00.000Z',
1050
+ updatedAt: '2026-05-15T00:00:02.000Z',
1051
+ startedAt: '2026-05-15T00:00:01.000Z',
1052
+ timeline: [],
1053
+ logs: [],
1054
+ artifacts: [],
1055
+ approvalIds: [],
1056
+ },
1057
+ thread: {
1058
+ id: 'thread-1',
1059
+ workspaceId: 'external-user-project',
1060
+ title: 'Thread',
1061
+ createdAt: '2026-05-15T00:00:00.000Z',
1062
+ updatedAt: '2026-05-15T00:00:02.000Z',
1063
+ messages: [{
1064
+ id: 'message-1',
1065
+ role: 'assistant',
1066
+ content: 'final answer',
1067
+ timestamp: '2026-05-15T00:00:02.000Z',
1068
+ kind: 'final',
1069
+ }],
1070
+ },
1071
+ }),
1072
+ });
1073
+ const server = new server_js_1.LocalAiCoreServer(bindings, { port: 0 });
1074
+ const response = await invokeServer(server, 'POST', '/api/local/v1/openai/chat/completions', {
1075
+ model: 'model-1',
1076
+ stream: false,
1077
+ metadata: {
1078
+ user_id: 'user-1',
1079
+ project_id: 'project-1',
1080
+ thread_id: 'thread-1',
1081
+ agent_type: 'pi',
1082
+ },
1083
+ messages: [
1084
+ { role: 'system', content: 'Be concise.' },
1085
+ { role: 'user', content: 'Hello' },
1086
+ ],
1087
+ });
1088
+ strict_1.default.equal(response.statusCode, 200);
1089
+ strict_1.default.equal(response.body.object, 'chat.completion');
1090
+ strict_1.default.equal(response.body.choices[0].message.content, 'final answer');
1091
+ strict_1.default.equal(externalInput.user_id, 'user-1');
1092
+ strict_1.default.equal(externalInput.external_project_id, 'project-1');
1093
+ strict_1.default.equal(externalInput.external_thread_id, 'thread-1');
1094
+ strict_1.default.equal(externalInput.permission_mode, 'bypassPermissions');
1095
+ strict_1.default.equal(externalInput.runtime_env.AGENTDOCK_OPENAI_COMPAT, '1');
1096
+ strict_1.default.match(externalInput.prompt, /\[system\]\nBe concise\./);
1097
+ strict_1.default.match(externalInput.prompt, /Hello/);
891
1098
  });
892
1099
  (0, node_test_1.default)('channel and scheduler capabilities use registry targets instead of Lark-specific routing', () => {
893
1100
  const registry = new plugin_registry_js_1.LocalCorePluginRegistry();
@@ -50,6 +50,26 @@ const runtime_detection_service_js_1 = require("../../services/local-ai-core/src
50
50
  (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
51
51
  }
52
52
  });
53
+ (0, node_test_1.default)('runtime detection service does not probe runtimes on startup', async () => {
54
+ const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'runtime-detection-service-startup-'));
55
+ try {
56
+ let calls = 0;
57
+ const service = new runtime_detection_service_js_1.RuntimeDetectionService({
58
+ userDataPath,
59
+ readConfig: async () => null,
60
+ detect: () => {
61
+ calls += 1;
62
+ return [runtimeResult({ version: '9.9.9' })];
63
+ },
64
+ });
65
+ await service.refreshOnStartup();
66
+ strict_1.default.equal(calls, 0);
67
+ strict_1.default.equal(service.list('opencode')[0]?.status, 'unknown');
68
+ }
69
+ finally {
70
+ (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
71
+ }
72
+ });
53
73
  (0, node_test_1.default)('runtime detection service ignores corrupted persisted state', () => {
54
74
  const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'runtime-detection-service-corrupt-'));
55
75
  try {
@@ -71,30 +91,59 @@ const runtime_detection_service_js_1 = require("../../services/local-ai-core/src
71
91
  const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'runtime-detection-service-events-'));
72
92
  try {
73
93
  const events = [];
94
+ const runtimeIds = [];
74
95
  const service = new runtime_detection_service_js_1.RuntimeDetectionService({
75
96
  userDataPath,
76
97
  readConfig: async () => null,
77
- detect: () => [
78
- runtimeResult({ runtimeId: 'opencode', agentType: 'opencode' }),
79
- runtimeResult({ runtimeId: 'codex', agentType: 'codex' }),
80
- ],
98
+ detect: (options) => {
99
+ runtimeIds.push(options.runtimeId);
100
+ return [runtimeResult({ runtimeId: 'codex', agentType: 'codex' })];
101
+ },
81
102
  emit: (event) => {
82
103
  events.push(event.type);
83
104
  },
84
105
  });
85
106
  const refreshed = await service.refresh('codex');
86
107
  strict_1.default.deepEqual(refreshed.map((runtime) => runtime.runtimeId), ['codex']);
108
+ strict_1.default.deepEqual(runtimeIds, ['codex']);
109
+ strict_1.default.equal(service.list('opencode')[0]?.status, 'unknown');
110
+ strict_1.default.equal(service.list('codex')[0]?.status, 'installed');
87
111
  strict_1.default.deepEqual(events, [
88
112
  'runtime.detect.started',
89
113
  'runtime.detect.completed',
90
114
  'runtime.status.changed',
91
- 'runtime.status.changed',
92
115
  ]);
93
116
  }
94
117
  finally {
95
118
  (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
96
119
  }
97
120
  });
121
+ (0, node_test_1.default)('runtime detection service records runtime launch errors as failed readiness', () => {
122
+ const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'runtime-detection-service-launch-error-'));
123
+ try {
124
+ const service = new runtime_detection_service_js_1.RuntimeDetectionService({
125
+ userDataPath,
126
+ readConfig: async () => null,
127
+ detect: () => [runtimeResult({ runtimeId: 'hermes', agentType: 'hermes', displayName: 'Hermes' })],
128
+ });
129
+ service.recordLaunchError('hermes', {
130
+ code: 'runtime_not_found',
131
+ message: 'ACP agent command not found: hermes',
132
+ userMessage: 'Hermes is not installed or is not available on PATH.',
133
+ severity: 'error',
134
+ retryable: false,
135
+ suggestedAction: 'Install Hermes or update the service PATH.',
136
+ });
137
+ const runtime = service.list('hermes')[0];
138
+ strict_1.default.equal(runtime?.status, 'error');
139
+ strict_1.default.equal(runtime?.readiness, 'failed');
140
+ strict_1.default.equal(runtime?.lastLaunchError?.code, 'runtime_not_found');
141
+ strict_1.default.equal(runtime?.recommendedActions[0]?.label, 'Fix runtime launch');
142
+ }
143
+ finally {
144
+ (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
145
+ }
146
+ });
98
147
  function runtimeResult(input = {}) {
99
148
  return {
100
149
  agentType: input.agentType || 'opencode',
@@ -112,5 +161,8 @@ function runtimeResult(input = {}) {
112
161
  recommendedActions: input.recommendedActions || [],
113
162
  source: input.source || 'path',
114
163
  error: input.error,
164
+ readiness: input.readiness,
165
+ lastLaunchError: input.lastLaunchError,
166
+ lastCheckedAt: input.lastCheckedAt,
115
167
  };
116
168
  }