@kafca/agentdock 0.1.13 → 0.1.15

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 (27) hide show
  1. package/dist/renderer/assets/{index-BM0Jl5gB.js → index-BSFT2vom.js} +136 -121
  2. package/dist/renderer/assets/index-tUukeThX.css +11 -0
  3. package/dist/renderer/index.html +2 -2
  4. package/dist-electron/electron/agent-runtime-detector.test.js +121 -0
  5. package/dist-electron/electron/lac-cli.test.js +63 -0
  6. package/dist-electron/electron/local-core-refactor.test.js +54 -0
  7. package/dist-electron/electron/plugin-kernel.test.js +6 -6
  8. package/dist-electron/electron/runtime-detection-service.test.js +116 -0
  9. package/dist-electron/electron/security-approval-store.test.js +89 -0
  10. package/dist-electron/electron/workspace-task-store.test.js +66 -0
  11. package/dist-electron/packages/core-sdk/src/index.js +124 -0
  12. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-backend.js +68 -0
  13. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-store.js +617 -0
  14. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-turn-coordinator.js +11 -0
  15. package/dist-electron/services/local-ai-core/src/cli/lac.js +19 -12
  16. package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +5 -0
  17. package/dist-electron/services/local-ai-core/src/plugins/builtin/scheduler-local-plugin.js +35 -0
  18. package/dist-electron/services/local-ai-core/src/router/workspace-router.js +197 -0
  19. package/dist-electron/services/local-ai-core/src/runtime/agent-runtime-detector.js +237 -0
  20. package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +72 -0
  21. package/dist-electron/services/local-ai-core/src/runtime/runtime-detection-service.js +138 -0
  22. package/dist-electron/services/local-ai-core/src/runtime/runtime-detection-store.js +46 -0
  23. package/dist-electron/services/local-ai-core/src/runtime/server.js +151 -0
  24. package/dist-electron/services/local-ai-core/src/scheduler/local-schedule-adapter.js +61 -0
  25. package/dist-electron/services/local-ai-core/src/security/command-risk.js +57 -0
  26. package/package.json +1 -1
  27. package/dist/renderer/assets/index-DrLbJPYN.css +0 -11
@@ -45,21 +45,28 @@ async function handleAdd(flags, env, io, json) {
45
45
  const description = getRequiredFlag(flags, 'desc');
46
46
  const executionMode = getExecutionMode(flags);
47
47
  const context = resolveContext(flags, env);
48
- if (!context.workspaceId || !context.threadId) {
49
- throw new Error('scheduler add requires a current workspace/thread context.');
50
- }
51
- if (context.platform !== 'lark' || !['channel.chat', 'lark_chat'].includes(context.routeType) || !context.chatId || !context.platformUserId) {
52
- throw new Error('scheduler add requires a bound Lark route. Missing LOCAL_AI_CHAT_ID or LOCAL_AI_PLATFORM_USER_ID.');
48
+ if (!context.workspaceId) {
49
+ throw new Error('scheduler add requires a workspace context. Set LOCAL_AI_WORKSPACE_ID or pass --workspace.');
53
50
  }
51
+ const isLarkRoute = context.platform === 'lark'
52
+ && ['channel.chat', 'lark_chat'].includes(context.routeType)
53
+ && context.chatId
54
+ && context.platformUserId;
54
55
  const job = await request(context.baseUrl, 'POST', '/scheduler/jobs', {
55
56
  workspaceId: context.workspaceId,
56
- platform: 'lark',
57
- route: {
58
- type: 'channel.chat',
59
- channelId: context.chatId,
60
- participantId: context.platformUserId,
61
- threadId: context.threadId,
62
- },
57
+ platform: isLarkRoute ? 'lark' : 'local',
58
+ route: isLarkRoute
59
+ ? {
60
+ type: 'channel.chat',
61
+ channelId: context.chatId,
62
+ participantId: context.platformUserId,
63
+ ...(context.threadId ? { threadId: context.threadId } : {}),
64
+ }
65
+ : {
66
+ type: 'local.thread',
67
+ channelId: context.workspaceId,
68
+ ...(context.threadId ? { threadId: context.threadId } : {}),
69
+ },
63
70
  executionMode,
64
71
  triggerType: 'cron',
65
72
  cronExpr,
@@ -15,6 +15,7 @@ const knowledge_ai_vector_plugin_js_1 = require("../plugins/builtin/knowledge-ai
15
15
  const knowledge_noop_plugin_js_1 = require("../plugins/builtin/knowledge-noop-plugin.js");
16
16
  const scheduler_cron_plugin_js_1 = require("../plugins/builtin/scheduler-cron-plugin.js");
17
17
  const scheduler_lark_plugin_js_1 = require("../plugins/builtin/scheduler-lark-plugin.js");
18
+ const scheduler_local_plugin_js_1 = require("../plugins/builtin/scheduler-local-plugin.js");
18
19
  const scheduler_weixin_plugin_js_1 = require("../plugins/builtin/scheduler-weixin-plugin.js");
19
20
  const workspace_router_js_1 = require("../router/workspace-router.js");
20
21
  const local_core_runtime_state_js_1 = require("../runtime/local-core-runtime-state.js");
@@ -187,6 +188,10 @@ function bootstrapLocalCoreRuntime(options) {
187
188
  setConfig: (input) => state.updateKnowledgeConfig(input),
188
189
  });
189
190
  const schedulerPlugins = [
191
+ (0, scheduler_local_plugin_js_1.createBuiltinLocalSchedulerPlugin)({
192
+ store,
193
+ getWorkspaceRouter: () => workspaceRouter,
194
+ }),
190
195
  (0, scheduler_lark_plugin_js_1.createBuiltinLarkSchedulerPlugin)({
191
196
  store,
192
197
  getWorkspaceRouter: () => workspaceRouter,
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createBuiltinLocalSchedulerPlugin = createBuiltinLocalSchedulerPlugin;
4
+ const local_schedule_adapter_js_1 = require("../../scheduler/local-schedule-adapter.js");
5
+ function createBuiltinLocalSchedulerPlugin(options) {
6
+ return {
7
+ manifest: {
8
+ id: 'builtin.scheduler-local',
9
+ kind: 'scheduler',
10
+ version: '0.1.0',
11
+ provides: ['scheduler.delivery.local'],
12
+ },
13
+ capabilities: {
14
+ schedulers: [
15
+ {
16
+ id: 'scheduler.delivery.local',
17
+ triggerTypes: [],
18
+ deliveryTargets: ['local'],
19
+ enabled: true,
20
+ displayName: 'Local Scheduled Execution',
21
+ },
22
+ ],
23
+ },
24
+ createRuntime() {
25
+ return {
26
+ executors: [
27
+ new local_schedule_adapter_js_1.LocalScheduleAdapter({
28
+ store: options.store,
29
+ getWorkspaceRouter: options.getWorkspaceRouter,
30
+ }),
31
+ ],
32
+ };
33
+ },
34
+ };
35
+ }
@@ -2,8 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.WorkspaceRouter = exports.encodeThreadId = exports.decodeThreadId = void 0;
4
4
  exports.createWorkspaceRouter = createWorkspaceRouter;
5
+ const node_fs_1 = require("node:fs");
6
+ const node_child_process_1 = require("node:child_process");
5
7
  const local_core_acp_backend_js_1 = require("../acp/local-core-acp-backend.js");
6
8
  const workspace_thread_id_js_1 = require("../thread/workspace-thread-id.js");
9
+ const command_risk_js_1 = require("../security/command-risk.js");
7
10
  const workspace_route_config_js_1 = require("./workspace-route-config.js");
8
11
  var workspace_thread_id_js_2 = require("../thread/workspace-thread-id.js");
9
12
  Object.defineProperty(exports, "decodeThreadId", { enumerable: true, get: function () { return workspace_thread_id_js_2.decodeThreadId; } });
@@ -79,6 +82,95 @@ class WorkspaceRouter {
79
82
  }
80
83
  return [...workspaceMap.values()].sort((a, b) => a.name.localeCompare(b.name));
81
84
  }
85
+ async listWorkspaceRegistry() {
86
+ await this.syncConfiguredWorkspaces();
87
+ return this.store.listWorkspaceRegistry();
88
+ }
89
+ async getWorkspaceRegistryEntry(workspaceId) {
90
+ await this.syncConfiguredWorkspaces();
91
+ const workspace = this.store.getWorkspaceRegistryEntry(workspaceId);
92
+ if (!workspace) {
93
+ throw new Error(`Workspace not found: ${workspaceId}`);
94
+ }
95
+ return workspace;
96
+ }
97
+ async createWorkspaceRegistryEntry(input) {
98
+ return this.store.upsertWorkspaceRegistryEntry({
99
+ ...input,
100
+ deviceId: 'local',
101
+ git: detectGitSummary(input.path),
102
+ health: workspaceHealth(input.path),
103
+ });
104
+ }
105
+ async updateWorkspaceRegistryEntry(workspaceId, input) {
106
+ const next = this.store.updateWorkspaceRegistryEntry(workspaceId, input);
107
+ return this.store.upsertWorkspaceRegistryEntry({
108
+ workspaceId: next.workspaceId,
109
+ displayName: next.displayName,
110
+ path: next.path,
111
+ deviceId: next.deviceId,
112
+ defaultRuntimeId: next.defaultRuntimeId,
113
+ git: detectGitSummary(next.path),
114
+ health: workspaceHealth(next.path),
115
+ metadata: next.metadata,
116
+ });
117
+ }
118
+ async deleteWorkspaceRegistryEntry(workspaceId) {
119
+ return this.store.deleteWorkspaceRegistryEntry(workspaceId);
120
+ }
121
+ listAgentTasks(query = {}) {
122
+ return this.store.listAgentTasks(query);
123
+ }
124
+ getAgentTask(taskId) {
125
+ const task = this.store.getAgentTask(taskId);
126
+ if (!task) {
127
+ throw new Error(`Task not found: ${taskId}`);
128
+ }
129
+ return task;
130
+ }
131
+ createAgentTask(input) {
132
+ return this.store.createAgentTask({ ...input, deviceId: 'local' });
133
+ }
134
+ updateAgentTask(taskId, input) {
135
+ return this.store.updateAgentTask(taskId, input);
136
+ }
137
+ getWorkspaceSecuritySettings(workspaceId) {
138
+ return this.store.getWorkspaceSecuritySettings(workspaceId);
139
+ }
140
+ updateWorkspaceSecuritySettings(workspaceId, input) {
141
+ return this.store.updateWorkspaceSecuritySettings(workspaceId, input);
142
+ }
143
+ classifyCommand(command, workspaceId) {
144
+ const classification = (0, command_risk_js_1.classifyCommandRisk)(command);
145
+ this.store.createAuditEvent({
146
+ type: 'command.classified',
147
+ workspaceId,
148
+ actor: 'local',
149
+ summary: `Command classified as ${classification.riskLevel}.`,
150
+ riskLevel: classification.riskLevel,
151
+ metadata: { classification: { ...classification } },
152
+ });
153
+ return classification;
154
+ }
155
+ listApprovalRequests(query = {}) {
156
+ return this.store.listApprovalRequests(query);
157
+ }
158
+ getApprovalRequest(approvalId) {
159
+ const approval = this.store.getApprovalRequest(approvalId);
160
+ if (!approval) {
161
+ throw new Error(`Approval not found: ${approvalId}`);
162
+ }
163
+ return approval;
164
+ }
165
+ createApprovalRequest(input) {
166
+ return this.store.createApprovalRequest(input);
167
+ }
168
+ resolveApprovalRequest(approvalId, input) {
169
+ return this.store.resolveApprovalRequest(approvalId, input);
170
+ }
171
+ listAuditEvents(query = {}) {
172
+ return this.store.listAuditEvents(query);
173
+ }
82
174
  async listThreads(workspaceId) {
83
175
  await this.getWorkspaceRoute(workspaceId);
84
176
  return this.localCoreAcp.listThreads(workspaceId);
@@ -348,6 +440,29 @@ class WorkspaceRouter {
348
440
  const projects = Array.isArray(configState.parsed?.projects) ? configState.parsed.projects : [];
349
441
  return projects.filter((project) => this.resolveProjectRoute(configState, project));
350
442
  }
443
+ async syncConfiguredWorkspaces() {
444
+ const config = await this.options.readConfigState();
445
+ const projects = Array.isArray(config.parsed?.projects) ? config.parsed.projects : [];
446
+ for (const project of projects) {
447
+ const route = this.resolveProjectRoute(config, project);
448
+ if (!route) {
449
+ continue;
450
+ }
451
+ const path = inferWorkspacePath(project);
452
+ this.store.upsertWorkspaceRegistryEntry({
453
+ workspaceId: project.name,
454
+ displayName: project.name,
455
+ path,
456
+ deviceId: 'local',
457
+ defaultRuntimeId: route.agentType,
458
+ git: detectGitSummary(path),
459
+ health: workspaceHealth(path),
460
+ metadata: {
461
+ platforms: (0, workspace_route_config_js_1.normalizePlatformTypes)(project),
462
+ },
463
+ });
464
+ }
465
+ }
351
466
  resolveProjectRoute(configState, project) {
352
467
  for (const runtime of this.options.getAgentRuntimes?.() || []) {
353
468
  if (!runtime.matchesProject(project)) {
@@ -387,3 +502,85 @@ exports.WorkspaceRouter = WorkspaceRouter;
387
502
  function createWorkspaceRouter(options) {
388
503
  return new WorkspaceRouter(options);
389
504
  }
505
+ function inferWorkspacePath(project) {
506
+ const options = project.agent?.options || {};
507
+ for (const key of ['cwd', 'path', 'workspacePath', 'root']) {
508
+ const value = options[key];
509
+ if (typeof value === 'string' && value.trim()) {
510
+ return value.trim();
511
+ }
512
+ }
513
+ return '';
514
+ }
515
+ function workspaceHealth(path) {
516
+ if (!path) {
517
+ return {
518
+ status: 'warning',
519
+ summary: 'Workspace path is not configured.',
520
+ issues: [{
521
+ code: 'workspace_path_missing',
522
+ severity: 'warning',
523
+ message: 'Workspace path is not configured.',
524
+ help: 'Set a workspace path in the project agent options.',
525
+ }],
526
+ checkedAt: new Date().toISOString(),
527
+ };
528
+ }
529
+ if (!(0, node_fs_1.existsSync)(path)) {
530
+ return {
531
+ status: 'error',
532
+ summary: 'Workspace path does not exist.',
533
+ issues: [{
534
+ code: 'workspace_path_not_found',
535
+ severity: 'error',
536
+ message: `Workspace path does not exist: ${path}`,
537
+ help: 'Update the workspace path or restore the missing directory.',
538
+ }],
539
+ checkedAt: new Date().toISOString(),
540
+ };
541
+ }
542
+ return {
543
+ status: 'healthy',
544
+ summary: 'Workspace is available.',
545
+ issues: [],
546
+ checkedAt: new Date().toISOString(),
547
+ };
548
+ }
549
+ function detectGitSummary(path) {
550
+ if (!path || !(0, node_fs_1.existsSync)(path)) {
551
+ return { isRepo: false };
552
+ }
553
+ try {
554
+ const isRepo = git(path, ['rev-parse', '--is-inside-work-tree']) === 'true';
555
+ if (!isRepo) {
556
+ return { isRepo: false };
557
+ }
558
+ const branch = git(path, ['branch', '--show-current']) || undefined;
559
+ const remote = git(path, ['config', '--get', 'remote.origin.url']) || undefined;
560
+ const status = git(path, ['status', '--porcelain']);
561
+ const sha = git(path, ['rev-parse', '--short', 'HEAD']) || '';
562
+ const message = git(path, ['log', '-1', '--pretty=%s']) || '';
563
+ const committedAt = git(path, ['log', '-1', '--pretty=%cI']) || undefined;
564
+ return {
565
+ isRepo: true,
566
+ branch,
567
+ remote,
568
+ dirty: Boolean(status),
569
+ lastCommit: sha ? { sha, message, committedAt } : undefined,
570
+ };
571
+ }
572
+ catch (error) {
573
+ return {
574
+ isRepo: false,
575
+ error: error instanceof Error ? error.message : String(error),
576
+ };
577
+ }
578
+ }
579
+ function git(cwd, args) {
580
+ return (0, node_child_process_1.execFileSync)('git', args, {
581
+ cwd,
582
+ encoding: 'utf8',
583
+ timeout: 1500,
584
+ stdio: ['ignore', 'pipe', 'ignore'],
585
+ }).trim();
586
+ }
@@ -0,0 +1,237 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.detectInstalledAgentRuntimes = detectInstalledAgentRuntimes;
4
+ const node_fs_1 = require("node:fs");
5
+ const node_path_1 = require("node:path");
6
+ const node_module_1 = require("node:module");
7
+ const node_child_process_1 = require("node:child_process");
8
+ const desktop_js_1 = require("../../../../shared/desktop.js");
9
+ const DISPLAY_NAMES = {
10
+ opencode: 'OpenCode',
11
+ codex: 'Codex',
12
+ claudecode: 'Claude Code',
13
+ cursor: 'Cursor',
14
+ gemini: 'Gemini',
15
+ qoder: 'Qoder',
16
+ iflow: 'iFlow',
17
+ [desktop_js_1.LOCALCORE_ACP_AGENT_TYPE]: 'LocalCore ACP',
18
+ };
19
+ const COMMAND_CANDIDATES = {
20
+ opencode: ['opencode'],
21
+ codex: ['codex'],
22
+ claudecode: ['claude-agent-acp'],
23
+ cursor: ['cursor-agent', 'cursor'],
24
+ gemini: ['gemini'],
25
+ qoder: ['qoder'],
26
+ iflow: ['iflow'],
27
+ };
28
+ const VERSION_ARGUMENTS = {
29
+ opencode: ['--version'],
30
+ codex: ['--version'],
31
+ claudecode: ['--version'],
32
+ cursor: ['--version'],
33
+ gemini: ['--version'],
34
+ qoder: ['--version'],
35
+ iflow: ['--version'],
36
+ };
37
+ function detectInstalledAgentRuntimes(options = {}) {
38
+ const env = options.env || process.env;
39
+ const configuredCommands = collectConfiguredAgentCommands(options.config);
40
+ const detectedAt = (options.now || new Date()).toISOString();
41
+ const versionTimeoutMs = options.versionTimeoutMs ?? 2500;
42
+ return desktop_js_1.DESKTOP_AGENT_TYPE_OPTIONS.map((agentType) => {
43
+ if (agentType === desktop_js_1.LOCALCORE_ACP_AGENT_TYPE) {
44
+ return {
45
+ agentType,
46
+ runtimeId: agentType,
47
+ displayName: displayName(agentType),
48
+ status: 'installed',
49
+ installed: true,
50
+ detectedAt,
51
+ summary: `${displayName(agentType)} is built in.`,
52
+ issues: [],
53
+ recommendedActions: [],
54
+ source: 'builtin',
55
+ };
56
+ }
57
+ const configured = configuredCommands.get(agentType);
58
+ if (configured) {
59
+ const resolved = resolveCommand(configured, env);
60
+ return resolved
61
+ ? installedRuntime(agentType, resolved, 'config', detectedAt, env, versionTimeoutMs)
62
+ : missingRuntime(agentType, detectedAt, `Configured command not found: ${configured}`);
63
+ }
64
+ if (agentType === 'claudecode') {
65
+ const bundled = resolveBundledClaudeAgentAcp(options.requireFrom);
66
+ if (bundled) {
67
+ return installedRuntime(agentType, bundled, 'bundled', detectedAt, env, versionTimeoutMs);
68
+ }
69
+ }
70
+ for (const command of COMMAND_CANDIDATES[agentType] || [agentType]) {
71
+ const resolved = resolveCommand(command, env);
72
+ if (resolved) {
73
+ return installedRuntime(agentType, resolved, 'path', detectedAt, env, versionTimeoutMs);
74
+ }
75
+ }
76
+ return missingRuntime(agentType, detectedAt);
77
+ });
78
+ }
79
+ function installedRuntime(agentType, command, source, detectedAt, env, versionTimeoutMs) {
80
+ const versionResult = detectRuntimeVersion(agentType, command, env, versionTimeoutMs);
81
+ const issues = versionResult.issue ? [versionResult.issue] : [];
82
+ return {
83
+ agentType,
84
+ runtimeId: agentType,
85
+ displayName: displayName(agentType),
86
+ status: 'installed',
87
+ installed: true,
88
+ command,
89
+ binaryPath: command,
90
+ version: versionResult.version,
91
+ detectedAt,
92
+ summary: versionResult.version
93
+ ? `${displayName(agentType)} ${versionResult.version} is installed.`
94
+ : `${displayName(agentType)} is installed.`,
95
+ details: issues[0]?.message,
96
+ issues,
97
+ recommendedActions: [],
98
+ source,
99
+ };
100
+ }
101
+ function missingRuntime(agentType, detectedAt, error) {
102
+ const name = displayName(agentType);
103
+ return {
104
+ agentType,
105
+ runtimeId: agentType,
106
+ displayName: name,
107
+ status: error ? 'error' : 'not_installed',
108
+ installed: false,
109
+ detectedAt,
110
+ summary: error || `${name} was not found on PATH or in project configuration.`,
111
+ details: error,
112
+ issues: [
113
+ {
114
+ code: error ? 'configured_command_not_found' : 'runtime_not_found',
115
+ severity: error ? 'error' : 'info',
116
+ message: error || `${name} is not installed or is not available on PATH.`,
117
+ help: error
118
+ ? 'Update the project runtime command or make the configured binary executable.'
119
+ : 'Install the runtime manually, then refresh detection.',
120
+ },
121
+ ],
122
+ recommendedActions: [
123
+ {
124
+ label: error ? 'Fix configured command' : `Install ${name}`,
125
+ description: error
126
+ ? 'Check the workspace agent command path in configuration.'
127
+ : `Install ${name} manually and make sure its command is available on PATH.`,
128
+ },
129
+ ],
130
+ source: 'path',
131
+ error,
132
+ };
133
+ }
134
+ function displayName(agentType) {
135
+ return DISPLAY_NAMES[agentType] || agentType;
136
+ }
137
+ function collectConfiguredAgentCommands(config) {
138
+ const commands = new Map();
139
+ for (const project of Array.isArray(config?.projects) ? config.projects : []) {
140
+ const agentType = String(project?.agent?.type || '').trim().toLowerCase();
141
+ const command = String(project?.agent?.options?.command || '').trim();
142
+ if (agentType && command && !commands.has(agentType)) {
143
+ commands.set(agentType, command);
144
+ }
145
+ }
146
+ return commands;
147
+ }
148
+ function resolveCommand(command, env) {
149
+ const normalized = command.trim();
150
+ if (!normalized) {
151
+ return null;
152
+ }
153
+ if ((0, node_path_1.isAbsolute)(normalized) || normalized.includes('/') || normalized.includes('\\')) {
154
+ return isExecutableFile(normalized) ? normalized : null;
155
+ }
156
+ for (const dir of String(env.PATH || '').split(node_path_1.delimiter).filter(Boolean)) {
157
+ for (const candidate of commandCandidates(normalized, env)) {
158
+ const fullPath = (0, node_path_1.join)(dir, candidate);
159
+ if (isExecutableFile(fullPath)) {
160
+ return fullPath;
161
+ }
162
+ }
163
+ }
164
+ return null;
165
+ }
166
+ function commandCandidates(command, env) {
167
+ if (process.platform !== 'win32' || /\.[a-z0-9]+$/i.test(command)) {
168
+ return [command];
169
+ }
170
+ const extensions = String(env.PATHEXT || '.COM;.EXE;.BAT;.CMD')
171
+ .split(';')
172
+ .map((ext) => ext.trim())
173
+ .filter(Boolean);
174
+ return [command, ...extensions.map((ext) => `${command}${ext}`)];
175
+ }
176
+ function isExecutableFile(path) {
177
+ try {
178
+ (0, node_fs_1.accessSync)(path, node_fs_1.constants.X_OK);
179
+ return true;
180
+ }
181
+ catch {
182
+ return false;
183
+ }
184
+ }
185
+ function detectRuntimeVersion(agentType, command, env, timeoutMs) {
186
+ const args = VERSION_ARGUMENTS[agentType];
187
+ if (!args || agentType === desktop_js_1.LOCALCORE_ACP_AGENT_TYPE) {
188
+ return {};
189
+ }
190
+ try {
191
+ const output = (0, node_child_process_1.execFileSync)(command, args, {
192
+ env,
193
+ timeout: timeoutMs,
194
+ encoding: 'utf8',
195
+ stdio: ['ignore', 'pipe', 'pipe'],
196
+ });
197
+ return { version: parseVersionOutput(output) };
198
+ }
199
+ catch (err) {
200
+ const timedOut = err?.code === 'ETIMEDOUT' || err?.signal === 'SIGTERM';
201
+ return {
202
+ issue: {
203
+ code: timedOut ? 'version_detection_timeout' : 'version_detection_failed',
204
+ severity: 'warning',
205
+ message: timedOut
206
+ ? `${displayName(agentType)} was found, but version detection timed out.`
207
+ : `${displayName(agentType)} was found, but its version could not be detected.`,
208
+ help: 'Installation detection remains valid because the executable was resolved.',
209
+ },
210
+ };
211
+ }
212
+ }
213
+ function parseVersionOutput(output) {
214
+ const text = output.trim().split(/\r?\n/).find(Boolean) || '';
215
+ const match = text.match(/\d+(?:\.\d+)+(?:[-+._a-zA-Z0-9]*)?/);
216
+ return match?.[0] || text || undefined;
217
+ }
218
+ function resolveBundledClaudeAgentAcp(requireFrom) {
219
+ try {
220
+ const require = (0, node_module_1.createRequire)(requireFrom || (0, node_path_1.join)(process.cwd(), 'package.json'));
221
+ const packageJsonPath = require.resolve(`${desktop_js_1.DESKTOP_CLAUDECODE_ACP_PACKAGE}/package.json`);
222
+ const packageDir = (0, node_path_1.dirname)(packageJsonPath);
223
+ for (const candidate of [
224
+ (0, node_path_1.join)(packageDir, 'dist', 'cli.js'),
225
+ (0, node_path_1.join)(packageDir, 'bin', 'claude-agent-acp.js'),
226
+ (0, node_path_1.join)(packageDir, 'cli.js'),
227
+ ]) {
228
+ if ((0, node_fs_1.existsSync)(candidate)) {
229
+ return candidate;
230
+ }
231
+ }
232
+ return packageJsonPath;
233
+ }
234
+ catch {
235
+ return null;
236
+ }
237
+ }
@@ -4,6 +4,7 @@ exports.LocalCoreController = void 0;
4
4
  const node_events_1 = require("node:events");
5
5
  const desktop_js_1 = require("../../../../shared/desktop.js");
6
6
  const bootstrap_js_1 = require("../kernel/bootstrap.js");
7
+ const runtime_detection_service_js_1 = require("./runtime-detection-service.js");
7
8
  class LocalCoreController extends node_events_1.EventEmitter {
8
9
  userDataPath;
9
10
  state;
@@ -14,6 +15,7 @@ class LocalCoreController extends node_events_1.EventEmitter {
14
15
  scheduler;
15
16
  kernel;
16
17
  runtime;
18
+ runtimeDetection;
17
19
  busUnsubscribers = [];
18
20
  constructor(userDataPath, runtime) {
19
21
  super();
@@ -30,6 +32,12 @@ class LocalCoreController extends node_events_1.EventEmitter {
30
32
  this.channelRuntime = this.runtime.channelRuntime;
31
33
  this.weixinChannelRuntime = this.runtime.weixinChannelRuntime;
32
34
  this.scheduler = this.runtime.scheduler;
35
+ this.runtimeDetection = new runtime_detection_service_js_1.RuntimeDetectionService({
36
+ userDataPath,
37
+ readConfig: async () => (await this.readConfigFile()).parsed,
38
+ log: (message) => this.handleLog(message),
39
+ emit: (event) => this.handleRuntimeDetectionEvent(event),
40
+ });
33
41
  this.busUnsubscribers.push(this.kernel.context.bus.on('platform.bridge.updated', (event) => {
34
42
  this.emit('bridge', event);
35
43
  }), this.kernel.context.bus.on('scheduler.job.updated', (job) => {
@@ -43,6 +51,7 @@ class LocalCoreController extends node_events_1.EventEmitter {
43
51
  async init() {
44
52
  await this.runtime.start();
45
53
  await this.emitRuntime();
54
+ void this.runtimeDetection.refreshOnStartup();
46
55
  }
47
56
  async close() {
48
57
  for (const unsubscribe of this.busUnsubscribers) {
@@ -122,6 +131,57 @@ class LocalCoreController extends node_events_1.EventEmitter {
122
131
  async listWorkspaces() {
123
132
  return this.workspaceRouter.listWorkspaces();
124
133
  }
134
+ async listWorkspaceRegistry() {
135
+ return this.workspaceRouter.listWorkspaceRegistry();
136
+ }
137
+ async getWorkspaceRegistryEntry(workspaceId) {
138
+ return this.workspaceRouter.getWorkspaceRegistryEntry(workspaceId);
139
+ }
140
+ async createWorkspaceRegistryEntry(input) {
141
+ return this.workspaceRouter.createWorkspaceRegistryEntry(input);
142
+ }
143
+ async updateWorkspaceRegistryEntry(workspaceId, input) {
144
+ return this.workspaceRouter.updateWorkspaceRegistryEntry(workspaceId, input);
145
+ }
146
+ async deleteWorkspaceRegistryEntry(workspaceId) {
147
+ return this.workspaceRouter.deleteWorkspaceRegistryEntry(workspaceId);
148
+ }
149
+ async listAgentTasks(query = {}) {
150
+ return this.workspaceRouter.listAgentTasks(query);
151
+ }
152
+ async getAgentTask(taskId) {
153
+ return this.workspaceRouter.getAgentTask(taskId);
154
+ }
155
+ async createAgentTask(input) {
156
+ return this.workspaceRouter.createAgentTask(input);
157
+ }
158
+ async updateAgentTask(taskId, input) {
159
+ return this.workspaceRouter.updateAgentTask(taskId, input);
160
+ }
161
+ async getWorkspaceSecuritySettings(workspaceId) {
162
+ return this.workspaceRouter.getWorkspaceSecuritySettings(workspaceId);
163
+ }
164
+ async updateWorkspaceSecuritySettings(workspaceId, input) {
165
+ return this.workspaceRouter.updateWorkspaceSecuritySettings(workspaceId, input);
166
+ }
167
+ async classifyCommand(command, workspaceId) {
168
+ return this.workspaceRouter.classifyCommand(command, workspaceId);
169
+ }
170
+ async listApprovalRequests(query = {}) {
171
+ return this.workspaceRouter.listApprovalRequests(query);
172
+ }
173
+ async getApprovalRequest(approvalId) {
174
+ return this.workspaceRouter.getApprovalRequest(approvalId);
175
+ }
176
+ async createApprovalRequest(input) {
177
+ return this.workspaceRouter.createApprovalRequest(input);
178
+ }
179
+ async resolveApprovalRequest(approvalId, input) {
180
+ return this.workspaceRouter.resolveApprovalRequest(approvalId, input);
181
+ }
182
+ async listAuditEvents(query = {}) {
183
+ return this.workspaceRouter.listAuditEvents(query);
184
+ }
125
185
  async listScheduledJobs(workspaceId) {
126
186
  return this.scheduler.listJobs(workspaceId);
127
187
  }
@@ -228,12 +288,24 @@ class LocalCoreController extends node_events_1.EventEmitter {
228
288
  async getCapabilitySnapshot() {
229
289
  return this.kernel.getCapabilitySnapshot().snapshot;
230
290
  }
291
+ async listInstalledAgentRuntimes() {
292
+ return this.runtimeDetection.list();
293
+ }
294
+ async refreshInstalledAgentRuntimes(runtimeId) {
295
+ return this.runtimeDetection.refresh(runtimeId);
296
+ }
297
+ isRuntimeDetectionRunning(runtimeId) {
298
+ return this.runtimeDetection.isChecking(runtimeId);
299
+ }
231
300
  async getPluginDiagnostics() {
232
301
  return this.kernel.diagnostics.snapshot();
233
302
  }
234
303
  async probeWorkspaceStreaming(workspaceId) {
235
304
  return this.workspaceRouter.probeWorkspaceStreaming(workspaceId);
236
305
  }
306
+ handleRuntimeDetectionEvent(event) {
307
+ this.emit('runtime-detection', event);
308
+ }
237
309
  async listChannelGatewayStatuses(platform) {
238
310
  if (!platform) {
239
311
  const [larkStatuses, weixinStatuses] = await Promise.all([