@kafca/agentdock 0.1.58 → 0.1.62

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 (127) hide show
  1. package/README.md +18 -0
  2. package/dist/renderer/assets/AutomationList-BgJgAdVO.js +16 -0
  3. package/dist/renderer/assets/{Badge-BfY1VaHS.js → Badge-Bq2hPVcR.js} +1 -1
  4. package/dist/renderer/assets/{Card-DWA_Tww_.js → Card-BPJNSZce.js} +1 -1
  5. package/dist/renderer/assets/{Config-nHj3g1U5.js → Config-aSFqlI88.js} +2 -2
  6. package/dist/renderer/assets/Dashboard-D3ezFmb7.js +36 -0
  7. package/dist/renderer/assets/{EmptyState-zatDjLj_.js → EmptyState-DK496gBq.js} +1 -1
  8. package/dist/renderer/assets/{HighlightedMarkdown-CsM5n70d.js → HighlightedMarkdown-Ckre5jNv.js} +1 -1
  9. package/dist/renderer/assets/{Input-R1XdFK1K.js → Input-C5C1d0ba.js} +1 -1
  10. package/dist/renderer/assets/{KnowledgeDetail-Co5sxeGd.js → KnowledgeDetail-N8Ey1ikc.js} +2 -2
  11. package/dist/renderer/assets/{KnowledgeHome-DwziPw5m.js → KnowledgeHome-9aF8nJEf.js} +2 -2
  12. package/dist/renderer/assets/{Logs-NwZUtR_G.js → Logs-sgAdwEeS.js} +2 -2
  13. package/dist/renderer/assets/{Modal-CrcIwIFL.js → Modal-BB10hTRc.js} +3 -3
  14. package/dist/renderer/assets/{Page-Dp0obeSw.js → Page-DfC319Ck.js} +1 -1
  15. package/dist/renderer/assets/{Select-6mVQaf84.js → Select-RHJBmNER.js} +1 -1
  16. package/dist/renderer/assets/{ThreadChat-BFzbn3L6.js → ThreadChat-BU1zCdr4.js} +3 -3
  17. package/dist/renderer/assets/{Workspace-DCmo8BMm.js → Workspace-BoZCz7S8.js} +1 -1
  18. package/dist/renderer/assets/{arrow-left-9rjg_Hp4.js → arrow-left-D2V1eZSD.js} +1 -1
  19. package/dist/renderer/assets/{book-open-Bu8GCHnG.js → book-open-DUcBn7Bb.js} +1 -1
  20. package/dist/renderer/assets/{channels-BxVbG-6j.js → channels-DMhDSlf2.js} +2 -2
  21. package/dist/renderer/assets/{chevron-down-BE8uQmWM.js → chevron-down-Ca2FQDB7.js} +1 -1
  22. package/dist/renderer/assets/en-Bnr4nMzg.js +1 -0
  23. package/dist/renderer/assets/es-B1GIFh3i.js +1 -0
  24. package/dist/renderer/assets/{index-BIKL3fTP.css → index-B6WcpkJF.css} +1 -1
  25. package/dist/renderer/assets/{index-B7TeF6Es.js → index-BlnvlekJ.js} +1 -1
  26. package/dist/renderer/assets/index-DRJ5wgw_.js +162 -0
  27. package/dist/renderer/assets/ja-CDWPWqEW.js +1 -0
  28. package/dist/renderer/assets/{knowledge-Kl19SqGS.js → knowledge-D1-k56eY.js} +1 -1
  29. package/dist/renderer/assets/{pencil-CPB7SK1m.js → pencil-BhWdv_86.js} +1 -1
  30. package/dist/renderer/assets/{plus-Bt26z-4q.js → plus-BacLMlK3.js} +1 -1
  31. package/dist/renderer/assets/{save-Une7GMxT.js → save-Bpoxbzau.js} +1 -1
  32. package/dist/renderer/assets/{search-DE0agJjJ.js → search-BVJcpdFX.js} +1 -1
  33. package/dist/renderer/assets/{shield-check-De7xaXZh.js → shield-check-C2cw_Ip6.js} +1 -1
  34. package/dist/renderer/assets/{threads-BnsNL5ei.js → threads-DOsBQBg_.js} +1 -1
  35. package/dist/renderer/assets/{trash-2-6iGF59Ac.js → trash-2-Bs3rEm3g.js} +1 -1
  36. package/dist/renderer/assets/zh-MSddxMDX.js +1 -0
  37. package/dist/renderer/assets/zh-TW-C_LNWX0o.js +1 -0
  38. package/dist/renderer/index.html +2 -2
  39. package/dist-electron/electron/managed-skills/agent-browser/SKILL.md +41 -0
  40. package/dist-electron/electron/managed-skills/agent-browser/references/authentication.md +83 -0
  41. package/dist-electron/electron/managed-skills/agent-browser/references/commands.md +50 -0
  42. package/dist-electron/electron/managed-skills/agent-browser/references/profiling.md +20 -0
  43. package/dist-electron/electron/managed-skills/agent-browser/references/proxy-support.md +18 -0
  44. package/dist-electron/electron/managed-skills/agent-browser/references/session-management.md +17 -0
  45. package/dist-electron/electron/managed-skills/agent-browser/references/snapshot-refs.md +17 -0
  46. package/dist-electron/electron/managed-skills/agent-browser/references/video-recording.md +21 -0
  47. package/dist-electron/electron/managed-skills/agent-browser/templates/authenticated-session.sh +11 -0
  48. package/dist-electron/electron/managed-skills/agent-browser/templates/capture-workflow.sh +12 -0
  49. package/dist-electron/electron/managed-skills/agent-browser/templates/form-automation.sh +10 -0
  50. package/dist-electron/electron/managed-skills/condition-trigger/SKILL.md +21 -0
  51. package/dist-electron/electron/managed-skills/condition-trigger/scripts/register-condition-trigger.sh +106 -0
  52. package/dist-electron/electron/managed-skills/knowledge-base/SKILL.md +13 -0
  53. package/dist-electron/electron/managed-skills/knowledge-base/scripts/search-knowledge.sh +80 -0
  54. package/dist-electron/packages/contracts/src/automations.js +211 -0
  55. package/dist-electron/packages/contracts/src/index.js +1 -0
  56. package/dist-electron/packages/core-sdk/src/automations.js +87 -0
  57. package/dist-electron/packages/core-sdk/src/client.js +101 -0
  58. package/dist-electron/packages/core-sdk/src/index.js +2 -1
  59. package/dist-electron/services/local-ai-core/src/acp/store/automation-script-store.js +415 -0
  60. package/dist-electron/services/local-ai-core/src/acp/store/automation-store.js +829 -0
  61. package/dist-electron/services/local-ai-core/src/acp/store/local-core-acp-store.js +158 -0
  62. package/dist-electron/services/local-ai-core/src/acp/store/schema.js +81 -0
  63. package/dist-electron/services/local-ai-core/src/automation/automation-action-executor.js +144 -0
  64. package/dist-electron/services/local-ai-core/src/automation/automation-condition-engine.js +79 -0
  65. package/dist-electron/services/local-ai-core/src/automation/automation-conversation-executor.js +39 -73
  66. package/dist-electron/services/local-ai-core/src/automation/automation-monitor-service.js +695 -260
  67. package/dist-electron/services/local-ai-core/src/automation/automation-script-service.js +507 -0
  68. package/dist-electron/services/local-ai-core/src/automation/automation-service.js +1019 -0
  69. package/dist-electron/services/local-ai-core/src/automation/automation-trigger-engine.js +141 -0
  70. package/dist-electron/services/local-ai-core/src/automation/condition-evaluator.js +60 -17
  71. package/dist-electron/services/local-ai-core/src/automation/legacy-automation-mappers.js +286 -0
  72. package/dist-electron/services/local-ai-core/src/automation/scripts/anthropic-sandbox-runner.js +792 -0
  73. package/dist-electron/services/local-ai-core/src/automation/scripts/sandbox-runner.js +2 -0
  74. package/dist-electron/services/local-ai-core/src/automation/scripts/script-package.js +475 -0
  75. package/dist-electron/services/local-ai-core/src/automation/scripts/script-protocol-runner.js +266 -0
  76. package/dist-electron/services/local-ai-core/src/automation/scripts/secret-resolver.js +37 -0
  77. package/dist-electron/services/local-ai-core/src/channel/weixin/inbound-poller.js +1 -2
  78. package/dist-electron/services/local-ai-core/src/channel/weixin/transport.js +9 -8
  79. package/dist-electron/services/local-ai-core/src/cli/lac.js +229 -0
  80. package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +21 -0
  81. package/dist-electron/services/local-ai-core/src/runtime/deployment-diagnostics.js +76 -0
  82. package/dist-electron/services/local-ai-core/src/runtime/handlers/automations-handler.js +322 -0
  83. package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +20 -0
  84. package/dist-electron/services/local-ai-core/src/runtime/managed-skill-catalog.js +37 -0
  85. package/dist-electron/services/local-ai-core/src/runtime/server-helpers.js +14 -4
  86. package/dist-electron/services/local-ai-core/src/runtime/server-routes.js +75 -0
  87. package/dist-electron/services/local-ai-core/src/runtime/server.js +21 -0
  88. package/dist-electron/services/local-ai-core/src/runtime/standalone.js +1 -0
  89. package/dist-electron/services/local-ai-core/src/scheduler/cron.js +151 -39
  90. package/dist-electron/services/local-ai-core/src/scheduler/scheduled-conversation-executor.js +2 -4
  91. package/dist-electron/services/local-ai-core/src/scheduler/scheduled-job-application-service.js +64 -12
  92. package/dist-electron/services/local-ai-core/src/scheduler/scheduler-service.js +71 -4
  93. package/dist-electron/services/local-ai-core/src/scheduler/thread-resolution.js +10 -0
  94. package/dist-electron/services/local-ai-core/src/thread/agent-message-policy.js +16 -1
  95. package/dist-electron/src/pages/Automation/automation-page-model.js +76 -0
  96. package/dist-electron/tests/contracts/agent-message-policy.test.js +32 -0
  97. package/dist-electron/tests/contracts/architecture-docs.test.js +30 -0
  98. package/dist-electron/tests/contracts/automation-contracts.test.js +154 -0
  99. package/dist-electron/tests/contracts/automation-renderer-model.test.js +47 -0
  100. package/dist-electron/tests/electron/automation-action-executor.test.js +75 -0
  101. package/dist-electron/tests/electron/automation-compatibility.test.js +463 -0
  102. package/dist-electron/tests/electron/automation-deployment.test.js +66 -0
  103. package/dist-electron/tests/electron/automation-engine.test.js +264 -0
  104. package/dist-electron/tests/electron/automation-monitor.test.js +1333 -0
  105. package/dist-electron/tests/electron/automation-sandbox-runner.test.js +333 -0
  106. package/dist-electron/tests/electron/automation-script-approval.test.js +553 -0
  107. package/dist-electron/tests/electron/automation-script-package.test.js +499 -0
  108. package/dist-electron/tests/electron/automation-script-runner.test.js +120 -0
  109. package/dist-electron/tests/electron/automation-service.test.js +1078 -0
  110. package/dist-electron/tests/electron/automation-store.test.js +593 -0
  111. package/dist-electron/tests/electron/condition-trigger-skill.test.js +93 -0
  112. package/dist-electron/tests/electron/core-client.test.js +99 -0
  113. package/dist-electron/tests/electron/lac-cli.test.js +110 -0
  114. package/dist-electron/tests/electron/workspace-task-store.test.js +25 -0
  115. package/dist-electron/tests/integration/automation-routes.test.js +186 -0
  116. package/dist-electron/tests/integration/automation-sandbox-runtime.test.js +92 -0
  117. package/package.json +5 -6
  118. package/dist/renderer/assets/CronList-BwkA_I7v.js +0 -1
  119. package/dist/renderer/assets/Dashboard-D0_A7k02.js +0 -41
  120. package/dist/renderer/assets/MonitorList-CS1uLS5A.js +0 -1
  121. package/dist/renderer/assets/en-CmAr_0e6.js +0 -1
  122. package/dist/renderer/assets/es-Cs9x52ca.js +0 -1
  123. package/dist/renderer/assets/index-BkI9POxj.js +0 -167
  124. package/dist/renderer/assets/ja-DtvtBRTW.js +0 -1
  125. package/dist/renderer/assets/play-DGAO4OCu.js +0 -6
  126. package/dist/renderer/assets/zh-CcJ9vBZj.js +0 -1
  127. package/dist/renderer/assets/zh-TW-elI72AC1.js +0 -1
@@ -14,6 +14,8 @@ const local_core_runtime_state_js_1 = require("../runtime/local-core-runtime-sta
14
14
  const scheduled_job_application_service_js_1 = require("../scheduler/scheduled-job-application-service.js");
15
15
  const scheduler_service_js_1 = require("../scheduler/scheduler-service.js");
16
16
  const automation_monitor_service_js_1 = require("../automation/automation-monitor-service.js");
17
+ const automation_action_executor_js_1 = require("../automation/automation-action-executor.js");
18
+ const automation_service_js_1 = require("../automation/automation-service.js");
17
19
  function bootstrapLocalCoreKernel(options) {
18
20
  const capabilities = new capability_registry_js_1.LocalCoreCapabilityRegistry();
19
21
  const plugins = new plugin_registry_js_1.LocalCorePluginRegistry(options?.disabledPluginIds);
@@ -206,8 +208,20 @@ function bootstrapLocalCoreRuntime(options) {
206
208
  knowledgeAttachments,
207
209
  log: options.log,
208
210
  });
211
+ const automationActionExecutor = new automation_action_executor_js_1.AutomationActionExecutor({
212
+ store,
213
+ getWorkspaceRouter: () => workspaceRouter,
214
+ getChannelRuntime: (platform) => channelRuntimes.find((runtime) => runtime.platform === platform || platform.startsWith(`${runtime.platform}:`)),
215
+ });
216
+ const automations = new automation_service_js_1.AutomationService({
217
+ store,
218
+ actionExecutor: automationActionExecutor,
219
+ eventBus: kernel.context.bus,
220
+ log: options.log,
221
+ });
209
222
  const scheduler = new scheduler_service_js_1.SchedulerService({
210
223
  store,
224
+ automations,
211
225
  triggers: schedulerTriggers,
212
226
  executors: schedulerExecutors,
213
227
  eventBus: kernel.context.bus,
@@ -216,9 +230,12 @@ function bootstrapLocalCoreRuntime(options) {
216
230
  const scheduledJobs = new scheduled_job_application_service_js_1.ScheduledJobApplicationService({
217
231
  store,
218
232
  scheduler,
233
+ automations,
234
+ eventBus: kernel.context.bus,
219
235
  });
220
236
  const automationMonitors = new automation_monitor_service_js_1.AutomationMonitorService({
221
237
  store,
238
+ automations,
222
239
  providers: monitorProviders,
223
240
  getWorkspaceRouter: () => workspaceRouter,
224
241
  getChannelRuntime: (platform) => channelRuntimes.find((runtime) => runtime.platform === platform || platform.startsWith(`${runtime.platform}:`)),
@@ -246,13 +263,17 @@ function bootstrapLocalCoreRuntime(options) {
246
263
  scheduler,
247
264
  scheduledJobs,
248
265
  automationMonitors,
266
+ automationActionExecutor,
267
+ automations,
249
268
  async start() {
250
269
  await kernel.lifecycle.startAll();
270
+ await automations.start();
251
271
  await scheduler.start();
252
272
  await automationMonitors.start();
253
273
  },
254
274
  async stop() {
255
275
  await automationMonitors.stop();
276
+ await automations.stop();
256
277
  await scheduler.stop();
257
278
  await kernel.lifecycle.stopAll();
258
279
  workspaceRouter.close();
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.runDeploymentDiagnostics = runDeploymentDiagnostics;
4
+ exports.automationSandboxDiagnosticChecks = automationSandboxDiagnosticChecks;
4
5
  const node_fs_1 = require("node:fs");
5
6
  const superai_contracts_1 = require("../../../../packages/contracts/src/index.js");
6
7
  const sandbox_config_js_1 = require("../sandbox/sandbox-config.js");
8
+ const anthropic_sandbox_runner_js_1 = require("../automation/scripts/anthropic-sandbox-runner.js");
7
9
  async function runDeploymentDiagnostics(input) {
8
10
  const env = input.env || process.env;
9
11
  const checkedAt = new Date().toISOString();
@@ -31,6 +33,7 @@ async function runDeploymentDiagnostics(input) {
31
33
  workspacePathCheck(config, profile.id),
32
34
  allowlistCheck(config, env),
33
35
  sandboxImageCheck(config),
36
+ ...(await automationSandboxChecks()),
34
37
  await opensandboxHealthCheck(opensandboxUrl, sandboxProvider.api_key_env, env),
35
38
  ];
36
39
  return {
@@ -39,6 +42,79 @@ async function runDeploymentDiagnostics(input) {
39
42
  checks,
40
43
  };
41
44
  }
45
+ async function automationSandboxChecks() {
46
+ try {
47
+ const capability = await new anthropic_sandbox_runner_js_1.AnthropicSandboxRunner().probe();
48
+ return automationSandboxDiagnosticChecks(capability);
49
+ }
50
+ catch (error) {
51
+ return [{
52
+ id: 'automation.sandbox',
53
+ label: 'Automation Sandbox Runtime',
54
+ status: 'fail',
55
+ summary: error instanceof Error ? error.message : String(error),
56
+ }];
57
+ }
58
+ }
59
+ /**
60
+ * Convert the runner's stable capability identifiers into one doctor row per
61
+ * host prerequisite. Operators can then repair only the failed capability.
62
+ */
63
+ function automationSandboxDiagnosticChecks(capability) {
64
+ if (capability.platform === 'windows') {
65
+ return [{
66
+ id: 'automation.sandbox',
67
+ label: 'Automation scripts on Windows',
68
+ status: 'fail',
69
+ summary: 'Condition-script execution is unsupported on Windows and remains fail-closed.',
70
+ }];
71
+ }
72
+ const missing = new Set(capability.missing);
73
+ const unverified = new Set(capability.unverified || []);
74
+ const check = (id, label, capabilityId) => ({
75
+ id,
76
+ label,
77
+ status: missing.has(capabilityId) ? 'fail' : 'pass',
78
+ summary: unverified.has(capabilityId)
79
+ ? `${label} is blocked and could not be behaviorally verified because an earlier prerequisite failed.`
80
+ : missing.has(capabilityId) ? `${label} is unavailable.` : `${label} is available.`,
81
+ });
82
+ const runtimeMissing = missing.has('sandbox_runtime') || missing.has('sandbox_unavailable');
83
+ const aggregate = {
84
+ id: 'automation.sandbox',
85
+ label: 'Automation script sandbox',
86
+ status: capability.available ? 'pass' : 'fail',
87
+ summary: capability.available
88
+ ? `Automation script sandbox is available on ${capability.platform}.`
89
+ : `Automation scripts are blocked on ${capability.platform}; missing ${capability.missing.join(', ')}.`,
90
+ };
91
+ const runtime = {
92
+ id: 'automation.runtime',
93
+ label: 'Anthropic Sandbox Runtime',
94
+ status: runtimeMissing ? 'fail' : 'pass',
95
+ summary: runtimeMissing
96
+ ? `Anthropic Sandbox Runtime is unavailable on ${capability.platform}.`
97
+ : `Anthropic Sandbox Runtime is available on ${capability.platform}.`,
98
+ };
99
+ if (capability.platform === 'linux') {
100
+ return [
101
+ aggregate,
102
+ runtime,
103
+ check('automation.linux.bwrap', 'Bubblewrap (bwrap)', 'bwrap'),
104
+ check('automation.linux.socat', 'socat proxy bridge', 'socat'),
105
+ check('automation.linux.rg', 'ripgrep (rg)', 'rg'),
106
+ check('automation.linux.userns-apparmor', 'user namespace / AppArmor permission', 'apparmor.userns'),
107
+ check('automation.linux.network-namespace', 'network namespace support', 'network.namespace'),
108
+ check('automation.linux.seccomp', 'seccomp support', 'seccomp'),
109
+ ];
110
+ }
111
+ return [
112
+ aggregate,
113
+ runtime,
114
+ check('automation.macos.sandbox-exec', 'macOS sandbox-exec', 'sandbox-exec'),
115
+ check('automation.macos.rg', 'ripgrep (rg)', 'rg'),
116
+ ];
117
+ }
42
118
  function dockerSocketCheck(profileId) {
43
119
  if (profileId === 'docker-compose') {
44
120
  return {
@@ -0,0 +1,322 @@
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
+ exports.registerUnifiedAutomationHandlers = registerUnifiedAutomationHandlers;
7
+ const node_fs_1 = require("node:fs");
8
+ const node_path_1 = require("node:path");
9
+ const node_process_1 = __importDefault(require("node:process"));
10
+ const anthropic_sandbox_runner_js_1 = require("../../automation/scripts/anthropic-sandbox-runner.js");
11
+ const server_helpers_js_1 = require("../server-helpers.js");
12
+ const request_validation_js_1 = require("../request-validation.js");
13
+ /**
14
+ * Unified automation endpoints deliberately accept writable contract fields only.
15
+ * Package locations, hashes, interpreter facts, approval state, evaluation state,
16
+ * and health are created and maintained by server-owned services.
17
+ */
18
+ function registerUnifiedAutomationHandlers(map, options) {
19
+ const { automations, store } = options;
20
+ const activeTestClaims = new Set();
21
+ const stagedPackageUsers = new Map();
22
+ const emitVersion = (version) => options.emitScriptVersion?.(version);
23
+ map.set('automations.list', async (_route, _req, res, url) => {
24
+ const workspaceId = requiredWorkspace(url);
25
+ (0, server_helpers_js_1.json)(res, 200, { automations: automations.list(workspaceId) });
26
+ });
27
+ map.set('automations.create', async (_route, req, res) => {
28
+ const body = await strictBody(req, {
29
+ workspaceId: { kind: 'string', required: true }, title: { kind: 'string', required: true }, enabled: { kind: 'boolean', required: true },
30
+ activation: { kind: 'object', required: true }, condition: { kind: 'object', required: true },
31
+ action: { kind: 'object', required: true }, delivery: { kind: 'object', required: true }, policies: { kind: 'object', required: true },
32
+ });
33
+ (0, server_helpers_js_1.json)(res, 200, automations.create(body));
34
+ });
35
+ map.set('automation.get', async (route, _req, res, url) => {
36
+ (0, server_helpers_js_1.json)(res, 200, requireAutomation(automations, automationId(route), requiredWorkspace(url)));
37
+ });
38
+ map.set('automation.update', async (route, req, res, url) => {
39
+ requireAutomation(automations, automationId(route), requiredWorkspace(url));
40
+ const body = await strictBody(req, {
41
+ title: 'string', enabled: 'boolean', activation: 'object', condition: 'object', action: 'object', delivery: 'object', policies: 'object',
42
+ });
43
+ if (Object.keys(body).length === 0)
44
+ throw new request_validation_js_1.RequestValidationError('Request body must update at least one writable Automation field.');
45
+ (0, server_helpers_js_1.json)(res, 200, automations.update(automationId(route), body));
46
+ });
47
+ map.set('automation.delete', async (route, _req, res, url) => {
48
+ requireAutomation(automations, automationId(route), requiredWorkspace(url));
49
+ (0, server_helpers_js_1.json)(res, 200, automations.delete(automationId(route)));
50
+ });
51
+ map.set('automation.check', async (route, _req, res, url) => {
52
+ requireAutomation(automations, automationId(route), requiredWorkspace(url));
53
+ (0, server_helpers_js_1.json)(res, 200, await automations.checkNow(automationId(route)));
54
+ });
55
+ map.set('automation.evaluations', async (route, _req, res, url) => {
56
+ requireAutomation(automations, automationId(route), requiredWorkspace(url));
57
+ (0, server_helpers_js_1.json)(res, 200, { evaluations: automations.listEvaluations(automationId(route)) });
58
+ });
59
+ map.set('automation.runs', async (route, _req, res, url) => {
60
+ requireAutomation(automations, automationId(route), requiredWorkspace(url));
61
+ (0, server_helpers_js_1.json)(res, 200, { runs: automations.listRuns(automationId(route)) });
62
+ });
63
+ map.set('automation-scripts.list', async (_route, _req, res, url) => {
64
+ const workspaceId = requiredWorkspace(url);
65
+ (0, server_helpers_js_1.json)(res, 200, { scripts: store.listAutomationScripts(workspaceId) });
66
+ });
67
+ map.set('automation-scripts.create', async (_route, req, res) => {
68
+ const body = await strictBody(req, {
69
+ workspaceId: { kind: 'string', required: true }, title: { kind: 'string', required: true }, description: 'string',
70
+ });
71
+ (0, server_helpers_js_1.json)(res, 200, store.createAutomationScript(body));
72
+ });
73
+ map.set('automation-script.get', async (route, _req, res, url) => {
74
+ (0, server_helpers_js_1.json)(res, 200, requireScript(store, scriptId(route), requiredWorkspace(url)));
75
+ });
76
+ map.set('automation-script.update', async (route, req, res, url) => {
77
+ requireScript(store, scriptId(route), requiredWorkspace(url));
78
+ const body = await strictBody(req, { title: 'string', description: 'string' });
79
+ if (Object.keys(body).length === 0)
80
+ throw new request_validation_js_1.RequestValidationError('Request body must update at least one writable script field.');
81
+ (0, server_helpers_js_1.json)(res, 200, store.updateAutomationScript(scriptId(route), body));
82
+ });
83
+ map.set('automation-script.versions', async (route, _req, res, url) => {
84
+ requireScript(store, scriptId(route), requiredWorkspace(url));
85
+ (0, server_helpers_js_1.json)(res, 200, { versions: store.listAutomationScriptVersions(scriptId(route)) });
86
+ });
87
+ map.set('automation-script.version.submit', async (route, req, res, url) => {
88
+ const id = scriptId(route);
89
+ requireScript(store, id, requiredWorkspace(url));
90
+ const body = await strictBody(req, {
91
+ files: { kind: 'array', required: true, elementKind: 'object' },
92
+ }, MAX_SOURCE_UPLOAD_BODY_BYTES);
93
+ let staged;
94
+ let committed = false;
95
+ let version;
96
+ try {
97
+ staged = store.stageAutomationScriptSource({ scriptId: id, files: validateSourceFiles(body.files) });
98
+ stagedPackageUsers.set(staged.packagePath, (stagedPackageUsers.get(staged.packagePath) || 0) + 1);
99
+ const interpreter = await resolveServerInterpreter(staged.shebang, staged.packagePath);
100
+ version = store.createAutomationScriptVersionFromStaged({
101
+ scriptId: id, staged, interpreterPath: interpreter.path, interpreterVersion: interpreter.version,
102
+ });
103
+ committed = true;
104
+ }
105
+ catch (error) {
106
+ throw error;
107
+ }
108
+ finally {
109
+ if (staged) {
110
+ const remaining = (stagedPackageUsers.get(staged.packagePath) || 1) - 1;
111
+ if (remaining <= 0) {
112
+ stagedPackageUsers.delete(staged.packagePath);
113
+ if (!committed)
114
+ store.discardUnreferencedAutomationScriptPackage(id, staged);
115
+ }
116
+ else {
117
+ stagedPackageUsers.set(staged.packagePath, remaining);
118
+ }
119
+ }
120
+ }
121
+ emitVersion(version);
122
+ (0, server_helpers_js_1.json)(res, 200, version);
123
+ });
124
+ map.set('automation-script-version.test-approval', async (route, req, res, url) => {
125
+ requireVersion(store, versionId(route), requiredWorkspace(url));
126
+ const { actor } = await strictBody(req, { actor: { kind: 'string', required: true } });
127
+ const approval = store.requestAutomationScriptTestApproval(versionId(route), actor);
128
+ emitVersion(store.getAutomationScriptVersion(versionId(route)));
129
+ (0, server_helpers_js_1.json)(res, 200, approval);
130
+ });
131
+ map.set('automation-script-version.test', async (route, req, res, url) => {
132
+ requireVersion(store, versionId(route), requiredWorkspace(url));
133
+ const { actor } = await strictBody(req, { actor: { kind: 'string', required: true } });
134
+ if (!options.executeScriptTest)
135
+ throw new Error('Automation script test executor is unavailable.');
136
+ if (activeTestClaims.has(versionId(route))) {
137
+ throw new request_validation_js_1.RequestValidationError('Automation script test is already executing.');
138
+ }
139
+ // Claim before awaiting the sandbox. A second request observes `testing` and
140
+ // cannot start a competing one-shot execution.
141
+ store.claimAutomationScriptTestExecution(versionId(route));
142
+ activeTestClaims.add(versionId(route));
143
+ try {
144
+ let report;
145
+ try {
146
+ report = await options.executeScriptTest(versionId(route), actor);
147
+ }
148
+ catch {
149
+ report = { status: 'failed', finishedAt: new Date().toISOString(), summary: 'Sandbox test execution failed.' };
150
+ }
151
+ try {
152
+ const version = store.recordAutomationScriptTestResult(versionId(route), report);
153
+ emitVersion(version);
154
+ (0, server_helpers_js_1.json)(res, 200, version);
155
+ }
156
+ catch (error) {
157
+ try {
158
+ store.failClaimedAutomationScriptTestExecution(versionId(route));
159
+ }
160
+ catch { /* retry/lease recovery remains fail-closed */ }
161
+ throw error;
162
+ }
163
+ }
164
+ finally {
165
+ activeTestClaims.delete(versionId(route));
166
+ }
167
+ });
168
+ map.set('automation-script-version.enable-approval', async (route, req, res, url) => {
169
+ requireVersion(store, versionId(route), requiredWorkspace(url));
170
+ const { actor } = await strictBody(req, { actor: { kind: 'string', required: true } });
171
+ const approval = store.requestAutomationScriptEnableApproval(versionId(route), actor);
172
+ emitVersion(store.getAutomationScriptVersion(versionId(route)));
173
+ (0, server_helpers_js_1.json)(res, 200, approval);
174
+ });
175
+ for (const routeName of ['automation-script-version.approve', 'automation-script-version.reject']) {
176
+ map.set(routeName, async (route, req, res, url) => {
177
+ requireVersion(store, versionId(route), requiredWorkspace(url));
178
+ const { approvalId, actor } = await strictBody(req, {
179
+ approvalId: { kind: 'string', required: true }, actor: { kind: 'string', required: true },
180
+ });
181
+ const approval = store.getApprovalRequest(approvalId);
182
+ const expectedDecision = routeName === 'automation-script-version.approve' ? 'approved' : 'rejected';
183
+ if (!approval || approval.status !== expectedDecision) {
184
+ throw new request_validation_js_1.RequestValidationError(`Automation script ${routeName.endsWith('.approve') ? 'approve' : 'reject'} requires a ${expectedDecision} approval decision.`);
185
+ }
186
+ // Approval status is resolved by /approvals/:id/resolve. This route only
187
+ // applies the existing immutable approval decision to the matching version.
188
+ const current = store.getAutomationScriptVersion(versionId(route));
189
+ const version = current.status === 'pending_test_approval'
190
+ ? store.authorizeAutomationScriptTest(versionId(route), approvalId, actor)
191
+ : current.status === 'pending_approval'
192
+ ? store.approveAutomationScriptVersion(versionId(route), approvalId, actor)
193
+ : (() => { throw new request_validation_js_1.RequestValidationError(`Automation script version cannot apply an approval from ${current.status}.`); })();
194
+ emitVersion(version);
195
+ (0, server_helpers_js_1.json)(res, 200, version);
196
+ });
197
+ }
198
+ map.set('automation-script-version.revoke', async (route, req, res, url) => {
199
+ requireVersion(store, versionId(route), requiredWorkspace(url));
200
+ const { actor } = await strictBody(req, { actor: { kind: 'string', required: true } });
201
+ const version = store.revokeAutomationScriptVersion(versionId(route), actor);
202
+ emitVersion(version);
203
+ (0, server_helpers_js_1.json)(res, 200, version);
204
+ });
205
+ }
206
+ function requiredWorkspace(url) {
207
+ const workspaceId = url.searchParams.get('workspace_id')?.trim();
208
+ if (!workspaceId)
209
+ throw new request_validation_js_1.RequestValidationError('workspace_id is required.');
210
+ return workspaceId;
211
+ }
212
+ function requireAutomation(automations, automationId, workspaceId) {
213
+ const automation = automations.get(automationId);
214
+ if (!automation || automation.workspaceId !== workspaceId)
215
+ throw new request_validation_js_1.RequestValidationError('Automation was not found in this workspace.');
216
+ return automation;
217
+ }
218
+ function requireScript(store, scriptId, workspaceId) {
219
+ const script = store.getAutomationScript(scriptId);
220
+ if (!script || script.workspaceId !== workspaceId)
221
+ throw new request_validation_js_1.RequestValidationError('Automation script was not found in this workspace.');
222
+ return script;
223
+ }
224
+ function requireVersion(store, versionId, workspaceId) {
225
+ const version = store.getAutomationScriptVersion(versionId);
226
+ if (!version)
227
+ throw new request_validation_js_1.RequestValidationError('Automation script version was not found in this workspace.');
228
+ requireScript(store, version.scriptId, workspaceId);
229
+ return version;
230
+ }
231
+ function automationId(route) {
232
+ return route.automationId;
233
+ }
234
+ function scriptId(route) {
235
+ return route.scriptId;
236
+ }
237
+ function versionId(route) {
238
+ return route.versionId;
239
+ }
240
+ function validateSourceFiles(value) {
241
+ if (!Array.isArray(value))
242
+ throw new request_validation_js_1.RequestValidationError('Request body.files must be an array.');
243
+ return value.map((file, index) => {
244
+ if (!file || typeof file !== 'object' || Array.isArray(file)) {
245
+ throw new request_validation_js_1.RequestValidationError(`Request body.files[${index}] must be an object.`);
246
+ }
247
+ const record = file;
248
+ for (const key of Object.keys(record)) {
249
+ if (key !== 'path' && key !== 'content')
250
+ throw new request_validation_js_1.RequestValidationError(`Request body.files[${index}].${key} is not writable.`);
251
+ }
252
+ if (typeof record.path !== 'string' || !record.path.trim() || typeof record.content !== 'string') {
253
+ throw new request_validation_js_1.RequestValidationError(`Request body.files[${index}] requires text path and content.`);
254
+ }
255
+ return { path: record.path, content: record.content };
256
+ });
257
+ }
258
+ /** Derives interpreter facts from a staged shebang; no client path or version is trusted. */
259
+ async function resolveServerInterpreter(shebang, packagePath) {
260
+ const tokens = shebang.replace(/^#!\s*/, '').trim().split(/\s+/).filter(Boolean);
261
+ if (tokens.length !== 1 && !(tokens.length === 2 && tokens[0] === '/usr/bin/env')) {
262
+ throw new request_validation_js_1.RequestValidationError('Automation script shebang must name one interpreter (or /usr/bin/env NAME).');
263
+ }
264
+ const requested = tokens[0] === '/usr/bin/env' ? resolvePathInterpreter(tokens[1]) : tokens[0];
265
+ if (!(0, node_path_1.isAbsolute)(requested) || !(0, node_fs_1.existsSync)(requested)) {
266
+ throw new request_validation_js_1.RequestValidationError('Automation script shebang interpreter is unavailable on this server.');
267
+ }
268
+ let interpreterPath;
269
+ try {
270
+ (0, node_fs_1.accessSync)(requested, node_fs_1.constants.X_OK);
271
+ interpreterPath = (0, node_fs_1.realpathSync)(requested);
272
+ }
273
+ catch {
274
+ throw new request_validation_js_1.RequestValidationError('Automation script shebang interpreter is not executable on this server.');
275
+ }
276
+ const sandbox = (0, anthropic_sandbox_runner_js_1.createAnthropicSandboxRunner)();
277
+ const probe = await sandbox.probe();
278
+ if (!probe.available)
279
+ throw new Error(`sandbox_unavailable: ${probe.missing.join(', ') || probe.platform}`);
280
+ const result = await sandbox.run({
281
+ command: `${quoteShell(interpreterPath)} --version`,
282
+ interpreterPath,
283
+ cwd: packagePath,
284
+ packagePath,
285
+ network: 'none',
286
+ timeoutMs: 5_000,
287
+ stdoutBytes: 16_384,
288
+ stderrBytes: 16_384,
289
+ });
290
+ if (result.exitCode !== 0 || result.signal || result.outputLimitExceeded || !result.stdout.trim()) {
291
+ throw new request_validation_js_1.RequestValidationError('Automation script interpreter version probe failed in the sandbox.');
292
+ }
293
+ return { path: interpreterPath, version: result.stdout.trim() };
294
+ }
295
+ function resolvePathInterpreter(name) {
296
+ if (!/^[A-Za-z0-9._+-]+$/.test(name))
297
+ throw new request_validation_js_1.RequestValidationError('Automation script env shebang interpreter name is invalid.');
298
+ for (const directory of String(node_process_1.default.env.PATH || '').split(node_path_1.delimiter).filter(Boolean)) {
299
+ const candidate = (0, node_path_1.join)(directory, name);
300
+ try {
301
+ (0, node_fs_1.accessSync)(candidate, node_fs_1.constants.X_OK);
302
+ return candidate;
303
+ }
304
+ catch {
305
+ // Only server PATH entries are considered for /usr/bin/env resolution.
306
+ }
307
+ }
308
+ throw new request_validation_js_1.RequestValidationError('Automation script env shebang interpreter is unavailable on this server.');
309
+ }
310
+ function quoteShell(value) {
311
+ return `'${value.replace(/'/g, "'\\''")}'`;
312
+ }
313
+ const MAX_SOURCE_UPLOAD_BODY_BYTES = 1_200_000;
314
+ async function strictBody(req, schema, maxBytes) {
315
+ const body = await (0, server_helpers_js_1.readJsonBody)(req, maxBytes);
316
+ const allowed = new Set(Object.keys(schema));
317
+ for (const field of Object.keys(body)) {
318
+ if (!allowed.has(field))
319
+ throw new request_validation_js_1.RequestValidationError(`Request body.${field} is not writable.`);
320
+ }
321
+ return (0, request_validation_js_1.validateBody)(body, schema);
322
+ }
@@ -20,6 +20,8 @@ class LocalCoreController extends node_events_1.EventEmitter {
20
20
  externalService;
21
21
  scheduledJobs;
22
22
  automationMonitors;
23
+ automations;
24
+ automationActionExecutor;
23
25
  runtimeDetection;
24
26
  kernel;
25
27
  errorReporter;
@@ -58,6 +60,18 @@ class LocalCoreController extends node_events_1.EventEmitter {
58
60
  runMonitorNow: async () => { throw new Error('Automation monitor service is not available.'); },
59
61
  listRuns: () => [],
60
62
  };
63
+ this.automations = this.runtime.automations || {
64
+ list: () => [],
65
+ get: () => undefined,
66
+ create: () => { throw new Error('Automation service is not available.'); },
67
+ update: () => { throw new Error('Automation service is not available.'); },
68
+ delete: () => ({ deleted: false }),
69
+ checkNow: async () => { throw new Error('Automation service is not available.'); },
70
+ listEvaluations: () => [],
71
+ listRuns: () => [],
72
+ getRuntimeStatus: () => ({ status: 'stopped' }),
73
+ };
74
+ this.automationActionExecutor = this.runtime.automationActionExecutor;
61
75
  this.errorReporter = new local_core_errors_js_1.LocalCoreErrorReporter((message) => this.handleLog(message));
62
76
  this.runtimeDetection = new runtime_detection_service_js_1.RuntimeDetectionService({
63
77
  userDataPath,
@@ -78,6 +92,12 @@ class LocalCoreController extends node_events_1.EventEmitter {
78
92
  this.emit('automation-monitor', monitor);
79
93
  }), this.kernel.context.bus.on('automation.monitor.run.updated', (run) => {
80
94
  this.emit('automation-monitor-run', run);
95
+ }), this.kernel.context.bus.on('automation.definition.updated', (automation) => {
96
+ this.emit('automation-definition', automation);
97
+ }), this.kernel.context.bus.on('automation.evaluation.updated', (evaluation) => {
98
+ this.emit('automation-evaluation', evaluation);
99
+ }), this.kernel.context.bus.on('automation.run.updated', (run) => {
100
+ this.emit('automation-run', run);
81
101
  }), this.kernel.context.bus.on('runtime.state.changed', () => {
82
102
  void this.emitRuntime();
83
103
  }), this.kernel.context.bus.on('localcore.error', (event) => {
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ManagedSkillCatalog = void 0;
4
+ const node_fs_1 = require("node:fs");
5
+ const node_path_1 = require("node:path");
6
+ /** Loads packaged skills by name without constructing instructions from user input. */
7
+ class ManagedSkillCatalog {
8
+ rootDir;
9
+ constructor(options = {}) {
10
+ this.rootDir = (0, node_path_1.resolve)(options.rootDir || resolveManagedSkillsRoot());
11
+ }
12
+ get(id) {
13
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(id))
14
+ return undefined;
15
+ const path = (0, node_path_1.resolve)(this.rootDir, id, 'SKILL.md');
16
+ if (!path.startsWith(`${this.rootDir}/`) || !(0, node_fs_1.existsSync)(path))
17
+ return undefined;
18
+ return { id, content: (0, node_fs_1.readFileSync)(path, 'utf8') };
19
+ }
20
+ /** Resolves a packaged helper only when it belongs to the declared managed skill. */
21
+ getHelperPath(id, relativePath) {
22
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(id))
23
+ return undefined;
24
+ if (!/^(?:[a-z0-9][a-z0-9._-]*\/)*[a-z0-9][a-z0-9._-]*$/.test(relativePath))
25
+ return undefined;
26
+ const skillRoot = (0, node_path_1.resolve)(this.rootDir, id);
27
+ const path = (0, node_path_1.resolve)(skillRoot, relativePath);
28
+ if (!path.startsWith(`${skillRoot}/`) || !(0, node_fs_1.existsSync)(path))
29
+ return undefined;
30
+ return path;
31
+ }
32
+ }
33
+ exports.ManagedSkillCatalog = ManagedSkillCatalog;
34
+ function resolveManagedSkillsRoot() {
35
+ const packaged = (0, node_path_1.resolve)(process.cwd(), 'dist-electron', 'electron', 'managed-skills');
36
+ return (0, node_fs_1.existsSync)(packaged) ? packaged : (0, node_path_1.resolve)(process.cwd(), 'electron', 'managed-skills');
37
+ }
@@ -40,8 +40,8 @@ function jsonError(res, statusCode, error) {
40
40
  res.setHeader('Content-Type', 'application/json; charset=utf-8');
41
41
  res.end(JSON.stringify((0, local_core_errors_js_1.errorInfoToHttpBody)(info)));
42
42
  }
43
- async function readJsonBody(req) {
44
- const body = await readRawBody(req);
43
+ async function readJsonBody(req, maxBytes) {
44
+ const body = await readRawBody(req, maxBytes);
45
45
  if (!body.length) {
46
46
  return {};
47
47
  }
@@ -55,10 +55,20 @@ async function readJsonBody(req) {
55
55
  throw new request_validation_js_1.RequestValidationError('Request body must contain valid JSON.');
56
56
  }
57
57
  }
58
- async function readRawBody(req) {
58
+ async function readRawBody(req, maxBytes) {
59
+ const contentLength = Number(req.headers?.['content-length'] || '');
60
+ if (maxBytes !== undefined && Number.isFinite(contentLength) && contentLength > maxBytes) {
61
+ throw new request_validation_js_1.RequestValidationError(`Request body exceeds ${maxBytes} bytes.`);
62
+ }
59
63
  const chunks = [];
64
+ let total = 0;
60
65
  for await (const chunk of req) {
61
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
66
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
67
+ total += buffer.byteLength;
68
+ if (maxBytes !== undefined && total > maxBytes) {
69
+ throw new request_validation_js_1.RequestValidationError(`Request body exceeds ${maxBytes} bytes.`);
70
+ }
71
+ chunks.push(buffer);
62
72
  }
63
73
  return Buffer.concat(chunks);
64
74
  }