@kafca/agentdock 0.1.31 → 0.1.34

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 (73) hide show
  1. package/LICENSE.md +73 -0
  2. package/README.md +11 -0
  3. package/dist/renderer/assets/{Badge-DaHt-aUT.js → Badge-BDxYXRKv.js} +1 -1
  4. package/dist/renderer/assets/{Chat-Dzeq3kiD.js → Chat-B-ZXd4Ld.js} +1 -1
  5. package/dist/renderer/assets/{ChatMarkdown-B-L5Roai.js → ChatMarkdown-Dn4XvCke.js} +1 -1
  6. package/dist/renderer/assets/{Config-BI4LJ7Fw.js → Config-DMOYyKOD.js} +1 -1
  7. package/dist/renderer/assets/{CronList-Dic7pblj.js → CronList-CMBhMs1J.js} +1 -1
  8. package/dist/renderer/assets/{Dashboard-C0_ATyGe.js → Dashboard-DApD4wOA.js} +2 -2
  9. package/dist/renderer/assets/{EmptyState-SQsG1DZP.js → EmptyState-CqcPky2i.js} +1 -1
  10. package/dist/renderer/assets/{KnowledgeDetail-D4yUJzp7.js → KnowledgeDetail-DMOXK9Nz.js} +1 -1
  11. package/dist/renderer/assets/{KnowledgeHome-L0tsg-oo.js → KnowledgeHome-Dv4Ttb6N.js} +1 -1
  12. package/dist/renderer/assets/{Logs-C2Jdr3uQ.js → Logs-CHrUm25Y.js} +1 -1
  13. package/dist/renderer/assets/{Modal-GUH3iPXo.js → Modal-Cj_0qtXr.js} +1 -1
  14. package/dist/renderer/assets/{Page-m42FBy97.js → Page-BlKjqLN4.js} +1 -1
  15. package/dist/renderer/assets/{ProjectDetail-RF_sagPO.js → ProjectDetail-aMl1VGTt.js} +1 -1
  16. package/dist/renderer/assets/{ProjectList-Cyr8RDK3.js → ProjectList-R2YvsII0.js} +1 -1
  17. package/dist/renderer/assets/{Select-CRlDgI5s.js → Select-CrqwGrrm.js} +1 -1
  18. package/dist/renderer/assets/{SessionChat-_HhzMwEO.js → SessionChat-CGtpyGKR.js} +1 -1
  19. package/dist/renderer/assets/{SessionList-DNwZjGTv.js → SessionList-D8gRUc8M.js} +1 -1
  20. package/dist/renderer/assets/{ThreadChat-DxBmdsAm.js → ThreadChat-BCGm6bQP.js} +1 -1
  21. package/dist/renderer/assets/{Workspace-DFphG85O.js → Workspace-DnePK6tn.js} +1 -1
  22. package/dist/renderer/assets/{arrow-left-bILMzse7.js → arrow-left-SVmCyBR4.js} +1 -1
  23. package/dist/renderer/assets/{book-open-BNBWEEve.js → book-open-CZi1avEJ.js} +1 -1
  24. package/dist/renderer/assets/{check-MGp5kvnb.js → check-C76I9BFh.js} +1 -1
  25. package/dist/renderer/assets/{chevron-down-zsfoTlh7.js → chevron-down-C7YSxzHw.js} +1 -1
  26. package/dist/renderer/assets/{circle-BX_xUSg-.js → circle-_s8tmsAJ.js} +1 -1
  27. package/dist/renderer/assets/{desktop-CgrO_cdQ.js → desktop-66ot_8sB.js} +1 -1
  28. package/dist/renderer/assets/{funnel-7442U1wq.js → funnel-cDV0Q_Mr.js} +1 -1
  29. package/dist/renderer/assets/{heart-B3d8bnSH.js → heart-qMUQm0tf.js} +1 -1
  30. package/dist/renderer/assets/{index-BBaYsVjU.js → index-BY88QEsS.js} +2 -2
  31. package/dist/renderer/assets/{index-5YUqCvBf.js → index-Cr6erCLm.js} +1 -1
  32. package/dist/renderer/assets/{pencil-QzWgkmoH.js → pencil-C3MA3apq.js} +1 -1
  33. package/dist/renderer/assets/{play-Bsc8hSI7.js → play-VZKHv5dN.js} +1 -1
  34. package/dist/renderer/assets/{plug-CRY7sjvB.js → plug-Cm7l0fVk.js} +1 -1
  35. package/dist/renderer/assets/{plus-Dr5Ei8Ud.js → plus-DjGK7f-K.js} +1 -1
  36. package/dist/renderer/assets/projects-Dcki78KB.js +1 -0
  37. package/dist/renderer/assets/{save-giJnp2Ki.js → save-Doi-FwDR.js} +1 -1
  38. package/dist/renderer/assets/{search-DcvHLmKs.js → search-DHqpbK_c.js} +1 -1
  39. package/dist/renderer/assets/{server-D_Qu8If5.js → server-Bmtxxlph.js} +1 -1
  40. package/dist/renderer/assets/{session-utils-CsASahfU.js → session-utils-Z-jLtVLY.js} +1 -1
  41. package/dist/renderer/assets/{sessions-eMpPqp4P.js → sessions-C5TSSo_R.js} +1 -1
  42. package/dist/renderer/assets/{shield-check-BpF0uIw-.js → shield-check-DhZ05nhI.js} +1 -1
  43. package/dist/renderer/assets/{trash-2-OJFmx3xk.js → trash-2-DiyFflVR.js} +1 -1
  44. package/dist/renderer/assets/{user-GL_ivaV6.js → user-CgsIPd7T.js} +1 -1
  45. package/dist/renderer/assets/{wifi-off-CQT5VNQl.js → wifi-off-CqWbWFzp.js} +1 -1
  46. package/dist/renderer/index.html +1 -1
  47. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-backend.js +3 -6
  48. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-response-processor.js +2 -4
  49. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-session-coordinator.js +23 -6
  50. package/dist-electron/services/local-ai-core/src/cli/lac.js +9 -2
  51. package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +9 -14
  52. package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +9 -39
  53. package/dist-electron/services/local-ai-core/src/scheduler/lark-schedule-adapter.js +20 -10
  54. package/dist-electron/services/local-ai-core/src/scheduler/local-schedule-adapter.js +10 -2
  55. package/dist-electron/services/local-ai-core/src/scheduler/scheduled-conversation-executor.js +22 -0
  56. package/dist-electron/services/local-ai-core/src/scheduler/scheduled-job-application-service.js +84 -0
  57. package/dist-electron/services/local-ai-core/src/scheduler/scheduled-job-route.js +57 -0
  58. package/dist-electron/services/local-ai-core/src/scheduler/weixin-schedule-adapter.js +19 -10
  59. package/dist-electron/{electron → tests/contracts}/local-core-contracts.test.js +1 -1
  60. package/dist-electron/{electron → tests/electron}/agent-runtime-detector.test.js +2 -2
  61. package/dist-electron/{electron → tests/electron}/lac-cli.test.js +56 -3
  62. package/dist-electron/{electron → tests/electron}/plugin-kernel.test.js +14 -13
  63. package/dist-electron/{electron → tests/electron}/renderer-packaging.test.js +11 -6
  64. package/dist-electron/{electron → tests/electron}/runtime-detection-service.test.js +1 -1
  65. package/dist-electron/{electron → tests/electron}/security-approval-store.test.js +2 -2
  66. package/dist-electron/{electron → tests/electron}/workspace-task-store.test.js +217 -6
  67. package/dist-electron/{electron → tests/integration}/channel-content.test.js +2 -2
  68. package/dist-electron/{electron → tests/integration}/local-core-acp-content.test.js +1 -1
  69. package/dist-electron/{electron → tests/integration}/local-core-refactor.test.js +27 -18
  70. package/package.json +3 -2
  71. package/dist/renderer/assets/projects-BDYSOFIJ.js +0 -1
  72. /package/dist-electron/{electron → tests/contracts}/architecture-docs.test.js +0 -0
  73. /package/dist-electron/{electron → tests/electron}/knowledge-skill-script.test.js +0 -0
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ScheduledConversationExecutor = void 0;
4
+ const scheduled_job_route_js_1 = require("./scheduled-job-route.js");
4
5
  const TERMINAL_RUN_STATES = new Set(['completed', 'failed', 'interrupted']);
5
6
  const SCHEDULED_RUN_PERMISSION_MODE = 'bypassPermissions';
6
7
  class ScheduledConversationExecutor {
@@ -14,6 +15,7 @@ class ScheduledConversationExecutor {
14
15
  try {
15
16
  const sendResult = await this.options.workspaceRouter.sendThreadMessage(target.threadId, prompt, {
16
17
  permissionMode: SCHEDULED_RUN_PERMISSION_MODE,
18
+ runtimeEnv: this.buildScheduledRuntimeEnv(target),
17
19
  });
18
20
  await this.waitForRun(sendResult.runId, timeoutMs);
19
21
  const thread = await this.options.workspaceRouter.getThread(target.threadId);
@@ -31,6 +33,26 @@ class ScheduledConversationExecutor {
31
33
  await policy.afterExecute?.(target, job);
32
34
  }
33
35
  }
36
+ buildScheduledRuntimeEnv(target) {
37
+ const route = target.route;
38
+ const basePlatform = (0, scheduled_job_route_js_1.getChannelPlatformBase)(target.platform);
39
+ const env = {};
40
+ if (basePlatform && basePlatform !== 'local') {
41
+ env.LOCAL_AI_PLATFORM = basePlatform;
42
+ env.LOCAL_AI_ROUTE_TYPE = (0, scheduled_job_route_js_1.routeTypeForPlatform)(target.platform);
43
+ }
44
+ const instanceId = route.instanceId || (0, scheduled_job_route_js_1.getChannelPlatformInstanceId)(target.platform);
45
+ if (instanceId) {
46
+ env.LOCAL_AI_PLATFORM_INSTANCE_ID = instanceId;
47
+ }
48
+ if (route.channelId) {
49
+ env.LOCAL_AI_CHAT_ID = route.channelId;
50
+ }
51
+ if (route.participantId) {
52
+ env.LOCAL_AI_PLATFORM_USER_ID = route.participantId;
53
+ }
54
+ return env;
55
+ }
34
56
  async waitForRun(runId, timeoutMs) {
35
57
  const startedAt = Date.now();
36
58
  while (Date.now() - startedAt < timeoutMs) {
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ScheduledJobApplicationService = void 0;
4
+ const scheduled_job_route_js_1 = require("./scheduled-job-route.js");
5
+ class ScheduledJobApplicationService {
6
+ options;
7
+ constructor(options) {
8
+ this.options = options;
9
+ }
10
+ listJobs(workspaceId) {
11
+ return this.options.scheduler.listJobs(workspaceId);
12
+ }
13
+ getJob(jobId) {
14
+ return this.options.scheduler.getJob(jobId);
15
+ }
16
+ createJob(input) {
17
+ return this.options.scheduler.createJob(this.resolveCreateInput(input));
18
+ }
19
+ createCronJob(input) {
20
+ return this.options.scheduler.createJob({
21
+ workspaceId: input.workspaceId,
22
+ platform: input.platform,
23
+ route: this.resolveExplicitRoute(input.platform, input.route),
24
+ triggerType: 'cron',
25
+ cronExpr: input.schedule,
26
+ promptTemplate: input.message,
27
+ description: `${input.name} · ${input.scheduleDescription}`,
28
+ enabled: true,
29
+ });
30
+ }
31
+ updateJob(jobId, input) {
32
+ return this.options.scheduler.updateJob(jobId, {
33
+ ...input,
34
+ ...(input.route ? { route: (0, scheduled_job_route_js_1.withoutThreadRoute)(input.route) } : {}),
35
+ });
36
+ }
37
+ deleteJob(jobId) {
38
+ return this.options.scheduler.deleteJob(jobId);
39
+ }
40
+ runJobNow(jobId) {
41
+ return this.options.scheduler.runJobNow(jobId);
42
+ }
43
+ listJobRuns(jobId) {
44
+ return this.options.scheduler.listJobRuns(jobId);
45
+ }
46
+ listJobsForThread(threadId) {
47
+ const binding = this.options.store.getPlatformThreadBindingByThreadId(threadId);
48
+ return this.options.scheduler
49
+ .listJobs()
50
+ .filter((job) => job.route.threadId === threadId ||
51
+ (binding ? (0, scheduled_job_route_js_1.scheduledJobMatchesPlatformBinding)(job, binding) : false));
52
+ }
53
+ resolveCreateInput(input) {
54
+ if (input.platform && input.route) {
55
+ return {
56
+ ...input,
57
+ route: this.resolveExplicitRoute(input.platform, input.route),
58
+ };
59
+ }
60
+ const threadId = String(input.threadId || input.route?.threadId || '').trim();
61
+ if (threadId) {
62
+ const binding = this.options.store.getPlatformThreadBindingByThreadId(threadId);
63
+ if (binding && binding.workspace_id === input.workspaceId) {
64
+ return {
65
+ ...input,
66
+ platform: binding.platform,
67
+ route: (0, scheduled_job_route_js_1.routeFromPlatformThreadBinding)(binding),
68
+ };
69
+ }
70
+ }
71
+ return {
72
+ ...input,
73
+ platform: 'local',
74
+ route: {
75
+ type: 'local.thread',
76
+ channelId: input.workspaceId,
77
+ },
78
+ };
79
+ }
80
+ resolveExplicitRoute(platform, route) {
81
+ return (0, scheduled_job_route_js_1.routeWithPlatformInstance)((0, scheduled_job_route_js_1.withoutThreadRoute)(route), platform);
82
+ }
83
+ }
84
+ exports.ScheduledJobApplicationService = ScheduledJobApplicationService;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.withoutThreadRoute = withoutThreadRoute;
4
+ exports.getChannelPlatformBase = getChannelPlatformBase;
5
+ exports.getChannelPlatformInstanceId = getChannelPlatformInstanceId;
6
+ exports.platformMatches = platformMatches;
7
+ exports.routeWithPlatformInstance = routeWithPlatformInstance;
8
+ exports.routeTypeForPlatform = routeTypeForPlatform;
9
+ exports.routeFromPlatformThreadBinding = routeFromPlatformThreadBinding;
10
+ exports.scheduledJobMatchesPlatformBinding = scheduledJobMatchesPlatformBinding;
11
+ exports.scheduledJobMatchesCliContext = scheduledJobMatchesCliContext;
12
+ function withoutThreadRoute(route) {
13
+ const { threadId: _threadId, ...deliveryRoute } = route;
14
+ return deliveryRoute;
15
+ }
16
+ function getChannelPlatformBase(platform) {
17
+ return String(platform || '').trim().toLowerCase().split(':', 1)[0] || '';
18
+ }
19
+ function getChannelPlatformInstanceId(platform) {
20
+ const normalized = String(platform || '').trim().toLowerCase();
21
+ const separator = normalized.indexOf(':');
22
+ return separator >= 0 ? normalized.slice(separator + 1) : '';
23
+ }
24
+ function platformMatches(candidate, expectedBase) {
25
+ return getChannelPlatformBase(candidate) === expectedBase;
26
+ }
27
+ function routeWithPlatformInstance(route, platform) {
28
+ const instanceId = route.instanceId || getChannelPlatformInstanceId(platform);
29
+ return instanceId ? { ...route, instanceId } : route;
30
+ }
31
+ function routeTypeForPlatform(platform) {
32
+ const base = getChannelPlatformBase(platform);
33
+ return base === 'lark' || base === 'weixin' ? 'channel.chat' : base;
34
+ }
35
+ function routeFromPlatformThreadBinding(binding) {
36
+ return {
37
+ type: routeTypeForPlatform(binding.platform),
38
+ channelId: binding.chat_id,
39
+ instanceId: getChannelPlatformInstanceId(binding.platform) || undefined,
40
+ participantId: binding.platform_user_id,
41
+ };
42
+ }
43
+ function scheduledJobMatchesPlatformBinding(job, binding) {
44
+ return (job.workspaceId === binding.workspace_id &&
45
+ getChannelPlatformBase(job.platform) === getChannelPlatformBase(binding.platform) &&
46
+ job.route.channelId === binding.chat_id &&
47
+ String(job.route.participantId || '') === String(binding.platform_user_id || ''));
48
+ }
49
+ function scheduledJobMatchesCliContext(job, context) {
50
+ if (!context.workspaceId || !context.platform || !context.chatId) {
51
+ return false;
52
+ }
53
+ return (job.workspaceId === context.workspaceId &&
54
+ getChannelPlatformBase(job.platform) === getChannelPlatformBase(context.platform) &&
55
+ job.route.channelId === context.chatId &&
56
+ String(job.route.participantId || '') === String(context.platformUserId || ''));
57
+ }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.WeixinScheduleAdapter = void 0;
4
4
  const scheduled_conversation_executor_js_1 = require("./scheduled-conversation-executor.js");
5
5
  const weixin_execution_policies_js_1 = require("./weixin-execution-policies.js");
6
+ const scheduled_job_route_js_1 = require("./scheduled-job-route.js");
6
7
  class WeixinScheduleAdapter {
7
8
  options;
8
9
  executor;
@@ -15,7 +16,7 @@ class WeixinScheduleAdapter {
15
16
  });
16
17
  }
17
18
  supports(job) {
18
- return job.platform === 'weixin' && (job.route.type === 'channel.chat' || job.route.type === 'weixin_chat');
19
+ return (0, scheduled_job_route_js_1.platformMatches)(job.platform, 'weixin') && (job.route.type === 'channel.chat' || job.route.type === 'weixin_chat');
19
20
  }
20
21
  async execute(context) {
21
22
  const { job } = context;
@@ -31,7 +32,7 @@ class WeixinScheduleAdapter {
31
32
  if (!channelRuntime.sendScheduledMessage) {
32
33
  throw new Error('WeChat channel runtime does not support scheduled delivery.');
33
34
  }
34
- platformMessageId = await channelRuntime.sendScheduledMessage(job.workspaceId, job.route, execution.replyText);
35
+ platformMessageId = await channelRuntime.sendScheduledMessage(job.workspaceId, (0, scheduled_job_route_js_1.routeWithPlatformInstance)((0, scheduled_job_route_js_1.withoutThreadRoute)(job.route), job.platform), execution.replyText);
35
36
  if (!platformMessageId) {
36
37
  throw new Error('WeChat gateway did not return a message id for scheduled delivery.');
37
38
  }
@@ -46,16 +47,15 @@ class WeixinScheduleAdapter {
46
47
  async resolveThread(job) {
47
48
  const workspaceRouter = this.options.getWorkspaceRouter();
48
49
  const route = job.route;
49
- if (route.threadId) {
50
- await workspaceRouter.getThread(route.threadId);
51
- return route.threadId;
52
- }
53
50
  const channelId = route.channelId;
54
51
  const participantId = route.participantId || '';
55
- const binding = this.options.store.getPlatformThreadBinding(job.workspaceId, channelId, participantId, 'weixin');
56
- if (binding?.thread_id) {
52
+ const binding = this.options.store.getPlatformThreadBinding(job.workspaceId, channelId, participantId, job.platform);
53
+ if (binding?.thread_id && await this.threadExists(binding.thread_id)) {
57
54
  return binding.thread_id;
58
55
  }
56
+ if (route.threadId && await this.threadExists(route.threadId)) {
57
+ return route.threadId;
58
+ }
59
59
  const thread = await workspaceRouter.createThread(job.workspaceId, job.description || `Scheduled ${job.platform} task`);
60
60
  const now = new Date().toISOString();
61
61
  this.options.store.upsertPlatformThreadBinding({
@@ -68,11 +68,20 @@ class WeixinScheduleAdapter {
68
68
  created_at: now,
69
69
  updated_at: now,
70
70
  });
71
- const authorized = this.options.store.getAuthorizedUser(job.workspaceId, participantId, 'weixin');
71
+ const authorized = this.options.store.getAuthorizedUser(job.workspaceId, participantId, job.platform);
72
72
  if (authorized) {
73
- this.options.store.updateAuthorizedUserThread(job.workspaceId, participantId, thread.id, 'weixin');
73
+ this.options.store.updateAuthorizedUserThread(job.workspaceId, participantId, thread.id, job.platform);
74
74
  }
75
75
  return thread.id;
76
76
  }
77
+ async threadExists(threadId) {
78
+ try {
79
+ await this.options.getWorkspaceRouter().getThread(threadId);
80
+ return true;
81
+ }
82
+ catch {
83
+ return false;
84
+ }
85
+ }
77
86
  }
78
87
  exports.WeixinScheduleAdapter = WeixinScheduleAdapter;
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const node_test_1 = __importDefault(require("node:test"));
7
7
  const strict_1 = __importDefault(require("node:assert/strict"));
8
- const index_js_1 = require("../packages/contracts/src/index.js");
8
+ const index_js_1 = require("../../packages/contracts/src/index.js");
9
9
  (0, node_test_1.default)('scheduled job execution mode normalization accepts canonical aliases', () => {
10
10
  strict_1.default.equal((0, index_js_1.normalizeScheduledJobExecutionMode)(undefined), 'same-thread');
11
11
  strict_1.default.equal((0, index_js_1.normalizeScheduledJobExecutionMode)('same_thread'), 'same-thread');
@@ -8,8 +8,8 @@ const strict_1 = __importDefault(require("node:assert/strict"));
8
8
  const node_fs_1 = require("node:fs");
9
9
  const node_path_1 = require("node:path");
10
10
  const node_os_1 = require("node:os");
11
- const agent_runtime_detector_js_1 = require("../services/local-ai-core/src/runtime/agent-runtime-detector.js");
12
- const index_js_1 = require("../services/local-ai-core/src/agents/index.js");
11
+ const agent_runtime_detector_js_1 = require("../../services/local-ai-core/src/runtime/agent-runtime-detector.js");
12
+ const index_js_1 = require("../../services/local-ai-core/src/agents/index.js");
13
13
  (0, node_test_1.default)('agent runtime definitions own runtime detection metadata', () => {
14
14
  strict_1.default.deepEqual((0, index_js_1.resolveAgentRuntimeDefinition)('hermes')?.detection?.commandCandidates, ['hermes']);
15
15
  strict_1.default.deepEqual((0, index_js_1.resolveAgentRuntimeDefinition)('codex')?.detection?.bundledRuntimes?.[0], {
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const node_test_1 = __importDefault(require("node:test"));
7
7
  const strict_1 = __importDefault(require("node:assert/strict"));
8
8
  const node_http_1 = require("node:http");
9
- const lac_js_1 = require("../services/local-ai-core/src/cli/lac.js");
9
+ const lac_js_1 = require("../../services/local-ai-core/src/cli/lac.js");
10
10
  function createIo() {
11
11
  let stdout = '';
12
12
  let stderr = '';
@@ -214,6 +214,56 @@ function createIo() {
214
214
  });
215
215
  strict_1.default.match(read().stdout, /Sent file out\.pdf to chat-1: msg-file-1/);
216
216
  });
217
+ (0, node_test_1.default)('lac channel send-file normalizes instance-qualified platform env into route instance', async () => {
218
+ let capturedUrl = '';
219
+ let capturedBody = null;
220
+ const server = (0, node_http_1.createServer)(async (req, res) => {
221
+ if (req.method === 'POST') {
222
+ capturedUrl = req.url || '';
223
+ const chunks = [];
224
+ for await (const chunk of req) {
225
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
226
+ }
227
+ capturedBody = Buffer.concat(chunks).toString('utf8');
228
+ res.setHeader('content-type', 'application/json');
229
+ res.end(JSON.stringify({
230
+ ok: true,
231
+ data: {
232
+ platform: 'lark',
233
+ workspaceId: '知识库',
234
+ channelId: 'chat-1',
235
+ messageIds: ['msg-file-1'],
236
+ attachments: [{ kind: 'file', fileName: 'out.pdf', fileSize: 123 }],
237
+ },
238
+ }));
239
+ return;
240
+ }
241
+ res.statusCode = 404;
242
+ res.end(JSON.stringify({ ok: false, error: 'not found' }));
243
+ });
244
+ await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
245
+ const address = server.address();
246
+ (0, strict_1.default)(address && typeof address === 'object');
247
+ const { io } = createIo();
248
+ const exitCode = await (0, lac_js_1.runCli)(['channel', 'send-file', '--path', 'reports/out.pdf'], {
249
+ LOCAL_AI_CORE_BASE: `http://127.0.0.1:${address.port}/api/local/v1`,
250
+ LOCAL_AI_WORKSPACE_ID: '知识库',
251
+ LOCAL_AI_WORKSPACE_PATH: '/workspace/project',
252
+ LOCAL_AI_PLATFORM: 'lark:lark-1',
253
+ LOCAL_AI_CHAT_ID: 'chat-1',
254
+ LOCAL_AI_PLATFORM_USER_ID: 'user-1',
255
+ }, io);
256
+ server.close();
257
+ strict_1.default.equal(exitCode, 0);
258
+ strict_1.default.equal(capturedUrl, '/api/local/v1/platforms/lark/%E7%9F%A5%E8%AF%86%E5%BA%93/messages');
259
+ (0, strict_1.default)(capturedBody);
260
+ strict_1.default.deepEqual(JSON.parse(capturedBody).route, {
261
+ type: 'channel.chat',
262
+ channelId: 'chat-1',
263
+ instanceId: 'lark-1',
264
+ participantId: 'user-1',
265
+ });
266
+ });
217
267
  (0, node_test_1.default)('lac channel send-file posts allowed absolute paths through the same outbound message path', async () => {
218
268
  let capturedBody = null;
219
269
  const server = (0, node_http_1.createServer)(async (req, res) => {
@@ -516,7 +566,7 @@ function createIo() {
516
566
  id: 'job-1',
517
567
  workspaceId: '知识库',
518
568
  platform: 'lark',
519
- route: { type: 'channel.chat', channelId: 'chat-1', participantId: 'user-1', threadId: 'thread-1' },
569
+ route: { type: 'channel.chat', channelId: 'chat-1', participantId: 'user-1' },
520
570
  executionMode: 'same-thread',
521
571
  triggerType: 'cron',
522
572
  cronExpr: '*/2 * * * *',
@@ -531,7 +581,7 @@ function createIo() {
531
581
  id: 'job-2',
532
582
  workspaceId: '知识库',
533
583
  platform: 'lark',
534
- route: { type: 'channel.chat', channelId: 'chat-2', participantId: 'user-2', threadId: 'thread-2' },
584
+ route: { type: 'channel.chat', channelId: 'chat-2', participantId: 'user-2' },
535
585
  executionMode: 'side-thread',
536
586
  triggerType: 'cron',
537
587
  cronExpr: '0 9 * * *',
@@ -558,6 +608,9 @@ function createIo() {
558
608
  LOCAL_AI_CORE_BASE: `http://127.0.0.1:${address.port}/api/local/v1`,
559
609
  LOCAL_AI_WORKSPACE_ID: '知识库',
560
610
  LOCAL_AI_THREAD_ID: 'thread-1',
611
+ LOCAL_AI_PLATFORM: 'lark',
612
+ LOCAL_AI_CHAT_ID: 'chat-1',
613
+ LOCAL_AI_PLATFORM_USER_ID: 'user-1',
561
614
  }, io);
562
615
  server.close();
563
616
  strict_1.default.equal(exitCode, 0);
@@ -9,15 +9,15 @@ const node_events_1 = require("node:events");
9
9
  const node_fs_1 = require("node:fs");
10
10
  const node_os_1 = require("node:os");
11
11
  const node_path_1 = require("node:path");
12
- const bootstrap_js_1 = require("../services/local-ai-core/src/kernel/bootstrap.js");
13
- const bootstrap_js_2 = require("../services/local-ai-core/src/kernel/bootstrap.js");
14
- const capability_registry_js_1 = require("../services/local-ai-core/src/kernel/capability-registry.js");
15
- const event_bus_js_1 = require("../services/local-ai-core/src/kernel/event-bus.js");
16
- const lifecycle_manager_js_1 = require("../services/local-ai-core/src/kernel/lifecycle-manager.js");
17
- const plugin_registry_js_1 = require("../services/local-ai-core/src/kernel/plugin-registry.js");
18
- const local_core_controller_js_1 = require("../services/local-ai-core/src/runtime/local-core-controller.js");
19
- const local_core_runtime_state_js_1 = require("../services/local-ai-core/src/runtime/local-core-runtime-state.js");
20
- const server_js_1 = require("../services/local-ai-core/src/runtime/server.js");
12
+ const bootstrap_js_1 = require("../../services/local-ai-core/src/kernel/bootstrap.js");
13
+ const bootstrap_js_2 = require("../../services/local-ai-core/src/kernel/bootstrap.js");
14
+ const capability_registry_js_1 = require("../../services/local-ai-core/src/kernel/capability-registry.js");
15
+ const event_bus_js_1 = require("../../services/local-ai-core/src/kernel/event-bus.js");
16
+ const lifecycle_manager_js_1 = require("../../services/local-ai-core/src/kernel/lifecycle-manager.js");
17
+ const plugin_registry_js_1 = require("../../services/local-ai-core/src/kernel/plugin-registry.js");
18
+ const local_core_controller_js_1 = require("../../services/local-ai-core/src/runtime/local-core-controller.js");
19
+ const local_core_runtime_state_js_1 = require("../../services/local-ai-core/src/runtime/local-core-runtime-state.js");
20
+ const server_js_1 = require("../../services/local-ai-core/src/runtime/server.js");
21
21
  function plugin(id, dependsOn = []) {
22
22
  return {
23
23
  manifest: {
@@ -46,10 +46,10 @@ function plugin(id, dependsOn = []) {
46
46
  },
47
47
  snapshot: {
48
48
  agents: [
49
- { id: 'agent.cursor', agentType: 'cursor', displayName: 'cursor' },
50
- { id: 'agent.gemini', agentType: 'gemini', displayName: 'gemini' },
51
- { id: 'agent.qoder', agentType: 'qoder', displayName: 'qoder' },
52
- { id: 'agent.iflow', agentType: 'iflow', displayName: 'iflow' },
49
+ { id: 'agent.cursor', agentType: 'cursor', displayName: 'Cursor' },
50
+ { id: 'agent.gemini', agentType: 'gemini', displayName: 'Gemini' },
51
+ { id: 'agent.qoder', agentType: 'qoder', displayName: 'Qoder' },
52
+ { id: 'agent.iflow', agentType: 'iflow', displayName: 'iFlow' },
53
53
  { id: 'agent.localcore-acp', agentType: 'localcore-acp', displayName: 'LocalCore ACP' },
54
54
  ],
55
55
  channels: [
@@ -562,6 +562,7 @@ model = "deepseek-v4-flash"
562
562
  knowledgeAttachments: {},
563
563
  workspaceRouter: {},
564
564
  scheduler: {},
565
+ scheduledJobs: {},
565
566
  start: async () => {
566
567
  started = true;
567
568
  },
@@ -21,7 +21,9 @@ const rootDir = node_process_1.default.cwd();
21
21
  strict_1.default.match(testScript, /\bpnpm build:renderer\b/, 'pnpm test must build renderer assets');
22
22
  strict_1.default.match(testScript, /\bpnpm build:electron\b/, 'pnpm test must compile Electron and Local AI Core tests');
23
23
  strict_1.default.match(testScript, /\bnode --test\b/, 'pnpm test must run the Node test suite');
24
- strict_1.default.match(testScript, /dist-electron\/electron\/\*\.test\.js/, 'pnpm test must include Electron tests');
24
+ strict_1.default.match(testScript, /dist-electron\/tests\/electron\/\*\.test\.js/, 'pnpm test must include Electron tests');
25
+ strict_1.default.match(testScript, /dist-electron\/tests\/contracts\/\*\.test\.js/, 'pnpm test must include contract tests');
26
+ strict_1.default.match(testScript, /dist-electron\/tests\/integration\/\*\.test\.js/, 'pnpm test must include integration tests');
25
27
  strict_1.default.match(testScript, /dist-electron\/packages\/knowledge-api\/test\/\*\.test\.js/, 'pnpm test must include package tests');
26
28
  strict_1.default.match(testScript, /dist-electron\/src\/pages\/Threads\/thread-chat-permission\.test\.js/, 'pnpm test must include renderer state tests');
27
29
  const buildIndex = smokeScript.indexOf('pnpm build');
@@ -44,20 +46,23 @@ const rootDir = node_process_1.default.cwd();
44
46
  });
45
47
  (0, node_test_1.default)('release workflow keeps validation artifacts separate from formal releases', () => {
46
48
  const ciWorkflow = (0, node_fs_1.readFileSync)((0, node_path_1.join)(rootDir, '.github', 'workflows', 'ci.yml'), 'utf8');
49
+ const packageMacosWorkflow = (0, node_fs_1.readFileSync)((0, node_path_1.join)(rootDir, '.github', 'workflows', 'package-macos.yml'), 'utf8');
47
50
  const releaseWorkflow = (0, node_fs_1.readFileSync)((0, node_path_1.join)(rootDir, '.github', 'workflows', 'release.yml'), 'utf8');
48
51
  const releaseDocs = (0, node_fs_1.readFileSync)((0, node_path_1.join)(rootDir, 'docs', 'operations', 'release-workflow.md'), 'utf8');
49
52
  strict_1.default.match(ciWorkflow, /pull_request:/, 'CI must run before merge');
50
53
  strict_1.default.match(ciWorkflow, /branches:\s*\n\s*- main/, 'CI must run on main pushes');
51
- strict_1.default.match(ciWorkflow, /needs: test/, 'main branch packaging must wait for tests');
52
- strict_1.default.match(ciWorkflow, /--publish never/, 'main branch artifacts must not publish formal releases');
53
- strict_1.default.match(ciWorkflow, /retention-days: 14/, 'main branch validation artifacts must have bounded retention');
54
+ strict_1.default.doesNotMatch(ciWorkflow, /macos-latest/, 'main branch CI must not run macOS packaging');
55
+ strict_1.default.match(packageMacosWorkflow, /workflow_dispatch:/, 'macOS validation packaging must be manual');
56
+ strict_1.default.match(packageMacosWorkflow, /Run tests[\s\S]*?run: pnpm test/, 'manual macOS packaging must run tests first');
57
+ strict_1.default.match(packageMacosWorkflow, /--publish never/, 'manual macOS artifacts must not publish formal releases');
58
+ strict_1.default.match(packageMacosWorkflow, /retention-days: 14/, 'manual macOS validation artifacts must have bounded retention');
54
59
  strict_1.default.match(releaseWorkflow, /tags:\s*\n\s*- 'v\*'/, 'formal releases must be tied to version tags');
55
60
  strict_1.default.match(releaseWorkflow, /Run tests[\s\S]*?run: pnpm test/, 'release packaging must run the fast gate');
56
61
  strict_1.default.match(releaseWorkflow, /Build app[\s\S]*?run: pnpm build/, 'release packaging must run a production build');
57
62
  strict_1.default.match(releaseWorkflow, /--publish always/, 'release artifacts must publish only from the release workflow');
58
63
  strict_1.default.match(releaseWorkflow, /tag_version.*package_version/s, 'tag releases must verify the package version');
59
- strict_1.default.match(releaseDocs, /validation only/i, 'release docs must describe main artifacts as validation only');
60
- strict_1.default.match(releaseDocs, /not formal releases/i, 'release docs must separate main artifacts from formal releases');
64
+ strict_1.default.match(releaseDocs, /manual package artifacts are intended for validation only/i, 'release docs must describe manual artifacts as validation only');
65
+ strict_1.default.match(releaseDocs, /not formal releases/i, 'release docs must separate manual artifacts from formal releases');
61
66
  });
62
67
  (0, node_test_1.default)('production smoke gate launches the built app and checks runtime capabilities', () => {
63
68
  const smokeScript = (0, node_fs_1.readFileSync)((0, node_path_1.join)(rootDir, 'scripts', 'e2e-smoke.mjs'), 'utf8');
@@ -8,7 +8,7 @@ const strict_1 = __importDefault(require("node:assert/strict"));
8
8
  const node_fs_1 = require("node:fs");
9
9
  const node_path_1 = require("node:path");
10
10
  const node_os_1 = require("node:os");
11
- const runtime_detection_service_js_1 = require("../services/local-ai-core/src/runtime/runtime-detection-service.js");
11
+ const runtime_detection_service_js_1 = require("../../services/local-ai-core/src/runtime/runtime-detection-service.js");
12
12
  (0, node_test_1.default)('runtime detection service returns unknown results before first detection', () => {
13
13
  const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'runtime-detection-service-'));
14
14
  try {
@@ -8,8 +8,8 @@ const strict_1 = __importDefault(require("node:assert/strict"));
8
8
  const node_fs_1 = require("node:fs");
9
9
  const node_os_1 = require("node:os");
10
10
  const node_path_1 = require("node:path");
11
- const local_core_acp_store_js_1 = require("../services/local-ai-core/src/acp/local-core-acp-store.js");
12
- const command_risk_js_1 = require("../services/local-ai-core/src/security/command-risk.js");
11
+ const local_core_acp_store_js_1 = require("../../services/local-ai-core/src/acp/local-core-acp-store.js");
12
+ const command_risk_js_1 = require("../../services/local-ai-core/src/security/command-risk.js");
13
13
  (0, node_test_1.default)('workspace security settings persist and create audit events', () => {
14
14
  const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'security-settings-'));
15
15
  const store = new local_core_acp_store_js_1.LocalCoreAcpStore(userDataPath);