@kafca/agentdock 0.1.24 → 0.1.26

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 (83) hide show
  1. package/README.md +2 -0
  2. package/dist/renderer/assets/{Badge-CXcUMqb-.js → Badge-qZHTUGI3.js} +1 -1
  3. package/dist/renderer/assets/{Chat-BRxdPzi-.js → Chat-W35BzaZk.js} +1 -1
  4. package/dist/renderer/assets/{ChatMarkdown-sWGBIfIb.js → ChatMarkdown-DxKT_YGp.js} +1 -1
  5. package/dist/renderer/assets/{Config-DqxuxIGg.js → Config-COTX6LDN.js} +1 -1
  6. package/dist/renderer/assets/{CronList-OgHxbti5.js → CronList-kxlfu9Iq.js} +1 -1
  7. package/dist/renderer/assets/{Dashboard-DmklXiH1.js → Dashboard-Y4PGxzuV.js} +2 -2
  8. package/dist/renderer/assets/{EmptyState-BvDmi8dq.js → EmptyState-Cjq33EOF.js} +1 -1
  9. package/dist/renderer/assets/{KnowledgeDetail-Bj59Hdpo.js → KnowledgeDetail-CztNt7i8.js} +1 -1
  10. package/dist/renderer/assets/{KnowledgeHome-CigQ-nSo.js → KnowledgeHome-BAOyy3I9.js} +1 -1
  11. package/dist/renderer/assets/{Logs-Bfc7gBpY.js → Logs-PRZ1IIEi.js} +1 -1
  12. package/dist/renderer/assets/{Modal-CfmiFknz.js → Modal-B1rgqkBM.js} +1 -1
  13. package/dist/renderer/assets/{Page-BxtLEw_b.js → Page-CjzAhgaU.js} +1 -1
  14. package/dist/renderer/assets/{ProjectDetail-CcUphxGm.js → ProjectDetail-XGh_5XxQ.js} +1 -1
  15. package/dist/renderer/assets/{ProjectList-CVkvDIfy.js → ProjectList-BkPTpQRZ.js} +1 -1
  16. package/dist/renderer/assets/{Select-DgQMZ0MD.js → Select-BZd1QJVw.js} +1 -1
  17. package/dist/renderer/assets/{SessionChat-CRmGKfBx.js → SessionChat-C3sOTWGT.js} +1 -1
  18. package/dist/renderer/assets/{SessionList-D0E8XbYw.js → SessionList-B5WK1big.js} +1 -1
  19. package/dist/renderer/assets/ThreadChat-DWiBLHC7.js +23 -0
  20. package/dist/renderer/assets/{Workspace-Q4g4rlRT.js → Workspace-CQ64VK-o.js} +1 -1
  21. package/dist/renderer/assets/{arrow-left-BySN_zhF.js → arrow-left-FLdaWHZ8.js} +1 -1
  22. package/dist/renderer/assets/{book-open-BGlrnrTc.js → book-open-DaSioPS9.js} +1 -1
  23. package/dist/renderer/assets/{check-BiJogEa5.js → check-BiSmsuGR.js} +1 -1
  24. package/dist/renderer/assets/{chevron-down-D2fHRJv6.js → chevron-down-pZ2KjmDR.js} +1 -1
  25. package/dist/renderer/assets/{circle-CIcjixmI.js → circle-296S3nw9.js} +1 -1
  26. package/dist/renderer/assets/{desktop-DyA_NYew.js → desktop-88jgnOZV.js} +1 -1
  27. package/dist/renderer/assets/{desktop-DAXY9Bxv.js → desktop-BkMW-h5p.js} +2 -2
  28. package/dist/renderer/assets/{funnel-BUzwb-id.js → funnel-Dvz31PLy.js} +1 -1
  29. package/dist/renderer/assets/{heart-DZEQ1z_V.js → heart-D9mxSZVO.js} +1 -1
  30. package/dist/renderer/assets/{index-DgsIo4vJ.js → index-CZA4lqvM.js} +1 -1
  31. package/dist/renderer/assets/{index-CB_KyLOr.js → index-dOdOWAa2.js} +3 -3
  32. package/dist/renderer/assets/{pencil-DGL4agcl.js → pencil-B2fRbaAo.js} +1 -1
  33. package/dist/renderer/assets/{play-DzasCLM4.js → play-CWPRHypD.js} +1 -1
  34. package/dist/renderer/assets/{plug-dubNMe_m.js → plug-B7IBVwSo.js} +1 -1
  35. package/dist/renderer/assets/{plus-DN4kML4k.js → plus-Cn3Awirs.js} +1 -1
  36. package/dist/renderer/assets/projects-CmCopAi6.js +1 -0
  37. package/dist/renderer/assets/{save-CNIc9tjl.js → save-MVjXRqom.js} +1 -1
  38. package/dist/renderer/assets/{search-C0EGbF3I.js → search-Bu5c7wTX.js} +1 -1
  39. package/dist/renderer/assets/{server-BMxQ4zmc.js → server-aDvfqBg_.js} +1 -1
  40. package/dist/renderer/assets/{session-utils-Bf5O-iMi.js → session-utils-DjDpjT9r.js} +1 -1
  41. package/dist/renderer/assets/{sessions-BMSs6rEI.js → sessions-D7dV8h0H.js} +1 -1
  42. package/dist/renderer/assets/{shield-check-i3Tf1TUz.js → shield-check-DJdEhc_e.js} +1 -1
  43. package/dist/renderer/assets/{trash-2-SKrcp18f.js → trash-2-BF3ejnXL.js} +1 -1
  44. package/dist/renderer/assets/{user-cNUZddjZ.js → user-BwuWCrin.js} +1 -1
  45. package/dist/renderer/assets/{wifi-off-Dyb84ANR.js → wifi-off-BahJZfDs.js} +1 -1
  46. package/dist/renderer/index.html +1 -1
  47. package/dist-electron/electron/agent-runtime-detector.test.js +64 -0
  48. package/dist-electron/electron/architecture-docs.test.js +56 -0
  49. package/dist-electron/electron/channel-content.test.js +8 -0
  50. package/dist-electron/electron/lac-cli.test.js +248 -0
  51. package/dist-electron/electron/local-core-refactor.test.js +795 -20
  52. package/dist-electron/electron/plugin-kernel.test.js +108 -0
  53. package/dist-electron/electron/renderer-packaging.test.js +77 -0
  54. package/dist-electron/electron/workspace-task-store.test.js +57 -0
  55. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-backend.js +67 -6
  56. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-permission-lifecycle.js +76 -0
  57. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-progress.js +201 -0
  58. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-session-coordinator.js +29 -0
  59. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-store.js +30 -18
  60. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-turn-coordinator.js +112 -210
  61. package/dist-electron/services/local-ai-core/src/acp/local-core-slash-commands.js +60 -0
  62. package/dist-electron/services/local-ai-core/src/channel/lark/local-core-lark-gateway.js +44 -27
  63. package/dist-electron/services/local-ai-core/src/channel/lark/runtime-state.js +104 -12
  64. package/dist-electron/services/local-ai-core/src/channel/shared/file-utils.js +13 -1
  65. package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js +8 -1
  66. package/dist-electron/services/local-ai-core/src/cli/lac.js +3 -1
  67. package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +1 -0
  68. package/dist-electron/services/local-ai-core/src/plugins/builtin/agent-localcore-acp-plugin.js +9 -0
  69. package/dist-electron/services/local-ai-core/src/router/workspace-route-config.js +221 -10
  70. package/dist-electron/services/local-ai-core/src/runtime/agent-runtime-detector.js +39 -9
  71. package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +1 -0
  72. package/dist-electron/services/local-ai-core/src/runtime/runtime-detection-service.js +1 -0
  73. package/dist-electron/services/local-ai-core/src/runtime/server-routes.js +439 -0
  74. package/dist-electron/services/local-ai-core/src/runtime/server.js +208 -303
  75. package/dist-electron/shared/desktop.js +7 -3
  76. package/dist-electron/src/pages/Threads/thread-chat-message-blocks.js +187 -0
  77. package/dist-electron/src/pages/Threads/thread-chat-model.js +1 -0
  78. package/dist-electron/src/pages/Threads/thread-chat-page-state.js +96 -0
  79. package/dist-electron/src/pages/Threads/thread-chat-permission.js +18 -0
  80. package/dist-electron/src/pages/Threads/thread-chat-permission.test.js +277 -0
  81. package/package.json +4 -2
  82. package/dist/renderer/assets/ThreadChat-DUgqZSBB.js +0 -23
  83. package/dist/renderer/assets/projects-migPtfRe.js +0 -1
@@ -2,8 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createLarkTurnState = createLarkTurnState;
4
4
  exports.foldToolResultForLark = foldToolResultForLark;
5
+ exports.summarizeToolCallForLark = summarizeToolCallForLark;
6
+ exports.isPendingToolProgressForLark = isPendingToolProgressForLark;
5
7
  exports.consumeLarkBridgeEvent = consumeLarkBridgeEvent;
6
8
  exports.renderLarkBridgeEventMessage = renderLarkBridgeEventMessage;
9
+ exports.renderLarkBridgeEventMessages = renderLarkBridgeEventMessages;
7
10
  exports.getLarkRenderedMessageId = getLarkRenderedMessageId;
8
11
  exports.setLarkRenderedMessageId = setLarkRenderedMessageId;
9
12
  function createLarkTurnState(sessionKey, sourceMessageId) {
@@ -16,6 +19,8 @@ function createLarkTurnState(sessionKey, sourceMessageId) {
16
19
  finalText: '',
17
20
  progressMessageIds: {},
18
21
  thinkingSteps: [],
22
+ pendingThoughtKey: undefined,
23
+ pendingThoughtText: undefined,
19
24
  toolCalls: [],
20
25
  statusLines: [],
21
26
  buttonRows: [],
@@ -33,6 +38,21 @@ function foldToolResultForLark(content) {
33
38
  return normalized;
34
39
  return parts.slice(0, 2).join(' - ');
35
40
  }
41
+ function summarizeToolCallForLark(event, content) {
42
+ const toolCall = event.toolCall;
43
+ if (toolCall) {
44
+ const name = String(toolCall.name || '').trim() || summarizeToolContentForLark(content);
45
+ const input = formatToolInputForLark(toolCall.input);
46
+ return [name.startsWith('🔧 ') ? name : `🔧 ${name}`, input ? `参数:${input}` : '']
47
+ .filter(Boolean)
48
+ .join('\n\n');
49
+ }
50
+ return summarizeToolContentForLark(content);
51
+ }
52
+ function isPendingToolProgressForLark(content) {
53
+ const normalized = foldToolResultForLark(content).toLowerCase();
54
+ return normalized.startsWith('🔧 ') && normalized.includes(' - pending');
55
+ }
36
56
  function consumeLarkBridgeEvent(turn, event, options) {
37
57
  const content = foldToolResultForLark(String(event.content || '').trim());
38
58
  if (event.type === 'typing_start') {
@@ -45,6 +65,8 @@ function consumeLarkBridgeEvent(turn, event, options) {
45
65
  turn.previewText = '';
46
66
  turn.finalText = '';
47
67
  turn.thinkingSteps = [];
68
+ turn.pendingThoughtKey = undefined;
69
+ turn.pendingThoughtText = undefined;
48
70
  turn.toolCalls = [];
49
71
  turn.buttonRows = [];
50
72
  turn.statusLines = [];
@@ -56,6 +78,12 @@ function consumeLarkBridgeEvent(turn, event, options) {
56
78
  return;
57
79
  }
58
80
  if (event.type === 'preview_start' || event.type === 'update_message') {
81
+ if (content.startsWith('💭 ')) {
82
+ turn.pendingThoughtKey = event.previewHandle || 'thinking-preview';
83
+ turn.pendingThoughtText = content;
84
+ pushUniqueLarkTurnLine(turn.thinkingSteps, content.slice(3).trim());
85
+ return;
86
+ }
59
87
  turn.previewText = content;
60
88
  return;
61
89
  }
@@ -77,6 +105,8 @@ function consumeLarkBridgeEvent(turn, event, options) {
77
105
  return;
78
106
  }
79
107
  if (content.startsWith('💭 ')) {
108
+ turn.pendingThoughtKey = progressKey('thinking', event);
109
+ turn.pendingThoughtText = content;
80
110
  pushUniqueLarkTurnLine(turn.thinkingSteps, content.slice(3).trim());
81
111
  return;
82
112
  }
@@ -95,10 +125,13 @@ function renderLarkBridgeEventMessage(turn, event) {
95
125
  const content = foldToolResultForLark(String(event.content || '').trim());
96
126
  if (event.type === 'preview_start' || event.type === 'update_message') {
97
127
  if (content.startsWith('💭 ')) {
98
- return renderProgressMessage(event.previewHandle || 'thinking-preview', content);
128
+ return { key: 'noop', text: '', buttonRows: [], isFinal: false };
99
129
  }
100
130
  if (content.startsWith('🔧 ')) {
101
- return renderProgressMessage(progressKey('tool', event), content);
131
+ if (isPendingToolProgressForLark(content)) {
132
+ return { key: 'noop', text: '', buttonRows: [], isFinal: false };
133
+ }
134
+ return renderToolMessage(progressKey('tool', event), summarizeToolCallForLark(event, content));
102
135
  }
103
136
  return {
104
137
  key: 'final',
@@ -113,10 +146,13 @@ function renderLarkBridgeEventMessage(turn, event) {
113
146
  }
114
147
  if (event.type === 'reply') {
115
148
  if (content.startsWith('💭 ')) {
116
- return renderProgressMessage(progressKey('thinking', event), content);
149
+ return { key: 'noop', text: '', buttonRows: [], isFinal: false };
117
150
  }
118
151
  if (content.startsWith('🔧 ')) {
119
- return renderProgressMessage(progressKey('tool', event), content);
152
+ if (isPendingToolProgressForLark(content)) {
153
+ return { key: 'noop', text: '', buttonRows: [], isFinal: false };
154
+ }
155
+ return renderToolMessage(progressKey('tool', event), summarizeToolCallForLark(event, content));
120
156
  }
121
157
  if (content.startsWith('⏳ ') || content.startsWith('📤 ')) {
122
158
  return renderProgressMessage(progressKey('status', event), content);
@@ -139,23 +175,35 @@ function renderLarkBridgeEventMessage(turn, event) {
139
175
  isFinal: false,
140
176
  };
141
177
  }
178
+ function renderLarkBridgeEventMessages(turn, event) {
179
+ const rendered = renderLarkBridgeEventMessage(turn, event);
180
+ if (isThoughtBridgeEvent(event)) {
181
+ return [rendered];
182
+ }
183
+ const pendingThought = takePendingLarkThoughtRender(turn);
184
+ return pendingThought ? [pendingThought, rendered] : [rendered];
185
+ }
186
+ function takePendingLarkThoughtRender(turn) {
187
+ const text = String(turn.pendingThoughtText || '').trim();
188
+ if (!text) {
189
+ return null;
190
+ }
191
+ const rendered = renderProgressMessage(turn.pendingThoughtKey || 'thinking-preview', text);
192
+ turn.pendingThoughtKey = undefined;
193
+ turn.pendingThoughtText = undefined;
194
+ return rendered;
195
+ }
142
196
  function getLarkRenderedMessageId(turn, rendered) {
143
197
  if (rendered.isFinal) {
144
- if (rendered.finalSource === 'reply') {
145
- return turn.replyMessageId;
146
- }
147
- return turn.finalMessageId || turn.messageId;
198
+ return turn.finalMessageId || turn.messageId || turn.replyMessageId;
148
199
  }
149
200
  return turn.progressMessageIds[rendered.key];
150
201
  }
151
202
  function setLarkRenderedMessageId(turn, rendered, messageId) {
152
203
  if (rendered.isFinal) {
153
- if (rendered.finalSource === 'reply') {
154
- turn.replyMessageId = messageId;
155
- return;
156
- }
157
204
  turn.finalMessageId = messageId;
158
205
  turn.messageId = messageId;
206
+ turn.replyMessageId = messageId;
159
207
  return;
160
208
  }
161
209
  turn.progressMessageIds[rendered.key] = messageId;
@@ -168,6 +216,12 @@ function renderProgressMessage(key, text) {
168
216
  isFinal: false,
169
217
  };
170
218
  }
219
+ function renderToolMessage(key, text) {
220
+ return {
221
+ ...renderProgressMessage(key, text),
222
+ updatePolicy: 'create-only',
223
+ };
224
+ }
171
225
  function progressKey(prefix, event) {
172
226
  const stableId = String(event.messageId || event.previewHandle || '').trim();
173
227
  if (stableId) {
@@ -176,6 +230,44 @@ function progressKey(prefix, event) {
176
230
  const content = String(event.content || '').trim().replace(/\s+/g, ' ');
177
231
  return `${prefix}:${content.slice(0, 120)}`;
178
232
  }
233
+ function isThoughtBridgeEvent(event) {
234
+ if (event.type !== 'preview_start' && event.type !== 'update_message' && event.type !== 'reply') {
235
+ return false;
236
+ }
237
+ return String(event.content || '').trim().startsWith('💭 ');
238
+ }
239
+ function summarizeToolContentForLark(content) {
240
+ const normalized = String(content || '').trim();
241
+ if (!normalized.startsWith('🔧 ')) {
242
+ return normalized;
243
+ }
244
+ const withoutOutput = normalized.split(' - ')[0]?.trim() || normalized;
245
+ return withoutOutput
246
+ .replace(/\s+-\s*(running|pending|completed|failed|error|cancelled|canceled)\s*$/i, '')
247
+ .replace(/:\s*(running|pending|completed|failed|error|cancelled|canceled)\s*$/i, '')
248
+ .trim();
249
+ }
250
+ function formatToolInputForLark(input) {
251
+ if (input == null) {
252
+ return '';
253
+ }
254
+ if (typeof input === 'string') {
255
+ return inlineCode(input);
256
+ }
257
+ try {
258
+ return inlineCode(JSON.stringify(input));
259
+ }
260
+ catch {
261
+ return inlineCode(String(input));
262
+ }
263
+ }
264
+ function inlineCode(value) {
265
+ const normalized = value.trim();
266
+ if (!normalized) {
267
+ return '';
268
+ }
269
+ return `\`${normalized.replace(/`/g, '\\`')}\``;
270
+ }
179
271
  function pushUniqueLarkTurnLine(target, value) {
180
272
  const normalized = value.trim();
181
273
  if (!normalized) {
@@ -1,11 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.prepareChannelFile = prepareChannelFile;
4
+ exports.resolveChannelFilePath = resolveChannelFilePath;
4
5
  exports.sanitizeChannelFileName = sanitizeChannelFileName;
5
6
  const promises_1 = require("node:fs/promises");
6
7
  const node_path_1 = require("node:path");
7
8
  async function prepareChannelFile(input) {
8
- const filePath = String(input.path || '').trim();
9
+ const filePath = resolveChannelFilePath(input.path, input.workspacePath);
9
10
  if (!filePath) {
10
11
  throw new Error('Missing file path');
11
12
  }
@@ -27,6 +28,17 @@ async function prepareChannelFile(input) {
27
28
  fileSize: fileStat.size,
28
29
  };
29
30
  }
31
+ function resolveChannelFilePath(filePath, workspacePath) {
32
+ const normalized = String(filePath || '').trim();
33
+ if (!normalized) {
34
+ return '';
35
+ }
36
+ if ((0, node_path_1.isAbsolute)(normalized)) {
37
+ return normalized;
38
+ }
39
+ const workspaceRoot = String(workspacePath || '').trim();
40
+ return workspaceRoot ? (0, node_path_1.resolve)(workspaceRoot, normalized) : normalized;
41
+ }
30
42
  function sanitizeChannelFileName(fileName) {
31
43
  const normalized = String(fileName || '').trim().replace(/[\/\\]+/g, '_');
32
44
  return normalized || 'file';
@@ -643,6 +643,10 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
643
643
  if (slashCommand?.name === 'new') {
644
644
  const title = slashCommand.args.join(' ').trim() || `${input.displayName || 'WeChat'} ${new Date().toLocaleTimeString()}`;
645
645
  const nextThread = await router.createThread(input.workspaceId, title);
646
+ const inheritedMode = this.options.store.getThreadRow?.(threadId)?.agent_mode || '';
647
+ if (inheritedMode && inheritedMode !== 'default') {
648
+ this.options.store.updateThreadAgentMode?.(nextThread.id, inheritedMode);
649
+ }
646
650
  const now = new Date().toISOString();
647
651
  this.options.store.updateAuthorizedUserThread(input.workspaceId, input.platformUserId, nextThread.id, platformKey);
648
652
  this.options.store.upsertPlatformThreadBinding({
@@ -676,7 +680,9 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
676
680
  return { paired: true, threadId };
677
681
  }
678
682
  this.options.store.updatePlatformThreadMessageId(input.workspaceId, input.chatId, input.platformUserId, input.contextToken || input.messageId, platformKey);
679
- const wrappedText = (0, desktop_js_1.wrapUserMessageWithSchedulerProtocol)(input.text);
683
+ const wrappedText = slashCommand
684
+ ? input.text
685
+ : (0, desktop_js_1.wrapUserMessageWithSchedulerProtocol)(input.text);
680
686
  await router.sendThreadMessage(threadId, (0, content_js_1.createChannelThreadMessageInput)(wrappedText, input.contentParts));
681
687
  return { paired: true, threadId };
682
688
  }
@@ -920,6 +926,7 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
920
926
  const file = await (0, file_utils_js_1.prepareChannelFile)({
921
927
  path: part.path,
922
928
  fileName: part.fileName,
929
+ workspacePath: typeof part.metadata?.workspacePath === 'string' ? part.metadata.workspacePath : undefined,
923
930
  maxBytes: WEIXIN_MAX_UPLOAD_FILE_SIZE,
924
931
  platformLabel: 'WeChat',
925
932
  });
@@ -75,6 +75,7 @@ async function handleChannelSendFile(flags, env, io, json) {
75
75
  type: 'file',
76
76
  path: filePath,
77
77
  fileName: getFlag(flags, 'name') || undefined,
78
+ metadata: context.workspacePath ? { workspacePath: context.workspacePath } : undefined,
78
79
  }],
79
80
  });
80
81
  const file = result.attachments?.[0];
@@ -226,6 +227,7 @@ function resolveContext(flags, env) {
226
227
  return {
227
228
  baseUrl: getFlag(flags, 'base-url') || String(env.LOCAL_AI_CORE_BASE || DEFAULT_BASE_URL),
228
229
  workspaceId: getFlag(flags, 'workspace') || String(env.LOCAL_AI_WORKSPACE_ID || ''),
230
+ workspacePath: getFlag(flags, 'workspace-path') || String(env.LOCAL_AI_WORKSPACE_PATH || ''),
229
231
  threadId: normalizeMaybeBooleanFlag(getFlag(flags, 'thread')) || String(env.LOCAL_AI_THREAD_ID || ''),
230
232
  platform,
231
233
  routeType: String(env.LOCAL_AI_ROUTE_TYPE || '') || (platform === 'lark' && chatId && platformUserId ? 'channel.chat' : ''),
@@ -301,7 +303,7 @@ function printUsage(output) {
301
303
  ' lac scheduler edit <job-id> [--cron "<expr>"] [--message "<text>"] [--desc "<label>"] [--enabled true|false] [--execution-mode same-thread|side-thread] [--json]',
302
304
  ' lac scheduler del <job-id> [--json]',
303
305
  ' lac scheduler run <job-id> [--json]',
304
- ' lac channel send-file --path "<file>" [--target <chat-or-user-id>] [--workspace <id>] [--platform lark] [--name <filename>] [--json]',
306
+ ' lac channel send-file --path "<file>" [--target <chat-or-user-id>] [--workspace <id>] [--workspace-path <path>] [--platform lark] [--name <filename>] [--json]',
305
307
  ].join('\n') + '\n');
306
308
  }
307
309
  function formatJobLine(job) {
@@ -163,6 +163,7 @@ function bootstrapLocalCoreRuntime(options) {
163
163
  }
164
164
  const agentPlugins = [
165
165
  localCoreAgentPlugin,
166
+ (0, agent_localcore_acp_plugin_js_1.createBuiltinPiAgentPlugin)(),
166
167
  (0, agent_localcore_acp_plugin_js_1.createBuiltinOpencodeAgentPlugin)(),
167
168
  (0, agent_localcore_acp_plugin_js_1.createBuiltinCodexAgentPlugin)(),
168
169
  (0, agent_localcore_acp_plugin_js_1.createBuiltinClaudeCodeAgentPlugin)(),
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createBuiltinLocalCoreAcpAgentPlugin = createBuiltinLocalCoreAcpAgentPlugin;
4
4
  exports.createBuiltinOpencodeAgentPlugin = createBuiltinOpencodeAgentPlugin;
5
+ exports.createBuiltinPiAgentPlugin = createBuiltinPiAgentPlugin;
5
6
  exports.createBuiltinCodexAgentPlugin = createBuiltinCodexAgentPlugin;
6
7
  exports.createBuiltinClaudeCodeAgentPlugin = createBuiltinClaudeCodeAgentPlugin;
7
8
  exports.createBuiltinStaticAgentCapabilityPlugin = createBuiltinStaticAgentCapabilityPlugin;
@@ -86,6 +87,14 @@ function createBuiltinOpencodeAgentPlugin() {
86
87
  displayName: 'OpenCode',
87
88
  });
88
89
  }
90
+ function createBuiltinPiAgentPlugin() {
91
+ return createBuiltinAgentPlugin({
92
+ pluginId: 'builtin.agent-pi',
93
+ agentType: 'pi',
94
+ match: (normalizedAgentType) => normalizedAgentType === 'pi',
95
+ displayName: 'Pi',
96
+ });
97
+ }
89
98
  function createBuiltinCodexAgentPlugin() {
90
99
  return createBuiltinAgentPlugin({
91
100
  pluginId: 'builtin.agent-codex',
@@ -4,6 +4,7 @@ exports.normalizePlatformTypes = normalizePlatformTypes;
4
4
  exports.isLocalCoreNativeAcpProject = isLocalCoreNativeAcpProject;
5
5
  exports.toLocalCoreProjectConfig = toLocalCoreProjectConfig;
6
6
  const node_module_1 = require("node:module");
7
+ const node_fs_1 = require("node:fs");
7
8
  const node_path_1 = require("node:path");
8
9
  const desktop_js_1 = require("../../../../shared/desktop.js");
9
10
  function normalizePlatformTypes(project) {
@@ -17,10 +18,13 @@ function isLocalCoreNativeAcpProject(project) {
17
18
  || agentType === 'acp'
18
19
  || agentType === desktop_js_1.LOCALCORE_ACP_AGENT_TYPE;
19
20
  }
20
- function resolveOpencodeModel(project, providers) {
21
+ function resolveAgentModel(project, providers) {
21
22
  const agentType = String(project.agent?.type || '').trim().toLowerCase();
22
23
  const rawModel = String(project.agent?.options?.model || '').trim();
23
24
  const normalizedModel = (0, desktop_js_1.normalizeDesktopAgentModel)(agentType, rawModel);
25
+ if (agentType === 'pi') {
26
+ return rawModel || getFirstProviderModelId(providers);
27
+ }
24
28
  if (agentType !== 'opencode') {
25
29
  return normalizedModel;
26
30
  }
@@ -31,6 +35,15 @@ function resolveOpencodeModel(project, providers) {
31
35
  }
32
36
  return normalizedModel;
33
37
  }
38
+ function getFirstProviderModelId(providers) {
39
+ for (const provider of providers) {
40
+ const modelId = getProviderDefaultModelId(provider);
41
+ if (modelId) {
42
+ return modelId;
43
+ }
44
+ }
45
+ return '';
46
+ }
34
47
  function getFirstProviderModelRef(providers) {
35
48
  for (const provider of providers) {
36
49
  const providerId = normalizeOpencodeProviderId(provider.name);
@@ -131,6 +144,108 @@ function collectProviderEnv(providers) {
131
144
  }
132
145
  return env;
133
146
  }
147
+ function collectPiProviderEnv(providers) {
148
+ const env = {};
149
+ for (const provider of providers) {
150
+ const apiKey = String(provider.api_key || '').trim();
151
+ const envName = piProviderApiKeyEnvName(provider.name);
152
+ if (apiKey && envName) {
153
+ env[envName] = apiKey;
154
+ }
155
+ }
156
+ return env;
157
+ }
158
+ function resolvePiProviderModel(providers, model) {
159
+ const [explicitProvider, explicitModel] = splitProviderModelRef(model);
160
+ if (explicitProvider && explicitModel) {
161
+ return {
162
+ providerId: normalizePiProviderId(explicitProvider),
163
+ modelId: explicitModel,
164
+ };
165
+ }
166
+ for (const provider of providers) {
167
+ const providerId = normalizePiProviderId(provider.name);
168
+ const modelId = getProviderDefaultModelId(provider);
169
+ if (providerId && modelId && (!model || model === modelId)) {
170
+ return { providerId, modelId };
171
+ }
172
+ }
173
+ const fallbackProvider = providers.find((provider) => normalizePiProviderId(provider.name));
174
+ return {
175
+ providerId: normalizePiProviderId(fallbackProvider?.name),
176
+ modelId: model,
177
+ };
178
+ }
179
+ function splitProviderModelRef(value) {
180
+ const trimmed = value.trim();
181
+ const slashIndex = trimmed.indexOf('/');
182
+ if (slashIndex <= 0 || slashIndex >= trimmed.length - 1) {
183
+ return ['', trimmed];
184
+ }
185
+ return [trimmed.slice(0, slashIndex), trimmed.slice(slashIndex + 1)];
186
+ }
187
+ function normalizePiProviderId(providerName) {
188
+ const providerId = normalizeProviderId(providerName);
189
+ const aliases = {
190
+ google: 'google',
191
+ gemini: 'google',
192
+ kimi: 'kimi-coding',
193
+ moonshot: 'moonshotai',
194
+ zhipuai: 'zai',
195
+ };
196
+ return aliases[providerId] || providerId;
197
+ }
198
+ function piProviderAgentDir(configState, projectName) {
199
+ const configDir = (0, node_path_1.dirname)(configState.path);
200
+ const safeProjectName = String(projectName || 'workspace')
201
+ .trim()
202
+ .replace(/[^a-zA-Z0-9._-]+/g, '-')
203
+ .replace(/^-+|-+$/g, '') || 'workspace';
204
+ return (0, node_path_1.resolve)(configDir, '.pi-agent', safeProjectName);
205
+ }
206
+ function writeJsonFile(path, value, mode = 0o600) {
207
+ (0, node_fs_1.writeFileSync)(path, `${JSON.stringify(value, null, 2)}\n`, { encoding: 'utf8', mode });
208
+ (0, node_fs_1.chmodSync)(path, mode);
209
+ }
210
+ function writePiProviderRuntimeConfig(configState, project, providers, model) {
211
+ if (providers.length === 0) {
212
+ return {};
213
+ }
214
+ const { providerId, modelId } = resolvePiProviderModel(providers, model);
215
+ const agentDir = piProviderAgentDir(configState, project.name);
216
+ (0, node_fs_1.mkdirSync)(agentDir, { recursive: true, mode: 0o700 });
217
+ (0, node_fs_1.chmodSync)(agentDir, 0o700);
218
+ const auth = {};
219
+ const modelProviders = {};
220
+ for (const provider of providers) {
221
+ const id = normalizePiProviderId(provider.name);
222
+ if (!id) {
223
+ continue;
224
+ }
225
+ const apiKey = String(provider.api_key || '').trim();
226
+ const baseUrl = String(provider.base_url || '').trim();
227
+ if (apiKey) {
228
+ auth[id] = { type: 'api_key', key: apiKey };
229
+ }
230
+ if (baseUrl || apiKey) {
231
+ modelProviders[id] = {
232
+ name: String(provider.name || id).trim() || id,
233
+ ...(baseUrl ? { baseUrl } : {}),
234
+ ...(apiKey ? { apiKey } : {}),
235
+ };
236
+ }
237
+ }
238
+ writeJsonFile((0, node_path_1.resolve)(agentDir, 'auth.json'), auth);
239
+ writeJsonFile((0, node_path_1.resolve)(agentDir, 'models.json'), { providers: modelProviders });
240
+ writeJsonFile((0, node_path_1.resolve)(agentDir, 'settings.json'), {
241
+ ...(providerId ? { defaultProvider: providerId } : {}),
242
+ ...(modelId ? { defaultModel: modelId } : {}),
243
+ quietStartup: true,
244
+ }, 0o644);
245
+ return {
246
+ PI_CODING_AGENT_DIR: agentDir,
247
+ };
248
+ }
134
249
  function normalizeOpencodeProviderId(value) {
135
250
  return String(value || '')
136
251
  .trim()
@@ -138,6 +253,48 @@ function normalizeOpencodeProviderId(value) {
138
253
  .replace(/[^a-z0-9._-]+/g, '-')
139
254
  .replace(/^-+|-+$/g, '');
140
255
  }
256
+ function normalizeProviderId(value) {
257
+ return String(value || '')
258
+ .trim()
259
+ .toLowerCase()
260
+ .replace(/[^a-z0-9]+/g, '-')
261
+ .replace(/^-+|-+$/g, '');
262
+ }
263
+ function piProviderApiKeyEnvName(providerName) {
264
+ const providerId = normalizeProviderId(providerName);
265
+ const envNames = {
266
+ anthropic: 'ANTHROPIC_API_KEY',
267
+ 'azure-openai': 'AZURE_OPENAI_API_KEY',
268
+ 'azure-openai-responses': 'AZURE_OPENAI_API_KEY',
269
+ cerebras: 'CEREBRAS_API_KEY',
270
+ cloudflare: 'CLOUDFLARE_API_KEY',
271
+ 'cloudflare-ai-gateway': 'CLOUDFLARE_API_KEY',
272
+ 'cloudflare-workers-ai': 'CLOUDFLARE_API_KEY',
273
+ deepseek: 'DEEPSEEK_API_KEY',
274
+ fireworks: 'FIREWORKS_API_KEY',
275
+ gemini: 'GEMINI_API_KEY',
276
+ google: 'GEMINI_API_KEY',
277
+ groq: 'GROQ_API_KEY',
278
+ huggingface: 'HF_TOKEN',
279
+ kimi: 'KIMI_API_KEY',
280
+ 'kimi-coding': 'KIMI_API_KEY',
281
+ minimax: 'MINIMAX_API_KEY',
282
+ 'minimax-cn': 'MINIMAX_CN_API_KEY',
283
+ mistral: 'MISTRAL_API_KEY',
284
+ moonshot: 'KIMI_API_KEY',
285
+ openai: 'OPENAI_API_KEY',
286
+ opencode: 'OPENCODE_API_KEY',
287
+ 'opencode-go': 'OPENCODE_API_KEY',
288
+ openrouter: 'OPENROUTER_API_KEY',
289
+ siliconflow: 'SILICONFLOW_API_KEY',
290
+ vercel: 'AI_GATEWAY_API_KEY',
291
+ 'vercel-ai-gateway': 'AI_GATEWAY_API_KEY',
292
+ xai: 'XAI_API_KEY',
293
+ zai: 'ZAI_API_KEY',
294
+ zhipuai: 'ZAI_API_KEY',
295
+ };
296
+ return envNames[providerId] || '';
297
+ }
141
298
  function opencodeProviderApiKeyEnvName(providerId) {
142
299
  const suffix = providerId.toUpperCase().replace(/[^A-Z0-9]+/g, '_').replace(/^_+|_+$/g, '');
143
300
  return `AI_WORKSTATION_OPENCODE_${suffix || 'PROVIDER'}_API_KEY`;
@@ -152,7 +309,7 @@ function shouldUseOpenAiCompatibleProvider(providerId) {
152
309
  }
153
310
  function resolveBundledAcpCommand(packageName, binName) {
154
311
  const require = (0, node_module_1.createRequire)(__filename);
155
- const packageJsonPath = require.resolve(`${packageName}/package.json`);
312
+ const packageJsonPath = resolveBundledPackageJsonPath(packageName);
156
313
  const packageJson = require(packageJsonPath);
157
314
  const binField = packageJson.bin;
158
315
  const relativeBinPath = typeof binField === 'string'
@@ -166,12 +323,44 @@ function resolveBundledAcpCommand(packageName, binName) {
166
323
  args: [(0, node_path_1.resolve)((0, node_path_1.dirname)(packageJsonPath), relativeBinPath)],
167
324
  };
168
325
  }
326
+ function resolveBundledPackageJsonPath(packageName) {
327
+ const require = (0, node_module_1.createRequire)(__filename);
328
+ try {
329
+ return require.resolve(`${packageName}/package.json`);
330
+ }
331
+ catch (error) {
332
+ if (error?.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED') {
333
+ throw error;
334
+ }
335
+ }
336
+ for (const basePath of require.resolve.paths(packageName) || []) {
337
+ const packageJsonPath = (0, node_path_1.resolve)(basePath, ...packageName.split('/'), 'package.json');
338
+ if ((0, node_fs_1.existsSync)(packageJsonPath)) {
339
+ return packageJsonPath;
340
+ }
341
+ }
342
+ let current = (0, node_path_1.dirname)(require.resolve(packageName));
343
+ while (current && current !== (0, node_path_1.dirname)(current)) {
344
+ const packageJsonPath = (0, node_path_1.resolve)(current, 'package.json');
345
+ if ((0, node_fs_1.existsSync)(packageJsonPath)) {
346
+ return packageJsonPath;
347
+ }
348
+ current = (0, node_path_1.dirname)(current);
349
+ }
350
+ throw new Error(`Bundled package "${packageName}" package.json could not be resolved.`);
351
+ }
169
352
  function resolveBundledCodexCommand() {
170
353
  return resolveBundledAcpCommand(desktop_js_1.DESKTOP_CODEX_ACP_PACKAGE, 'codex-acp');
171
354
  }
172
355
  function resolveBundledClaudeCodeCommand() {
173
356
  return resolveBundledAcpCommand(desktop_js_1.DESKTOP_CLAUDECODE_ACP_PACKAGE, 'claude-agent-acp');
174
357
  }
358
+ function resolveBundledPiAcpCommand() {
359
+ return resolveBundledAcpCommand(desktop_js_1.DESKTOP_PI_ACP_PACKAGE, 'pi-acp');
360
+ }
361
+ function resolveBundledPiCommand() {
362
+ return resolveBundledAcpCommand(desktop_js_1.DESKTOP_PI_CODING_AGENT_PACKAGE, 'pi').args[0];
363
+ }
175
364
  function toLocalCoreProjectConfig(configState, project) {
176
365
  const rawWorkDir = String(project.agent?.options?.work_dir || '.').trim() || '.';
177
366
  const configDir = (0, node_path_1.dirname)(configState.path);
@@ -188,8 +377,11 @@ function toLocalCoreProjectConfig(configState, project) {
188
377
  : {};
189
378
  const agentType = String(project.agent?.type || '').trim().toLowerCase();
190
379
  const providers = Array.isArray(project.agent?.providers) ? project.agent.providers : [];
191
- const model = resolveOpencodeModel(project, providers);
380
+ const model = resolveAgentModel(project, providers);
192
381
  const providerEnv = collectProviderEnv(providers);
382
+ const piProviderEnv = agentType === 'pi'
383
+ ? collectPiProviderEnv(providers)
384
+ : {};
193
385
  const opencodeInlineConfig = agentType === 'opencode'
194
386
  ? buildOpencodeInlineConfig(model, providers)
195
387
  : null;
@@ -204,6 +396,22 @@ function toLocalCoreProjectConfig(configState, project) {
204
396
  ANTHROPIC_MODEL: model,
205
397
  }
206
398
  : {};
399
+ const bundledPiAcp = agentType === 'pi'
400
+ ? resolveBundledPiAcpCommand()
401
+ : null;
402
+ const bundledPiCommand = agentType === 'pi'
403
+ ? resolveBundledPiCommand()
404
+ : '';
405
+ const piProviderRuntimeEnv = agentType === 'pi'
406
+ ? writePiProviderRuntimeConfig(configState, project, providers, model)
407
+ : {};
408
+ const inferredPiEnv = agentType === 'pi'
409
+ ? {
410
+ ...piProviderEnv,
411
+ ...piProviderRuntimeEnv,
412
+ PI_ACP_PI_COMMAND: bundledPiCommand,
413
+ }
414
+ : {};
207
415
  const bundledCodex = agentType === 'codex'
208
416
  ? resolveBundledCodexCommand()
209
417
  : null;
@@ -212,18 +420,20 @@ function toLocalCoreProjectConfig(configState, project) {
212
420
  : null;
213
421
  const inferredCommand = agentType === 'opencode'
214
422
  ? 'opencode'
215
- : bundledCodex?.command || bundledClaudeCode?.command || '';
423
+ : bundledPiAcp?.command || bundledCodex?.command || bundledClaudeCode?.command || '';
216
424
  const command = String(project.agent?.options?.command || inferredCommand).trim();
217
425
  if (!command) {
218
426
  throw new Error(`Workspace "${project.name}" requires [projects.agent.options].command for Local AI Core ACP execution.`);
219
427
  }
220
428
  const defaultArgs = agentType === 'opencode'
221
429
  ? ['acp']
222
- : agentType === 'codex'
223
- ? [...(bundledCodex?.args || [])]
224
- : agentType === 'claudecode'
225
- ? [...(bundledClaudeCode?.args || [])]
226
- : [];
430
+ : agentType === 'pi'
431
+ ? [...(bundledPiAcp?.args || [])]
432
+ : agentType === 'codex'
433
+ ? [...(bundledCodex?.args || [])]
434
+ : agentType === 'claudecode'
435
+ ? [...(bundledClaudeCode?.args || [])]
436
+ : [];
227
437
  return {
228
438
  workspaceId: project.name,
229
439
  agentType,
@@ -231,9 +441,10 @@ function toLocalCoreProjectConfig(configState, project) {
231
441
  command,
232
442
  args: args.length > 0 ? args : defaultArgs,
233
443
  env: {
234
- ...providerEnv,
235
444
  ...inferredOpencodeEnv,
236
445
  ...inferredClaudeCodeEnv,
446
+ ...inferredPiEnv,
447
+ ...providerEnv,
237
448
  ...env,
238
449
  },
239
450
  model,