@kafca/agentdock 0.1.47 → 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 (69) hide show
  1. package/README.md +5 -0
  2. package/dist/renderer/assets/{Badge-BYREM3t3.js → Badge-C92lMj9Q.js} +1 -1
  3. package/dist/renderer/assets/{Chat-Dtb5X7sB.js → Chat-DqoKCPwz.js} +1 -1
  4. package/dist/renderer/assets/{ChatMarkdown-_slLloCh.js → ChatMarkdown-C4psQvmt.js} +1 -1
  5. package/dist/renderer/assets/{Config-pHichs7H.js → Config-1uk8FQL7.js} +2 -2
  6. package/dist/renderer/assets/{CronList-CJQMeCjz.js → CronList-DnKCiYWC.js} +1 -1
  7. package/dist/renderer/assets/{Dashboard-CY77Imde.js → Dashboard-rOAQcJA0.js} +2 -2
  8. package/dist/renderer/assets/{EmptyState-DCqmct2y.js → EmptyState-bF6Sc61O.js} +1 -1
  9. package/dist/renderer/assets/{KnowledgeDetail-DFrxa-pv.js → KnowledgeDetail-C5-Dp95R.js} +2 -2
  10. package/dist/renderer/assets/{KnowledgeHome-DL-vAfrH.js → KnowledgeHome-DGa1EWXD.js} +2 -2
  11. package/dist/renderer/assets/{Logs-jw9Uv590.js → Logs-CKKVW2k8.js} +1 -1
  12. package/dist/renderer/assets/{Modal-ChSm7tk6.js → Modal-DIc9HxD5.js} +3 -3
  13. package/dist/renderer/assets/{MonitorList-DrEwyS71.js → MonitorList-D2YOJRIX.js} +1 -1
  14. package/dist/renderer/assets/{Page-DhD-zgMU.js → Page-vZ-_l6Kn.js} +1 -1
  15. package/dist/renderer/assets/{ProjectDetail-BVSjpfpL.js → ProjectDetail-CqTa6kAh.js} +2 -2
  16. package/dist/renderer/assets/{ProjectList-CHGbTJjk.js → ProjectList-LgDQVVyZ.js} +1 -1
  17. package/dist/renderer/assets/{Select-rHs9CSam.js → Select-BN2HMOmY.js} +1 -1
  18. package/dist/renderer/assets/SessionChat-Beco1N1T.js +1 -0
  19. package/dist/renderer/assets/{SessionList-Mr1uAnRW.js → SessionList-3NkskCJJ.js} +1 -1
  20. package/dist/renderer/assets/ThreadChat-Ji8pOm5p.js +28 -0
  21. package/dist/renderer/assets/{Workspace-MRRg8ndJ.js → Workspace-yarrGItB.js} +1 -1
  22. package/dist/renderer/assets/{arrow-left-BU2NB2LA.js → arrow-left-DNovvfPp.js} +1 -1
  23. package/dist/renderer/assets/{book-open-D-TGvWwa.js → book-open-CarZFsXv.js} +1 -1
  24. package/dist/renderer/assets/{bot-IYNh8tH5.js → bot-g16rGMp6.js} +1 -1
  25. package/dist/renderer/assets/{check-CCZ0JO_S.js → check-DjFX73Zi.js} +1 -1
  26. package/dist/renderer/assets/{chevron-down-00bRZi15.js → chevron-down-B02YlQS_.js} +1 -1
  27. package/dist/renderer/assets/{circle-CpQ8x4k_.js → circle-DPfWdG1F.js} +1 -1
  28. package/dist/renderer/assets/{desktop-D0k2aGSj.js → desktop-DMkue1T6.js} +1 -1
  29. package/dist/renderer/assets/{funnel-UbcrCBCp.js → funnel-Dy1VL-D9.js} +1 -1
  30. package/dist/renderer/assets/{heart-DvLsV7tL.js → heart-YjvGwq5-.js} +1 -1
  31. package/dist/renderer/assets/{index-B2L4G-b_.js → index-CZN1STMc.js} +2 -2
  32. package/dist/renderer/assets/index-CjCuR6DI.css +11 -0
  33. package/dist/renderer/assets/{index-dy-g2Bh4.js → index-mvjDN9uV.js} +9 -9
  34. package/dist/renderer/assets/{pencil-qBYOgW5M.js → pencil-qpMjLIFd.js} +1 -1
  35. package/dist/renderer/assets/{play-Dn6b4AGj.js → play-CPCcNQcH.js} +1 -1
  36. package/dist/renderer/assets/{plus-CaApUwEm.js → plus-B76JWGKd.js} +1 -1
  37. package/dist/renderer/assets/projects-C1nFINjh.js +1 -0
  38. package/dist/renderer/assets/{save-CN6yvTfD.js → save-DtvrD3W1.js} +1 -1
  39. package/dist/renderer/assets/{search-Dn2MxYiO.js → search-Ddth-YHH.js} +1 -1
  40. package/dist/renderer/assets/{server-DpcVLHeh.js → server-D0zmghb3.js} +1 -1
  41. package/dist/renderer/assets/{session-utils-DnD2Q_y1.js → session-utils-C9oe-21F.js} +1 -1
  42. package/dist/renderer/assets/{sessions-eK5D4FLt.js → sessions-BXOnLLah.js} +1 -1
  43. package/dist/renderer/assets/{shield-check-DSSJkacw.js → shield-check-Cv5d_blH.js} +1 -1
  44. package/dist/renderer/assets/{trash-2-DmYJlEvO.js → trash-2-C2mimX2b.js} +1 -1
  45. package/dist/renderer/assets/{user-Cvf_uiyQ.js → user-BrLJ4elW.js} +1 -1
  46. package/dist/renderer/assets/{wifi-off-CCTloj-V.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 +51 -6
  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/channel/shared/base-channel-gateway.js +4 -1
  55. package/dist-electron/services/local-ai-core/src/channel/shared/session-command-runtime.js +14 -3
  56. package/dist-electron/services/local-ai-core/src/router/workspace-router.js +8 -1
  57. package/dist-electron/services/local-ai-core/src/runtime/handlers/thread-handler.js +4 -0
  58. package/dist-electron/services/local-ai-core/src/runtime/server-routes.js +3 -0
  59. package/dist-electron/services/local-ai-core/src/thread/thread-command-service.js +23 -2
  60. package/dist-electron/services/local-ai-core/src/thread/thread-slash-command-dispatcher.js +1 -0
  61. package/dist-electron/src/pages/Threads/thread-chat-model.js +2 -0
  62. package/dist-electron/tests/electron/workspace-task-store.test.js +22 -3
  63. package/dist-electron/tests/integration/local-core-refactor.test.js +177 -0
  64. package/dist-electron/tests/integration/local-core-routes.test.js +45 -0
  65. package/package.json +2 -2
  66. package/dist/renderer/assets/SessionChat-CDc2FNMA.js +0 -1
  67. package/dist/renderer/assets/ThreadChat-Detu5UfN.js +0 -23
  68. package/dist/renderer/assets/index-CO4fGqmv.css +0 -11
  69. package/dist/renderer/assets/projects-BwF5RlNV.js +0 -1
@@ -1,4 +1,4 @@
1
- import{c as a}from"./index-dy-g2Bh4.js";/**
1
+ import{c as a}from"./index-mvjDN9uV.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as o}from"./index-dy-g2Bh4.js";/**
1
+ import{c as o}from"./index-mvjDN9uV.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as e}from"./index-dy-g2Bh4.js";/**
1
+ import{c as e}from"./index-mvjDN9uV.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -0,0 +1 @@
1
+ import{D as t}from"./index-mvjDN9uV.js";const e=()=>t.get("/projects"),r=o=>t.get(`/projects/${o}`);export{r as g,e as l};
@@ -1,4 +1,4 @@
1
- import{c as a}from"./index-dy-g2Bh4.js";/**
1
+ import{c as a}from"./index-mvjDN9uV.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c}from"./index-dy-g2Bh4.js";/**
1
+ import{c}from"./index-mvjDN9uV.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as e}from"./index-dy-g2Bh4.js";/**
1
+ import{c as e}from"./index-mvjDN9uV.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as o}from"./index-dy-g2Bh4.js";/**
1
+ import{c as o}from"./index-mvjDN9uV.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1 +1 @@
1
- import{A as o}from"./index-dy-g2Bh4.js";const i=s=>o.get(`/projects/${s}/sessions`),c=(s,e,t)=>o.get(`/projects/${s}/sessions/${e}`,{history_limit:String(t)}),r=(s,e,t)=>o.patch(`/projects/${s}/sessions/${e}`,t),a=(s,e)=>o.delete(`/projects/${s}/sessions/${e}`),p=(s,e)=>o.post(`/projects/${s}/sessions/switch`,e),$=(s,e)=>o.post(`/projects/${s}/send`,e);export{$ as a,a as d,c as g,i as l,r,p as s};
1
+ import{D as o}from"./index-mvjDN9uV.js";const i=s=>o.get(`/projects/${s}/sessions`),c=(s,e,t)=>o.get(`/projects/${s}/sessions/${e}`,{history_limit:String(t)}),r=(s,e,t)=>o.patch(`/projects/${s}/sessions/${e}`,t),a=(s,e)=>o.delete(`/projects/${s}/sessions/${e}`),p=(s,e)=>o.post(`/projects/${s}/sessions/switch`,e),$=(s,e)=>o.post(`/projects/${s}/send`,e);export{$ as a,a as d,c as g,i as l,r,p as s};
@@ -1,4 +1,4 @@
1
- import{c}from"./index-dy-g2Bh4.js";/**
1
+ import{c}from"./index-mvjDN9uV.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as e}from"./index-dy-g2Bh4.js";/**
1
+ import{c as e}from"./index-mvjDN9uV.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c}from"./index-dy-g2Bh4.js";/**
1
+ import{c}from"./index-mvjDN9uV.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as a}from"./index-dy-g2Bh4.js";/**
1
+ import{c as a}from"./index-mvjDN9uV.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -5,8 +5,8 @@
5
5
  <link rel="icon" type="image/svg+xml" href="./favicon.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>AgentDock</title>
8
- <script type="module" crossorigin src="./assets/index-dy-g2Bh4.js"></script>
9
- <link rel="stylesheet" crossorigin href="./assets/index-CO4fGqmv.css">
8
+ <script type="module" crossorigin src="./assets/index-mvjDN9uV.js"></script>
9
+ <link rel="stylesheet" crossorigin href="./assets/index-CjCuR6DI.css">
10
10
  </head>
11
11
  <body class="bg-[#f5f5f7] dark:bg-[#151516]">
12
12
  <div id="root"></div>
@@ -85,6 +85,7 @@ exports.createThread = createThread;
85
85
  exports.getThread = getThread;
86
86
  exports.renameThread = renameThread;
87
87
  exports.updateThreadKnowledgeBases = updateThreadKnowledgeBases;
88
+ exports.updateThreadMode = updateThreadMode;
88
89
  exports.deleteThread = deleteThread;
89
90
  exports.sendMessage = sendMessage;
90
91
  exports.sendAction = sendAction;
@@ -498,6 +499,9 @@ async function renameThread(threadId, title) {
498
499
  async function updateThreadKnowledgeBases(threadId, knowledgeBaseIds) {
499
500
  return coreRequest('PATCH', `/threads/${encodeURIComponent(threadId)}/knowledge-bases`, { knowledgeBaseIds });
500
501
  }
502
+ async function updateThreadMode(threadId, mode) {
503
+ return coreRequest('PATCH', `/threads/${encodeURIComponent(threadId)}/mode`, { mode });
504
+ }
501
505
  async function deleteThread(threadId) {
502
506
  return coreRequest('DELETE', `/threads/${encodeURIComponent(threadId)}`);
503
507
  }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LocalCoreAcpSessionCoordinator = void 0;
4
4
  exports.buildAgentPath = buildAgentPath;
5
5
  const node_path_1 = require("node:path");
6
+ const node_fs_1 = require("node:fs");
6
7
  const node_os_1 = require("node:os");
7
8
  const scheduled_job_route_js_1 = require("../scheduler/scheduled-job-route.js");
8
9
  const env_utils_js_1 = require("../runtime/env-utils.js");
@@ -290,11 +291,14 @@ exports.LocalCoreAcpSessionCoordinator = LocalCoreAcpSessionCoordinator;
290
291
  function acpSessionCwd(config) {
291
292
  return config.sandbox?.enabled ? config.sandbox.workspaceMountPath : config.workDir;
292
293
  }
293
- function buildAgentPath(existingPath, cliBinDir) {
294
+ function buildAgentPath(existingPath, cliBinDir, options = {}) {
295
+ const platform = options.platform || process.platform;
296
+ const pathDelimiter = platform === 'win32' ? ';' : node_path_1.delimiter;
294
297
  const entries = [
295
298
  cliBinDir,
296
- ...userBinDirs(),
297
- ...String(existingPath || '').split(node_path_1.delimiter),
299
+ ...userBinDirs(platform),
300
+ ...windowsGitBashDirs(existingPath, platform, options.pathExists || node_fs_1.existsSync),
301
+ ...String(existingPath || '').split(pathDelimiter),
298
302
  ];
299
303
  const seen = new Set();
300
304
  return entries
@@ -306,12 +310,53 @@ function buildAgentPath(existingPath, cliBinDir) {
306
310
  seen.add(entry);
307
311
  return true;
308
312
  })
309
- .join(node_path_1.delimiter);
313
+ .join(pathDelimiter);
310
314
  }
311
- function userBinDirs() {
312
- if (process.platform === 'win32') {
315
+ function userBinDirs(platform) {
316
+ if (platform === 'win32') {
313
317
  return [];
314
318
  }
315
319
  const home = process.env.HOME || (0, node_os_1.homedir)();
316
320
  return home ? [`${home}/.local/bin`, `${home}/bin`] : [];
317
321
  }
322
+ function windowsGitBashDirs(existingPath, platform, pathExists) {
323
+ if (platform !== 'win32') {
324
+ return [];
325
+ }
326
+ const pathEntries = String(existingPath || '')
327
+ .split(';')
328
+ .map((entry) => entry.trim())
329
+ .filter(Boolean);
330
+ const gitRoots = new Set();
331
+ for (const entry of pathEntries) {
332
+ const normalized = node_path_1.win32.normalize(entry).replace(/[\\/]+$/, '');
333
+ const base = node_path_1.win32.basename(normalized).toLowerCase();
334
+ const parent = node_path_1.win32.dirname(normalized);
335
+ const parentBase = node_path_1.win32.basename(parent).toLowerCase();
336
+ const grandParent = node_path_1.win32.dirname(parent);
337
+ if (base === 'cmd' && parentBase === 'git') {
338
+ gitRoots.add(parent);
339
+ }
340
+ else if (base === 'bin' && parentBase === 'git') {
341
+ gitRoots.add(parent);
342
+ }
343
+ else if (base === 'bin' && parentBase === 'usr' && node_path_1.win32.basename(grandParent).toLowerCase() === 'git') {
344
+ gitRoots.add(grandParent);
345
+ }
346
+ }
347
+ for (const root of ['C:\\Program Files\\Git', 'C:\\Program Files (x86)\\Git', 'D:\\Program Files\\Git']) {
348
+ gitRoots.add(root);
349
+ }
350
+ const dirs = [];
351
+ for (const root of gitRoots) {
352
+ const binDir = node_path_1.win32.join(root, 'bin');
353
+ if (pathExists(node_path_1.win32.join(binDir, 'bash.exe'))) {
354
+ dirs.push(binDir);
355
+ }
356
+ const usrBinDir = node_path_1.win32.join(root, 'usr', 'bin');
357
+ if (pathExists(node_path_1.win32.join(usrBinDir, 'bash.exe'))) {
358
+ dirs.push(usrBinDir);
359
+ }
360
+ }
361
+ return dirs;
362
+ }
@@ -281,6 +281,9 @@ class LocalCoreAcpStore {
281
281
  upsertPlatformThreadBinding(input) {
282
282
  this.platform.upsertPlatformThreadBinding(input);
283
283
  }
284
+ updatePlatformThreadPreferredAgent(workspaceId, chatId, platformUserId, agentType, platform = 'lark') {
285
+ this.platform.updatePlatformThreadPreferredAgent(workspaceId, chatId, platformUserId, agentType, platform);
286
+ }
284
287
  updatePlatformThreadMessageId(workspaceId, chatId, platformUserId, messageId, platform = 'lark') {
285
288
  this.platform.updatePlatformThreadMessageId(workspaceId, chatId, platformUserId, messageId, platform);
286
289
  }
@@ -107,14 +107,14 @@ class LocalPlatformStore {
107
107
  }
108
108
  getPlatformThreadBinding(workspaceId, chatId, platformUserId, platform = 'lark') {
109
109
  return this.db.prepare(`
110
- SELECT workspace_id, platform, chat_id, platform_user_id, thread_id, last_platform_message_id, created_at, updated_at
110
+ SELECT workspace_id, platform, chat_id, platform_user_id, thread_id, last_platform_message_id, preferred_agent_type, created_at, updated_at
111
111
  FROM platform_thread_bindings
112
112
  WHERE workspace_id = ? AND platform = ? AND chat_id = ? AND platform_user_id = ?
113
113
  `).get(workspaceId, platform, chatId, platformUserId);
114
114
  }
115
115
  getPlatformThreadBindingByThreadId(threadId) {
116
116
  return this.db.prepare(`
117
- SELECT workspace_id, platform, chat_id, platform_user_id, thread_id, last_platform_message_id, created_at, updated_at
117
+ SELECT workspace_id, platform, chat_id, platform_user_id, thread_id, last_platform_message_id, preferred_agent_type, created_at, updated_at
118
118
  FROM platform_thread_bindings
119
119
  WHERE thread_id = ?
120
120
  ORDER BY updated_at DESC
@@ -127,13 +127,21 @@ class LocalPlatformStore {
127
127
  upsertPlatformThreadBinding(input) {
128
128
  this.db.prepare(`
129
129
  INSERT INTO platform_thread_bindings
130
- (workspace_id, platform, chat_id, platform_user_id, thread_id, last_platform_message_id, created_at, updated_at)
131
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
130
+ (workspace_id, platform, chat_id, platform_user_id, thread_id, last_platform_message_id, preferred_agent_type, created_at, updated_at)
131
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
132
132
  ON CONFLICT(workspace_id, platform, chat_id, platform_user_id) DO UPDATE SET
133
133
  thread_id = excluded.thread_id,
134
134
  last_platform_message_id = COALESCE(excluded.last_platform_message_id, platform_thread_bindings.last_platform_message_id),
135
+ preferred_agent_type = COALESCE(excluded.preferred_agent_type, platform_thread_bindings.preferred_agent_type),
135
136
  updated_at = excluded.updated_at
136
- `).run(input.workspace_id, input.platform || 'lark', input.chat_id, input.platform_user_id, input.thread_id, input.last_platform_message_id, input.created_at, input.updated_at);
137
+ `).run(input.workspace_id, input.platform || 'lark', input.chat_id, input.platform_user_id, input.thread_id, input.last_platform_message_id, input.preferred_agent_type ?? null, input.created_at, input.updated_at);
138
+ }
139
+ updatePlatformThreadPreferredAgent(workspaceId, chatId, platformUserId, agentType, platform = 'lark') {
140
+ this.db.prepare(`
141
+ UPDATE platform_thread_bindings
142
+ SET preferred_agent_type = ?, updated_at = ?
143
+ WHERE workspace_id = ? AND platform = ? AND chat_id = ? AND platform_user_id = ?
144
+ `).run(agentType, new Date().toISOString(), workspaceId, platform, chatId, platformUserId);
137
145
  }
138
146
  updatePlatformThreadMessageId(workspaceId, chatId, platformUserId, messageId, platform = 'lark') {
139
147
  this.db.prepare(`
@@ -301,6 +301,7 @@ function ensureLocalCoreAcpSchema(db) {
301
301
  ensureColumn(db, 'messages', 'tool_call_json', 'TEXT');
302
302
  ensureColumn(db, 'messages', 'bridge_kind', 'TEXT');
303
303
  ensureColumn(db, 'messages', 'bridge_status', 'TEXT');
304
+ ensureColumn(db, 'platform_thread_bindings', 'preferred_agent_type', 'TEXT');
304
305
  ensureColumn(db, 'scheduled_jobs', 'execution_mode', "TEXT NOT NULL DEFAULT 'same-thread'");
305
306
  ensureColumn(db, 'scheduled_job_runs', 'platform_message_ids_json', 'TEXT');
306
307
  ensureColumn(db, 'scheduled_job_runs', 'delivery_mode', 'TEXT');
@@ -31,6 +31,7 @@ class LocalThreadStore {
31
31
  runId: undefined,
32
32
  bridgeSessionKey: row.bridge_session_key,
33
33
  agentType: row.agent_type,
34
+ agentMode: row.agent_mode,
34
35
  }));
35
36
  }
36
37
  count(workspaceId) {
@@ -57,6 +58,7 @@ class LocalThreadStore {
57
58
  excerpt: '',
58
59
  bridgeSessionKey,
59
60
  agentType,
61
+ agentMode: agentMode || 'default',
60
62
  messages: [],
61
63
  selectedKnowledgeBaseIds: [],
62
64
  pendingPermissionRequest: null,
@@ -84,6 +86,7 @@ class LocalThreadStore {
84
86
  excerpt: row.excerpt,
85
87
  bridgeSessionKey: row.bridge_session_key,
86
88
  agentType: row.agent_type,
89
+ agentMode: row.agent_mode,
87
90
  messages: messages.map((message) => ({
88
91
  id: message.id,
89
92
  role: message.role,
@@ -35,9 +35,12 @@ class BaseChannelGateway extends node_events_1.EventEmitter {
35
35
  getLatestRunForThread: (threadId) => options.store.getLatestRunForThread(threadId),
36
36
  createAuditEvent: (input) => options.store.createAuditEvent(input),
37
37
  getAgentTypes: () => options.getWorkspaceRouter().getAgentTypes(),
38
- setThreadMode: (threadId, mode) => options.getWorkspaceRouter().setThreadMode(threadId, mode),
38
+ setThreadMode: async (threadId, mode) => {
39
+ await options.getWorkspaceRouter().setThreadMode(threadId, mode);
40
+ },
39
41
  closeThreadSession: (threadId) => options.getWorkspaceRouter().closeThreadSession(threadId),
40
42
  interruptRun: (runId) => options.getWorkspaceRouter().interruptRun(runId),
43
+ setChannelPreferredAgent: (input) => options.store.updatePlatformThreadPreferredAgent(input.workspaceId, input.chatId, input.platformUserId, input.agentType, input.platform),
41
44
  log: options.log,
42
45
  },
43
46
  });
@@ -13,6 +13,11 @@ class ChannelSessionCommandRuntime {
13
13
  content: input.text,
14
14
  defaultTitle: input.defaultTitle,
15
15
  defaultAgentType: input.defaultAgentType,
16
+ channel: {
17
+ chatId: input.chatId,
18
+ platformUserId: input.platformUserId,
19
+ platform: input.platformKey,
20
+ },
16
21
  });
17
22
  if (!result.handled) {
18
23
  return { handled: false, threadId: input.currentThreadId };
@@ -20,7 +25,7 @@ class ChannelSessionCommandRuntime {
20
25
  let activeThreadId = input.currentThreadId;
21
26
  for (const effect of result.effects || []) {
22
27
  if (effect.type === 'created_thread') {
23
- this.inheritAgentMode(input.currentThreadId, effect.threadId);
28
+ this.inheritAgentState(input, input.currentThreadId, effect.threadId);
24
29
  }
25
30
  if (effect.type === 'activate_thread') {
26
31
  activeThreadId = effect.threadId;
@@ -30,11 +35,17 @@ class ChannelSessionCommandRuntime {
30
35
  await this.options.sendResult(input, result);
31
36
  return { handled: true, threadId: activeThreadId, result };
32
37
  }
33
- inheritAgentMode(sourceThreadId, targetThreadId) {
34
- const inheritedMode = this.options.store.getThreadRow?.(sourceThreadId)?.agent_mode || '';
38
+ inheritAgentState(input, sourceThreadId, targetThreadId) {
39
+ const sourceRow = this.options.store.getThreadRow?.(sourceThreadId);
40
+ const inheritedMode = sourceRow?.agent_mode || '';
35
41
  if (inheritedMode && inheritedMode !== 'default') {
36
42
  this.options.store.updateThreadAgentMode?.(targetThreadId, inheritedMode);
37
43
  }
44
+ const binding = this.options.store.getPlatformThreadBinding?.(input.workspaceId, input.chatId, input.platformUserId, input.platformKey);
45
+ const preferredAgent = binding?.preferred_agent_type || sourceRow?.agent_type || '';
46
+ if (preferredAgent) {
47
+ this.options.store.updateThreadAgentType?.(targetThreadId, preferredAgent);
48
+ }
38
49
  }
39
50
  activateThread(input, threadId) {
40
51
  const now = new Date().toISOString();
@@ -5,6 +5,7 @@ exports.createWorkspaceRouter = createWorkspaceRouter;
5
5
  const node_fs_1 = require("node:fs");
6
6
  const node_child_process_1 = require("node:child_process");
7
7
  const local_core_acp_backend_js_1 = require("../acp/local-core-acp-backend.js");
8
+ const local_core_slash_commands_js_1 = require("../acp/local-core-slash-commands.js");
8
9
  const workspace_thread_id_js_1 = require("../thread/workspace-thread-id.js");
9
10
  const command_risk_js_1 = require("../security/command-risk.js");
10
11
  const workspace_route_config_js_1 = require("./workspace-route-config.js");
@@ -226,7 +227,13 @@ class WorkspaceRouter {
226
227
  async setThreadMode(threadId, mode) {
227
228
  const { workspaceId } = (0, workspace_thread_id_js_1.decodeThreadId)(threadId);
228
229
  await this.getWorkspaceRoute(workspaceId);
229
- return this.localCoreAcp.setThreadMode(threadId, mode);
230
+ const normalizedMode = (0, local_core_slash_commands_js_1.normalizeAgentMode)(mode) || (String(mode || '').trim() === '' ? local_core_slash_commands_js_1.DEFAULT_AGENT_MODE : '');
231
+ if (!normalizedMode) {
232
+ throw new Error(`Unknown thread mode: ${mode}`);
233
+ }
234
+ this.store.updateThreadAgentMode(threadId, normalizedMode);
235
+ await this.localCoreAcp.setThreadMode(threadId, normalizedMode);
236
+ return this.getThread(threadId);
230
237
  }
231
238
  closeThreadSession(threadId) {
232
239
  this.localCoreAcp.closeThreadSession(threadId);
@@ -21,6 +21,10 @@ function registerThreadHandlers(map, workspaceRouter) {
21
21
  : [];
22
22
  (0, server_helpers_js_1.json)(res, 200, await workspaceRouter.updateThreadKnowledgeBases(route.threadId, knowledgeBaseIds));
23
23
  });
24
+ map.set('thread.update-mode', async (route, req, res) => {
25
+ const body = await (0, server_helpers_js_1.readJsonBody)(req);
26
+ (0, server_helpers_js_1.json)(res, 200, await workspaceRouter.setThreadMode(route.threadId, String(body.mode || '')));
27
+ });
24
28
  map.set('thread.rename', async (route, req, res) => {
25
29
  const body = await (0, server_helpers_js_1.readJsonBody)(req);
26
30
  (0, server_helpers_js_1.json)(res, 200, await workspaceRouter.renameThread(route.threadId, String(body.title || '')));
@@ -239,6 +239,9 @@ function parseThreadsRoute(method, segments) {
239
239
  if (method === 'PATCH' && segments.length === 3 && segments[2] === 'knowledge-bases') {
240
240
  return { name: 'thread.update-knowledge-bases', threadId };
241
241
  }
242
+ if (method === 'PATCH' && segments.length === 3 && segments[2] === 'mode') {
243
+ return { name: 'thread.update-mode', threadId };
244
+ }
242
245
  if (method === 'POST' && segments.length === 3 && segments[2] === 'messages') {
243
246
  return { name: 'thread.messages.send', threadId };
244
247
  }
@@ -27,7 +27,7 @@ class ThreadCommandService {
27
27
  names: ['agent'],
28
28
  execute: (command, input) => ({
29
29
  handled: true,
30
- displayText: this.executeAgentCommand(input.threadId, input.workspaceId, command.args, input.defaultAgentType),
30
+ displayText: this.executeAgentCommand(input.threadId, input.workspaceId, command.args, input.defaultAgentType, input.channel),
31
31
  }),
32
32
  });
33
33
  }
@@ -64,7 +64,7 @@ class ThreadCommandService {
64
64
  }
65
65
  return `已切换到 ${(0, local_core_slash_commands_js_1.formatAgentMode)(mode)} 模式。`;
66
66
  }
67
- executeAgentCommand(threadId, workspaceId, args, defaultAgentType) {
67
+ executeAgentCommand(threadId, workspaceId, args, defaultAgentType, channel) {
68
68
  const row = this.options.getThreadRow(threadId);
69
69
  const currentAgent = (0, local_core_slash_commands_js_1.normalizeAgentCommandTarget)(row?.agent_type || defaultAgentType);
70
70
  const defaultAgent = (0, local_core_slash_commands_js_1.normalizeAgentCommandTarget)(defaultAgentType);
@@ -84,6 +84,7 @@ class ThreadCommandService {
84
84
  }
85
85
  if (action === 'reset') {
86
86
  if (currentAgent === defaultAgent) {
87
+ this.persistChannelPreferredAgent(channel, workspaceId, null);
87
88
  return `当前线程已经使用默认 Agent:${defaultAgent}。`;
88
89
  }
89
90
  const activeRun = this.hasActiveRun(threadId);
@@ -91,6 +92,7 @@ class ThreadCommandService {
91
92
  if (!activeRun) {
92
93
  this.options.closeThreadSession?.(threadId);
93
94
  }
95
+ this.persistChannelPreferredAgent(channel, workspaceId, null);
94
96
  this.options.createAuditEvent({
95
97
  type: 'agent.changed',
96
98
  workspaceId,
@@ -114,6 +116,7 @@ class ThreadCommandService {
114
116
  return `Agent "${canonicalAgent}" 当前不可用。\n可用 Agent:${availableAgents.length ? availableAgents.join(', ') : '无'}`;
115
117
  }
116
118
  if (canonicalAgent === currentAgent) {
119
+ this.persistChannelPreferredAgent(channel, workspaceId, canonicalAgent);
117
120
  return `当前线程已经使用 Agent:${canonicalAgent}。`;
118
121
  }
119
122
  const activeRun = this.hasActiveRun(threadId);
@@ -121,6 +124,7 @@ class ThreadCommandService {
121
124
  if (!activeRun) {
122
125
  this.options.closeThreadSession?.(threadId);
123
126
  }
127
+ this.persistChannelPreferredAgent(channel, workspaceId, canonicalAgent);
124
128
  const runningNote = activeRun
125
129
  ? `\n当前正在运行的任务仍会继续使用 ${currentAgent},下一轮开始生效。`
126
130
  : '';
@@ -133,6 +137,23 @@ class ThreadCommandService {
133
137
  });
134
138
  return `已将当前线程 Agent 切换为 ${canonicalAgent}。\n后续消息将使用 ${canonicalAgent} 处理。${runningNote}`;
135
139
  }
140
+ persistChannelPreferredAgent(channel, workspaceId, agentType) {
141
+ if (!channel || !this.options.setChannelPreferredAgent) {
142
+ return;
143
+ }
144
+ try {
145
+ this.options.setChannelPreferredAgent({
146
+ workspaceId,
147
+ chatId: channel.chatId,
148
+ platformUserId: channel.platformUserId,
149
+ platform: channel.platform,
150
+ agentType,
151
+ });
152
+ }
153
+ catch (error) {
154
+ this.options.log?.(`setChannelPreferredAgent failed: ${error instanceof Error ? error.message : String(error)}`);
155
+ }
156
+ }
136
157
  async executeStopCommand(threadId, workspaceId) {
137
158
  const latestRun = this.options.getLatestRunForThread(threadId);
138
159
  if (!latestRun || !['queued', 'running', 'awaiting_input'].includes(latestRun.status)) {
@@ -48,6 +48,7 @@ class ThreadSlashCommandDispatcher {
48
48
  workspaceId: context.workspaceId,
49
49
  content: context.content,
50
50
  defaultAgentType: context.defaultAgentType,
51
+ channel: context.channel,
51
52
  });
52
53
  }
53
54
  return { handled: false, displayText: '' };
@@ -170,6 +170,7 @@ function toChatThreadSummary(project, session) {
170
170
  updatedAt: session.updated_at,
171
171
  excerpt: extractVisibleMessageContent(session.last_message?.content || ''),
172
172
  agentType: session.agent_type,
173
+ agentMode: undefined,
173
174
  bridgeSessionKey: session.session_key,
174
175
  };
175
176
  }
@@ -183,6 +184,7 @@ function toCoreChatThreadSummary(thread) {
183
184
  updatedAt: thread.updatedAt,
184
185
  excerpt: extractVisibleMessageContent(thread.excerpt),
185
186
  agentType: thread.agentType,
187
+ agentMode: thread.agentMode,
186
188
  bridgeSessionKey: thread.bridgeSessionKey,
187
189
  };
188
190
  }
@@ -585,7 +585,7 @@ const env_utils_js_1 = require("../../services/local-ai-core/src/runtime/env-uti
585
585
  process.env.HOME = '/home/agentdock-test';
586
586
  try {
587
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');
588
+ const path = (0, local_core_acp_session_coordinator_js_1.buildAgentPath)(existingPath, '/opt/agentdock/bin', { pathExists: () => false });
589
589
  const expectedEntries = process.platform === 'win32'
590
590
  ? ['/opt/agentdock/bin', '/usr/bin', '/bin']
591
591
  : ['/opt/agentdock/bin', '/home/agentdock-test/.local/bin', '/home/agentdock-test/bin', '/usr/bin', '/bin'];
@@ -600,6 +600,21 @@ const env_utils_js_1 = require("../../services/local-ai-core/src/runtime/env-uti
600
600
  }
601
601
  }
602
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
+ });
603
618
  (0, node_test_1.default)('ACP runtime reads Windows Path env regardless of key casing', () => {
604
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');
605
620
  strict_1.default.equal((0, env_utils_js_1.getPathEnv)({ PATH: '/usr/bin:/bin' }), '/usr/bin:/bin');
@@ -635,8 +650,9 @@ const env_utils_js_1 = require("../../services/local-ai-core/src/runtime/env-uti
635
650
  });
636
651
  (0, node_test_1.default)('ACP scheduled session can override permission mode without changing thread mode', async () => {
637
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;
638
654
  try {
639
- const store = new local_core_acp_store_js_1.LocalCoreAcpStore(userDataPath);
655
+ store = new local_core_acp_store_js_1.LocalCoreAcpStore(userDataPath);
640
656
  const thread = store.createThread('workspace-a', 'Thread');
641
657
  const capturedSessionNewParams = [];
642
658
  let closeCount = 0;
@@ -702,13 +718,15 @@ const env_utils_js_1 = require("../../services/local-ai-core/src/runtime/env-uti
702
718
  });
703
719
  }
704
720
  finally {
721
+ store?.close();
705
722
  (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
706
723
  }
707
724
  });
708
725
  (0, node_test_1.default)('ACP sandbox sessions use container workspace cwd', async () => {
709
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;
710
728
  try {
711
- const store = new local_core_acp_store_js_1.LocalCoreAcpStore(userDataPath);
729
+ store = new local_core_acp_store_js_1.LocalCoreAcpStore(userDataPath);
712
730
  const thread = store.createThread('workspace-a', 'Thread');
713
731
  let sessionNewParams;
714
732
  const coordinator = new local_core_acp_session_coordinator_js_1.LocalCoreAcpSessionCoordinator({
@@ -779,6 +797,7 @@ const env_utils_js_1 = require("../../services/local-ai-core/src/runtime/env-uti
779
797
  strict_1.default.equal(sessionNewParams?.cwd, '/workspace');
780
798
  }
781
799
  finally {
800
+ store?.close();
782
801
  (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
783
802
  }
784
803
  });