@kafca/agentdock 0.1.14 → 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
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const node_test_1 = __importDefault(require("node:test"));
7
+ const strict_1 = __importDefault(require("node:assert/strict"));
8
+ const node_fs_1 = require("node:fs");
9
+ const node_path_1 = require("node:path");
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");
12
+ (0, node_test_1.default)('runtime detection service returns unknown results before first detection', () => {
13
+ const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'runtime-detection-service-'));
14
+ try {
15
+ const service = new runtime_detection_service_js_1.RuntimeDetectionService({
16
+ userDataPath,
17
+ readConfig: async () => null,
18
+ detect: () => [],
19
+ });
20
+ const runtimes = service.list();
21
+ const opencode = runtimes.find((runtime) => runtime.runtimeId === 'opencode');
22
+ strict_1.default.equal(opencode?.status, 'unknown');
23
+ strict_1.default.equal(opencode?.installed, false);
24
+ strict_1.default.equal(runtimes.find((runtime) => runtime.runtimeId === 'localcore-acp')?.status, 'installed');
25
+ }
26
+ finally {
27
+ (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
28
+ }
29
+ });
30
+ (0, node_test_1.default)('runtime detection service refresh persists latest results', async () => {
31
+ const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'runtime-detection-service-persist-'));
32
+ try {
33
+ const detectedAt = '2026-04-27T00:00:00.000Z';
34
+ const result = runtimeResult({ detectedAt, version: '1.2.3' });
35
+ const service = new runtime_detection_service_js_1.RuntimeDetectionService({
36
+ userDataPath,
37
+ readConfig: async () => null,
38
+ detect: () => [result],
39
+ });
40
+ strict_1.default.deepEqual(await service.refresh(), [result]);
41
+ const nextService = new runtime_detection_service_js_1.RuntimeDetectionService({
42
+ userDataPath,
43
+ readConfig: async () => null,
44
+ detect: () => [],
45
+ });
46
+ strict_1.default.equal(nextService.list()[0]?.version, '1.2.3');
47
+ strict_1.default.match((0, node_fs_1.readFileSync)((0, node_path_1.join)(userDataPath, 'runtime', 'runtime-detection.json'), 'utf8'), /"version": "1.2.3"/);
48
+ }
49
+ finally {
50
+ (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
51
+ }
52
+ });
53
+ (0, node_test_1.default)('runtime detection service ignores corrupted persisted state', () => {
54
+ const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'runtime-detection-service-corrupt-'));
55
+ try {
56
+ const statePath = (0, node_path_1.join)(userDataPath, 'runtime', 'runtime-detection.json');
57
+ (0, node_fs_1.mkdirSync)((0, node_path_1.join)(userDataPath, 'runtime'), { recursive: true });
58
+ (0, node_fs_1.writeFileSync)(statePath, '{not-json', 'utf8');
59
+ const service = new runtime_detection_service_js_1.RuntimeDetectionService({
60
+ userDataPath,
61
+ readConfig: async () => null,
62
+ detect: () => [],
63
+ });
64
+ strict_1.default.equal(service.list().find((runtime) => runtime.runtimeId === 'opencode')?.status, 'unknown');
65
+ }
66
+ finally {
67
+ (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
68
+ }
69
+ });
70
+ (0, node_test_1.default)('runtime detection service emits detection events and filters single runtime refresh response', async () => {
71
+ const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'runtime-detection-service-events-'));
72
+ try {
73
+ const events = [];
74
+ const service = new runtime_detection_service_js_1.RuntimeDetectionService({
75
+ userDataPath,
76
+ readConfig: async () => null,
77
+ detect: () => [
78
+ runtimeResult({ runtimeId: 'opencode', agentType: 'opencode' }),
79
+ runtimeResult({ runtimeId: 'codex', agentType: 'codex' }),
80
+ ],
81
+ emit: (event) => {
82
+ events.push(event.type);
83
+ },
84
+ });
85
+ const refreshed = await service.refresh('codex');
86
+ strict_1.default.deepEqual(refreshed.map((runtime) => runtime.runtimeId), ['codex']);
87
+ strict_1.default.deepEqual(events, [
88
+ 'runtime.detect.started',
89
+ 'runtime.detect.completed',
90
+ 'runtime.status.changed',
91
+ 'runtime.status.changed',
92
+ ]);
93
+ }
94
+ finally {
95
+ (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
96
+ }
97
+ });
98
+ function runtimeResult(input = {}) {
99
+ return {
100
+ agentType: input.agentType || 'opencode',
101
+ runtimeId: input.runtimeId || input.agentType || 'opencode',
102
+ displayName: input.displayName || 'OpenCode',
103
+ status: input.status || 'installed',
104
+ installed: input.installed ?? true,
105
+ command: input.command || '/tmp/opencode',
106
+ binaryPath: input.binaryPath || input.command || '/tmp/opencode',
107
+ version: input.version,
108
+ detectedAt: input.detectedAt || '2026-04-27T00:00:00.000Z',
109
+ summary: input.summary || 'OpenCode is installed.',
110
+ details: input.details,
111
+ issues: input.issues || [],
112
+ recommendedActions: input.recommendedActions || [],
113
+ source: input.source || 'path',
114
+ error: input.error,
115
+ };
116
+ }
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const node_test_1 = __importDefault(require("node:test"));
7
+ const strict_1 = __importDefault(require("node:assert/strict"));
8
+ const node_fs_1 = require("node:fs");
9
+ const node_os_1 = require("node:os");
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");
13
+ (0, node_test_1.default)('workspace security settings persist and create audit events', () => {
14
+ const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'security-settings-'));
15
+ const store = new local_core_acp_store_js_1.LocalCoreAcpStore(userDataPath);
16
+ const settings = store.updateWorkspaceSecuritySettings('workspace-a', {
17
+ permissions: {
18
+ 'command.execute': 'deny',
19
+ 'network.access': 'allow',
20
+ },
21
+ allowPaths: ['/tmp/project'],
22
+ denyPaths: ['/tmp/project/.env'],
23
+ updatedBy: 'tester',
24
+ });
25
+ strict_1.default.equal(settings.permissions['command.execute'], 'deny');
26
+ strict_1.default.equal(settings.permissions['workspace.read'], 'allow');
27
+ strict_1.default.deepEqual(settings.allowPaths, ['/tmp/project']);
28
+ strict_1.default.deepEqual(settings.denyPaths, ['/tmp/project/.env']);
29
+ const audit = store.listAuditEvents({ workspaceId: 'workspace-a', type: 'permission.changed' });
30
+ strict_1.default.equal(audit.events.length, 1);
31
+ strict_1.default.equal(audit.events[0].actor, 'tester');
32
+ store.close();
33
+ const reopened = new local_core_acp_store_js_1.LocalCoreAcpStore(userDataPath);
34
+ strict_1.default.equal(reopened.getWorkspaceSecuritySettings('workspace-a').permissions['command.execute'], 'deny');
35
+ reopened.close();
36
+ });
37
+ (0, node_test_1.default)('approval requests persist, resolve, attach to tasks, and audit lifecycle', () => {
38
+ const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'approval-requests-'));
39
+ const store = new local_core_acp_store_js_1.LocalCoreAcpStore(userDataPath);
40
+ const task = store.createAgentTask({
41
+ workspaceId: 'workspace-a',
42
+ deviceId: 'local',
43
+ runtimeId: 'codex',
44
+ threadId: 'thread-a',
45
+ runId: 'run-a',
46
+ title: 'Dangerous command',
47
+ status: 'waiting_for_user',
48
+ });
49
+ const approval = store.createApprovalRequest({
50
+ workspaceId: 'workspace-a',
51
+ taskId: task.taskId,
52
+ threadId: 'thread-a',
53
+ runId: 'run-a',
54
+ kind: 'command',
55
+ riskLevel: 'high',
56
+ title: 'Approve rm',
57
+ description: 'rm -rf /tmp/project TOKEN=secret-value',
58
+ requestedAction: 'rm -rf /tmp/project',
59
+ command: 'rm -rf /tmp/project',
60
+ scopes: ['command.execute', 'workspace.write'],
61
+ requestedBy: 'agent',
62
+ });
63
+ strict_1.default.equal(approval.status, 'pending');
64
+ strict_1.default.match(approval.description, /\[REDACTED_SECRET\]/);
65
+ strict_1.default.ok(store.getAgentTask(task.taskId)?.approvalIds.includes(approval.approvalId));
66
+ const resolved = store.resolveApprovalRequest(approval.approvalId, {
67
+ status: 'approved',
68
+ resolvedBy: 'tester',
69
+ resolution: 'allow once',
70
+ });
71
+ strict_1.default.equal(resolved.status, 'approved');
72
+ strict_1.default.equal(resolved.resolvedBy, 'tester');
73
+ strict_1.default.equal(store.listApprovalRequests({ status: 'approved' }).approvals.length, 1);
74
+ strict_1.default.equal(store.listAuditEvents({ approvalId: approval.approvalId }).events.length, 2);
75
+ store.close();
76
+ });
77
+ (0, node_test_1.default)('command risk classification and secret redaction cover baseline rules', () => {
78
+ const high = (0, command_risk_js_1.classifyCommandRisk)('git reset --hard HEAD');
79
+ strict_1.default.equal(high.riskLevel, 'high');
80
+ strict_1.default.equal(high.requiresApproval, true);
81
+ strict_1.default.ok(high.scopes.includes('git.modify'));
82
+ const medium = (0, command_risk_js_1.classifyCommandRisk)('pnpm install');
83
+ strict_1.default.equal(medium.riskLevel, 'medium');
84
+ strict_1.default.ok(medium.scopes.includes('network.access'));
85
+ const low = (0, command_risk_js_1.classifyCommandRisk)('ls src');
86
+ strict_1.default.equal(low.riskLevel, 'low');
87
+ strict_1.default.equal(low.requiresApproval, false);
88
+ strict_1.default.equal((0, local_core_acp_store_js_1.redactSecrets)('OPENAI_API_KEY=sk-test1234567890 Bearer abc.def'), 'OPENAI_API_KEY=[REDACTED_SECRET] Bearer [REDACTED_SECRET]');
89
+ });
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const node_test_1 = __importDefault(require("node:test"));
7
+ const strict_1 = __importDefault(require("node:assert/strict"));
8
+ const node_fs_1 = require("node:fs");
9
+ const node_path_1 = require("node:path");
10
+ const node_os_1 = require("node:os");
11
+ const local_core_acp_store_js_1 = require("../services/local-ai-core/src/acp/local-core-acp-store.js");
12
+ (0, node_test_1.default)('workspace registry entries persist in LocalCoreAcpStore', () => {
13
+ const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'workspace-registry-'));
14
+ try {
15
+ const store = new local_core_acp_store_js_1.LocalCoreAcpStore(userDataPath);
16
+ store.upsertWorkspaceRegistryEntry({
17
+ workspaceId: 'workspace-a',
18
+ displayName: 'Workspace A',
19
+ path: '/tmp/workspace-a',
20
+ deviceId: 'local',
21
+ defaultRuntimeId: 'opencode',
22
+ health: { status: 'healthy', summary: 'ok', issues: [] },
23
+ git: { isRepo: false },
24
+ });
25
+ store.close();
26
+ const nextStore = new local_core_acp_store_js_1.LocalCoreAcpStore(userDataPath);
27
+ const workspace = nextStore.getWorkspaceRegistryEntry('workspace-a');
28
+ strict_1.default.equal(workspace?.displayName, 'Workspace A');
29
+ strict_1.default.equal(workspace?.defaultRuntimeId, 'opencode');
30
+ strict_1.default.equal(workspace?.health.status, 'healthy');
31
+ nextStore.close();
32
+ }
33
+ finally {
34
+ (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
35
+ }
36
+ });
37
+ (0, node_test_1.default)('agent tasks persist, update status, and can be found by run id', () => {
38
+ const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agent-task-store-'));
39
+ try {
40
+ const store = new local_core_acp_store_js_1.LocalCoreAcpStore(userDataPath);
41
+ const task = store.createAgentTask({
42
+ workspaceId: 'workspace-a',
43
+ deviceId: 'local',
44
+ runtimeId: 'opencode',
45
+ threadId: 'thread-1',
46
+ runId: 'run-1',
47
+ title: 'Implement feature',
48
+ prompt: 'Please implement feature',
49
+ status: 'running',
50
+ });
51
+ const updated = store.updateAgentTask(task.taskId, {
52
+ status: 'completed',
53
+ summary: 'done',
54
+ log: { level: 'info', message: 'finished' },
55
+ });
56
+ strict_1.default.equal(updated.status, 'completed');
57
+ strict_1.default.equal(updated.summary, 'done');
58
+ strict_1.default.equal(updated.logs[0]?.message, 'finished');
59
+ strict_1.default.equal(store.getAgentTaskByRunId('run-1')?.taskId, task.taskId);
60
+ strict_1.default.equal(store.listAgentTasks({ status: 'completed' }).tasks.length, 1);
61
+ store.close();
62
+ }
63
+ finally {
64
+ (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
65
+ }
66
+ });
@@ -8,6 +8,11 @@ exports.startCoreService = startCoreService;
8
8
  exports.stopCoreService = stopCoreService;
9
9
  exports.restartCoreService = restartCoreService;
10
10
  exports.getCoreLogs = getCoreLogs;
11
+ exports.listInstalledAgentRuntimes = listInstalledAgentRuntimes;
12
+ exports.listRuntimeDetections = listRuntimeDetections;
13
+ exports.getRuntimeDetection = getRuntimeDetection;
14
+ exports.refreshRuntimeDetections = refreshRuntimeDetections;
15
+ exports.refreshRuntimeDetection = refreshRuntimeDetection;
11
16
  exports.readCoreConfigFile = readCoreConfigFile;
12
17
  exports.saveCoreRawConfigFile = saveCoreRawConfigFile;
13
18
  exports.saveCoreStructuredConfigFile = saveCoreStructuredConfigFile;
@@ -33,6 +38,23 @@ exports.listLarkAuthorizedUsers = listLarkAuthorizedUsers;
33
38
  exports.getWeixinQrCode = getWeixinQrCode;
34
39
  exports.checkWeixinQrCodeStatus = checkWeixinQrCodeStatus;
35
40
  exports.listWorkspaces = listWorkspaces;
41
+ exports.listWorkspaceRegistry = listWorkspaceRegistry;
42
+ exports.getWorkspaceRegistryEntry = getWorkspaceRegistryEntry;
43
+ exports.createWorkspaceRegistryEntry = createWorkspaceRegistryEntry;
44
+ exports.updateWorkspaceRegistryEntry = updateWorkspaceRegistryEntry;
45
+ exports.deleteWorkspaceRegistryEntry = deleteWorkspaceRegistryEntry;
46
+ exports.listAgentTasks = listAgentTasks;
47
+ exports.getAgentTask = getAgentTask;
48
+ exports.createAgentTask = createAgentTask;
49
+ exports.updateAgentTask = updateAgentTask;
50
+ exports.getWorkspaceSecuritySettings = getWorkspaceSecuritySettings;
51
+ exports.updateWorkspaceSecuritySettings = updateWorkspaceSecuritySettings;
52
+ exports.classifyCommand = classifyCommand;
53
+ exports.listApprovalRequests = listApprovalRequests;
54
+ exports.getApprovalRequest = getApprovalRequest;
55
+ exports.createApprovalRequest = createApprovalRequest;
56
+ exports.resolveApprovalRequest = resolveApprovalRequest;
57
+ exports.listAuditEvents = listAuditEvents;
36
58
  exports.listScheduledJobs = listScheduledJobs;
37
59
  exports.getScheduledJob = getScheduledJob;
38
60
  exports.createScheduledJob = createScheduledJob;
@@ -116,6 +138,10 @@ function ensureEventSource() {
116
138
  };
117
139
  [
118
140
  'runtime.updated',
141
+ 'runtime.detect.started',
142
+ 'runtime.detect.completed',
143
+ 'runtime.detect.failed',
144
+ 'runtime.status.changed',
119
145
  'thread.updated',
120
146
  'message.created',
121
147
  'message.updated',
@@ -180,6 +206,21 @@ async function getCoreLogs(limit) {
180
206
  const suffix = typeof limit === 'number' ? `?limit=${encodeURIComponent(String(limit))}` : '';
181
207
  return coreRequest('GET', `/runtime/logs${suffix}`);
182
208
  }
209
+ async function listInstalledAgentRuntimes() {
210
+ return coreRequest('GET', '/runtime/agent-runtimes');
211
+ }
212
+ async function listRuntimeDetections() {
213
+ return coreRequest('GET', '/runtimes');
214
+ }
215
+ async function getRuntimeDetection(runtimeId) {
216
+ return coreRequest('GET', `/runtimes/${encodeURIComponent(runtimeId)}`);
217
+ }
218
+ async function refreshRuntimeDetections() {
219
+ return coreRequest('POST', '/runtimes/refresh');
220
+ }
221
+ async function refreshRuntimeDetection(runtimeId) {
222
+ return coreRequest('POST', `/runtimes/${encodeURIComponent(runtimeId)}/refresh`);
223
+ }
183
224
  async function readCoreConfigFile() {
184
225
  return coreRequest('GET', '/runtime/config');
185
226
  }
@@ -257,6 +298,89 @@ async function checkWeixinQrCodeStatus(workspaceId, ticket) {
257
298
  async function listWorkspaces() {
258
299
  return coreRequest('GET', '/workspaces');
259
300
  }
301
+ async function listWorkspaceRegistry() {
302
+ return coreRequest('GET', '/workspace-registry');
303
+ }
304
+ async function getWorkspaceRegistryEntry(workspaceId) {
305
+ return coreRequest('GET', `/workspace-registry/${encodeURIComponent(workspaceId)}`);
306
+ }
307
+ async function createWorkspaceRegistryEntry(input) {
308
+ return coreRequest('POST', '/workspace-registry', input);
309
+ }
310
+ async function updateWorkspaceRegistryEntry(workspaceId, input) {
311
+ return coreRequest('PATCH', `/workspace-registry/${encodeURIComponent(workspaceId)}`, input);
312
+ }
313
+ async function deleteWorkspaceRegistryEntry(workspaceId) {
314
+ return coreRequest('DELETE', `/workspace-registry/${encodeURIComponent(workspaceId)}`);
315
+ }
316
+ async function listAgentTasks(query = {}) {
317
+ const params = new URLSearchParams();
318
+ if (query.workspaceId)
319
+ params.set('workspace_id', query.workspaceId);
320
+ if (query.runtimeId)
321
+ params.set('runtime_id', query.runtimeId);
322
+ if (query.status)
323
+ params.set('status', Array.isArray(query.status) ? query.status.join(',') : query.status);
324
+ if (query.limit)
325
+ params.set('limit', String(query.limit));
326
+ const suffix = params.toString() ? `?${params.toString()}` : '';
327
+ return coreRequest('GET', `/tasks${suffix}`);
328
+ }
329
+ async function getAgentTask(taskId) {
330
+ return coreRequest('GET', `/tasks/${encodeURIComponent(taskId)}`);
331
+ }
332
+ async function createAgentTask(input) {
333
+ return coreRequest('POST', '/tasks', input);
334
+ }
335
+ async function updateAgentTask(taskId, input) {
336
+ return coreRequest('PATCH', `/tasks/${encodeURIComponent(taskId)}`, input);
337
+ }
338
+ async function getWorkspaceSecuritySettings(workspaceId) {
339
+ return coreRequest('GET', `/workspace-security/${encodeURIComponent(workspaceId)}`);
340
+ }
341
+ async function updateWorkspaceSecuritySettings(workspaceId, input) {
342
+ return coreRequest('PATCH', `/workspace-security/${encodeURIComponent(workspaceId)}`, input);
343
+ }
344
+ async function classifyCommand(command, workspaceId) {
345
+ return coreRequest('POST', '/security/command-risk', { command, workspaceId });
346
+ }
347
+ async function listApprovalRequests(query = {}) {
348
+ const params = new URLSearchParams();
349
+ if (query.workspaceId)
350
+ params.set('workspace_id', query.workspaceId);
351
+ if (query.taskId)
352
+ params.set('task_id', query.taskId);
353
+ if (query.status)
354
+ params.set('status', Array.isArray(query.status) ? query.status.join(',') : query.status);
355
+ if (query.limit)
356
+ params.set('limit', String(query.limit));
357
+ const suffix = params.toString() ? `?${params.toString()}` : '';
358
+ return coreRequest('GET', `/approvals${suffix}`);
359
+ }
360
+ async function getApprovalRequest(approvalId) {
361
+ return coreRequest('GET', `/approvals/${encodeURIComponent(approvalId)}`);
362
+ }
363
+ async function createApprovalRequest(input) {
364
+ return coreRequest('POST', '/approvals', input);
365
+ }
366
+ async function resolveApprovalRequest(approvalId, input) {
367
+ return coreRequest('POST', `/approvals/${encodeURIComponent(approvalId)}/resolve`, input);
368
+ }
369
+ async function listAuditEvents(query = {}) {
370
+ const params = new URLSearchParams();
371
+ if (query.workspaceId)
372
+ params.set('workspace_id', query.workspaceId);
373
+ if (query.taskId)
374
+ params.set('task_id', query.taskId);
375
+ if (query.approvalId)
376
+ params.set('approval_id', query.approvalId);
377
+ if (query.type)
378
+ params.set('type', Array.isArray(query.type) ? query.type.join(',') : query.type);
379
+ if (query.limit)
380
+ params.set('limit', String(query.limit));
381
+ const suffix = params.toString() ? `?${params.toString()}` : '';
382
+ return coreRequest('GET', `/audit-events${suffix}`);
383
+ }
260
384
  async function listScheduledJobs(workspaceId) {
261
385
  const suffix = workspaceId ? `?workspace_id=${encodeURIComponent(workspaceId)}` : '';
262
386
  return coreRequest('GET', `/scheduler/jobs${suffix}`);
@@ -7,6 +7,7 @@ const local_core_acp_transport_js_1 = require("./local-core-acp-transport.js");
7
7
  const local_core_acp_turn_coordinator_js_1 = require("./local-core-acp-turn-coordinator.js");
8
8
  const local_core_acp_session_coordinator_js_1 = require("./local-core-acp-session-coordinator.js");
9
9
  const local_core_acp_response_processor_js_1 = require("./local-core-acp-response-processor.js");
10
+ const command_risk_js_1 = require("../security/command-risk.js");
10
11
  const ACP_PROMPT_TIMEOUT_MS = 15 * 60 * 1000;
11
12
  class LocalCoreAcpBackend {
12
13
  options;
@@ -32,6 +33,42 @@ class LocalCoreAcpBackend {
32
33
  },
33
34
  updateRunStatus: (runId, threadId, status) => {
34
35
  this.options.store.updateRun(runId, threadId, status);
36
+ const task = this.options.store.getAgentTaskByRunId(runId);
37
+ if (task) {
38
+ this.options.store.updateAgentTask(task.taskId, {
39
+ status: status === 'awaiting_input' ? 'waiting_for_user' : 'running',
40
+ });
41
+ }
42
+ },
43
+ createApprovalRequest: ({ threadId, runId, title, description, command, options }) => {
44
+ const row = this.options.store.getThreadRow(threadId);
45
+ if (!row) {
46
+ return undefined;
47
+ }
48
+ const task = this.options.store.getAgentTaskByRunId(runId);
49
+ const classification = (0, command_risk_js_1.classifyCommandRisk)(command || description || title);
50
+ const approval = this.options.store.createApprovalRequest({
51
+ workspaceId: row.workspace_id,
52
+ taskId: task?.taskId,
53
+ threadId,
54
+ runId,
55
+ deviceId: 'local',
56
+ kind: classification.scopes.includes('git.modify') ? 'git' : 'command',
57
+ riskLevel: classification.riskLevel,
58
+ title,
59
+ description,
60
+ requestedAction: command || description || title,
61
+ command,
62
+ scopes: classification.scopes,
63
+ options: options.map((option) => ({
64
+ optionId: option.optionId,
65
+ label: option.name || option.optionId,
66
+ action: option.normalizedAction === 'deny' ? 'reject' : 'approve',
67
+ })),
68
+ requestedBy: 'agent',
69
+ metadata: { classification },
70
+ });
71
+ return approval.approvalId;
35
72
  },
36
73
  sendRaw: (session, payload) => this.transport.sendRaw(session, payload),
37
74
  });
@@ -112,6 +149,16 @@ class LocalCoreAcpBackend {
112
149
  const runId = `run:${threadId}:${Date.now()}`;
113
150
  this.options.runThreadMap.set(runId, threadId);
114
151
  this.options.store.updateRun(runId, threadId, 'running');
152
+ this.options.store.createAgentTask({
153
+ workspaceId: row.workspace_id,
154
+ deviceId: 'local',
155
+ runtimeId: row.agent_type,
156
+ threadId,
157
+ runId,
158
+ title: content.trim().slice(0, 80) || row.title || 'Agent task',
159
+ prompt: content,
160
+ status: 'running',
161
+ });
115
162
  this.options.eventBus.emit({
116
163
  type: 'run.started',
117
164
  payload: {
@@ -154,6 +201,13 @@ class LocalCoreAcpBackend {
154
201
  if (!accepted) {
155
202
  throw new Error(session.closeReason || 'ACP session is not writable');
156
203
  }
204
+ if (pendingPermission.approvalId) {
205
+ this.options.store.resolveApprovalRequest(pendingPermission.approvalId, {
206
+ status: matched.normalizedAction === 'deny' ? 'rejected' : 'approved',
207
+ resolvedBy: 'local',
208
+ resolution: matched.name || matched.optionId,
209
+ });
210
+ }
157
211
  session.pendingPermissionByRun.delete(session.currentRunId);
158
212
  this.emitBridgeEvent({
159
213
  type: 'typing_start',
@@ -280,6 +334,13 @@ class LocalCoreAcpBackend {
280
334
  }
281
335
  const nextStatus = result?.stopReason === 'cancelled' ? 'interrupted' : 'completed';
282
336
  this.options.store.updateRun(runId, threadId, nextStatus);
337
+ const task = this.options.store.getAgentTaskByRunId(runId);
338
+ if (task) {
339
+ this.options.store.updateAgentTask(task.taskId, {
340
+ status: nextStatus === 'interrupted' ? 'cancelled' : 'completed',
341
+ summary: result?.stopReason === 'cancelled' ? 'Request cancelled.' : 'Task completed.',
342
+ });
343
+ }
283
344
  this.options.eventBus.emit({
284
345
  type: 'run.completed',
285
346
  payload: {
@@ -298,6 +359,13 @@ class LocalCoreAcpBackend {
298
359
  catch (error) {
299
360
  const errorContent = `Agent error: ${error instanceof Error ? error.message : String(error)}`;
300
361
  this.options.store.updateRun(runId, threadId, 'failed');
362
+ const task = this.options.store.getAgentTaskByRunId(runId);
363
+ if (task) {
364
+ this.options.store.updateAgentTask(task.taskId, {
365
+ status: 'failed',
366
+ error: error instanceof Error ? error.message : String(error),
367
+ });
368
+ }
301
369
  this.options.store.appendMessage(threadId, 'assistant', errorContent, 'final');
302
370
  this.options.eventBus.emit({
303
371
  type: 'thread.message.accepted',