@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.
- package/README.md +18 -0
- package/dist/renderer/assets/AutomationList-BgJgAdVO.js +16 -0
- package/dist/renderer/assets/{Badge-BfY1VaHS.js → Badge-Bq2hPVcR.js} +1 -1
- package/dist/renderer/assets/{Card-DWA_Tww_.js → Card-BPJNSZce.js} +1 -1
- package/dist/renderer/assets/{Config-nHj3g1U5.js → Config-aSFqlI88.js} +2 -2
- package/dist/renderer/assets/Dashboard-D3ezFmb7.js +36 -0
- package/dist/renderer/assets/{EmptyState-zatDjLj_.js → EmptyState-DK496gBq.js} +1 -1
- package/dist/renderer/assets/{HighlightedMarkdown-CsM5n70d.js → HighlightedMarkdown-Ckre5jNv.js} +1 -1
- package/dist/renderer/assets/{Input-R1XdFK1K.js → Input-C5C1d0ba.js} +1 -1
- package/dist/renderer/assets/{KnowledgeDetail-Co5sxeGd.js → KnowledgeDetail-N8Ey1ikc.js} +2 -2
- package/dist/renderer/assets/{KnowledgeHome-DwziPw5m.js → KnowledgeHome-9aF8nJEf.js} +2 -2
- package/dist/renderer/assets/{Logs-NwZUtR_G.js → Logs-sgAdwEeS.js} +2 -2
- package/dist/renderer/assets/{Modal-CrcIwIFL.js → Modal-BB10hTRc.js} +3 -3
- package/dist/renderer/assets/{Page-Dp0obeSw.js → Page-DfC319Ck.js} +1 -1
- package/dist/renderer/assets/{Select-6mVQaf84.js → Select-RHJBmNER.js} +1 -1
- package/dist/renderer/assets/{ThreadChat-BFzbn3L6.js → ThreadChat-BU1zCdr4.js} +3 -3
- package/dist/renderer/assets/{Workspace-DCmo8BMm.js → Workspace-BoZCz7S8.js} +1 -1
- package/dist/renderer/assets/{arrow-left-9rjg_Hp4.js → arrow-left-D2V1eZSD.js} +1 -1
- package/dist/renderer/assets/{book-open-Bu8GCHnG.js → book-open-DUcBn7Bb.js} +1 -1
- package/dist/renderer/assets/{channels-BxVbG-6j.js → channels-DMhDSlf2.js} +2 -2
- package/dist/renderer/assets/{chevron-down-BE8uQmWM.js → chevron-down-Ca2FQDB7.js} +1 -1
- package/dist/renderer/assets/en-Bnr4nMzg.js +1 -0
- package/dist/renderer/assets/es-B1GIFh3i.js +1 -0
- package/dist/renderer/assets/{index-BIKL3fTP.css → index-B6WcpkJF.css} +1 -1
- package/dist/renderer/assets/{index-B7TeF6Es.js → index-BlnvlekJ.js} +1 -1
- package/dist/renderer/assets/index-DRJ5wgw_.js +162 -0
- package/dist/renderer/assets/ja-CDWPWqEW.js +1 -0
- package/dist/renderer/assets/{knowledge-Kl19SqGS.js → knowledge-D1-k56eY.js} +1 -1
- package/dist/renderer/assets/{pencil-CPB7SK1m.js → pencil-BhWdv_86.js} +1 -1
- package/dist/renderer/assets/{plus-Bt26z-4q.js → plus-BacLMlK3.js} +1 -1
- package/dist/renderer/assets/{save-Une7GMxT.js → save-Bpoxbzau.js} +1 -1
- package/dist/renderer/assets/{search-DE0agJjJ.js → search-BVJcpdFX.js} +1 -1
- package/dist/renderer/assets/{shield-check-De7xaXZh.js → shield-check-C2cw_Ip6.js} +1 -1
- package/dist/renderer/assets/{threads-BnsNL5ei.js → threads-DOsBQBg_.js} +1 -1
- package/dist/renderer/assets/{trash-2-6iGF59Ac.js → trash-2-Bs3rEm3g.js} +1 -1
- package/dist/renderer/assets/zh-MSddxMDX.js +1 -0
- package/dist/renderer/assets/zh-TW-C_LNWX0o.js +1 -0
- package/dist/renderer/index.html +2 -2
- package/dist-electron/electron/managed-skills/agent-browser/SKILL.md +41 -0
- package/dist-electron/electron/managed-skills/agent-browser/references/authentication.md +83 -0
- package/dist-electron/electron/managed-skills/agent-browser/references/commands.md +50 -0
- package/dist-electron/electron/managed-skills/agent-browser/references/profiling.md +20 -0
- package/dist-electron/electron/managed-skills/agent-browser/references/proxy-support.md +18 -0
- package/dist-electron/electron/managed-skills/agent-browser/references/session-management.md +17 -0
- package/dist-electron/electron/managed-skills/agent-browser/references/snapshot-refs.md +17 -0
- package/dist-electron/electron/managed-skills/agent-browser/references/video-recording.md +21 -0
- package/dist-electron/electron/managed-skills/agent-browser/templates/authenticated-session.sh +11 -0
- package/dist-electron/electron/managed-skills/agent-browser/templates/capture-workflow.sh +12 -0
- package/dist-electron/electron/managed-skills/agent-browser/templates/form-automation.sh +10 -0
- package/dist-electron/electron/managed-skills/condition-trigger/SKILL.md +21 -0
- package/dist-electron/electron/managed-skills/condition-trigger/scripts/register-condition-trigger.sh +106 -0
- package/dist-electron/electron/managed-skills/knowledge-base/SKILL.md +13 -0
- package/dist-electron/electron/managed-skills/knowledge-base/scripts/search-knowledge.sh +80 -0
- package/dist-electron/packages/contracts/src/automations.js +211 -0
- package/dist-electron/packages/contracts/src/index.js +1 -0
- package/dist-electron/packages/core-sdk/src/automations.js +87 -0
- package/dist-electron/packages/core-sdk/src/client.js +101 -0
- package/dist-electron/packages/core-sdk/src/index.js +2 -1
- package/dist-electron/services/local-ai-core/src/acp/store/automation-script-store.js +415 -0
- package/dist-electron/services/local-ai-core/src/acp/store/automation-store.js +829 -0
- package/dist-electron/services/local-ai-core/src/acp/store/local-core-acp-store.js +158 -0
- package/dist-electron/services/local-ai-core/src/acp/store/schema.js +81 -0
- package/dist-electron/services/local-ai-core/src/automation/automation-action-executor.js +144 -0
- package/dist-electron/services/local-ai-core/src/automation/automation-condition-engine.js +79 -0
- package/dist-electron/services/local-ai-core/src/automation/automation-conversation-executor.js +39 -73
- package/dist-electron/services/local-ai-core/src/automation/automation-monitor-service.js +695 -260
- package/dist-electron/services/local-ai-core/src/automation/automation-script-service.js +507 -0
- package/dist-electron/services/local-ai-core/src/automation/automation-service.js +1019 -0
- package/dist-electron/services/local-ai-core/src/automation/automation-trigger-engine.js +141 -0
- package/dist-electron/services/local-ai-core/src/automation/condition-evaluator.js +60 -17
- package/dist-electron/services/local-ai-core/src/automation/legacy-automation-mappers.js +286 -0
- package/dist-electron/services/local-ai-core/src/automation/scripts/anthropic-sandbox-runner.js +792 -0
- package/dist-electron/services/local-ai-core/src/automation/scripts/sandbox-runner.js +2 -0
- package/dist-electron/services/local-ai-core/src/automation/scripts/script-package.js +475 -0
- package/dist-electron/services/local-ai-core/src/automation/scripts/script-protocol-runner.js +266 -0
- package/dist-electron/services/local-ai-core/src/automation/scripts/secret-resolver.js +37 -0
- package/dist-electron/services/local-ai-core/src/channel/weixin/inbound-poller.js +1 -2
- package/dist-electron/services/local-ai-core/src/channel/weixin/transport.js +9 -8
- package/dist-electron/services/local-ai-core/src/cli/lac.js +229 -0
- package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +21 -0
- package/dist-electron/services/local-ai-core/src/runtime/deployment-diagnostics.js +76 -0
- package/dist-electron/services/local-ai-core/src/runtime/handlers/automations-handler.js +322 -0
- package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +20 -0
- package/dist-electron/services/local-ai-core/src/runtime/managed-skill-catalog.js +37 -0
- package/dist-electron/services/local-ai-core/src/runtime/server-helpers.js +14 -4
- package/dist-electron/services/local-ai-core/src/runtime/server-routes.js +75 -0
- package/dist-electron/services/local-ai-core/src/runtime/server.js +21 -0
- package/dist-electron/services/local-ai-core/src/runtime/standalone.js +1 -0
- package/dist-electron/services/local-ai-core/src/scheduler/cron.js +151 -39
- package/dist-electron/services/local-ai-core/src/scheduler/scheduled-conversation-executor.js +2 -4
- package/dist-electron/services/local-ai-core/src/scheduler/scheduled-job-application-service.js +64 -12
- package/dist-electron/services/local-ai-core/src/scheduler/scheduler-service.js +71 -4
- package/dist-electron/services/local-ai-core/src/scheduler/thread-resolution.js +10 -0
- package/dist-electron/services/local-ai-core/src/thread/agent-message-policy.js +16 -1
- package/dist-electron/src/pages/Automation/automation-page-model.js +76 -0
- package/dist-electron/tests/contracts/agent-message-policy.test.js +32 -0
- package/dist-electron/tests/contracts/architecture-docs.test.js +30 -0
- package/dist-electron/tests/contracts/automation-contracts.test.js +154 -0
- package/dist-electron/tests/contracts/automation-renderer-model.test.js +47 -0
- package/dist-electron/tests/electron/automation-action-executor.test.js +75 -0
- package/dist-electron/tests/electron/automation-compatibility.test.js +463 -0
- package/dist-electron/tests/electron/automation-deployment.test.js +66 -0
- package/dist-electron/tests/electron/automation-engine.test.js +264 -0
- package/dist-electron/tests/electron/automation-monitor.test.js +1333 -0
- package/dist-electron/tests/electron/automation-sandbox-runner.test.js +333 -0
- package/dist-electron/tests/electron/automation-script-approval.test.js +553 -0
- package/dist-electron/tests/electron/automation-script-package.test.js +499 -0
- package/dist-electron/tests/electron/automation-script-runner.test.js +120 -0
- package/dist-electron/tests/electron/automation-service.test.js +1078 -0
- package/dist-electron/tests/electron/automation-store.test.js +593 -0
- package/dist-electron/tests/electron/condition-trigger-skill.test.js +93 -0
- package/dist-electron/tests/electron/core-client.test.js +99 -0
- package/dist-electron/tests/electron/lac-cli.test.js +110 -0
- package/dist-electron/tests/electron/workspace-task-store.test.js +25 -0
- package/dist-electron/tests/integration/automation-routes.test.js +186 -0
- package/dist-electron/tests/integration/automation-sandbox-runtime.test.js +92 -0
- package/package.json +5 -6
- package/dist/renderer/assets/CronList-BwkA_I7v.js +0 -1
- package/dist/renderer/assets/Dashboard-D0_A7k02.js +0 -41
- package/dist/renderer/assets/MonitorList-CS1uLS5A.js +0 -1
- package/dist/renderer/assets/en-CmAr_0e6.js +0 -1
- package/dist/renderer/assets/es-Cs9x52ca.js +0 -1
- package/dist/renderer/assets/index-BkI9POxj.js +0 -167
- package/dist/renderer/assets/ja-DtvtBRTW.js +0 -1
- package/dist/renderer/assets/play-DGAO4OCu.js +0 -6
- package/dist/renderer/assets/zh-CcJ9vBZj.js +0 -1
- 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
|
-
|
|
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
|
}
|