@kafca/agentdock 0.1.61 → 0.1.63
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-DR-micIT.js +16 -0
- package/dist/renderer/assets/{Badge-BLI4LHse.js → Badge-COE1Zm2i.js} +1 -1
- package/dist/renderer/assets/{Card-DJeLDdL3.js → Card-F6UNFxnQ.js} +1 -1
- package/dist/renderer/assets/{Config-D1awc81A.js → Config-BKqxfhZi.js} +2 -2
- package/dist/renderer/assets/Dashboard-Bvp7-_2z.js +36 -0
- package/dist/renderer/assets/{EmptyState-CkI1RwPX.js → EmptyState-BwV_J0Og.js} +1 -1
- package/dist/renderer/assets/{HighlightedMarkdown-BUhDlhgW.js → HighlightedMarkdown-CBIBaWyM.js} +1 -1
- package/dist/renderer/assets/{Input-DHYrn-Ne.js → Input-C8LZd4Bi.js} +1 -1
- package/dist/renderer/assets/{KnowledgeDetail-Bij-Xo90.js → KnowledgeDetail-DjXYNMIs.js} +2 -2
- package/dist/renderer/assets/{KnowledgeHome-DmPZ2IlI.js → KnowledgeHome-Cqk5_nJh.js} +2 -2
- package/dist/renderer/assets/{Logs-ISY99heA.js → Logs-B6cXFvW4.js} +2 -2
- package/dist/renderer/assets/{Modal-DAQfz0ot.js → Modal-CAj7soll.js} +3 -3
- package/dist/renderer/assets/{Page-Dw43UYDT.js → Page-DlnMnu4F.js} +1 -1
- package/dist/renderer/assets/{Select-DzmN5OGQ.js → Select-CVay_LUX.js} +1 -1
- package/dist/renderer/assets/{ThreadChat-B5CWqqMK.js → ThreadChat-BxXRpYa1.js} +3 -3
- package/dist/renderer/assets/{Workspace-C5g9LBj9.js → Workspace-YC0pC7cM.js} +1 -1
- package/dist/renderer/assets/{arrow-left-BLXh56Ip.js → arrow-left-Cwm09dxx.js} +1 -1
- package/dist/renderer/assets/{book-open-DhPGbc-7.js → book-open-BwcGC_im.js} +1 -1
- package/dist/renderer/assets/{channels-DfQrmuYO.js → channels-BPytmVCd.js} +2 -2
- package/dist/renderer/assets/{chevron-down-CEFtpPDc.js → chevron-down-2LP9oGHG.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-eoVERL5K.js +162 -0
- package/dist/renderer/assets/{index-CYhDUAR_.js → index-llCPCiYk.js} +1 -1
- package/dist/renderer/assets/ja-CDWPWqEW.js +1 -0
- package/dist/renderer/assets/{knowledge-uHRQ80sR.js → knowledge-QujAv-Zh.js} +1 -1
- package/dist/renderer/assets/{pencil-BD2Y31YC.js → pencil-0P2INETc.js} +1 -1
- package/dist/renderer/assets/{plus-DDcldp_c.js → plus-tiGRgE2v.js} +1 -1
- package/dist/renderer/assets/{save-CMW_cZI9.js → save-D8U93DNa.js} +1 -1
- package/dist/renderer/assets/{search-BM-0CvJr.js → search-C-9J9J3f.js} +1 -1
- package/dist/renderer/assets/{shield-check-DaMB0FKG.js → shield-check-BCKkPPTJ.js} +1 -1
- package/dist/renderer/assets/{threads-DZgt9tWj.js → threads-Bsrwwnpm.js} +1 -1
- package/dist/renderer/assets/{trash-2-DTXVbC5y.js → trash-2-CP4sn15n.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 +344 -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 +4 -3
- package/dist/renderer/assets/CronList-CupZoNwW.js +0 -1
- package/dist/renderer/assets/Dashboard-nLZnOtcs.js +0 -41
- package/dist/renderer/assets/MonitorList-BlX82rXF.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-Dd00oqgY.js +0 -167
- package/dist/renderer/assets/ja-DtvtBRTW.js +0 -1
- package/dist/renderer/assets/play-DBJ8wsSy.js +0 -6
- package/dist/renderer/assets/zh-CcJ9vBZj.js +0 -1
- package/dist/renderer/assets/zh-TW-elI72AC1.js +0 -1
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AutomationScriptService = void 0;
|
|
4
|
+
const node_crypto_1 = require("node:crypto");
|
|
5
|
+
const automation_script_store_js_1 = require("../acp/store/automation-script-store.js");
|
|
6
|
+
const DEFAULT_APPROVAL_TTL_MS = 24 * 60 * 60 * 1000;
|
|
7
|
+
const TEST_EXECUTION_LEASE_MS = 10 * 60 * 1000;
|
|
8
|
+
class ExpiredAutomationScriptApprovalError extends Error {
|
|
9
|
+
approvalId;
|
|
10
|
+
constructor(approvalId) {
|
|
11
|
+
super(`Automation script approval expired: ${approvalId}`);
|
|
12
|
+
this.approvalId = approvalId;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
class AutomationScriptService {
|
|
16
|
+
options;
|
|
17
|
+
clock;
|
|
18
|
+
approvalTtlMs;
|
|
19
|
+
constructor(options) {
|
|
20
|
+
this.options = options;
|
|
21
|
+
this.clock = options.clock || (() => new Date());
|
|
22
|
+
this.approvalTtlMs = options.approvalTtlMs ?? DEFAULT_APPROVAL_TTL_MS;
|
|
23
|
+
}
|
|
24
|
+
requestTestApproval(versionId, actor) {
|
|
25
|
+
return this.withTransaction(() => {
|
|
26
|
+
const version = this.requireVersion(versionId);
|
|
27
|
+
if (version.status !== 'draft') {
|
|
28
|
+
throw new Error(`Automation script test approval requires draft status, got ${version.status}.`);
|
|
29
|
+
}
|
|
30
|
+
const script = this.requireScript(version.scriptId);
|
|
31
|
+
const metadata = buildApprovalMetadata(version);
|
|
32
|
+
const approval = this.options.security.createApprovalRequest({
|
|
33
|
+
workspaceId: script.workspace_id,
|
|
34
|
+
kind: 'automation_script_test',
|
|
35
|
+
riskLevel: riskLevelFor(metadata.permissionSnapshot),
|
|
36
|
+
title: `Authorize automation script test: ${script.title}`,
|
|
37
|
+
description: `Authorize one test run for automation script version ${version.id}.`,
|
|
38
|
+
requestedAction: `Run one isolated test for automation script version ${version.id}.`,
|
|
39
|
+
scopes: metadata.permissionSnapshot.scopes,
|
|
40
|
+
requestedBy: actor,
|
|
41
|
+
expiresAt: this.expiresAt(),
|
|
42
|
+
metadata,
|
|
43
|
+
});
|
|
44
|
+
this.saveVersion({
|
|
45
|
+
...version,
|
|
46
|
+
status: 'pending_test_approval',
|
|
47
|
+
pendingTestApprovalId: approval.approvalId,
|
|
48
|
+
updatedAt: this.nowIso(),
|
|
49
|
+
});
|
|
50
|
+
return approval;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
authorizeTest(versionId, approvalId, actor) {
|
|
54
|
+
return this.withTransaction(() => {
|
|
55
|
+
const version = this.requireVersion(versionId);
|
|
56
|
+
if (version.status === 'test_authorized') {
|
|
57
|
+
throw new Error('Automation script test authorization is one-shot and already authorized.');
|
|
58
|
+
}
|
|
59
|
+
if (version.status !== 'pending_test_approval') {
|
|
60
|
+
throw new Error(`Automation script test authorization requires pending_test_approval status, got ${version.status}.`);
|
|
61
|
+
}
|
|
62
|
+
this.requirePendingApprovalId(version.pendingTestApprovalId, approvalId);
|
|
63
|
+
const script = this.requireScript(version.scriptId);
|
|
64
|
+
const approval = this.requireApproval(approvalId, 'automation_script_test', version.id, script.workspace_id);
|
|
65
|
+
if (approval.status === 'rejected') {
|
|
66
|
+
return this.rejectVersion(version, approval, actor);
|
|
67
|
+
}
|
|
68
|
+
this.requireApprovedApproval(approval);
|
|
69
|
+
this.assertApprovalSnapshotMatches(version, approval);
|
|
70
|
+
const now = this.nowIso();
|
|
71
|
+
const authorized = this.saveVersion({
|
|
72
|
+
...version,
|
|
73
|
+
status: 'test_authorized',
|
|
74
|
+
pendingTestApprovalId: undefined,
|
|
75
|
+
testAuthorization: { actor, at: now, approvalId },
|
|
76
|
+
updatedAt: now,
|
|
77
|
+
});
|
|
78
|
+
this.options.security.createAuditEvent({
|
|
79
|
+
type: 'automation.script.test_authorized',
|
|
80
|
+
workspaceId: script.workspace_id,
|
|
81
|
+
approvalId,
|
|
82
|
+
actor,
|
|
83
|
+
summary: `Automation script test authorized for version ${version.id}.`,
|
|
84
|
+
riskLevel: approval.riskLevel,
|
|
85
|
+
metadata: { versionId: version.id, scriptId: version.scriptId },
|
|
86
|
+
});
|
|
87
|
+
return authorized;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
recordTestResult(versionId, result) {
|
|
91
|
+
const version = this.requireVersion(versionId);
|
|
92
|
+
if (version.status === 'tested' || version.testReport) {
|
|
93
|
+
throw new Error('Automation script test result already recorded; test authorization is one-shot.');
|
|
94
|
+
}
|
|
95
|
+
if (version.status !== 'test_authorized' && version.status !== 'testing') {
|
|
96
|
+
throw new Error(`Automation script test result requires test_authorized or testing status, got ${version.status}.`);
|
|
97
|
+
}
|
|
98
|
+
const now = this.nowIso();
|
|
99
|
+
return this.saveVersion({
|
|
100
|
+
...version,
|
|
101
|
+
status: 'tested',
|
|
102
|
+
testReport: normalizeTestReport(result),
|
|
103
|
+
updatedAt: now,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/** Atomically consumes the one-shot test authorization before any sandbox work starts. */
|
|
107
|
+
claimTestExecution(versionId) {
|
|
108
|
+
let recovered = false;
|
|
109
|
+
const claimed = this.withTransaction(() => {
|
|
110
|
+
const version = this.requireVersion(versionId);
|
|
111
|
+
if (version.status === 'testing' && this.isExpiredTestExecution(version)) {
|
|
112
|
+
recovered = true;
|
|
113
|
+
return this.saveVersion(this.failedTestExecution(version, 'Sandbox test execution did not complete before its lease expired.'));
|
|
114
|
+
}
|
|
115
|
+
if (version.status !== 'test_authorized') {
|
|
116
|
+
throw new Error(`Automation script test execution requires test_authorized status, got ${version.status}.`);
|
|
117
|
+
}
|
|
118
|
+
return this.saveVersion({
|
|
119
|
+
...version,
|
|
120
|
+
status: 'testing',
|
|
121
|
+
updatedAt: this.nowIso(),
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
if (recovered)
|
|
125
|
+
throw new Error('Automation script test execution lease expired and was recorded as failed.');
|
|
126
|
+
return claimed;
|
|
127
|
+
}
|
|
128
|
+
failClaimedTestExecution(versionId) {
|
|
129
|
+
return this.withTransaction(() => {
|
|
130
|
+
const version = this.requireVersion(versionId);
|
|
131
|
+
if (version.status !== 'testing')
|
|
132
|
+
return version;
|
|
133
|
+
return this.saveVersion(this.failedTestExecution(version, 'Sandbox test execution could not persist its result.'));
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
requestEnableApproval(versionId, actor) {
|
|
137
|
+
return this.withTransaction(() => {
|
|
138
|
+
const version = this.requireVersion(versionId);
|
|
139
|
+
if (version.status !== 'tested') {
|
|
140
|
+
throw new Error(`Automation script enable approval requires tested status, got ${version.status}.`);
|
|
141
|
+
}
|
|
142
|
+
if (version.testReport?.status !== 'passed') {
|
|
143
|
+
throw new Error('Automation script enable approval requires a passing server-recorded test result.');
|
|
144
|
+
}
|
|
145
|
+
const script = this.requireScript(version.scriptId);
|
|
146
|
+
const metadata = buildApprovalMetadata(version);
|
|
147
|
+
const approval = this.options.security.createApprovalRequest({
|
|
148
|
+
workspaceId: script.workspace_id,
|
|
149
|
+
kind: 'automation_script_enable',
|
|
150
|
+
riskLevel: riskLevelFor(metadata.permissionSnapshot),
|
|
151
|
+
title: `Approve automation script: ${script.title}`,
|
|
152
|
+
description: `Approve automation script version ${version.id} for automation use.`,
|
|
153
|
+
requestedAction: `Enable automation script version ${version.id}.`,
|
|
154
|
+
scopes: metadata.permissionSnapshot.scopes,
|
|
155
|
+
requestedBy: actor,
|
|
156
|
+
expiresAt: this.expiresAt(),
|
|
157
|
+
metadata,
|
|
158
|
+
});
|
|
159
|
+
this.saveVersion({
|
|
160
|
+
...version,
|
|
161
|
+
status: 'pending_approval',
|
|
162
|
+
pendingApprovalId: approval.approvalId,
|
|
163
|
+
updatedAt: this.nowIso(),
|
|
164
|
+
});
|
|
165
|
+
return approval;
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
approveVersion(versionId, approvalId, actor) {
|
|
169
|
+
return this.withTransaction(() => {
|
|
170
|
+
const version = this.requireVersion(versionId);
|
|
171
|
+
if (version.status !== 'pending_approval') {
|
|
172
|
+
throw new Error(`Automation script approval requires pending_approval status, got ${version.status}.`);
|
|
173
|
+
}
|
|
174
|
+
this.requirePendingApprovalId(version.pendingApprovalId, approvalId);
|
|
175
|
+
const script = this.requireScript(version.scriptId);
|
|
176
|
+
const approval = this.requireApproval(approvalId, 'automation_script_enable', version.id, script.workspace_id);
|
|
177
|
+
if (approval.status === 'rejected') {
|
|
178
|
+
return this.rejectVersion(version, approval, actor);
|
|
179
|
+
}
|
|
180
|
+
this.requireApprovedApproval(approval);
|
|
181
|
+
this.assertApprovalSnapshotMatches(version, approval);
|
|
182
|
+
const now = this.nowIso();
|
|
183
|
+
const approved = this.saveVersion({
|
|
184
|
+
...version,
|
|
185
|
+
status: 'approved',
|
|
186
|
+
pendingApprovalId: undefined,
|
|
187
|
+
approval: { actor, at: now, approvalId },
|
|
188
|
+
updatedAt: now,
|
|
189
|
+
});
|
|
190
|
+
this.options.security.createAuditEvent({
|
|
191
|
+
type: 'automation.script.approved',
|
|
192
|
+
workspaceId: script.workspace_id,
|
|
193
|
+
approvalId,
|
|
194
|
+
actor,
|
|
195
|
+
summary: `Automation script version ${version.id} approved.`,
|
|
196
|
+
riskLevel: approval.riskLevel,
|
|
197
|
+
metadata: { versionId: version.id, scriptId: version.scriptId },
|
|
198
|
+
});
|
|
199
|
+
return approved;
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
revokeVersion(versionId, actor) {
|
|
203
|
+
return this.withTransaction(() => {
|
|
204
|
+
const version = this.requireVersion(versionId);
|
|
205
|
+
if (version.status !== 'approved') {
|
|
206
|
+
throw new Error(`Automation script revocation requires approved status, got ${version.status}.`);
|
|
207
|
+
}
|
|
208
|
+
const now = this.nowIso();
|
|
209
|
+
const revoked = this.saveVersion({
|
|
210
|
+
...version,
|
|
211
|
+
status: 'revoked',
|
|
212
|
+
revocation: { actor, at: now },
|
|
213
|
+
updatedAt: now,
|
|
214
|
+
});
|
|
215
|
+
const script = this.requireScript(version.scriptId);
|
|
216
|
+
this.options.security.createAuditEvent({
|
|
217
|
+
type: 'automation.script.revoked',
|
|
218
|
+
workspaceId: script.workspace_id,
|
|
219
|
+
actor,
|
|
220
|
+
summary: `Automation script version ${version.id} revoked.`,
|
|
221
|
+
metadata: { versionId: version.id, scriptId: version.scriptId },
|
|
222
|
+
});
|
|
223
|
+
return revoked;
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
rejectVersion(version, approval, actor) {
|
|
227
|
+
const now = this.nowIso();
|
|
228
|
+
return this.saveVersion({
|
|
229
|
+
...version,
|
|
230
|
+
status: 'rejected',
|
|
231
|
+
pendingTestApprovalId: undefined,
|
|
232
|
+
pendingApprovalId: undefined,
|
|
233
|
+
rejection: { actor: approval.resolvedBy || actor, at: now, approvalId: approval.approvalId },
|
|
234
|
+
updatedAt: now,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
requireApprovedApproval(approval) {
|
|
238
|
+
if (isExpired(approval, this.clock())) {
|
|
239
|
+
throw new ExpiredAutomationScriptApprovalError(approval.approvalId);
|
|
240
|
+
}
|
|
241
|
+
if (approval.status !== 'approved') {
|
|
242
|
+
throw new Error(`Automation script approval must be approved, got ${approval.status}.`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
assertApprovalSnapshotMatches(version, approval) {
|
|
246
|
+
const metadata = parseApprovalMetadata(approval.metadata);
|
|
247
|
+
const current = buildApprovalMetadata(version);
|
|
248
|
+
if (metadata.versionId !== version.id) {
|
|
249
|
+
throw new Error('Automation script approval metadata version ID mismatch.');
|
|
250
|
+
}
|
|
251
|
+
if (metadata.packageSha256 !== version.packageSha256) {
|
|
252
|
+
throw new Error('Automation script package hash changed since approval request.');
|
|
253
|
+
}
|
|
254
|
+
if (metadata.testPlanDigest !== current.testPlanDigest) {
|
|
255
|
+
throw new Error('Automation script test plan digest changed since approval request.');
|
|
256
|
+
}
|
|
257
|
+
if (!stringArraysEqual(metadata.permissionSnapshot.secretEnvRefs, current.permissionSnapshot.secretEnvRefs)) {
|
|
258
|
+
throw new Error('Automation script secret env references changed since approval request.');
|
|
259
|
+
}
|
|
260
|
+
if (stableStringify(metadata.permissionSnapshot) !== stableStringify(current.permissionSnapshot)) {
|
|
261
|
+
throw new Error('Automation script permission snapshot changed since approval request.');
|
|
262
|
+
}
|
|
263
|
+
if (metadata.permissionSnapshotDigest !== digest(metadata.permissionSnapshot)) {
|
|
264
|
+
throw new Error('Automation script permission snapshot digest mismatch.');
|
|
265
|
+
}
|
|
266
|
+
if (metadata.manifestDigest !== current.manifestDigest) {
|
|
267
|
+
throw new Error('Automation script manifest digest changed since approval request.');
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
requireApproval(approvalId, kind, versionId, workspaceId) {
|
|
271
|
+
const approval = this.options.security.getApprovalRequest(approvalId);
|
|
272
|
+
if (!approval)
|
|
273
|
+
throw new Error(`Approval not found: ${approvalId}`);
|
|
274
|
+
if (approval.kind !== kind) {
|
|
275
|
+
throw new Error(`Automation script approval kind mismatch: expected ${kind}, got ${approval.kind}.`);
|
|
276
|
+
}
|
|
277
|
+
if (approval.metadata?.versionId !== versionId) {
|
|
278
|
+
throw new Error('Automation script approval does not belong to this version.');
|
|
279
|
+
}
|
|
280
|
+
if (approval.workspaceId !== workspaceId) {
|
|
281
|
+
throw new Error('Automation script approval workspace does not match the script workspace.');
|
|
282
|
+
}
|
|
283
|
+
return approval;
|
|
284
|
+
}
|
|
285
|
+
requirePendingApprovalId(expectedApprovalId, actualApprovalId) {
|
|
286
|
+
if (!expectedApprovalId) {
|
|
287
|
+
throw new Error('Automation script pending approval ID is missing.');
|
|
288
|
+
}
|
|
289
|
+
if (expectedApprovalId !== actualApprovalId) {
|
|
290
|
+
throw new Error('Automation script approval ID does not match the pending approval ID.');
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
requireVersion(versionId) {
|
|
294
|
+
const version = this.getVersion(versionId);
|
|
295
|
+
if (!version)
|
|
296
|
+
throw new Error(`Automation script version not found: ${versionId}`);
|
|
297
|
+
return version;
|
|
298
|
+
}
|
|
299
|
+
getVersion(versionId) {
|
|
300
|
+
const row = this.options.db.prepare(`
|
|
301
|
+
SELECT id, script_id, status, package_sha256, package_path, shebang, interpreter_path, interpreter_version,
|
|
302
|
+
created_at, updated_at, version_json
|
|
303
|
+
FROM automation_script_versions
|
|
304
|
+
WHERE id = ?
|
|
305
|
+
`).get(versionId);
|
|
306
|
+
if (!row)
|
|
307
|
+
return undefined;
|
|
308
|
+
return (0, automation_script_store_js_1.parseAutomationScriptVersionRow)(row);
|
|
309
|
+
}
|
|
310
|
+
requireScript(scriptId) {
|
|
311
|
+
const script = this.options.db.prepare(`
|
|
312
|
+
SELECT id, workspace_id, title
|
|
313
|
+
FROM automation_scripts
|
|
314
|
+
WHERE id = ?
|
|
315
|
+
`).get(scriptId);
|
|
316
|
+
if (!script)
|
|
317
|
+
throw new Error(`Automation script not found: ${scriptId}`);
|
|
318
|
+
return script;
|
|
319
|
+
}
|
|
320
|
+
saveVersion(version) {
|
|
321
|
+
this.options.db.prepare(`
|
|
322
|
+
UPDATE automation_script_versions
|
|
323
|
+
SET status = ?, package_sha256 = ?, package_path = ?, shebang = ?, interpreter_path = ?,
|
|
324
|
+
interpreter_version = ?, updated_at = ?, version_json = ?
|
|
325
|
+
WHERE id = ?
|
|
326
|
+
`).run(version.status, version.packageSha256, version.packagePath, version.shebang, version.interpreterPath, version.interpreterVersion, version.updatedAt, JSON.stringify(version), version.id);
|
|
327
|
+
return this.requireVersion(version.id);
|
|
328
|
+
}
|
|
329
|
+
markApprovalExpired(approvalId) {
|
|
330
|
+
const now = this.nowIso();
|
|
331
|
+
this.options.db.prepare(`
|
|
332
|
+
UPDATE approval_requests
|
|
333
|
+
SET status = 'expired', updated_at = ?
|
|
334
|
+
WHERE id = ?
|
|
335
|
+
`).run(now, approvalId);
|
|
336
|
+
}
|
|
337
|
+
nowIso() {
|
|
338
|
+
return this.clock().toISOString();
|
|
339
|
+
}
|
|
340
|
+
isExpiredTestExecution(version) {
|
|
341
|
+
const startedAt = Date.parse(version.updatedAt);
|
|
342
|
+
return !Number.isFinite(startedAt) || this.clock().getTime() - startedAt >= TEST_EXECUTION_LEASE_MS;
|
|
343
|
+
}
|
|
344
|
+
failedTestExecution(version, summary) {
|
|
345
|
+
return {
|
|
346
|
+
...version,
|
|
347
|
+
status: 'tested',
|
|
348
|
+
testReport: { status: 'failed', finishedAt: this.nowIso(), summary },
|
|
349
|
+
updatedAt: this.nowIso(),
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
expiresAt() {
|
|
353
|
+
return new Date(this.clock().getTime() + this.approvalTtlMs).toISOString();
|
|
354
|
+
}
|
|
355
|
+
withTransaction(work) {
|
|
356
|
+
this.options.db.exec('BEGIN IMMEDIATE');
|
|
357
|
+
try {
|
|
358
|
+
const result = work();
|
|
359
|
+
this.options.db.exec('COMMIT');
|
|
360
|
+
return result;
|
|
361
|
+
}
|
|
362
|
+
catch (error) {
|
|
363
|
+
this.options.db.exec('ROLLBACK');
|
|
364
|
+
if (error instanceof ExpiredAutomationScriptApprovalError) {
|
|
365
|
+
this.markApprovalExpired(error.approvalId);
|
|
366
|
+
}
|
|
367
|
+
throw error;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
exports.AutomationScriptService = AutomationScriptService;
|
|
372
|
+
function buildApprovalMetadata(version) {
|
|
373
|
+
const permissionSnapshot = buildPermissionSnapshot(version);
|
|
374
|
+
return {
|
|
375
|
+
versionId: version.id,
|
|
376
|
+
packageSha256: version.packageSha256,
|
|
377
|
+
manifestDigest: digest({
|
|
378
|
+
capabilities: version.capabilities,
|
|
379
|
+
config: version.config,
|
|
380
|
+
configSchema: version.configSchema,
|
|
381
|
+
env: version.env,
|
|
382
|
+
limits: version.limits,
|
|
383
|
+
secretRefs: version.secretRefs,
|
|
384
|
+
}),
|
|
385
|
+
permissionSnapshot,
|
|
386
|
+
permissionSnapshotDigest: digest(permissionSnapshot),
|
|
387
|
+
testPlanDigest: digest(version.testPlan),
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
function buildPermissionSnapshot(version) {
|
|
391
|
+
const secretRefs = sortedStrings(version.secretRefs);
|
|
392
|
+
const secretEnvRefs = secretRefs.filter((ref) => ref.startsWith('env://'));
|
|
393
|
+
const scopes = new Set();
|
|
394
|
+
scopes.add('command.execute');
|
|
395
|
+
if (version.networkMode === 'public')
|
|
396
|
+
scopes.add('network.access');
|
|
397
|
+
if (version.internalAccess || version.allowedReadDirs.length > 0)
|
|
398
|
+
scopes.add('workspace.read');
|
|
399
|
+
if (secretRefs.length > 0)
|
|
400
|
+
scopes.add('secrets.access');
|
|
401
|
+
return {
|
|
402
|
+
scopes: [...scopes].sort(),
|
|
403
|
+
networkMode: version.networkMode,
|
|
404
|
+
internalAccess: version.internalAccess,
|
|
405
|
+
allowedReadDirs: sortedStrings(version.allowedReadDirs),
|
|
406
|
+
secretRefs,
|
|
407
|
+
secretEnvRefs,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
function riskLevelFor(snapshot) {
|
|
411
|
+
if (snapshot.secretRefs.length > 0 || snapshot.internalAccess)
|
|
412
|
+
return 'high';
|
|
413
|
+
if (snapshot.networkMode === 'public' || snapshot.allowedReadDirs.length > 0)
|
|
414
|
+
return 'medium';
|
|
415
|
+
return 'low';
|
|
416
|
+
}
|
|
417
|
+
function normalizeTestReport(result) {
|
|
418
|
+
if (!result || typeof result !== 'object' || Array.isArray(result)) {
|
|
419
|
+
throw new Error('Automation script test report must be an object.');
|
|
420
|
+
}
|
|
421
|
+
if (result.status !== 'passed' && result.status !== 'failed') {
|
|
422
|
+
throw new Error('Automation script test report status must be passed or failed.');
|
|
423
|
+
}
|
|
424
|
+
if (typeof result.finishedAt !== 'string' || Number.isNaN(Date.parse(result.finishedAt))) {
|
|
425
|
+
throw new Error('Automation script test report finishedAt must be a valid timestamp.');
|
|
426
|
+
}
|
|
427
|
+
if (result.summary !== undefined && typeof result.summary !== 'string') {
|
|
428
|
+
throw new Error('Automation script test report summary must be a string.');
|
|
429
|
+
}
|
|
430
|
+
return { ...result };
|
|
431
|
+
}
|
|
432
|
+
function parseApprovalMetadata(metadata) {
|
|
433
|
+
if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata)) {
|
|
434
|
+
throw new Error('Automation script approval metadata is missing.');
|
|
435
|
+
}
|
|
436
|
+
const input = metadata;
|
|
437
|
+
if (typeof input.versionId !== 'string')
|
|
438
|
+
throw new Error('Automation script approval metadata version ID is missing.');
|
|
439
|
+
if (typeof input.packageSha256 !== 'string')
|
|
440
|
+
throw new Error('Automation script approval metadata package hash is missing.');
|
|
441
|
+
const manifestDigest = requiredDigest(input.manifestDigest, 'manifest digest');
|
|
442
|
+
const permissionSnapshotDigest = requiredDigest(input.permissionSnapshotDigest, 'permission snapshot digest');
|
|
443
|
+
const testPlanDigest = requiredDigest(input.testPlanDigest, 'test plan digest');
|
|
444
|
+
if (!input.permissionSnapshot || typeof input.permissionSnapshot !== 'object') {
|
|
445
|
+
throw new Error('Automation script approval metadata permission snapshot is missing.');
|
|
446
|
+
}
|
|
447
|
+
return {
|
|
448
|
+
versionId: input.versionId,
|
|
449
|
+
packageSha256: input.packageSha256,
|
|
450
|
+
manifestDigest,
|
|
451
|
+
permissionSnapshot: normalizePermissionSnapshot(input.permissionSnapshot),
|
|
452
|
+
permissionSnapshotDigest,
|
|
453
|
+
testPlanDigest,
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
function requiredDigest(value, label) {
|
|
457
|
+
if (typeof value !== 'string' || !/^[a-f0-9]{64}$/.test(value)) {
|
|
458
|
+
throw new Error(`Automation script approval metadata ${label} must be a SHA-256 digest.`);
|
|
459
|
+
}
|
|
460
|
+
return value;
|
|
461
|
+
}
|
|
462
|
+
function normalizePermissionSnapshot(value) {
|
|
463
|
+
const snapshot = value;
|
|
464
|
+
return {
|
|
465
|
+
scopes: sortedStrings(snapshot.scopes).filter(isSecurityScope),
|
|
466
|
+
networkMode: snapshot.networkMode === 'public' ? 'public' : 'none',
|
|
467
|
+
internalAccess: snapshot.internalAccess === true,
|
|
468
|
+
allowedReadDirs: sortedStrings(snapshot.allowedReadDirs),
|
|
469
|
+
secretRefs: sortedStrings(snapshot.secretRefs),
|
|
470
|
+
secretEnvRefs: sortedStrings(snapshot.secretEnvRefs),
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
function isSecurityScope(scope) {
|
|
474
|
+
return (scope === 'workspace.read' ||
|
|
475
|
+
scope === 'workspace.write' ||
|
|
476
|
+
scope === 'command.execute' ||
|
|
477
|
+
scope === 'network.access' ||
|
|
478
|
+
scope === 'secrets.access' ||
|
|
479
|
+
scope === 'git.modify');
|
|
480
|
+
}
|
|
481
|
+
function isExpired(approval, now) {
|
|
482
|
+
return Boolean(approval.expiresAt && Date.parse(approval.expiresAt) <= now.getTime());
|
|
483
|
+
}
|
|
484
|
+
function digest(value) {
|
|
485
|
+
return (0, node_crypto_1.createHash)('sha256').update(stableStringify(value)).digest('hex');
|
|
486
|
+
}
|
|
487
|
+
function stableStringify(value) {
|
|
488
|
+
if (Array.isArray(value)) {
|
|
489
|
+
return `[${value.map((item) => stableStringify(item)).join(',')}]`;
|
|
490
|
+
}
|
|
491
|
+
if (value && typeof value === 'object') {
|
|
492
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(',')}}`;
|
|
493
|
+
}
|
|
494
|
+
return JSON.stringify(value);
|
|
495
|
+
}
|
|
496
|
+
function sortedStrings(value) {
|
|
497
|
+
if (!Array.isArray(value))
|
|
498
|
+
return [];
|
|
499
|
+
return value.filter((item) => typeof item === 'string' && item.trim().length > 0)
|
|
500
|
+
.map((item) => item.trim())
|
|
501
|
+
.sort();
|
|
502
|
+
}
|
|
503
|
+
function stringArraysEqual(left, right) {
|
|
504
|
+
if (left.length !== right.length)
|
|
505
|
+
return false;
|
|
506
|
+
return left.every((item, index) => item === right[index]);
|
|
507
|
+
}
|