@kafca/agentdock 0.1.14 → 0.1.16
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.
- package/bin/agentdock.mjs +73 -3
- package/dist/renderer/assets/Badge-O6YyBOXO.js +1 -0
- package/dist/renderer/assets/Chat-Bb42Xu99.js +2 -0
- package/dist/renderer/assets/ChatMarkdown-CKIl8nVw.js +45 -0
- package/dist/renderer/assets/Config-U1HHZNv-.js +16 -0
- package/dist/renderer/assets/CronList-Qfok-TFN.js +1 -0
- package/dist/renderer/assets/Dashboard-DIxtJDEV.js +26 -0
- package/dist/renderer/assets/EmptyState-Ce5baC0x.js +6 -0
- package/dist/renderer/assets/KnowledgeDetail-DKjqGzjI.js +16 -0
- package/dist/renderer/assets/KnowledgeHome-CsYBKv3L.js +11 -0
- package/dist/renderer/assets/Logs-BV93l6f2.js +1 -0
- package/dist/renderer/assets/Modal-JbmTZbCL.js +10 -0
- package/dist/renderer/assets/Page-854R0NMM.js +1 -0
- package/dist/renderer/assets/ProjectDetail-Ct7yASjV.js +11 -0
- package/dist/renderer/assets/ProjectList-mJJeoUcP.js +1 -0
- package/dist/renderer/assets/SessionChat-CClPXCjv.js +1 -0
- package/dist/renderer/assets/SessionList-Cxilghws.js +1 -0
- package/dist/renderer/assets/ThreadChat-BUFr0IbT.js +23 -0
- package/dist/renderer/assets/Workspace-B_sQxTj-.js +14 -0
- package/dist/renderer/assets/arrow-left-mguKZKTu.js +6 -0
- package/dist/renderer/assets/book-open-3Awj2zba.js +6 -0
- package/dist/renderer/assets/circle-BWSc4-qr.js +6 -0
- package/dist/renderer/assets/desktop-D4Z7M9RM.js +3 -0
- package/dist/renderer/assets/funnel-DE2QCcoy.js +6 -0
- package/dist/renderer/assets/heart-B6xi7jGt.js +6 -0
- package/dist/renderer/assets/index-B2JL40NF.js +232 -0
- package/dist/renderer/assets/index-CJZeKEpW.css +11 -0
- package/dist/renderer/assets/pencil-BD6oAfUg.js +6 -0
- package/dist/renderer/assets/play-C9Ue_X4n.js +6 -0
- package/dist/renderer/assets/plug-CIaRVhae.js +6 -0
- package/dist/renderer/assets/plus-BWHFEPZn.js +6 -0
- package/dist/renderer/assets/projects-DCdaAlgs.js +1 -0
- package/dist/renderer/assets/save-DsSaJ2pw.js +6 -0
- package/dist/renderer/assets/search-Ci4i20HH.js +6 -0
- package/dist/renderer/assets/server-07G4ybtv.js +11 -0
- package/dist/renderer/assets/session-utils-D9njomRn.js +6 -0
- package/dist/renderer/assets/sessions-C4c2fLQB.js +1 -0
- package/dist/renderer/assets/shield-check-DjJz3hYk.js +6 -0
- package/dist/renderer/assets/trash-2-C5_ZLIsu.js +6 -0
- package/dist/renderer/assets/user-DHBITt1w.js +11 -0
- package/dist/renderer/assets/wifi-off-DuwKaMRJ.js +11 -0
- package/dist/renderer/index.html +2 -2
- package/dist-electron/electron/agent-runtime-detector.test.js +121 -0
- package/dist-electron/electron/lac-cli.test.js +63 -0
- package/dist-electron/electron/local-core-refactor.test.js +54 -0
- package/dist-electron/electron/plugin-kernel.test.js +6 -6
- package/dist-electron/electron/runtime-detection-service.test.js +116 -0
- package/dist-electron/electron/security-approval-store.test.js +89 -0
- package/dist-electron/electron/workspace-task-store.test.js +66 -0
- package/dist-electron/packages/core-sdk/src/index.js +124 -0
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-backend.js +68 -0
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-store.js +617 -0
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-turn-coordinator.js +11 -0
- package/dist-electron/services/local-ai-core/src/cli/lac.js +19 -12
- package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +5 -0
- package/dist-electron/services/local-ai-core/src/plugins/builtin/scheduler-local-plugin.js +35 -0
- package/dist-electron/services/local-ai-core/src/router/workspace-router.js +197 -0
- package/dist-electron/services/local-ai-core/src/runtime/agent-runtime-detector.js +237 -0
- package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +72 -0
- package/dist-electron/services/local-ai-core/src/runtime/runtime-detection-service.js +138 -0
- package/dist-electron/services/local-ai-core/src/runtime/runtime-detection-store.js +46 -0
- package/dist-electron/services/local-ai-core/src/runtime/server.js +151 -0
- package/dist-electron/services/local-ai-core/src/scheduler/local-schedule-adapter.js +61 -0
- package/dist-electron/services/local-ai-core/src/security/command-risk.js +57 -0
- package/package.json +1 -1
- package/dist/renderer/assets/index-BM0Jl5gB.js +0 -487
- 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
|
|
49
|
-
throw new Error('scheduler add requires a
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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([
|