@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
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LocalCoreAcpSessionCoordinator = void 0;
4
+ exports.buildAgentPath = buildAgentPath;
4
5
  const node_path_1 = require("node:path");
6
+ const node_os_1 = require("node:os");
5
7
  const scheduled_job_route_js_1 = require("../scheduler/scheduled-job-route.js");
6
8
  class LocalCoreAcpSessionCoordinator {
7
9
  options;
@@ -14,6 +16,7 @@ class LocalCoreAcpSessionCoordinator {
14
16
  }
15
17
  closeAll() {
16
18
  for (const session of this.sessions.values()) {
19
+ this.clearIdleClose(session);
17
20
  this.options.transport.closeSession(session);
18
21
  }
19
22
  this.sessions.clear();
@@ -23,9 +26,21 @@ class LocalCoreAcpSessionCoordinator {
23
26
  if (!session) {
24
27
  return;
25
28
  }
29
+ this.clearIdleClose(session);
26
30
  this.options.transport.closeSession(session);
27
31
  this.sessions.delete(threadId);
28
32
  }
33
+ releaseThreadSession(threadId, config) {
34
+ if (!config.sandbox?.enabled || config.sandbox.lifecycle === 'per_run' || config.sandbox.stateScope === 'run') {
35
+ this.closeThreadSession(threadId);
36
+ return;
37
+ }
38
+ const session = this.sessions.get(threadId);
39
+ if (!session || session.closed) {
40
+ return;
41
+ }
42
+ this.scheduleIdleClose(session, config.sandbox.idleSeconds);
43
+ }
29
44
  async ensureSession(threadId, bridgeSessionKey, config, options = {}) {
30
45
  const existing = this.sessions.get(threadId);
31
46
  const permissionMode = this.resolveLaunchPermissionMode(threadId, options.permissionMode);
@@ -37,11 +52,15 @@ class LocalCoreAcpSessionCoordinator {
37
52
  && existing.launchPermissionMode === permissionMode
38
53
  && existing.launchConfigKey === configKey
39
54
  && existing.launchRuntimeEnvKey === runtimeEnvKey) {
55
+ this.clearIdleClose(existing);
56
+ this.options.log?.(`[acp.session:${threadId}] reuse existing session mode=${config.sandbox?.enabled ? 'sandbox' : 'local'}`);
40
57
  return existing;
41
58
  }
42
59
  if (existing) {
43
60
  this.closeThreadSession(threadId);
44
61
  }
62
+ const startedAt = Date.now();
63
+ this.options.log?.(`[acp.session:${threadId}] spawn mode=${config.sandbox?.enabled ? 'sandbox' : 'local'} transport=${config.execution?.transport || 'stdio'}`);
45
64
  const baseEnv = {
46
65
  ...process.env,
47
66
  ...config.env,
@@ -50,20 +69,21 @@ class LocalCoreAcpSessionCoordinator {
50
69
  threadId,
51
70
  bridgeSessionKey,
52
71
  config,
53
- runtimeEnv: this.buildAgentRuntimeEnv(threadId, String(baseEnv.PATH || ''), options.runtimeEnv),
72
+ runtimeEnv: this.buildAgentRuntimeEnv(threadId, String(baseEnv.PATH || ''), options.runtimeEnv, options.runId),
54
73
  });
55
74
  session.launchPermissionMode = permissionMode;
56
75
  session.launchConfigKey = configKey;
57
76
  session.launchRuntimeEnvKey = runtimeEnvKey;
58
77
  this.sessions.set(threadId, session);
59
78
  await this.options.transport.initializeSession(session);
79
+ this.options.log?.(`[acp.session:${threadId}] initialize done in ${Date.now() - startedAt}ms`);
60
80
  const row = this.options.store.getThreadRow(threadId);
61
81
  if (row?.acp_session_id && row.acp_supports_load && session.supportsLoad) {
62
82
  try {
63
83
  session.loadReplayMode = true;
64
84
  await this.options.transport.request(session, 'session/load', {
65
85
  sessionId: row.acp_session_id,
66
- cwd: config.workDir,
86
+ cwd: acpSessionCwd(config),
67
87
  mcpServers: [],
68
88
  }, 30000);
69
89
  session.sessionId = row.acp_session_id;
@@ -78,7 +98,7 @@ class LocalCoreAcpSessionCoordinator {
78
98
  if (!session.sessionId) {
79
99
  try {
80
100
  const created = await this.options.transport.request(session, 'session/new', {
81
- cwd: config.workDir,
101
+ cwd: acpSessionCwd(config),
82
102
  mcpServers: [],
83
103
  _meta: this.buildSessionMeta(threadId, permissionMode),
84
104
  }, 30000);
@@ -87,12 +107,14 @@ class LocalCoreAcpSessionCoordinator {
87
107
  throw new Error('ACP session/new did not return a sessionId');
88
108
  }
89
109
  this.options.store.updateThreadSession(threadId, session.sessionId, session.supportsLoad);
110
+ this.options.log?.(`[acp.session:${threadId}] session/new done in ${Date.now() - startedAt}ms`);
90
111
  }
91
112
  catch (error) {
92
113
  this.closeThreadSession(threadId);
93
114
  throw error;
94
115
  }
95
116
  }
117
+ this.options.log?.(`[acp.session:${threadId}] ready in ${Date.now() - startedAt}ms`);
96
118
  return session;
97
119
  }
98
120
  async setThreadMode(threadId, mode) {
@@ -152,6 +174,7 @@ class LocalCoreAcpSessionCoordinator {
152
174
  return;
153
175
  }
154
176
  const activeRunId = session.currentRunId;
177
+ this.clearIdleClose(session);
155
178
  this.options.transport.closeSessionWithError(session, error);
156
179
  if (activeRunId) {
157
180
  this.options.store.updateRun(activeRunId, session.threadId, 'failed');
@@ -166,13 +189,31 @@ class LocalCoreAcpSessionCoordinator {
166
189
  this.sessions.delete(session.threadId);
167
190
  }
168
191
  }
192
+ scheduleIdleClose(session, idleSeconds) {
193
+ this.clearIdleClose(session);
194
+ const seconds = Number.isFinite(idleSeconds) && idleSeconds > 0 ? idleSeconds : 900;
195
+ this.options.log?.(`[acp.session:${session.threadId}] keep sandbox session warm for ${seconds}s`);
196
+ session.idleCloseTimer = setTimeout(() => {
197
+ if (this.sessions.get(session.threadId) === session && !session.closed && !session.currentRunId) {
198
+ this.options.log?.(`[acp.session:${session.threadId}] idle timeout reached; closing sandbox session`);
199
+ this.closeThreadSession(session.threadId);
200
+ }
201
+ }, seconds * 1000);
202
+ session.idleCloseTimer.unref?.();
203
+ }
204
+ clearIdleClose(session) {
205
+ if (session.idleCloseTimer) {
206
+ clearTimeout(session.idleCloseTimer);
207
+ session.idleCloseTimer = undefined;
208
+ }
209
+ }
169
210
  buildRuntimeEnvKey(runtimeEnv = {}) {
170
211
  return Object.keys(runtimeEnv)
171
212
  .sort()
172
213
  .map((key) => `${key}=${runtimeEnv[key]}`)
173
214
  .join('\n');
174
215
  }
175
- buildAgentRuntimeEnv(threadId, existingPath, runtimeEnv = {}) {
216
+ buildAgentRuntimeEnv(threadId, existingPath, runtimeEnv = {}, runId = '') {
176
217
  const row = this.options.store.getThreadRow(threadId);
177
218
  if (!row) {
178
219
  return {};
@@ -182,6 +223,7 @@ class LocalCoreAcpSessionCoordinator {
182
223
  LOCAL_AI_CORE_BASE: this.options.localCoreBase || 'http://127.0.0.1:9831/api/local/v1',
183
224
  LOCAL_AI_WORKSPACE_ID: row.workspace_id,
184
225
  LOCAL_AI_THREAD_ID: threadId,
226
+ ...(runId ? { AGENTDOCK_SANDBOX_RUN_ID: runId } : {}),
185
227
  };
186
228
  const workspace = this.options.store.getWorkspaceRegistryEntry(row.workspace_id);
187
229
  if (workspace?.path) {
@@ -198,9 +240,10 @@ class LocalCoreAcpSessionCoordinator {
198
240
  env.LOCAL_AI_PLATFORM_USER_ID = binding.platform_user_id;
199
241
  }
200
242
  if (this.options.cliBinDir) {
201
- env.PATH = existingPath
202
- ? `${this.options.cliBinDir}${node_path_1.delimiter}${existingPath}`
203
- : this.options.cliBinDir;
243
+ env.PATH = buildAgentPath(existingPath, this.options.cliBinDir);
244
+ }
245
+ else {
246
+ env.PATH = buildAgentPath(existingPath);
204
247
  }
205
248
  return {
206
249
  ...env,
@@ -215,6 +258,8 @@ class LocalCoreAcpSessionCoordinator {
215
258
  args: config.args || [],
216
259
  env: config.env || {},
217
260
  model: config.model || '',
261
+ execution: config.execution || null,
262
+ sandbox: config.sandbox || null,
218
263
  });
219
264
  }
220
265
  resolveLaunchPermissionMode(threadId, permissionModeOverride = '') {
@@ -241,3 +286,31 @@ class LocalCoreAcpSessionCoordinator {
241
286
  }
242
287
  }
243
288
  exports.LocalCoreAcpSessionCoordinator = LocalCoreAcpSessionCoordinator;
289
+ function acpSessionCwd(config) {
290
+ return config.sandbox?.enabled ? config.sandbox.workspaceMountPath : config.workDir;
291
+ }
292
+ function buildAgentPath(existingPath, cliBinDir) {
293
+ const entries = [
294
+ cliBinDir,
295
+ ...userBinDirs(),
296
+ ...String(existingPath || '').split(node_path_1.delimiter),
297
+ ];
298
+ const seen = new Set();
299
+ return entries
300
+ .map((entry) => String(entry || '').trim())
301
+ .filter((entry) => {
302
+ if (!entry || seen.has(entry)) {
303
+ return false;
304
+ }
305
+ seen.add(entry);
306
+ return true;
307
+ })
308
+ .join(node_path_1.delimiter);
309
+ }
310
+ function userBinDirs() {
311
+ if (process.platform === 'win32') {
312
+ return [];
313
+ }
314
+ const home = process.env.HOME || (0, node_os_1.homedir)();
315
+ return home ? [`${home}/.local/bin`, `${home}/bin`] : [];
316
+ }
@@ -2,6 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LocalCoreAcpTransport = void 0;
4
4
  const node_child_process_1 = require("node:child_process");
5
+ const node_fs_1 = require("node:fs");
6
+ const local_core_errors_js_1 = require("../kernel/local-core-errors.js");
5
7
  class LocalCoreAcpTransport {
6
8
  options;
7
9
  constructor(options) {
@@ -12,8 +14,9 @@ class LocalCoreAcpTransport {
12
14
  ...process.env,
13
15
  ...input.config.env,
14
16
  };
17
+ const cwd = resolveTransportCwd(input.config);
15
18
  const child = (0, node_child_process_1.spawn)(input.config.command, input.config.args, {
16
- cwd: input.config.workDir,
19
+ cwd,
17
20
  env: {
18
21
  ...baseEnv,
19
22
  ...input.runtimeEnv,
@@ -51,7 +54,37 @@ class LocalCoreAcpTransport {
51
54
  this.handlePipeFailure(session, error);
52
55
  });
53
56
  child.on('exit', (code, signal) => {
54
- this.options.onSessionClosed(session, new Error(`ACP agent exited with code ${code ?? 'unknown'}${signal ? ` (${signal})` : ''}`));
57
+ if (session.closed) {
58
+ return;
59
+ }
60
+ this.options.onSessionClosed(session, new local_core_errors_js_1.LocalCoreError('runtime_exited', `ACP agent exited with code ${code ?? 'unknown'}${signal ? ` (${signal})` : ''}`, {
61
+ details: {
62
+ code: code ?? 'unknown',
63
+ signal: signal || '',
64
+ threadId: input.threadId,
65
+ runtimeId: input.config.agentType,
66
+ },
67
+ }));
68
+ });
69
+ child.on('error', (error) => {
70
+ this.options.onSessionClosed(session, error.code === 'ENOENT'
71
+ ? new local_core_errors_js_1.LocalCoreError('runtime_not_found', `ACP agent command not found: ${input.config.command}`, {
72
+ details: {
73
+ command: input.config.command,
74
+ cwd,
75
+ threadId: input.threadId,
76
+ runtimeId: input.config.agentType,
77
+ },
78
+ })
79
+ : new local_core_errors_js_1.LocalCoreError('runtime_start_failed', `ACP agent failed to start: ${error.message}`, {
80
+ cause: error.code,
81
+ details: {
82
+ command: input.config.command,
83
+ cwd,
84
+ threadId: input.threadId,
85
+ runtimeId: input.config.agentType,
86
+ },
87
+ }));
55
88
  });
56
89
  return session;
57
90
  }
@@ -79,7 +112,14 @@ class LocalCoreAcpTransport {
79
112
  return new Promise((resolve, reject) => {
80
113
  const timeout = setTimeout(() => {
81
114
  session.pending.delete(id);
82
- reject(new Error(`Timed out waiting for ACP ${method} after ${timeoutMs}ms`));
115
+ reject(new local_core_errors_js_1.LocalCoreError('runtime_protocol_timeout', `Timed out waiting for ACP ${method} after ${timeoutMs}ms`, {
116
+ details: {
117
+ method,
118
+ timeoutMs,
119
+ threadId: session.threadId,
120
+ runtimeId: session.currentTurn?.agentType || '',
121
+ },
122
+ }));
83
123
  }, timeoutMs);
84
124
  session.pending.set(id, {
85
125
  resolve: (value) => {
@@ -175,7 +215,15 @@ class LocalCoreAcpTransport {
175
215
  }
176
216
  session.pending.delete(payload.id);
177
217
  if (payload.error) {
178
- pending.reject(new Error(payload.error.message || `ACP request failed: ${payload.id}`));
218
+ pending.reject(new local_core_errors_js_1.LocalCoreError(errorCodeForAcpError(payload.error), payload.error.message || `ACP request failed: ${payload.id}`, {
219
+ details: {
220
+ payloadId: payload.id,
221
+ errorCode: payload.error.code,
222
+ errorData: payload.error.data,
223
+ threadId: session.threadId,
224
+ runtimeId: session.currentTurn?.agentType || '',
225
+ },
226
+ }));
179
227
  }
180
228
  else {
181
229
  pending.resolve(payload.result);
@@ -184,9 +232,34 @@ class LocalCoreAcpTransport {
184
232
  }
185
233
  }
186
234
  handlePipeFailure(session, error) {
235
+ if (session.closed) {
236
+ return;
237
+ }
187
238
  const message = error instanceof Error ? error.message : String(error);
188
239
  this.options.log?.(`[localcore-acp:${session.threadId}] stdin failure: ${message}`);
189
- this.options.onSessionClosed(session, new Error(message));
240
+ this.options.onSessionClosed(session, new local_core_errors_js_1.LocalCoreError('runtime_protocol_error', message, {
241
+ details: {
242
+ threadId: session.threadId,
243
+ runtimeId: session.currentTurn?.agentType || '',
244
+ },
245
+ }));
190
246
  }
191
247
  }
192
248
  exports.LocalCoreAcpTransport = LocalCoreAcpTransport;
249
+ function resolveTransportCwd(config) {
250
+ if (config.sandbox?.enabled) {
251
+ const proxyCwd = String(config.sandbox.proxyCwd || '').trim();
252
+ if (proxyCwd && (0, node_fs_1.existsSync)(proxyCwd)) {
253
+ return proxyCwd;
254
+ }
255
+ return process.cwd();
256
+ }
257
+ return config.workDir;
258
+ }
259
+ function errorCodeForAcpError(error) {
260
+ const message = String(error?.message || '').toLowerCase();
261
+ if (message.includes('authentication required') || message.includes('api key') || message.includes('oauth')) {
262
+ return 'provider_auth_failed';
263
+ }
264
+ return 'runtime_protocol_error';
265
+ }
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LocalExternalStore = void 0;
4
+ const utils_js_1 = require("./utils.js");
5
+ class LocalExternalStore {
6
+ db;
7
+ constructor(db) {
8
+ this.db = db;
9
+ }
10
+ getProject(userId, externalProjectId) {
11
+ const row = this.db.prepare(`
12
+ SELECT user_id, external_project_id, workspace_id, workspace_path, display_name, agent_type, provider_id, metadata_json, created_at, updated_at
13
+ FROM external_projects
14
+ WHERE user_id = ? AND external_project_id = ?
15
+ `).get(userId, externalProjectId);
16
+ return row ? this.toProject(row) : undefined;
17
+ }
18
+ upsertProject(input) {
19
+ const now = new Date().toISOString();
20
+ const existing = this.getProject(input.userId, input.externalProjectId);
21
+ this.db.prepare(`
22
+ INSERT INTO external_projects (
23
+ user_id, external_project_id, workspace_id, workspace_path, display_name, agent_type, provider_id,
24
+ metadata_json, created_at, updated_at
25
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
26
+ ON CONFLICT(user_id, external_project_id) DO UPDATE SET
27
+ workspace_id = excluded.workspace_id,
28
+ workspace_path = excluded.workspace_path,
29
+ display_name = excluded.display_name,
30
+ agent_type = excluded.agent_type,
31
+ provider_id = excluded.provider_id,
32
+ metadata_json = excluded.metadata_json,
33
+ updated_at = excluded.updated_at
34
+ `).run(input.userId, input.externalProjectId, input.workspaceId, input.workspacePath, input.displayName, input.agentType, input.providerId, JSON.stringify(input.metadata || existing?.metadata || {}), existing?.createdAt || input.createdAt || now, now);
35
+ return this.getProject(input.userId, input.externalProjectId);
36
+ }
37
+ getThread(userId, externalProjectId, externalThreadId) {
38
+ const row = this.db.prepare(`
39
+ SELECT user_id, external_project_id, external_thread_id, workspace_id, thread_id, workspace_path, metadata_json, created_at, updated_at
40
+ FROM external_threads
41
+ WHERE user_id = ? AND external_project_id = ? AND external_thread_id = ?
42
+ `).get(userId, externalProjectId, externalThreadId);
43
+ return row ? this.toThread(row) : undefined;
44
+ }
45
+ getThreadByThreadId(threadId) {
46
+ const row = this.db.prepare(`
47
+ SELECT user_id, external_project_id, external_thread_id, workspace_id, thread_id, workspace_path, metadata_json, created_at, updated_at
48
+ FROM external_threads
49
+ WHERE thread_id = ?
50
+ `).get(threadId);
51
+ return row ? this.toThread(row) : undefined;
52
+ }
53
+ upsertThread(input) {
54
+ const now = new Date().toISOString();
55
+ const existing = this.getThread(input.userId, input.externalProjectId, input.externalThreadId);
56
+ this.db.prepare(`
57
+ INSERT INTO external_threads (
58
+ user_id, external_project_id, external_thread_id, workspace_id, thread_id, workspace_path,
59
+ metadata_json, created_at, updated_at
60
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
61
+ ON CONFLICT(user_id, external_project_id, external_thread_id) DO UPDATE SET
62
+ workspace_id = excluded.workspace_id,
63
+ thread_id = excluded.thread_id,
64
+ workspace_path = excluded.workspace_path,
65
+ metadata_json = excluded.metadata_json,
66
+ updated_at = excluded.updated_at
67
+ `).run(input.userId, input.externalProjectId, input.externalThreadId, input.workspaceId, input.threadId, input.workspacePath, JSON.stringify(input.metadata || existing?.metadata || {}), existing?.createdAt || input.createdAt || now, now);
68
+ return this.getThread(input.userId, input.externalProjectId, input.externalThreadId);
69
+ }
70
+ toProject(row) {
71
+ return {
72
+ userId: row.user_id,
73
+ externalProjectId: row.external_project_id,
74
+ workspaceId: row.workspace_id,
75
+ workspacePath: row.workspace_path,
76
+ displayName: row.display_name,
77
+ agentType: row.agent_type,
78
+ providerId: row.provider_id,
79
+ createdAt: row.created_at,
80
+ updatedAt: row.updated_at,
81
+ metadata: (0, utils_js_1.parseJson)(row.metadata_json, {}),
82
+ };
83
+ }
84
+ toThread(row) {
85
+ return {
86
+ userId: row.user_id,
87
+ externalProjectId: row.external_project_id,
88
+ externalThreadId: row.external_thread_id,
89
+ workspaceId: row.workspace_id,
90
+ threadId: row.thread_id,
91
+ workspacePath: row.workspace_path,
92
+ createdAt: row.created_at,
93
+ updatedAt: row.updated_at,
94
+ metadata: (0, utils_js_1.parseJson)(row.metadata_json, {}),
95
+ };
96
+ }
97
+ }
98
+ exports.LocalExternalStore = LocalExternalStore;
@@ -13,6 +13,8 @@ const scheduler_store_js_1 = require("./scheduler-store.js");
13
13
  const security_store_js_1 = require("./security-store.js");
14
14
  const thread_store_js_1 = require("./thread-store.js");
15
15
  const workspace_registry_store_js_1 = require("./workspace-registry-store.js");
16
+ const model_provider_store_js_1 = require("./model-provider-store.js");
17
+ const external_store_js_1 = require("./external-store.js");
16
18
  class LocalCoreAcpStore {
17
19
  db;
18
20
  threads;
@@ -22,6 +24,8 @@ class LocalCoreAcpStore {
22
24
  scheduler;
23
25
  automationMonitors;
24
26
  platform;
27
+ modelProviders;
28
+ external;
25
29
  constructor(userDataPath) {
26
30
  const dbPath = (0, node_path_1.join)(userDataPath, 'runtime', 'local-core.db');
27
31
  (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(dbPath), { recursive: true });
@@ -37,6 +41,8 @@ class LocalCoreAcpStore {
37
41
  this.scheduler = new scheduler_store_js_1.LocalSchedulerStore(this.db);
38
42
  this.automationMonitors = new automation_monitor_store_js_1.LocalAutomationMonitorStore(this.db);
39
43
  this.platform = new platform_store_js_1.LocalPlatformStore(this.db);
44
+ this.modelProviders = new model_provider_store_js_1.LocalModelProviderStore(this.db);
45
+ this.external = new external_store_js_1.LocalExternalStore(this.db);
40
46
  (0, schema_js_1.ensureLocalCoreAcpSchema)(this.db);
41
47
  }
42
48
  close() {
@@ -95,6 +101,33 @@ class LocalCoreAcpStore {
95
101
  touchWorkspaceRegistryEntry(workspaceId) {
96
102
  this.workspaceRegistry.touch(workspaceId);
97
103
  }
104
+ listModelProviders() {
105
+ return this.modelProviders.list();
106
+ }
107
+ getModelProvider(providerId) {
108
+ return this.modelProviders.get(providerId);
109
+ }
110
+ upsertModelProvider(input) {
111
+ return this.modelProviders.upsert(input);
112
+ }
113
+ deleteModelProvider(providerId) {
114
+ return this.modelProviders.delete(providerId);
115
+ }
116
+ getExternalProject(userId, externalProjectId) {
117
+ return this.external.getProject(userId, externalProjectId);
118
+ }
119
+ upsertExternalProject(input) {
120
+ return this.external.upsertProject(input);
121
+ }
122
+ getExternalThread(userId, externalProjectId, externalThreadId) {
123
+ return this.external.getThread(userId, externalProjectId, externalThreadId);
124
+ }
125
+ getExternalThreadByThreadId(threadId) {
126
+ return this.external.getThreadByThreadId(threadId);
127
+ }
128
+ upsertExternalThread(input) {
129
+ return this.external.upsertThread(input);
130
+ }
98
131
  getWorkspaceSecuritySettings(workspaceId) {
99
132
  return this.security.getWorkspaceSecuritySettings(workspaceId);
100
133
  }
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LocalModelProviderStore = void 0;
4
+ exports.slugifyProviderId = slugifyProviderId;
5
+ const utils_js_1 = require("./utils.js");
6
+ class LocalModelProviderStore {
7
+ db;
8
+ constructor(db) {
9
+ this.db = db;
10
+ }
11
+ list() {
12
+ const rows = this.db.prepare(`
13
+ SELECT id, name, api_key, base_url, model, models_json, thinking, env_json, created_at, updated_at
14
+ FROM model_providers
15
+ ORDER BY name ASC, id ASC
16
+ `).all();
17
+ return rows.map((row) => this.toProvider(row));
18
+ }
19
+ get(providerId) {
20
+ const row = this.db.prepare(`
21
+ SELECT id, name, api_key, base_url, model, models_json, thinking, env_json, created_at, updated_at
22
+ FROM model_providers
23
+ WHERE id = ?
24
+ `).get(providerId);
25
+ return row ? this.toProvider(row) : undefined;
26
+ }
27
+ upsert(input) {
28
+ const now = new Date().toISOString();
29
+ const requestedId = String(input.id || '').trim();
30
+ const id = requestedId || this.allocateProviderId(input.name);
31
+ const existing = this.get(id);
32
+ this.db.prepare(`
33
+ INSERT INTO model_providers (
34
+ id, name, api_key, base_url, model, models_json, thinking, env_json, created_at, updated_at
35
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
36
+ ON CONFLICT(id) DO UPDATE SET
37
+ name = excluded.name,
38
+ api_key = excluded.api_key,
39
+ base_url = excluded.base_url,
40
+ model = excluded.model,
41
+ models_json = excluded.models_json,
42
+ thinking = excluded.thinking,
43
+ env_json = excluded.env_json,
44
+ updated_at = excluded.updated_at
45
+ `).run(id, String(input.name || id).trim() || id, stringOrNull(input.api_key), stringOrNull(input.base_url), stringOrNull(input.model), JSON.stringify(Array.isArray(input.models) ? input.models : []), stringOrNull(input.thinking), JSON.stringify(input.env && typeof input.env === 'object' ? input.env : {}), existing?.createdAt || now, now);
46
+ return this.get(id);
47
+ }
48
+ delete(providerId) {
49
+ this.db.prepare('DELETE FROM model_providers WHERE id = ?').run(providerId);
50
+ return { deleted: true };
51
+ }
52
+ allocateProviderId(name) {
53
+ const base = slugifyProviderId(name, 'provider');
54
+ let candidate = base;
55
+ let index = 2;
56
+ while (this.get(candidate)) {
57
+ candidate = `${base}-${index}`;
58
+ index += 1;
59
+ }
60
+ return candidate;
61
+ }
62
+ toProvider(row) {
63
+ return {
64
+ id: row.id,
65
+ name: row.name,
66
+ api_key: row.api_key || undefined,
67
+ base_url: row.base_url || undefined,
68
+ model: row.model || undefined,
69
+ models: (0, utils_js_1.parseJson)(row.models_json, []),
70
+ thinking: row.thinking || undefined,
71
+ env: (0, utils_js_1.parseJson)(row.env_json, {}),
72
+ createdAt: row.created_at,
73
+ updatedAt: row.updated_at,
74
+ };
75
+ }
76
+ }
77
+ exports.LocalModelProviderStore = LocalModelProviderStore;
78
+ function slugifyProviderId(value, fallback) {
79
+ return String(value || '')
80
+ .trim()
81
+ .toLowerCase()
82
+ .replace(/[^a-z0-9._-]+/g, '-')
83
+ .replace(/^-+|-+$/g, '') || fallback;
84
+ }
85
+ function stringOrNull(value) {
86
+ const normalized = String(value || '').trim();
87
+ return normalized || null;
88
+ }
@@ -176,6 +176,49 @@ function ensureLocalCoreAcpSchema(db) {
176
176
  last_opened_at TEXT
177
177
  );
178
178
  CREATE INDEX IF NOT EXISTS idx_workspace_registry_updated ON workspace_registry (updated_at DESC);
179
+ CREATE TABLE IF NOT EXISTS model_providers (
180
+ id TEXT PRIMARY KEY,
181
+ name TEXT NOT NULL,
182
+ api_key TEXT,
183
+ base_url TEXT,
184
+ model TEXT,
185
+ models_json TEXT NOT NULL DEFAULT '[]',
186
+ thinking TEXT,
187
+ env_json TEXT NOT NULL DEFAULT '{}',
188
+ created_at TEXT NOT NULL,
189
+ updated_at TEXT NOT NULL
190
+ );
191
+ CREATE INDEX IF NOT EXISTS idx_model_providers_name ON model_providers (name);
192
+ CREATE TABLE IF NOT EXISTS external_projects (
193
+ user_id TEXT NOT NULL,
194
+ external_project_id TEXT NOT NULL,
195
+ workspace_id TEXT NOT NULL,
196
+ workspace_path TEXT NOT NULL,
197
+ display_name TEXT NOT NULL,
198
+ agent_type TEXT NOT NULL,
199
+ provider_id TEXT NOT NULL,
200
+ metadata_json TEXT NOT NULL DEFAULT '{}',
201
+ created_at TEXT NOT NULL,
202
+ updated_at TEXT NOT NULL,
203
+ PRIMARY KEY (user_id, external_project_id),
204
+ UNIQUE(workspace_id)
205
+ );
206
+ CREATE TABLE IF NOT EXISTS external_threads (
207
+ user_id TEXT NOT NULL,
208
+ external_project_id TEXT NOT NULL,
209
+ external_thread_id TEXT NOT NULL,
210
+ workspace_id TEXT NOT NULL,
211
+ thread_id TEXT NOT NULL,
212
+ workspace_path TEXT NOT NULL,
213
+ metadata_json TEXT NOT NULL DEFAULT '{}',
214
+ created_at TEXT NOT NULL,
215
+ updated_at TEXT NOT NULL,
216
+ PRIMARY KEY (user_id, external_project_id, external_thread_id),
217
+ UNIQUE(thread_id),
218
+ FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE
219
+ );
220
+ CREATE INDEX IF NOT EXISTS idx_external_threads_thread ON external_threads (thread_id);
221
+ CREATE INDEX IF NOT EXISTS idx_external_threads_workspace ON external_threads (workspace_id);
179
222
  CREATE TABLE IF NOT EXISTS agent_tasks (
180
223
  id TEXT PRIMARY KEY,
181
224
  workspace_id TEXT NOT NULL,