@kafca/agentdock 0.1.46 → 0.1.48

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 (103) hide show
  1. package/README.md +11 -0
  2. package/dist/renderer/assets/{Badge-BIesaDYG.js → Badge-C92lMj9Q.js} +1 -1
  3. package/dist/renderer/assets/{Chat-CpataoDh.js → Chat-DqoKCPwz.js} +1 -1
  4. package/dist/renderer/assets/{ChatMarkdown-_Mdmltgv.js → ChatMarkdown-C4psQvmt.js} +1 -1
  5. package/dist/renderer/assets/{Config-Pe5-zKZk.js → Config-1uk8FQL7.js} +2 -2
  6. package/dist/renderer/assets/{CronList-BnaOSPT8.js → CronList-DnKCiYWC.js} +1 -1
  7. package/dist/renderer/assets/{Dashboard-D05l8GKE.js → Dashboard-rOAQcJA0.js} +2 -2
  8. package/dist/renderer/assets/{EmptyState-BDBavDll.js → EmptyState-bF6Sc61O.js} +1 -1
  9. package/dist/renderer/assets/{KnowledgeDetail-DvR798l_.js → KnowledgeDetail-C5-Dp95R.js} +2 -2
  10. package/dist/renderer/assets/{KnowledgeHome-r2ZuvU8-.js → KnowledgeHome-DGa1EWXD.js} +2 -2
  11. package/dist/renderer/assets/{Logs-BaljtmAV.js → Logs-CKKVW2k8.js} +1 -1
  12. package/dist/renderer/assets/{Modal-CXhD_OWR.js → Modal-DIc9HxD5.js} +3 -3
  13. package/dist/renderer/assets/{MonitorList-D7tyipbx.js → MonitorList-D2YOJRIX.js} +1 -1
  14. package/dist/renderer/assets/{Page-KUuFYgjg.js → Page-vZ-_l6Kn.js} +1 -1
  15. package/dist/renderer/assets/ProjectDetail-CqTa6kAh.js +11 -0
  16. package/dist/renderer/assets/{ProjectList-CbdHd5Ev.js → ProjectList-LgDQVVyZ.js} +1 -1
  17. package/dist/renderer/assets/{Select-Djc8BXMC.js → Select-BN2HMOmY.js} +1 -1
  18. package/dist/renderer/assets/SessionChat-Beco1N1T.js +1 -0
  19. package/dist/renderer/assets/{SessionList-DHgl1s1D.js → SessionList-3NkskCJJ.js} +1 -1
  20. package/dist/renderer/assets/ThreadChat-Ji8pOm5p.js +28 -0
  21. package/dist/renderer/assets/{Workspace-DTocqY-F.js → Workspace-yarrGItB.js} +1 -1
  22. package/dist/renderer/assets/{arrow-left-CXyya3hP.js → arrow-left-DNovvfPp.js} +1 -1
  23. package/dist/renderer/assets/{book-open-9n4Kxc7E.js → book-open-CarZFsXv.js} +1 -1
  24. package/dist/renderer/assets/{bot-C9HBk5XH.js → bot-g16rGMp6.js} +1 -1
  25. package/dist/renderer/assets/{check-CutSg9a5.js → check-DjFX73Zi.js} +1 -1
  26. package/dist/renderer/assets/{chevron-down-DrydR4i3.js → chevron-down-B02YlQS_.js} +1 -1
  27. package/dist/renderer/assets/{circle-BEjtahw6.js → circle-DPfWdG1F.js} +1 -1
  28. package/dist/renderer/assets/{desktop-DATgk_ka.js → desktop-DMkue1T6.js} +1 -1
  29. package/dist/renderer/assets/{funnel-BhqZpS4f.js → funnel-Dy1VL-D9.js} +1 -1
  30. package/dist/renderer/assets/{heart-Dp8Vs-7_.js → heart-YjvGwq5-.js} +1 -1
  31. package/dist/renderer/assets/{index-BKpv0P0s.js → index-CZN1STMc.js} +2 -2
  32. package/dist/renderer/assets/index-CjCuR6DI.css +11 -0
  33. package/dist/renderer/assets/{index-cI3pWBnN.js → index-mvjDN9uV.js} +9 -9
  34. package/dist/renderer/assets/{pencil-ywcJzeOo.js → pencil-qpMjLIFd.js} +1 -1
  35. package/dist/renderer/assets/{play-AtN492Cx.js → play-CPCcNQcH.js} +1 -1
  36. package/dist/renderer/assets/{plus-BEj14BBV.js → plus-B76JWGKd.js} +1 -1
  37. package/dist/renderer/assets/projects-C1nFINjh.js +1 -0
  38. package/dist/renderer/assets/{save-D6ov4CNv.js → save-DtvrD3W1.js} +1 -1
  39. package/dist/renderer/assets/{search-C3bvw7AR.js → search-Ddth-YHH.js} +1 -1
  40. package/dist/renderer/assets/{server-C1Uyedyx.js → server-D0zmghb3.js} +1 -1
  41. package/dist/renderer/assets/{session-utils-DpRwJ6Nh.js → session-utils-C9oe-21F.js} +1 -1
  42. package/dist/renderer/assets/{sessions-BWmAppwK.js → sessions-BXOnLLah.js} +1 -1
  43. package/dist/renderer/assets/{shield-check-CVx1mEs2.js → shield-check-Cv5d_blH.js} +1 -1
  44. package/dist/renderer/assets/{trash-2-o-heXOgs.js → trash-2-C2mimX2b.js} +1 -1
  45. package/dist/renderer/assets/{user-dPOzUqIc.js → user-BrLJ4elW.js} +1 -1
  46. package/dist/renderer/assets/{wifi-off-Fz5c3_x_.js → wifi-off-DvRJYLbG.js} +1 -1
  47. package/dist/renderer/index.html +2 -2
  48. package/dist-electron/packages/core-sdk/src/index.js +4 -0
  49. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-session-coordinator.js +53 -7
  50. package/dist-electron/services/local-ai-core/src/acp/store/local-core-acp-store.js +3 -0
  51. package/dist-electron/services/local-ai-core/src/acp/store/platform-store.js +13 -5
  52. package/dist-electron/services/local-ai-core/src/acp/store/schema.js +1 -0
  53. package/dist-electron/services/local-ai-core/src/acp/store/thread-store.js +3 -0
  54. package/dist-electron/services/local-ai-core/src/agents/pi/launch.js +1 -1
  55. package/dist-electron/services/local-ai-core/src/agents/shared/launch-utils.js +22 -0
  56. package/dist-electron/services/local-ai-core/src/channel/lark/local-core-lark-gateway.js +132 -348
  57. package/dist-electron/services/local-ai-core/src/channel/shared/base-channel-gateway.js +306 -0
  58. package/dist-electron/services/local-ai-core/src/channel/shared/session-command-runtime.js +14 -3
  59. package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js +131 -382
  60. package/dist-electron/services/local-ai-core/src/router/workspace-router.js +8 -1
  61. package/dist-electron/services/local-ai-core/src/runtime/agent-runtime-detector.js +2 -1
  62. package/dist-electron/services/local-ai-core/src/runtime/channel-service.js +150 -0
  63. package/dist-electron/services/local-ai-core/src/runtime/env-utils.js +12 -0
  64. package/dist-electron/services/local-ai-core/src/runtime/external-service.js +234 -0
  65. package/dist-electron/services/local-ai-core/src/runtime/handlers/automation-handler.js +34 -0
  66. package/dist-electron/services/local-ai-core/src/runtime/handlers/capabilities-handler.js +12 -0
  67. package/dist-electron/services/local-ai-core/src/runtime/handlers/channel-handler.js +65 -0
  68. package/dist-electron/services/local-ai-core/src/runtime/handlers/external-handler.js +17 -0
  69. package/dist-electron/services/local-ai-core/src/runtime/handlers/knowledge-handler.js +65 -0
  70. package/dist-electron/services/local-ai-core/src/runtime/handlers/openai-handler.js +496 -0
  71. package/dist-electron/services/local-ai-core/src/runtime/handlers/provider-handler.js +24 -0
  72. package/dist-electron/services/local-ai-core/src/runtime/handlers/runtime-handler.js +68 -0
  73. package/dist-electron/services/local-ai-core/src/runtime/handlers/runtimes-handler.js +37 -0
  74. package/dist-electron/services/local-ai-core/src/runtime/handlers/scheduler-handler.js +34 -0
  75. package/dist-electron/services/local-ai-core/src/runtime/handlers/security-handler.js +49 -0
  76. package/dist-electron/services/local-ai-core/src/runtime/handlers/task-handler.js +27 -0
  77. package/dist-electron/services/local-ai-core/src/runtime/handlers/thread-handler.js +46 -0
  78. package/dist-electron/services/local-ai-core/src/runtime/handlers/workspace-handler.js +29 -0
  79. package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +40 -592
  80. package/dist-electron/services/local-ai-core/src/runtime/server-helpers.js +96 -0
  81. package/dist-electron/services/local-ai-core/src/runtime/server-routes.js +3 -0
  82. package/dist-electron/services/local-ai-core/src/runtime/server.js +107 -1084
  83. package/dist-electron/services/local-ai-core/src/runtime/standalone.js +13 -1
  84. package/dist-electron/services/local-ai-core/src/thread/thread-command-service.js +23 -2
  85. package/dist-electron/services/local-ai-core/src/thread/thread-slash-command-dispatcher.js +1 -0
  86. package/dist-electron/src/pages/Projects/project-provider-config.js +75 -0
  87. package/dist-electron/src/pages/Threads/thread-chat-model.js +2 -0
  88. package/dist-electron/tests/contracts/architecture-docs.test.js +1 -0
  89. package/dist-electron/tests/electron/agent-runtime-detector.test.js +18 -0
  90. package/dist-electron/tests/electron/knowledge-skill-script.test.js +15 -5
  91. package/dist-electron/tests/electron/lac-cli.test.js +591 -688
  92. package/dist-electron/tests/electron/plugin-kernel.test.js +47 -31
  93. package/dist-electron/tests/electron/project-provider-config.test.js +71 -0
  94. package/dist-electron/tests/electron/workspace-task-store.test.js +40 -11
  95. package/dist-electron/tests/integration/local-core-cors.test.js +22 -0
  96. package/dist-electron/tests/integration/local-core-refactor.test.js +177 -0
  97. package/dist-electron/tests/integration/local-core-routes.test.js +45 -0
  98. package/package.json +2 -2
  99. package/dist/renderer/assets/ProjectDetail-Ddq94-yu.js +0 -11
  100. package/dist/renderer/assets/SessionChat-DOy0iKQR.js +0 -1
  101. package/dist/renderer/assets/ThreadChat-I7dYibDc.js +0 -23
  102. package/dist/renderer/assets/index-CO4fGqmv.css +0 -11
  103. package/dist/renderer/assets/projects-Clar8lgb.js +0 -1
@@ -437,7 +437,12 @@ OPENAI_API_KEY = "override-openai-key"
437
437
  strict_1.default.equal(route?.agentType, 'pi');
438
438
  strict_1.default.equal(route?.config.command, process.execPath);
439
439
  strict_1.default.match(route?.config.args[0] || '', /pi-acp[/\\]dist[/\\]index\.js$/);
440
- strict_1.default.match(route?.config.env.PI_ACP_PI_COMMAND || '', /@mariozechner[/\\]pi-coding-agent[/\\]dist[/\\]cli\.js$/);
440
+ if (process.platform === 'win32') {
441
+ strict_1.default.match(route?.config.env.PI_ACP_PI_COMMAND || '', /[\\/]node_modules[\\/]\.bin[\\/]pi\.CMD$/i);
442
+ }
443
+ else {
444
+ strict_1.default.match(route?.config.env.PI_ACP_PI_COMMAND || '', /@mariozechner[/\\]pi-coding-agent[/\\]dist[/\\]cli\.js$/);
445
+ }
441
446
  strict_1.default.equal(route?.config.env.OPENAI_API_KEY, 'override-openai-key');
442
447
  strict_1.default.match(route?.config.env.PI_CODING_AGENT_DIR || '', /[\\/]\.pi-agent[\\/]pi-workspace$/);
443
448
  strict_1.default.deepEqual(await runtime.workspaceRouter.listWorkspaces(), [
@@ -735,14 +740,14 @@ model = "deepseek-v4-flash"
735
740
  });
736
741
  await controller.init();
737
742
  strict_1.default.equal(started, true);
738
- strict_1.default.deepEqual(await controller.getCapabilities(), capabilitySnapshot);
739
- strict_1.default.deepEqual(await controller.getChannelQrCode('test-channel', 'workspace-1', 'instance-1'), {
743
+ strict_1.default.deepEqual(await controller.kernel.getCapabilitySnapshot(), capabilitySnapshot);
744
+ strict_1.default.deepEqual(await controller.channelService.getQrCode('test-channel', 'workspace-1', 'instance-1'), {
740
745
  ticket: 'ticket:workspace-1',
741
746
  expiresIn: 60,
742
747
  qrCodeUrl: 'https://example.test/qr',
743
748
  instanceId: 'instance-1',
744
749
  });
745
- strict_1.default.deepEqual(await controller.checkChannelQrCodeStatus('test-channel', 'workspace-1', 'signed-ticket'), {
750
+ strict_1.default.deepEqual(await controller.channelService.checkQrCodeStatus('test-channel', 'workspace-1', 'signed-ticket'), {
746
751
  status: 'signed',
747
752
  });
748
753
  await controller.saveStructuredConfigFile({ projects: [] });
@@ -936,8 +941,12 @@ model = "deepseek-v4-flash"
936
941
  });
937
942
  (0, node_test_1.default)('server QR routes dispatch through generic channel bindings', async () => {
938
943
  const calls = [];
939
- const bindings = Object.assign(new node_events_1.EventEmitter(), {
940
- getChannelQrCode: async (platform, workspaceId, instanceId) => {
944
+ const controller = Object.assign(new node_events_1.EventEmitter(), {
945
+ getChannelQrCode: undefined,
946
+ checkChannelQrCodeStatus: undefined,
947
+ });
948
+ const channelService = {
949
+ getQrCode: async (platform, workspaceId, instanceId) => {
941
950
  calls.push(`get:${platform}:${workspaceId}:${instanceId || ''}`);
942
951
  return {
943
952
  ticket: 'ticket-1',
@@ -946,11 +955,12 @@ model = "deepseek-v4-flash"
946
955
  instanceId,
947
956
  };
948
957
  },
949
- checkChannelQrCodeStatus: async (platform, workspaceId, ticket, instanceId) => {
958
+ checkQrCodeStatus: async (platform, workspaceId, ticket, instanceId) => {
950
959
  calls.push(`check:${platform}:${workspaceId}:${ticket}:${instanceId || ''}`);
951
960
  return { status: 'signed' };
952
961
  },
953
- });
962
+ };
963
+ const bindings = { controller, channelService };
954
964
  const server = new server_js_1.LocalAiCoreServer(bindings, { port: 0 });
955
965
  const qrResponse = await invokeServer(server, 'POST', '/api/local/v1/platforms/slack/workspace-1/qrcode?instance_id=bot-1');
956
966
  strict_1.default.equal(qrResponse.body.data.ticket, 'ticket-1');
@@ -962,7 +972,8 @@ model = "deepseek-v4-flash"
962
972
  ]);
963
973
  });
964
974
  (0, node_test_1.default)('server unknown routes return structured error responses', async () => {
965
- const bindings = new node_events_1.EventEmitter();
975
+ const controller = new node_events_1.EventEmitter();
976
+ const bindings = { controller };
966
977
  const server = new server_js_1.LocalAiCoreServer(bindings, { port: 0 });
967
978
  const response = await invokeServer(server, 'GET', '/api/local/v1/not-found');
968
979
  strict_1.default.equal(response.statusCode, 404);
@@ -973,24 +984,7 @@ model = "deepseek-v4-flash"
973
984
  });
974
985
  (0, node_test_1.default)('server diagnostics routes dispatch through generic bindings', async () => {
975
986
  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
- },
987
+ const controller = Object.assign(new node_events_1.EventEmitter(), {
994
988
  runDiagnosticsDoctor: async () => {
995
989
  calls.push('doctor');
996
990
  return {
@@ -1012,6 +1006,26 @@ model = "deepseek-v4-flash"
1012
1006
  };
1013
1007
  },
1014
1008
  });
1009
+ const errorReporter = {
1010
+ list: async () => {
1011
+ calls.push('errors');
1012
+ return [{
1013
+ key: 'channel.weixin:channel_session_expired:workspace-1',
1014
+ count: 2,
1015
+ firstSeenAt: '2026-05-12T00:00:00.000Z',
1016
+ lastSeenAt: '2026-05-12T00:05:00.000Z',
1017
+ errorInfo: {
1018
+ code: 'channel_session_expired',
1019
+ message: 'WeChat login expired.',
1020
+ userMessage: 'Channel login has expired.',
1021
+ severity: 'error',
1022
+ retryable: false,
1023
+ suggestedAction: 'Reconnect the channel.',
1024
+ },
1025
+ }];
1026
+ },
1027
+ };
1028
+ const bindings = { controller, errorReporter };
1015
1029
  const server = new server_js_1.LocalAiCoreServer(bindings, { port: 0 });
1016
1030
  const errorsResponse = await invokeServer(server, 'GET', '/api/local/v1/diagnostics/errors');
1017
1031
  const doctorResponse = await invokeServer(server, 'POST', '/api/local/v1/diagnostics/doctor');
@@ -1023,8 +1037,9 @@ model = "deepseek-v4-flash"
1023
1037
  });
1024
1038
  (0, node_test_1.default)('server OpenAI-compatible chat route maps metadata to sandbox yolo external run', async () => {
1025
1039
  let externalInput;
1026
- const bindings = Object.assign(new node_events_1.EventEmitter(), {
1027
- createExternalRun: async (input) => {
1040
+ const controller = new node_events_1.EventEmitter();
1041
+ const externalService = {
1042
+ createRun: async (input) => {
1028
1043
  externalInput = input;
1029
1044
  return {
1030
1045
  project: {},
@@ -1036,7 +1051,7 @@ model = "deepseek-v4-flash"
1036
1051
  events_url: '/api/local/v1/external/runs/run-1/events',
1037
1052
  };
1038
1053
  },
1039
- getExternalRunSnapshot: async () => ({
1054
+ getRunSnapshot: async () => ({
1040
1055
  runId: 'run-1',
1041
1056
  task: {
1042
1057
  taskId: 'task-1',
@@ -1069,7 +1084,8 @@ model = "deepseek-v4-flash"
1069
1084
  }],
1070
1085
  },
1071
1086
  }),
1072
- });
1087
+ };
1088
+ const bindings = { controller, externalService };
1073
1089
  const server = new server_js_1.LocalAiCoreServer(bindings, { port: 0 });
1074
1090
  const response = await invokeServer(server, 'POST', '/api/local/v1/openai/chat/completions', {
1075
1091
  model: 'model-1',
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const node_test_1 = __importDefault(require("node:test"));
7
+ const strict_1 = __importDefault(require("node:assert/strict"));
8
+ const project_provider_config_1 = require("../../src/pages/Projects/project-provider-config");
9
+ function createConfig() {
10
+ return {
11
+ projects: [{
12
+ name: 'agentdock',
13
+ agent: {
14
+ type: 'localcore-acp',
15
+ options: {
16
+ model: 'old-model',
17
+ provider_id: 'old-provider',
18
+ },
19
+ },
20
+ platforms: [],
21
+ }],
22
+ };
23
+ }
24
+ (0, node_test_1.default)('project provider selection updates agent provider_id without mutating the original config', () => {
25
+ const config = createConfig();
26
+ const next = (0, project_provider_config_1.selectProjectProvider)(config, 'agentdock', 'deepseek');
27
+ strict_1.default.equal((0, project_provider_config_1.getProjectProviderId)(next, 'agentdock'), 'deepseek');
28
+ strict_1.default.equal((0, project_provider_config_1.getProjectProviderId)(config, 'agentdock'), 'old-provider');
29
+ strict_1.default.equal(next.projects?.[0]?.agent.options?.model, 'old-model');
30
+ });
31
+ (0, node_test_1.default)('project model selection updates only the project model override', () => {
32
+ const config = createConfig();
33
+ const next = (0, project_provider_config_1.selectProjectModel)(config, 'agentdock', 'deepseek-chat');
34
+ strict_1.default.equal(next.projects?.[0]?.agent.options?.model, 'deepseek-chat');
35
+ strict_1.default.equal(next.projects?.[0]?.agent.options?.provider_id, 'old-provider');
36
+ strict_1.default.equal(config.projects?.[0]?.agent.options?.model, 'old-model');
37
+ });
38
+ (0, node_test_1.default)('project provider selection reports missing projects clearly', () => {
39
+ strict_1.default.throws(() => (0, project_provider_config_1.selectProjectProvider)(createConfig(), 'missing', 'deepseek'), /Project not found in config: missing/);
40
+ });
41
+ (0, node_test_1.default)('provider removal clears references from every project without mutating the original config', () => {
42
+ const config = createConfig();
43
+ config.projects?.push({
44
+ name: 'project-2',
45
+ agent: {
46
+ type: 'localcore-acp',
47
+ options: {
48
+ model: 'other-model',
49
+ provider_id: 'old-provider',
50
+ },
51
+ },
52
+ platforms: [],
53
+ });
54
+ config.projects?.push({
55
+ name: 'project-3',
56
+ agent: {
57
+ type: 'localcore-acp',
58
+ options: {
59
+ model: 'third-model',
60
+ provider_id: 'keep-provider',
61
+ },
62
+ },
63
+ platforms: [],
64
+ });
65
+ const next = (0, project_provider_config_1.removeProviderReferences)(config, 'old-provider');
66
+ strict_1.default.equal((0, project_provider_config_1.getProjectProviderId)(next, 'agentdock'), '');
67
+ strict_1.default.equal((0, project_provider_config_1.getProjectProviderId)(next, 'project-2'), '');
68
+ strict_1.default.equal((0, project_provider_config_1.getProjectProviderId)(next, 'project-3'), 'keep-provider');
69
+ strict_1.default.equal((0, project_provider_config_1.getProjectProviderId)(config, 'agentdock'), 'old-provider');
70
+ strict_1.default.equal((0, project_provider_config_1.getProjectProviderId)(config, 'project-2'), 'old-provider');
71
+ });
@@ -16,6 +16,7 @@ const scheduler_service_js_1 = require("../../services/local-ai-core/src/schedul
16
16
  const lark_schedule_adapter_js_1 = require("../../services/local-ai-core/src/scheduler/lark-schedule-adapter.js");
17
17
  const bootstrap_js_1 = require("../../services/local-ai-core/src/kernel/bootstrap.js");
18
18
  const local_core_controller_js_1 = require("../../services/local-ai-core/src/runtime/local-core-controller.js");
19
+ const env_utils_js_1 = require("../../services/local-ai-core/src/runtime/env-utils.js");
19
20
  (0, node_test_1.default)('workspace registry entries persist in LocalCoreAcpStore', () => {
20
21
  const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'workspace-registry-'));
21
22
  try {
@@ -130,9 +131,9 @@ const local_core_controller_js_1 = require("../../services/local-ai-core/src/run
130
131
  const config = await controller.readConfigFile();
131
132
  strict_1.default.equal(config.parsed?.projects?.[0]?.agent.options?.provider_id, 'deepseek');
132
133
  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');
134
+ const providers = controller.store.listModelProviders();
135
+ strict_1.default.equal(providers[0]?.id, 'deepseek');
136
+ strict_1.default.equal(providers[0]?.name, 'deepseek');
136
137
  await controller.close();
137
138
  }
138
139
  finally {
@@ -148,7 +149,7 @@ const local_core_controller_js_1 = require("../../services/local-ai-core/src/run
148
149
  log: () => { },
149
150
  });
150
151
  const controller = new local_core_controller_js_1.LocalCoreController(userDataPath, runtime);
151
- const provider = await controller.createModelProvider({
152
+ const provider = controller.store.upsertModelProvider({
152
153
  name: 'deepseek',
153
154
  api_key: 'secret',
154
155
  base_url: 'https://api.deepseek.com',
@@ -167,7 +168,7 @@ const local_core_controller_js_1 = require("../../services/local-ai-core/src/run
167
168
  platforms: [],
168
169
  }],
169
170
  });
170
- const workspaces = await controller.listWorkspaces();
171
+ const workspaces = await controller.workspaceRouter.listWorkspaces();
171
172
  strict_1.default.equal(workspaces[0]?.id, 'workspace-a');
172
173
  strict_1.default.equal(workspaces[0]?.agentType, 'pi');
173
174
  await controller.close();
@@ -197,7 +198,7 @@ const local_core_controller_js_1 = require("../../services/local-ai-core/src/run
197
198
  created_at: now,
198
199
  updated_at: now,
199
200
  });
200
- const job = await controller.createScheduledJob({
201
+ const job = await controller.scheduledJobs.createJob({
201
202
  workspaceId: 'workspace-a',
202
203
  threadId: thread.id,
203
204
  executionMode: 'side-thread',
@@ -240,7 +241,7 @@ const local_core_controller_js_1 = require("../../services/local-ai-core/src/run
240
241
  created_at: now,
241
242
  updated_at: now,
242
243
  });
243
- const job = await controller.createScheduledJob({
244
+ const job = await controller.scheduledJobs.createJob({
244
245
  workspaceId: 'workspace-a',
245
246
  threadId: thread.id,
246
247
  executionMode: 'side-thread',
@@ -583,8 +584,12 @@ const local_core_controller_js_1 = require("../../services/local-ai-core/src/run
583
584
  const previousHome = process.env.HOME;
584
585
  process.env.HOME = '/home/agentdock-test';
585
586
  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');
587
+ const existingPath = ['/usr/bin', '/bin'].join(node_path_1.delimiter);
588
+ const path = (0, local_core_acp_session_coordinator_js_1.buildAgentPath)(existingPath, '/opt/agentdock/bin', { pathExists: () => false });
589
+ const expectedEntries = process.platform === 'win32'
590
+ ? ['/opt/agentdock/bin', '/usr/bin', '/bin']
591
+ : ['/opt/agentdock/bin', '/home/agentdock-test/.local/bin', '/home/agentdock-test/bin', '/usr/bin', '/bin'];
592
+ strict_1.default.equal(path, expectedEntries.join(node_path_1.delimiter));
588
593
  }
589
594
  finally {
590
595
  if (previousHome === undefined) {
@@ -595,6 +600,26 @@ const local_core_controller_js_1 = require("../../services/local-ai-core/src/run
595
600
  }
596
601
  }
597
602
  });
603
+ (0, node_test_1.default)('ACP runtime PATH adds Git Bash bin when Windows PATH only has Git cmd', () => {
604
+ const existingPath = [
605
+ 'C:\\Windows\\System32',
606
+ 'D:\\Program Files\\Git\\cmd',
607
+ ].join(';');
608
+ const path = (0, local_core_acp_session_coordinator_js_1.buildAgentPath)(existingPath, undefined, {
609
+ platform: 'win32',
610
+ pathExists: (candidate) => candidate === 'D:\\Program Files\\Git\\bin\\bash.exe',
611
+ });
612
+ strict_1.default.equal(path, [
613
+ 'D:\\Program Files\\Git\\bin',
614
+ 'C:\\Windows\\System32',
615
+ 'D:\\Program Files\\Git\\cmd',
616
+ ].join(';'));
617
+ });
618
+ (0, node_test_1.default)('ACP runtime reads Windows Path env regardless of key casing', () => {
619
+ strict_1.default.equal((0, env_utils_js_1.getPathEnv)({ Path: 'C:\\Windows\\System32;C:\\Program Files\\nodejs' }), 'C:\\Windows\\System32;C:\\Program Files\\nodejs');
620
+ strict_1.default.equal((0, env_utils_js_1.getPathEnv)({ PATH: '/usr/bin:/bin' }), '/usr/bin:/bin');
621
+ strict_1.default.equal((0, env_utils_js_1.getPathEnv)({ Path: 'C:\\Windows\\System32', PATH: 'D:\\agentdock\\bin' }), 'D:\\agentdock\\bin');
622
+ });
598
623
  (0, node_test_1.default)('ACP transport reports missing agent commands without an unhandled process error', async () => {
599
624
  const closed = new Promise((resolve) => {
600
625
  const transport = new local_core_acp_transport_js_1.LocalCoreAcpTransport({
@@ -625,8 +650,9 @@ const local_core_controller_js_1 = require("../../services/local-ai-core/src/run
625
650
  });
626
651
  (0, node_test_1.default)('ACP scheduled session can override permission mode without changing thread mode', async () => {
627
652
  const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'scheduler-permission-mode-'));
653
+ let store = null;
628
654
  try {
629
- const store = new local_core_acp_store_js_1.LocalCoreAcpStore(userDataPath);
655
+ store = new local_core_acp_store_js_1.LocalCoreAcpStore(userDataPath);
630
656
  const thread = store.createThread('workspace-a', 'Thread');
631
657
  const capturedSessionNewParams = [];
632
658
  let closeCount = 0;
@@ -692,13 +718,15 @@ const local_core_controller_js_1 = require("../../services/local-ai-core/src/run
692
718
  });
693
719
  }
694
720
  finally {
721
+ store?.close();
695
722
  (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
696
723
  }
697
724
  });
698
725
  (0, node_test_1.default)('ACP sandbox sessions use container workspace cwd', async () => {
699
726
  const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'sandbox-session-cwd-'));
727
+ let store = null;
700
728
  try {
701
- const store = new local_core_acp_store_js_1.LocalCoreAcpStore(userDataPath);
729
+ store = new local_core_acp_store_js_1.LocalCoreAcpStore(userDataPath);
702
730
  const thread = store.createThread('workspace-a', 'Thread');
703
731
  let sessionNewParams;
704
732
  const coordinator = new local_core_acp_session_coordinator_js_1.LocalCoreAcpSessionCoordinator({
@@ -769,6 +797,7 @@ const local_core_controller_js_1 = require("../../services/local-ai-core/src/run
769
797
  strict_1.default.equal(sessionNewParams?.cwd, '/workspace');
770
798
  }
771
799
  finally {
800
+ store?.close();
772
801
  (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
773
802
  }
774
803
  });
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const node_test_1 = __importDefault(require("node:test"));
7
+ const strict_1 = __importDefault(require("node:assert/strict"));
8
+ const server_helpers_js_1 = require("../../services/local-ai-core/src/runtime/server-helpers.js");
9
+ class HeaderRecorder {
10
+ headers = new Map();
11
+ setHeader(name, value) {
12
+ this.headers.set(name, value);
13
+ }
14
+ }
15
+ (0, node_test_1.default)('local core CORS preflight allows provider update PUT requests', () => {
16
+ const req = { headers: { origin: 'http://127.0.0.1:5173' } };
17
+ const res = new HeaderRecorder();
18
+ (0, server_helpers_js_1.setCorsHeaders)(req, res);
19
+ strict_1.default.equal(res.headers.get('Access-Control-Allow-Origin'), 'http://127.0.0.1:5173');
20
+ strict_1.default.match(String(res.headers.get('Access-Control-Allow-Methods')), /(?:^|,)PUT(?:,|$)/);
21
+ strict_1.default.match(String(res.headers.get('Access-Control-Allow-Headers')), /Content-Type/);
22
+ });
@@ -313,11 +313,15 @@ const thread_slash_command_dispatcher_js_1 = require("../../services/local-ai-co
313
313
  try {
314
314
  const store = new local_core_acp_store_js_1.LocalCoreAcpStore(dir);
315
315
  const thread = store.createThread('workspace-a', 'Mode test', 'claudecode');
316
+ strict_1.default.equal(thread.agentMode, 'default');
316
317
  strict_1.default.equal(store.getThreadRow(thread.id)?.agent_mode, 'default');
317
318
  const inheritedThread = store.createThread('workspace-a', 'Inherited mode test', 'claudecode', 'bypassPermissions');
319
+ strict_1.default.equal(inheritedThread.agentMode, 'bypassPermissions');
318
320
  strict_1.default.equal(store.getThreadRow(inheritedThread.id)?.agent_mode, 'bypassPermissions');
319
321
  store.updateThreadAgentMode(thread.id, 'bypassPermissions');
320
322
  strict_1.default.equal(store.getThreadRow(thread.id)?.agent_mode, 'bypassPermissions');
323
+ strict_1.default.equal(store.getThread(thread.id, []).agentMode, 'bypassPermissions');
324
+ strict_1.default.equal(store.listThreadSummaries('workspace-a').find((item) => item.id === thread.id)?.agentMode, 'bypassPermissions');
321
325
  store.close();
322
326
  }
323
327
  finally {
@@ -894,3 +898,176 @@ const thread_slash_command_dispatcher_js_1 = require("../../services/local-ai-co
894
898
  sessionKey: 'session:thread-origin',
895
899
  });
896
900
  });
901
+ (0, node_test_1.default)('platform thread binding persists preferred agent across upserts and updates', () => {
902
+ const dir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-binding-pref-'));
903
+ try {
904
+ const store = new local_core_acp_store_js_1.LocalCoreAcpStore(dir);
905
+ const now = new Date().toISOString();
906
+ store.upsertPlatformThreadBinding({
907
+ workspace_id: 'workspace-a',
908
+ platform: 'lark',
909
+ chat_id: 'chat-1',
910
+ platform_user_id: 'user-1',
911
+ thread_id: 'thread-1',
912
+ last_platform_message_id: null,
913
+ created_at: now,
914
+ updated_at: now,
915
+ });
916
+ store.updatePlatformThreadPreferredAgent('workspace-a', 'chat-1', 'user-1', 'claudecode', 'lark');
917
+ let binding = store.getPlatformThreadBinding('workspace-a', 'chat-1', 'user-1', 'lark');
918
+ strict_1.default.equal(binding?.preferred_agent_type, 'claudecode');
919
+ const later = new Date().toISOString();
920
+ store.upsertPlatformThreadBinding({
921
+ workspace_id: 'workspace-a',
922
+ platform: 'lark',
923
+ chat_id: 'chat-1',
924
+ platform_user_id: 'user-1',
925
+ thread_id: 'thread-2',
926
+ last_platform_message_id: null,
927
+ created_at: later,
928
+ updated_at: later,
929
+ });
930
+ binding = store.getPlatformThreadBinding('workspace-a', 'chat-1', 'user-1', 'lark');
931
+ strict_1.default.equal(binding?.thread_id, 'thread-2');
932
+ strict_1.default.equal(binding?.preferred_agent_type, 'claudecode');
933
+ store.updatePlatformThreadPreferredAgent('workspace-a', 'chat-1', 'user-1', null, 'lark');
934
+ binding = store.getPlatformThreadBinding('workspace-a', 'chat-1', 'user-1', 'lark');
935
+ strict_1.default.equal(binding?.preferred_agent_type, null);
936
+ store.close();
937
+ }
938
+ finally {
939
+ (0, node_fs_1.rmSync)(dir, { recursive: true, force: true });
940
+ }
941
+ });
942
+ (0, node_test_1.default)('slash agent command in a channel writes preferred agent to the binding', async () => {
943
+ const dir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-channel-agent-cmd-'));
944
+ try {
945
+ const store = new local_core_acp_store_js_1.LocalCoreAcpStore(dir);
946
+ const thread = store.createThread('workspace-a', 'Channel thread', 'codex');
947
+ const now = new Date().toISOString();
948
+ store.upsertPlatformThreadBinding({
949
+ workspace_id: 'workspace-a',
950
+ platform: 'lark',
951
+ chat_id: 'chat-1',
952
+ platform_user_id: 'user-1',
953
+ thread_id: thread.id,
954
+ last_platform_message_id: null,
955
+ created_at: now,
956
+ updated_at: now,
957
+ });
958
+ const row = store.getThreadRow(thread.id);
959
+ const dispatcher = new thread_slash_command_dispatcher_js_1.ThreadSlashCommandDispatcher({
960
+ session: {
961
+ listThreads: () => [],
962
+ getThread: (id) => ({ id, title: row.title, messages: [] }),
963
+ createThread: (workspaceId, title) => ({ id: 'new-thread', title, messages: [] }),
964
+ renameThread: (id, title) => ({ id, title, messages: [] }),
965
+ deleteThread: () => ({ deleted: true }),
966
+ },
967
+ thread: {
968
+ getThreadRow: (id) => store.getThreadRow(id),
969
+ updateThreadAgentMode: (id, mode) => store.updateThreadAgentMode(id, mode),
970
+ updateThreadAgentType: (id, agentType) => store.updateThreadAgentType(id, agentType),
971
+ getLatestRunForThread: () => undefined,
972
+ createAuditEvent: () => { },
973
+ getAgentTypes: () => ['codex', 'pi', 'claudecode'],
974
+ setChannelPreferredAgent: (input) => store.updatePlatformThreadPreferredAgent(input.workspaceId, input.chatId, input.platformUserId, input.agentType, input.platform),
975
+ },
976
+ });
977
+ const useResult = await dispatcher.execute({
978
+ threadId: thread.id,
979
+ workspaceId: 'workspace-a',
980
+ content: '/agent use claudecode',
981
+ defaultAgentType: 'codex',
982
+ channel: { chatId: 'chat-1', platformUserId: 'user-1', platform: 'lark' },
983
+ });
984
+ strict_1.default.equal(useResult.handled, true);
985
+ strict_1.default.equal(store.getThreadRow(thread.id)?.agent_type, 'claudecode');
986
+ strict_1.default.equal(store.getPlatformThreadBinding('workspace-a', 'chat-1', 'user-1', 'lark')?.preferred_agent_type, 'claudecode');
987
+ const resetResult = await dispatcher.execute({
988
+ threadId: thread.id,
989
+ workspaceId: 'workspace-a',
990
+ content: '/agent reset',
991
+ defaultAgentType: 'codex',
992
+ channel: { chatId: 'chat-1', platformUserId: 'user-1', platform: 'lark' },
993
+ });
994
+ strict_1.default.equal(resetResult.handled, true);
995
+ strict_1.default.equal(store.getThreadRow(thread.id)?.agent_type, 'codex');
996
+ strict_1.default.equal(store.getPlatformThreadBinding('workspace-a', 'chat-1', 'user-1', 'lark')?.preferred_agent_type, null);
997
+ store.close();
998
+ }
999
+ finally {
1000
+ (0, node_fs_1.rmSync)(dir, { recursive: true, force: true });
1001
+ }
1002
+ });
1003
+ (0, node_test_1.default)('channel session runtime applies preferred agent when creating a new thread', async () => {
1004
+ const dir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-channel-new-inherit-'));
1005
+ try {
1006
+ const store = new local_core_acp_store_js_1.LocalCoreAcpStore(dir);
1007
+ const original = store.createThread('workspace-a', 'Original channel thread', 'codex');
1008
+ const now = new Date().toISOString();
1009
+ store.upsertPlatformThreadBinding({
1010
+ workspace_id: 'workspace-a',
1011
+ platform: 'lark',
1012
+ chat_id: 'chat-1',
1013
+ platform_user_id: 'user-1',
1014
+ thread_id: original.id,
1015
+ last_platform_message_id: null,
1016
+ created_at: now,
1017
+ updated_at: now,
1018
+ });
1019
+ store.updatePlatformThreadPreferredAgent('workspace-a', 'chat-1', 'user-1', 'claudecode', 'lark');
1020
+ store.updateThreadAgentType(original.id, 'claudecode');
1021
+ const createdThreadIds = [];
1022
+ const routes = new Map();
1023
+ const dispatcher = new thread_slash_command_dispatcher_js_1.ThreadSlashCommandDispatcher({
1024
+ session: {
1025
+ listThreads: () => [],
1026
+ getThread: (id) => ({ id, title: 'X', messages: [] }),
1027
+ createThread: (_workspaceId, title) => {
1028
+ const created = store.createThread('workspace-a', title, 'codex');
1029
+ createdThreadIds.push({ id: created.id, initialAgentType: 'codex' });
1030
+ return created;
1031
+ },
1032
+ renameThread: (id, title) => ({ id, title, messages: [] }),
1033
+ deleteThread: () => ({ deleted: true }),
1034
+ },
1035
+ thread: {
1036
+ getThreadRow: (id) => store.getThreadRow(id),
1037
+ updateThreadAgentMode: (id, mode) => store.updateThreadAgentMode(id, mode),
1038
+ updateThreadAgentType: (id, agentType) => store.updateThreadAgentType(id, agentType),
1039
+ getLatestRunForThread: () => undefined,
1040
+ createAuditEvent: () => { },
1041
+ getAgentTypes: () => ['codex', 'claudecode'],
1042
+ },
1043
+ });
1044
+ const runtime = new session_command_runtime_js_1.ChannelSessionCommandRuntime({
1045
+ dispatcher,
1046
+ store,
1047
+ getThreadSessionKey: (id) => `session:${id}`,
1048
+ setThreadRoute: (sessionKey, route) => { routes.set(sessionKey, route); },
1049
+ createRoute: (input, id) => ({ workspaceId: input.workspaceId, chatId: input.chatId, threadId: id }),
1050
+ sendResult: async () => { },
1051
+ });
1052
+ const result = await runtime.execute({
1053
+ workspaceId: 'workspace-a',
1054
+ currentThreadId: original.id,
1055
+ text: '/new Follow up',
1056
+ defaultTitle: 'Follow up',
1057
+ defaultAgentType: 'codex',
1058
+ chatId: 'chat-1',
1059
+ platformUserId: 'user-1',
1060
+ platformKey: 'lark',
1061
+ instanceId: 'default',
1062
+ });
1063
+ strict_1.default.equal(result.handled, true);
1064
+ strict_1.default.equal(result.threadId, createdThreadIds[0]?.id);
1065
+ strict_1.default.equal(createdThreadIds[0]?.initialAgentType, 'codex', 'createThread starts with default agent before inheritance');
1066
+ strict_1.default.equal(store.getThreadRow(result.threadId)?.agent_type, 'claudecode', 'new thread should inherit preferred agent after creation');
1067
+ strict_1.default.equal(store.getPlatformThreadBinding('workspace-a', 'chat-1', 'user-1', 'lark')?.preferred_agent_type, 'claudecode');
1068
+ store.close();
1069
+ }
1070
+ finally {
1071
+ (0, node_fs_1.rmSync)(dir, { recursive: true, force: true });
1072
+ }
1073
+ });
@@ -5,7 +5,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const node_test_1 = __importDefault(require("node:test"));
7
7
  const strict_1 = __importDefault(require("node:assert/strict"));
8
+ const node_stream_1 = require("node:stream");
8
9
  const server_routes_js_1 = require("../../services/local-ai-core/src/runtime/server-routes.js");
10
+ const thread_handler_js_1 = require("../../services/local-ai-core/src/runtime/handlers/thread-handler.js");
9
11
  (0, node_test_1.default)('local core route parser separates runtime refresh and runtime detail routes', () => {
10
12
  strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/logs'), { name: 'logs.list' });
11
13
  strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/runtimes'), { name: 'runtimes.list' });
@@ -45,6 +47,10 @@ const server_routes_js_1 = require("../../services/local-ai-core/src/runtime/ser
45
47
  name: 'thread.update-knowledge-bases',
46
48
  threadId: 'thread-1',
47
49
  });
50
+ strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('PATCH', '/api/local/v1/threads/thread-1/mode'), {
51
+ name: 'thread.update-mode',
52
+ threadId: 'thread-1',
53
+ });
48
54
  strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('POST', '/api/local/v1/threads/thread-1/messages'), {
49
55
  name: 'thread.messages.send',
50
56
  threadId: 'thread-1',
@@ -55,6 +61,45 @@ const server_routes_js_1 = require("../../services/local-ai-core/src/runtime/ser
55
61
  });
56
62
  strict_1.default.equal((0, server_routes_js_1.parseLocalAiCoreRoute)('GET', '/api/local/v1/threads/thread-1/messages'), null);
57
63
  });
64
+ (0, node_test_1.default)('thread mode route handler persists normalized mode through workspace router', async () => {
65
+ const calls = [];
66
+ const map = new Map();
67
+ (0, thread_handler_js_1.registerThreadHandlers)(map, {
68
+ setThreadMode: async (threadId, mode) => {
69
+ calls.push({ threadId, mode });
70
+ return {
71
+ id: threadId,
72
+ workspaceId: 'workspace-a',
73
+ title: 'Thread',
74
+ live: false,
75
+ updatedAt: '2026-06-14T00:00:00.000Z',
76
+ createdAt: '2026-06-14T00:00:00.000Z',
77
+ historyCount: 0,
78
+ excerpt: '',
79
+ agentMode: 'bypassPermissions',
80
+ messages: [],
81
+ selectedKnowledgeBaseIds: [],
82
+ };
83
+ },
84
+ });
85
+ const req = node_stream_1.Readable.from([Buffer.from(JSON.stringify({ mode: 'bypassPermissions' }))]);
86
+ const headers = {};
87
+ let responseBody = '';
88
+ const res = {
89
+ statusCode: 0,
90
+ setHeader: (name, value) => {
91
+ headers[name] = value;
92
+ },
93
+ end: (body) => {
94
+ responseBody = body;
95
+ },
96
+ };
97
+ await map.get('thread.update-mode')({ name: 'thread.update-mode', threadId: 'thread-1' }, req, res, new URL('http://127.0.0.1/api/local/v1/threads/thread-1/mode'));
98
+ strict_1.default.deepEqual(calls, [{ threadId: 'thread-1', mode: 'bypassPermissions' }]);
99
+ strict_1.default.equal(res.statusCode, 200);
100
+ strict_1.default.equal(headers['Content-Type'], 'application/json; charset=utf-8');
101
+ strict_1.default.equal(JSON.parse(responseBody).data.agentMode, 'bypassPermissions');
102
+ });
58
103
  (0, node_test_1.default)('local core route parser only accepts run interrupt action with POST', () => {
59
104
  strict_1.default.deepEqual((0, server_routes_js_1.parseLocalAiCoreRoute)('POST', '/api/local/v1/runs/run-1/interrupt'), {
60
105
  name: 'run.interrupt',