@kafca/agentdock 0.1.44 → 0.1.45

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 (113) hide show
  1. package/README.md +71 -11
  2. package/bin/agentdock.mjs +5 -5
  3. package/dist/renderer/assets/{Badge-Qk9qHovH.js → Badge-BIojwD6s.js} +1 -1
  4. package/dist/renderer/assets/{Chat-BK-fm0Xr.js → Chat-wjBR3XEX.js} +1 -1
  5. package/dist/renderer/assets/{ChatMarkdown-BKdW8OON.js → ChatMarkdown-DqszsPB_.js} +1 -1
  6. package/dist/renderer/assets/Config-BDCHs5A2.js +21 -0
  7. package/dist/renderer/assets/{CronList-Z3TvSRKP.js → CronList-CEWQYTov.js} +1 -1
  8. package/dist/renderer/assets/Dashboard-DrWftfk3.js +26 -0
  9. package/dist/renderer/assets/{EmptyState-kD9yAwtw.js → EmptyState-BqCx1oWr.js} +1 -1
  10. package/dist/renderer/assets/{KnowledgeDetail-BAMCLrjz.js → KnowledgeDetail-BwK8Gpfa.js} +2 -2
  11. package/dist/renderer/assets/{KnowledgeHome-ChvjOfrc.js → KnowledgeHome-hhDTZMDO.js} +3 -3
  12. package/dist/renderer/assets/{Logs-Cv0nth3j.js → Logs-C9xPEhFT.js} +1 -1
  13. package/dist/renderer/assets/{Modal-DwY4lecS.js → Modal-D2U4r2V0.js} +1 -1
  14. package/dist/renderer/assets/{MonitorList-JEvMoWUm.js → MonitorList-CayKbfwJ.js} +1 -1
  15. package/dist/renderer/assets/{Page-CnB9ePsb.js → Page-urf5GcT_.js} +1 -1
  16. package/dist/renderer/assets/ProjectDetail-B1GyAr7a.js +11 -0
  17. package/dist/renderer/assets/{ProjectList-D6CXumR_.js → ProjectList-BoipQypE.js} +1 -1
  18. package/dist/renderer/assets/{Select-zfoksLGB.js → Select-CAscWpy5.js} +1 -1
  19. package/dist/renderer/assets/{SessionChat-DUTh4WoM.js → SessionChat-BAVy5221.js} +1 -1
  20. package/dist/renderer/assets/{SessionList-D9o-lUst.js → SessionList-BZpuMvh0.js} +1 -1
  21. package/dist/renderer/assets/{ThreadChat-BYXlT-Kc.js → ThreadChat-JX7m9biG.js} +1 -1
  22. package/dist/renderer/assets/Workspace-CFW8otjU.js +14 -0
  23. package/dist/renderer/assets/{arrow-left-CNEAuElg.js → arrow-left-7HrNuS-V.js} +1 -1
  24. package/dist/renderer/assets/{book-open-BNJMhNI-.js → book-open-BDoAu82s.js} +1 -1
  25. package/dist/renderer/assets/{check-BnclClRU.js → check-DGlyq4xX.js} +1 -1
  26. package/dist/renderer/assets/{chevron-down-Bh53e0O-.js → chevron-down-Bb_3EHq9.js} +1 -1
  27. package/dist/renderer/assets/{circle-ClZTWCWO.js → circle-DIpqZnYw.js} +1 -1
  28. package/dist/renderer/assets/desktop-Ci1fnRDR.js +1 -0
  29. package/dist/renderer/assets/{desktop-C6q6wBae.js → desktop-Do_UrC-o.js} +5 -5
  30. package/dist/renderer/assets/en-CmAr_0e6.js +1 -0
  31. package/dist/renderer/assets/es-Cs9x52ca.js +1 -0
  32. package/dist/renderer/assets/{funnel-_5hsz0xI.js → funnel-Bbf0ERLP.js} +1 -1
  33. package/dist/renderer/assets/{heart-u4Q1aOqj.js → heart-CGqU-qfw.js} +1 -1
  34. package/dist/renderer/assets/{index-BNFx4uYv.js → index-Df-Trovt.js} +2 -2
  35. package/dist/renderer/assets/{index-B36eowj-.js → index-DwhTjLA1.js} +36 -36
  36. package/dist/renderer/assets/index-RaTga2BC.css +11 -0
  37. package/dist/renderer/assets/ja-DtvtBRTW.js +1 -0
  38. package/dist/renderer/assets/{pencil-DLh9QNmT.js → pencil-CHiy1MvK.js} +1 -1
  39. package/dist/renderer/assets/{play-BDTk9Scm.js → play-CGYnmC4Q.js} +1 -1
  40. package/dist/renderer/assets/{plug-BG0JHW6B.js → plug-Mexu4gyV.js} +1 -1
  41. package/dist/renderer/assets/{plus-BSc8POsz.js → plus-B_Q5-Oj7.js} +1 -1
  42. package/dist/renderer/assets/projects-C6bbkC-O.js +1 -0
  43. package/dist/renderer/assets/{save-D5AO_G6a.js → save-CJ_QFPw0.js} +1 -1
  44. package/dist/renderer/assets/{search-CniPk7Ey.js → search-D8Dkhsyq.js} +1 -1
  45. package/dist/renderer/assets/{server-GsXyQAZm.js → server-Cd96R7WL.js} +1 -1
  46. package/dist/renderer/assets/{session-utils-BDxzgTBC.js → session-utils-BNUbQBfF.js} +1 -1
  47. package/dist/renderer/assets/{sessions-CLcKA1Tt.js → sessions-BACfV8s1.js} +1 -1
  48. package/dist/renderer/assets/{shield-check-4i0E3hmf.js → shield-check-Cnn-yQUA.js} +1 -1
  49. package/dist/renderer/assets/{trash-2-DhtOCsO5.js → trash-2-oWCxwQ9D.js} +1 -1
  50. package/dist/renderer/assets/{user-BtC_pVXV.js → user-BzE9BwsY.js} +1 -1
  51. package/dist/renderer/assets/{wifi-off-BHPzPY_B.js → wifi-off-DHxnNkdY.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/local-core-lark-gateway.js +65 -10
  67. package/dist-electron/services/local-ai-core/src/channel/shared/session-command-runtime.js +4 -2
  68. package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js +139 -29
  69. package/dist-electron/services/local-ai-core/src/execution/agent-execution-backend.js +56 -0
  70. package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +18 -57
  71. package/dist-electron/services/local-ai-core/src/kernel/local-core-errors.js +255 -0
  72. package/dist-electron/services/local-ai-core/src/plugins/builtin/catalog.js +78 -0
  73. package/dist-electron/services/local-ai-core/src/router/workspace-route-config.js +3 -1
  74. package/dist-electron/services/local-ai-core/src/router/workspace-router.js +64 -9
  75. package/dist-electron/services/local-ai-core/src/runtime/agent-runtime-detector.js +5 -2
  76. package/dist-electron/services/local-ai-core/src/runtime/config-migration.js +152 -0
  77. package/dist-electron/services/local-ai-core/src/runtime/deployment-diagnostics.js +175 -0
  78. package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +330 -3
  79. package/dist-electron/services/local-ai-core/src/runtime/local-core-runtime-state.js +11 -2
  80. package/dist-electron/services/local-ai-core/src/runtime/provider-config-migration.js +32 -0
  81. package/dist-electron/services/local-ai-core/src/runtime/runtime-detection-service.js +76 -8
  82. package/dist-electron/services/local-ai-core/src/runtime/server-routes.js +62 -0
  83. package/dist-electron/services/local-ai-core/src/runtime/server.js +708 -4
  84. package/dist-electron/services/local-ai-core/src/runtime/standalone.js +13 -3
  85. package/dist-electron/services/local-ai-core/src/sandbox/opensandbox-client.js +113 -0
  86. package/dist-electron/services/local-ai-core/src/sandbox/sandbox-config.js +273 -0
  87. package/dist-electron/services/local-ai-core/src/sandbox/sandbox-manager.js +215 -0
  88. package/dist-electron/services/local-ai-core/src/sandbox/sandbox-stdio-proxy.js +188 -0
  89. package/dist-electron/services/local-ai-core/src/thread/slash-command-registry.js +3 -0
  90. package/dist-electron/services/local-ai-core/src/thread/thread-command-service.js +32 -0
  91. package/dist-electron/services/local-ai-core/src/thread/thread-slash-command-dispatcher.js +71 -0
  92. package/dist-electron/shared/desktop.js +85 -1
  93. package/dist-electron/tests/contracts/architecture-docs.test.js +83 -0
  94. package/dist-electron/tests/electron/plugin-kernel.test.js +225 -18
  95. package/dist-electron/tests/electron/runtime-detection-service.test.js +57 -5
  96. package/dist-electron/tests/electron/sandbox-config.test.js +541 -0
  97. package/dist-electron/tests/electron/workspace-task-store.test.js +257 -0
  98. package/dist-electron/tests/integration/lark-gateway.test.js +59 -0
  99. package/dist-electron/tests/integration/local-core-refactor.test.js +183 -20
  100. package/dist-electron/tests/integration/local-core-routes.test.js +34 -0
  101. package/package.json +2 -3
  102. package/dist/renderer/assets/Config-B93CQGvY.js +0 -16
  103. package/dist/renderer/assets/Dashboard-Ch3QkWwf.js +0 -26
  104. package/dist/renderer/assets/ProjectDetail-DJa3sMmU.js +0 -11
  105. package/dist/renderer/assets/Workspace-BrhSwRQ-.js +0 -14
  106. package/dist/renderer/assets/desktop-C5vBT0yM.js +0 -1
  107. package/dist/renderer/assets/en-D2SkCrhG.js +0 -1
  108. package/dist/renderer/assets/es-C8UBPhxM.js +0 -1
  109. package/dist/renderer/assets/index-CaQZSRbg.css +0 -11
  110. package/dist/renderer/assets/ja-ChF4vbBL.js +0 -1
  111. package/dist/renderer/assets/projects-DwfZwxDW.js +0 -1
  112. package/dist/renderer/assets/zh-DIcdHycM.js +0 -1
  113. package/dist/renderer/assets/zh-TW-BtRwreY_.js +0 -1
@@ -13,8 +13,8 @@ const local_core_slash_commands_js_1 = require("./local-core-slash-commands.js")
13
13
  const local_core_acp_progress_js_1 = require("./local-core-acp-progress.js");
14
14
  const index_js_1 = require("../agents/index.js");
15
15
  const scheduled_job_route_js_1 = require("../scheduler/scheduled-job-route.js");
16
- const thread_command_service_js_1 = require("../thread/thread-command-service.js");
17
- const session_command_service_js_1 = require("../thread/session-command-service.js");
16
+ const thread_slash_command_dispatcher_js_1 = require("../thread/thread-slash-command-dispatcher.js");
17
+ const local_core_errors_js_1 = require("../kernel/local-core-errors.js");
18
18
  const ACP_PROMPT_TIMEOUT_MS = 15 * 60 * 1000;
19
19
  class LocalCoreAcpBackend {
20
20
  options;
@@ -22,8 +22,7 @@ class LocalCoreAcpBackend {
22
22
  turnCoordinator;
23
23
  sessionCoordinator;
24
24
  responseProcessor;
25
- commandService;
26
- sessionCommandService;
25
+ slashCommands;
27
26
  constructor(options) {
28
27
  this.options = options;
29
28
  this.transport = new local_core_acp_transport_js_1.LocalCoreAcpTransport({
@@ -91,25 +90,28 @@ class LocalCoreAcpBackend {
91
90
  emitBridge: (event) => this.emitBridgeEvent(event),
92
91
  log: options.log,
93
92
  });
94
- this.commandService = new thread_command_service_js_1.ThreadCommandService({
95
- getThreadRow: (threadId) => this.options.store.getThreadRow(threadId),
96
- updateThreadAgentMode: (threadId, mode) => this.options.store.updateThreadAgentMode(threadId, mode),
97
- updateThreadAgentType: (threadId, agentType) => this.options.store.updateThreadAgentType(threadId, agentType),
98
- getLatestRunForThread: (threadId) => this.options.store.getLatestRunForThread(threadId),
99
- createAuditEvent: (input) => {
100
- this.options.store.createAuditEvent(input);
93
+ this.slashCommands = new thread_slash_command_dispatcher_js_1.ThreadSlashCommandDispatcher({
94
+ session: {
95
+ listThreads: (workspaceId) => this.listThreads(workspaceId),
96
+ getThread: (targetThreadId) => this.getThread(targetThreadId),
97
+ createThread: (workspaceId, title) => this.createThread(workspaceId, title),
98
+ renameThread: (targetThreadId, title) => this.renameThread(targetThreadId, title),
99
+ deleteThread: (targetThreadId) => this.deleteThread(targetThreadId),
100
+ },
101
+ thread: {
102
+ getThreadRow: (threadId) => this.options.store.getThreadRow(threadId),
103
+ updateThreadAgentMode: (threadId, mode) => this.options.store.updateThreadAgentMode(threadId, mode),
104
+ updateThreadAgentType: (threadId, agentType) => this.options.store.updateThreadAgentType(threadId, agentType),
105
+ getLatestRunForThread: (threadId) => this.options.store.getLatestRunForThread(threadId),
106
+ createAuditEvent: (input) => {
107
+ this.options.store.createAuditEvent(input);
108
+ },
109
+ getAgentTypes: options.getAgentTypes,
110
+ setThreadMode: (threadId, mode) => this.sessionCoordinator.setThreadMode(threadId, mode),
111
+ closeThreadSession: (threadId) => this.sessionCoordinator.closeThreadSession(threadId),
112
+ interruptRun: (runId) => this.sessionCoordinator.interruptRun(runId),
113
+ log: options.log,
101
114
  },
102
- getAgentTypes: options.getAgentTypes,
103
- setThreadMode: (threadId, mode) => this.sessionCoordinator.setThreadMode(threadId, mode),
104
- closeThreadSession: (threadId) => this.sessionCoordinator.closeThreadSession(threadId),
105
- log: options.log,
106
- });
107
- this.sessionCommandService = new session_command_service_js_1.SessionCommandService({
108
- listThreads: (workspaceId) => this.listThreads(workspaceId),
109
- getThread: (targetThreadId) => this.getThread(targetThreadId),
110
- createThread: (workspaceId, title) => this.createThread(workspaceId, title),
111
- renameThread: (targetThreadId, title) => this.renameThread(targetThreadId, title),
112
- deleteThread: (targetThreadId) => this.deleteThread(targetThreadId),
113
115
  });
114
116
  this.responseProcessor = new local_core_acp_response_processor_js_1.LocalCoreAcpResponseProcessor({
115
117
  getScheduledDeliveryBinding: (threadId) => {
@@ -173,22 +175,24 @@ class LocalCoreAcpBackend {
173
175
  source: 'user',
174
176
  },
175
177
  });
176
- const sessionCommandResult = await this.sessionCommandService.execute(content, {
177
- currentThreadId: threadId,
178
+ const slashCommandResult = await this.slashCommands.execute({
179
+ threadId,
178
180
  workspaceId: row.workspace_id,
181
+ content,
182
+ defaultAgentType: config.agentType,
179
183
  defaultTitle: `New thread ${new Date().toLocaleTimeString()}`,
180
184
  });
181
- if (sessionCommandResult.handled) {
182
- const activeThreadEffect = (sessionCommandResult.effects || [])
185
+ if (slashCommandResult.handled) {
186
+ const activeThreadEffect = (slashCommandResult.effects || [])
183
187
  .find((effect) => effect.type === 'activate_thread');
184
- this.options.store.appendMessage(threadId, 'assistant', sessionCommandResult.displayText, 'final');
188
+ this.options.store.appendMessage(threadId, 'assistant', slashCommandResult.displayText, 'final');
185
189
  this.options.eventBus.emit({
186
190
  type: 'thread.message.accepted',
187
191
  payload: {
188
192
  threadId,
189
193
  workspaceId: row.workspace_id,
190
194
  role: 'assistant',
191
- content: sessionCommandResult.displayText,
195
+ content: slashCommandResult.displayText,
192
196
  kind: 'final',
193
197
  source: 'system',
194
198
  },
@@ -207,37 +211,7 @@ class LocalCoreAcpBackend {
207
211
  this.emitBridgeEvent({
208
212
  type: 'reply',
209
213
  sessionKey: row.bridge_session_key,
210
- content: sessionCommandResult.displayText,
211
- });
212
- this.emitBridgeEvent({
213
- type: 'typing_stop',
214
- sessionKey: row.bridge_session_key,
215
- });
216
- return { runId: '' };
217
- }
218
- const commandResult = await this.commandService.execute({
219
- threadId,
220
- workspaceId: row.workspace_id,
221
- content,
222
- defaultAgentType: config.agentType,
223
- });
224
- if (commandResult.handled) {
225
- this.options.store.appendMessage(threadId, 'assistant', commandResult.displayText, 'final');
226
- this.options.eventBus.emit({
227
- type: 'thread.message.accepted',
228
- payload: {
229
- threadId,
230
- workspaceId: row.workspace_id,
231
- role: 'assistant',
232
- content: commandResult.displayText,
233
- kind: 'final',
234
- source: 'system',
235
- },
236
- });
237
- this.emitBridgeEvent({
238
- type: 'reply',
239
- sessionKey: row.bridge_session_key,
240
- content: commandResult.displayText,
214
+ content: slashCommandResult.displayText,
241
215
  });
242
216
  this.emitBridgeEvent({
243
217
  type: 'typing_stop',
@@ -257,6 +231,24 @@ class LocalCoreAcpBackend {
257
231
  title: content.trim().slice(0, 80) || row.title || 'Agent task',
258
232
  prompt: content,
259
233
  status: 'running',
234
+ metadata: {
235
+ execution: config.execution || {
236
+ mode: config.sandbox?.enabled ? 'sandbox' : 'local',
237
+ transport: config.sandbox?.enabled ? `sandbox-${config.sandbox.transport}-stdio-proxy` : 'stdio',
238
+ },
239
+ ...(config.sandbox?.enabled
240
+ ? {
241
+ sandbox: {
242
+ provider: config.sandbox.provider,
243
+ image: config.sandbox.image,
244
+ transport: config.sandbox.transport,
245
+ acpPort: config.sandbox.acpPort,
246
+ stateScope: config.sandbox.stateScope,
247
+ stateMount: config.sandbox.stateMount || null,
248
+ },
249
+ }
250
+ : {}),
251
+ },
260
252
  });
261
253
  this.options.eventBus.emit({
262
254
  type: 'run.started',
@@ -320,6 +312,12 @@ class LocalCoreAcpBackend {
320
312
  async interruptRun(runId) {
321
313
  return this.sessionCoordinator.interruptRun(runId);
322
314
  }
315
+ async setThreadMode(threadId, mode) {
316
+ return this.sessionCoordinator.setThreadMode(threadId, mode);
317
+ }
318
+ closeThreadSession(threadId) {
319
+ this.sessionCoordinator.closeThreadSession(threadId);
320
+ }
323
321
  async runPrompt(threadId, runId, bridgeSessionKey, config, input, options = {}) {
324
322
  const row = this.options.store.getThreadRow(threadId);
325
323
  if (!row) {
@@ -333,11 +331,14 @@ class LocalCoreAcpBackend {
333
331
  const message = (0, local_core_acp_content_js_1.normalizeThreadMessageInput)(input);
334
332
  const content = message.displayText;
335
333
  let session = null;
334
+ const runStartedAt = Date.now();
336
335
  try {
337
336
  session = await this.sessionCoordinator.ensureSession(threadId, bridgeSessionKey, config, {
338
337
  permissionMode: options.permissionMode,
339
338
  runtimeEnv: options.runtimeEnv,
339
+ runId,
340
340
  });
341
+ this.options.log?.(`[acp.run:${runId}] session ready in ${Date.now() - runStartedAt}ms`);
341
342
  const priorAssistantFinalMessages = this.options.store
342
343
  .getThread(threadId, [])
343
344
  .messages
@@ -385,8 +386,10 @@ class LocalCoreAcpBackend {
385
386
  messageId: (0, node_crypto_1.randomUUID)(),
386
387
  prompt: message.contentParts,
387
388
  }, ACP_PROMPT_TIMEOUT_MS);
389
+ this.options.log?.(`[acp.run:${runId}] prompt sent in ${Date.now() - runStartedAt}ms`);
388
390
  session.promptPromise = promptPromise;
389
391
  const result = await promptPromise;
392
+ this.options.log?.(`[acp.run:${runId}] prompt completed in ${Date.now() - runStartedAt}ms`);
390
393
  const currentTurn = session.currentTurn;
391
394
  if (!currentTurn || currentTurn.runId !== runId) {
392
395
  return;
@@ -492,13 +495,18 @@ class LocalCoreAcpBackend {
492
495
  });
493
496
  }
494
497
  catch (error) {
495
- const errorContent = `Agent error: ${error instanceof Error ? error.message : String(error)}`;
498
+ const errorInfo = (0, local_core_errors_js_1.toLocalCoreErrorInfo)(error, 'internal_error', {
499
+ threadId,
500
+ workspaceId: row.workspace_id,
501
+ runtimeId: config.agentType,
502
+ });
503
+ const errorContent = (0, local_core_errors_js_1.formatUserError)(errorInfo);
496
504
  this.options.store.updateRun(runId, threadId, 'failed');
497
505
  const task = this.options.store.getAgentTaskByRunId(runId);
498
506
  if (task) {
499
507
  this.options.store.updateAgentTask(task.taskId, {
500
508
  status: 'failed',
501
- error: error instanceof Error ? error.message : String(error),
509
+ error: errorInfo.message,
502
510
  });
503
511
  }
504
512
  this.options.store.appendMessage(threadId, 'assistant', errorContent, 'final');
@@ -519,7 +527,21 @@ class LocalCoreAcpBackend {
519
527
  runId,
520
528
  threadId,
521
529
  workspaceId: row.workspace_id,
522
- error: error instanceof Error ? error.message : String(error),
530
+ error: errorInfo.message,
531
+ errorInfo,
532
+ },
533
+ });
534
+ this.options.eventBus.emit({
535
+ type: 'localcore.error',
536
+ payload: {
537
+ scope: 'acp.run',
538
+ errorInfo,
539
+ context: {
540
+ threadId,
541
+ workspaceId: row.workspace_id,
542
+ runtimeId: config.agentType,
543
+ runId,
544
+ },
523
545
  },
524
546
  });
525
547
  this.emitBridgeEvent({
@@ -544,16 +566,43 @@ class LocalCoreAcpBackend {
544
566
  if (session) {
545
567
  session.promptPromise = null;
546
568
  }
569
+ if (config.sandbox?.enabled) {
570
+ this.sessionCoordinator.releaseThreadSession(threadId, config);
571
+ }
547
572
  }
548
573
  }
549
574
  handleAgentRequest(session, payload) {
550
575
  this.turnCoordinator.handleAgentRequest(session, payload);
551
576
  }
552
577
  handleAgentNotification(session, payload) {
578
+ if (session.currentTurn && !session.currentTurn.firstAgentUpdateLogged) {
579
+ session.currentTurn.firstAgentUpdateLogged = true;
580
+ this.options.log?.(`[acp.run:${session.currentTurn.runId}] first agent update received`);
581
+ }
553
582
  this.turnCoordinator.handleAgentNotification(session, payload);
554
583
  }
555
584
  handleTransportSessionClosed(session, error) {
585
+ const row = this.options.store.getThreadRow(session.threadId);
586
+ const runtimeId = session.currentTurn?.agentType || row?.agent_type || '';
587
+ const errorInfo = (0, local_core_errors_js_1.toLocalCoreErrorInfo)(error, 'runtime_exited', {
588
+ threadId: session.threadId,
589
+ workspaceId: row?.workspace_id || '',
590
+ runtimeId,
591
+ });
556
592
  this.sessionCoordinator.handleTransportSessionClosed(session, error);
593
+ this.options.eventBus.emit({
594
+ type: 'localcore.error',
595
+ payload: {
596
+ scope: 'acp.session',
597
+ errorInfo,
598
+ context: {
599
+ threadId: session.threadId,
600
+ workspaceId: row?.workspace_id || '',
601
+ runtimeId,
602
+ runId: session.currentRunId || '',
603
+ },
604
+ },
605
+ });
557
606
  }
558
607
  emitBridgeEvent(event) {
559
608
  if (event.replyCtx) {
@@ -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
+ }