@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
|
@@ -49,7 +49,7 @@ const CHANNEL_INSTRUCTION = [
|
|
|
49
49
|
'Only use the channel CLI when the user explicitly asks to send a file through the channel.',
|
|
50
50
|
'[/Channel Tools]',
|
|
51
51
|
].join('\n');
|
|
52
|
-
function composeAgentMessage(content, knowledgeBases = []) {
|
|
52
|
+
function composeAgentMessage(content, knowledgeBases = [], catalog = new managed_skill_catalog_js_1.ManagedSkillCatalog()) {
|
|
53
53
|
if (content.trim().startsWith('/')) {
|
|
54
54
|
return content;
|
|
55
55
|
}
|
|
@@ -60,12 +60,15 @@ function composeAgentMessage(content, knowledgeBases = []) {
|
|
|
60
60
|
'[/Selected Knowledge Bases]',
|
|
61
61
|
].join('\n')
|
|
62
62
|
: '';
|
|
63
|
+
const conditionSkill = isConditionAutomationRequest(content) ? catalog.get('condition-trigger')?.content || '' : '';
|
|
64
|
+
const conditionHelper = conditionSkill ? catalog.getHelperPath('condition-trigger', 'scripts/register-condition-trigger.sh') || '' : '';
|
|
63
65
|
return [
|
|
64
66
|
SCHEDULER_INSTRUCTION,
|
|
65
67
|
'',
|
|
66
68
|
MONITOR_INSTRUCTION,
|
|
67
69
|
'',
|
|
68
70
|
CHANNEL_INSTRUCTION,
|
|
71
|
+
...(conditionSkill ? ['', '[Condition Trigger Skill]', conditionSkill, '[/Condition Trigger Skill]', '[Condition Trigger Helper]', conditionHelper, '[/Condition Trigger Helper]'] : []),
|
|
69
72
|
...(knowledgeBlock ? ['', knowledgeBlock] : []),
|
|
70
73
|
'',
|
|
71
74
|
'[User Message]',
|
|
@@ -73,3 +76,15 @@ function composeAgentMessage(content, knowledgeBases = []) {
|
|
|
73
76
|
'[/User Message]',
|
|
74
77
|
].join('\n');
|
|
75
78
|
}
|
|
79
|
+
function isConditionAutomationRequest(content) {
|
|
80
|
+
const normalized = content.toLowerCase();
|
|
81
|
+
const englishCondition = /\b(?:condition(?:al)?|script[-\s]?(?:based|backed)?)\b/.test(normalized);
|
|
82
|
+
const englishAutomation = /\b(?:automation|monitor|task|schedule)\b/.test(normalized);
|
|
83
|
+
const englishRequest = /\b(?:create|add|set(?:\s+up)?|configure|build|author)\b/.test(normalized);
|
|
84
|
+
const englishNegation = /\b(?:do not|don't|dont|never|without).{0,24}\b(?:create|add|set(?:\s+up)?|configure|build|author)\b/.test(normalized);
|
|
85
|
+
const chineseCondition = /条件自动化|条件触发|脚本条件/.test(content);
|
|
86
|
+
const chineseRequest = /创建|新建|设置|添加|建立|配置|制作|实现/.test(content);
|
|
87
|
+
const chineseNegation = /(?:不要|不需要|无需|别|仅|只是|不想).{0,8}(?:创建|新建|设置|添加|建立|配置|制作|实现)/.test(content);
|
|
88
|
+
return (englishCondition && englishAutomation && englishRequest && !englishNegation) || (chineseCondition && chineseRequest && !chineseNegation);
|
|
89
|
+
}
|
|
90
|
+
const managed_skill_catalog_js_1 = require("../runtime/managed-skill-catalog.js");
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.deriveAutomationDisplayStatus = deriveAutomationDisplayStatus;
|
|
4
|
+
exports.filterAutomationRows = filterAutomationRows;
|
|
5
|
+
exports.approvalActionForVersion = approvalActionForVersion;
|
|
6
|
+
exports.approvalResolutionForVersion = approvalResolutionForVersion;
|
|
7
|
+
exports.formatEvaluation = formatEvaluation;
|
|
8
|
+
exports.formatRun = formatRun;
|
|
9
|
+
exports.redactSecretName = redactSecretName;
|
|
10
|
+
exports.originLabel = originLabel;
|
|
11
|
+
function deriveAutomationDisplayStatus(automation) {
|
|
12
|
+
if (automation.health === 'blocked')
|
|
13
|
+
return 'blocked';
|
|
14
|
+
return automation.enabled ? 'active' : 'paused';
|
|
15
|
+
}
|
|
16
|
+
function filterAutomationRows(automations, filter = {}) {
|
|
17
|
+
const query = filter.query?.trim().toLocaleLowerCase() || '';
|
|
18
|
+
return automations.filter((automation) => {
|
|
19
|
+
const origin = automation.originKind || 'native';
|
|
20
|
+
return (!filter.origin || filter.origin === 'all' || origin === filter.origin) &&
|
|
21
|
+
(!query || `${automation.title} ${automation.workspaceId}`.toLocaleLowerCase().includes(query));
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/** The API exposes pending IDs only for transitions the server currently authorizes. */
|
|
25
|
+
function approvalActionForVersion(version) {
|
|
26
|
+
switch (version.status) {
|
|
27
|
+
case 'pending_test_approval': return version.pendingTestApprovalId ? 'authorize-test' : null;
|
|
28
|
+
case 'test_authorized': return 'run-test';
|
|
29
|
+
case 'tested': return 'request-enable';
|
|
30
|
+
case 'pending_approval': return version.pendingApprovalId ? 'approve-enable' : null;
|
|
31
|
+
case 'approved': return 'revoke';
|
|
32
|
+
default: return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* An Automation version endpoint applies a decision; it never makes one. Keep
|
|
37
|
+
* the pending ID selected by the server and resolve that ApprovalRequest first.
|
|
38
|
+
*/
|
|
39
|
+
function approvalResolutionForVersion(version, decision) {
|
|
40
|
+
const approvalId = version.status === 'pending_test_approval'
|
|
41
|
+
? version.pendingTestApprovalId
|
|
42
|
+
: version.status === 'pending_approval'
|
|
43
|
+
? version.pendingApprovalId
|
|
44
|
+
: undefined;
|
|
45
|
+
return approvalId ? { approvalId, decision } : null;
|
|
46
|
+
}
|
|
47
|
+
function formatEvaluation(evaluation) {
|
|
48
|
+
if (!evaluation)
|
|
49
|
+
return '—';
|
|
50
|
+
if (evaluation.status === 'running')
|
|
51
|
+
return 'Running';
|
|
52
|
+
if (evaluation.errorCategory === 'sandbox_unavailable')
|
|
53
|
+
return 'Blocked: sandbox unavailable';
|
|
54
|
+
if (evaluation.conditionOutcome === 'error')
|
|
55
|
+
return `Error${evaluation.errorCategory ? `: ${evaluation.errorCategory}` : ''}`;
|
|
56
|
+
return `${evaluation.conditionOutcome || 'finished'} · ${evaluation.triggerDecision || 'not evaluated'}`;
|
|
57
|
+
}
|
|
58
|
+
function formatRun(run) {
|
|
59
|
+
if (!run)
|
|
60
|
+
return '—';
|
|
61
|
+
return `${run.status}${run.deliveryStatus ? ` · ${run.deliveryStatus}` : ''}`;
|
|
62
|
+
}
|
|
63
|
+
/** Never expose full secret names in list/detail views. */
|
|
64
|
+
function redactSecretName(reference) {
|
|
65
|
+
const name = reference.replace(/^env:\/\//, '').trim();
|
|
66
|
+
if (name.length < 7)
|
|
67
|
+
return '***';
|
|
68
|
+
return `${name.slice(0, 4)}…${name.slice(-4)}`;
|
|
69
|
+
}
|
|
70
|
+
function originLabel(origin) {
|
|
71
|
+
if (origin === 'scheduled-job')
|
|
72
|
+
return 'Cron';
|
|
73
|
+
if (origin === 'automation-monitor')
|
|
74
|
+
return 'Monitor';
|
|
75
|
+
return 'Native';
|
|
76
|
+
}
|
|
@@ -6,6 +6,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const node_test_1 = __importDefault(require("node:test"));
|
|
7
7
|
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
8
8
|
const agent_message_policy_js_1 = require("../../services/local-ai-core/src/thread/agent-message-policy.js");
|
|
9
|
+
const managed_skill_catalog_js_1 = require("../../services/local-ai-core/src/runtime/managed-skill-catalog.js");
|
|
10
|
+
const node_path_1 = require("node:path");
|
|
11
|
+
const node_fs_1 = require("node:fs");
|
|
9
12
|
(0, node_test_1.default)('agent message policy is owned by Local Core and includes selected knowledge context', () => {
|
|
10
13
|
const message = (0, agent_message_policy_js_1.composeAgentMessage)('Summarize the design', [
|
|
11
14
|
{ id: 'kb-1', name: 'Architecture notes' },
|
|
@@ -19,3 +22,32 @@ const agent_message_policy_js_1 = require("../../services/local-ai-core/src/thre
|
|
|
19
22
|
(0, node_test_1.default)('agent message policy leaves slash commands untouched', () => {
|
|
20
23
|
strict_1.default.equal((0, agent_message_policy_js_1.composeAgentMessage)('/agent use pi'), '/agent use pi');
|
|
21
24
|
});
|
|
25
|
+
(0, node_test_1.default)('agent message policy injects the exact managed condition trigger skill only for condition automations', () => {
|
|
26
|
+
const catalog = new managed_skill_catalog_js_1.ManagedSkillCatalog({ rootDir: (0, node_path_1.join)(process.cwd(), 'electron', 'managed-skills') });
|
|
27
|
+
const skill = catalog.get('condition-trigger');
|
|
28
|
+
(0, strict_1.default)(skill);
|
|
29
|
+
const matching = (0, agent_message_policy_js_1.composeAgentMessage)('Create an automation when a script condition matches.', [], catalog);
|
|
30
|
+
strict_1.default.match(matching, new RegExp(escapeRegex(skill.content)));
|
|
31
|
+
const nonMatching = (0, agent_message_policy_js_1.composeAgentMessage)('Schedule a reminder every day at noon.', [], catalog);
|
|
32
|
+
strict_1.default.doesNotMatch(nonMatching, /\[Condition Trigger Skill\]/);
|
|
33
|
+
});
|
|
34
|
+
(0, node_test_1.default)('condition trigger skill is injected for explicit Chinese condition automation requests, not generic trigger words', () => {
|
|
35
|
+
const catalog = new managed_skill_catalog_js_1.ManagedSkillCatalog({ rootDir: (0, node_path_1.join)(process.cwd(), 'electron', 'managed-skills') });
|
|
36
|
+
for (const message of ['创建一个条件自动化,在脚本条件满足时触发', '请设置条件触发任务', '为这个脚本条件创建自动化']) {
|
|
37
|
+
strict_1.default.match((0, agent_message_policy_js_1.composeAgentMessage)(message, [], catalog), /\[Condition Trigger Skill\]/);
|
|
38
|
+
}
|
|
39
|
+
for (const message of ['触发一次普通任务', 'Please trigger a response now.', '解释条件自动化的概念,但不要创建它', 'Do not create a condition automation.', '我不想创建条件自动化']) {
|
|
40
|
+
strict_1.default.doesNotMatch((0, agent_message_policy_js_1.composeAgentMessage)(message, [], catalog), /\[Condition Trigger Skill\]/);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
(0, node_test_1.default)('condition helper path is catalog-owned and remains valid outside the repository cwd', () => {
|
|
44
|
+
const catalog = new managed_skill_catalog_js_1.ManagedSkillCatalog({ rootDir: (0, node_path_1.join)(process.cwd(), 'electron', 'managed-skills') });
|
|
45
|
+
const helperPath = catalog.getHelperPath('condition-trigger', 'scripts/register-condition-trigger.sh');
|
|
46
|
+
(0, strict_1.default)(helperPath);
|
|
47
|
+
const message = (0, agent_message_policy_js_1.composeAgentMessage)('Create a script condition automation.', [], catalog);
|
|
48
|
+
strict_1.default.match(message, new RegExp(`\\[Condition Trigger Helper\\]\\n${escapeRegex(helperPath)}`));
|
|
49
|
+
strict_1.default.equal((0, node_fs_1.existsSync)(helperPath), true);
|
|
50
|
+
});
|
|
51
|
+
function escapeRegex(value) {
|
|
52
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
53
|
+
}
|
|
@@ -161,3 +161,33 @@ function readSourceFiles(scope) {
|
|
|
161
161
|
strict_1.default.match(highlighted, new RegExp(`highlight\\.js/lib/languages/${language}`), `${language} must remain in the supported subset`);
|
|
162
162
|
}
|
|
163
163
|
});
|
|
164
|
+
(0, node_test_1.default)('conditional automation architecture documents its security and ownership invariants', () => {
|
|
165
|
+
const content = (0, node_fs_1.readFileSync)((0, node_path_1.join)(rootDir, 'docs', 'architecture', 'conditional-automation.md'), 'utf8');
|
|
166
|
+
for (const invariant of [
|
|
167
|
+
'Activation → Condition → Action → Delivery',
|
|
168
|
+
'immutable package',
|
|
169
|
+
'two-stage approval',
|
|
170
|
+
'previousState',
|
|
171
|
+
'public egress',
|
|
172
|
+
'private-address deny',
|
|
173
|
+
'Windows fail-closed',
|
|
174
|
+
'30 days',
|
|
175
|
+
'1000',
|
|
176
|
+
'legacy Scheduler and Monitor facades',
|
|
177
|
+
'DNS rebinding',
|
|
178
|
+
'detached process',
|
|
179
|
+
]) {
|
|
180
|
+
strict_1.default.match(content, new RegExp(invariant, 'i'), `conditional automation docs must cover ${invariant}`);
|
|
181
|
+
}
|
|
182
|
+
strict_1.default.match(content, /macOS[^\n]+Linux|Linux[^\n]+macOS/i);
|
|
183
|
+
});
|
|
184
|
+
(0, node_test_1.default)('deployment docs require a dedicated AppArmor profile without weakening the global userns policy', () => {
|
|
185
|
+
const content = (0, node_fs_1.readFileSync)((0, node_path_1.join)(rootDir, 'docs', 'operations', 'release-workflow.md'), 'utf8');
|
|
186
|
+
strict_1.default.match(content, /Ubuntu 24\.04\+/);
|
|
187
|
+
strict_1.default.match(content, /dedicated AppArmor profile/i);
|
|
188
|
+
strict_1.default.match(content, /kernel\.apparmor_restrict_unprivileged_userns/);
|
|
189
|
+
strict_1.default.match(content, /must not|do not|禁止/i);
|
|
190
|
+
strict_1.default.match(content, /behaviorally executes|behavioral/i);
|
|
191
|
+
strict_1.default.match(content, /sysctl is only failure-classification context/i);
|
|
192
|
+
strict_1.default.doesNotMatch(content, /sysctl\s+-w\s+kernel\.apparmor_restrict_unprivileged_userns=0/);
|
|
193
|
+
});
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
7
|
+
const node_test_1 = __importDefault(require("node:test"));
|
|
8
|
+
const automations_js_1 = require("../../packages/contracts/src/automations.js");
|
|
9
|
+
const evaluationCreateInput = {
|
|
10
|
+
activationKind: 'cron',
|
|
11
|
+
startedAt: '2026-07-05T00:00:00.000Z',
|
|
12
|
+
};
|
|
13
|
+
const runningEvaluation = {
|
|
14
|
+
id: 'evaluation:1',
|
|
15
|
+
automationId: 'automation:1',
|
|
16
|
+
status: 'running',
|
|
17
|
+
activationKind: 'cron',
|
|
18
|
+
startedAt: '2026-07-05T00:00:00.000Z',
|
|
19
|
+
};
|
|
20
|
+
const evaluationFinishInput = {
|
|
21
|
+
conditionOutcome: 'error',
|
|
22
|
+
triggerDecision: 'not_evaluated',
|
|
23
|
+
finishedAt: '2026-07-05T00:00:01.000Z',
|
|
24
|
+
};
|
|
25
|
+
const concurrentSkipFinishInput = {
|
|
26
|
+
conditionOutcome: 'skipped',
|
|
27
|
+
triggerDecision: 'skipped_concurrent',
|
|
28
|
+
finishedAt: '2026-07-05T00:00:01.000Z',
|
|
29
|
+
};
|
|
30
|
+
const closedRunMode = 'same-thread';
|
|
31
|
+
void evaluationCreateInput;
|
|
32
|
+
void runningEvaluation;
|
|
33
|
+
void evaluationFinishInput;
|
|
34
|
+
void concurrentSkipFinishInput;
|
|
35
|
+
void closedRunMode;
|
|
36
|
+
function definition(overrides = {}) {
|
|
37
|
+
return {
|
|
38
|
+
id: 'automation:1',
|
|
39
|
+
workspaceId: 'ws',
|
|
40
|
+
title: 'Check API',
|
|
41
|
+
enabled: true,
|
|
42
|
+
health: 'healthy',
|
|
43
|
+
activation: { kind: 'cron', expression: '*/5 * * * *', timezone: 'Asia/Shanghai' },
|
|
44
|
+
condition: {
|
|
45
|
+
kind: 'approved-script',
|
|
46
|
+
scriptId: 'script:1',
|
|
47
|
+
approvedVersionId: 'version:1',
|
|
48
|
+
edge: 'rising',
|
|
49
|
+
},
|
|
50
|
+
action: {
|
|
51
|
+
kind: 'agent-prompt',
|
|
52
|
+
promptTemplate: 'analyze {{summary}}',
|
|
53
|
+
executionMode: 'side-thread',
|
|
54
|
+
},
|
|
55
|
+
delivery: {
|
|
56
|
+
platform: 'local',
|
|
57
|
+
route: { type: 'local.thread', channelId: 'ws' },
|
|
58
|
+
},
|
|
59
|
+
policies: { concurrency: 'skip-if-running', cooldownMs: 0 },
|
|
60
|
+
consecutiveEvaluationFailures: 0,
|
|
61
|
+
createdAt: '2026-07-05T00:00:00.000Z',
|
|
62
|
+
updatedAt: '2026-07-05T00:00:00.000Z',
|
|
63
|
+
...overrides,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
(0, node_test_1.default)('normalizes an automation with an approved-script condition', () => {
|
|
67
|
+
const result = (0, automations_js_1.normalizeAutomationDefinition)(definition());
|
|
68
|
+
strict_1.default.equal(result.condition.kind, 'approved-script');
|
|
69
|
+
});
|
|
70
|
+
(0, node_test_1.default)('keeps truthful condition outcomes separate from trigger decisions', () => {
|
|
71
|
+
strict_1.default.deepEqual([evaluationFinishInput, concurrentSkipFinishInput].map(({ conditionOutcome, triggerDecision }) => ({
|
|
72
|
+
conditionOutcome,
|
|
73
|
+
triggerDecision,
|
|
74
|
+
})), [
|
|
75
|
+
{ conditionOutcome: 'error', triggerDecision: 'not_evaluated' },
|
|
76
|
+
{ conditionOutcome: 'skipped', triggerDecision: 'skipped_concurrent' },
|
|
77
|
+
]);
|
|
78
|
+
});
|
|
79
|
+
(0, node_test_1.default)('normalizes every activation discriminator', () => {
|
|
80
|
+
const cases = [
|
|
81
|
+
{ input: { kind: 'cron', expression: '0 * * * *', timezone: 'UTC' }, kind: 'cron' },
|
|
82
|
+
{ input: { kind: 'once', runAt: '2026-07-05T00:00:00.000Z' }, kind: 'once' },
|
|
83
|
+
{ input: { kind: 'interval', intervalMs: 1 }, kind: 'interval' },
|
|
84
|
+
{ input: { kind: 'provider-event', sourceType: 'stock.quote', sourceConfig: {} }, kind: 'provider-event' },
|
|
85
|
+
];
|
|
86
|
+
for (const entry of cases) {
|
|
87
|
+
strict_1.default.equal((0, automations_js_1.normalizeAutomationActivation)(entry.input).kind, entry.kind);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
(0, node_test_1.default)('normalizes every condition discriminator', () => {
|
|
91
|
+
const cases = [
|
|
92
|
+
{ input: { kind: 'always' }, kind: 'always' },
|
|
93
|
+
{ input: { kind: 'expression', expression: 'price > 10' }, kind: 'expression' },
|
|
94
|
+
{
|
|
95
|
+
input: { kind: 'approved-script', scriptId: 'script:1', approvedVersionId: 'version:1', edge: 'rising' },
|
|
96
|
+
kind: 'approved-script',
|
|
97
|
+
},
|
|
98
|
+
];
|
|
99
|
+
for (const entry of cases) {
|
|
100
|
+
strict_1.default.equal((0, automations_js_1.normalizeAutomationCondition)(entry.input).kind, entry.kind);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
(0, node_test_1.default)('rejects invalid activation and condition variants', () => {
|
|
104
|
+
const invalidActivations = [
|
|
105
|
+
{ kind: 'unknown' },
|
|
106
|
+
{ kind: 'cron', expression: '', timezone: 'UTC' },
|
|
107
|
+
{ kind: 'once', runAt: 'tomorrow' },
|
|
108
|
+
{ kind: 'interval', intervalMs: 0 },
|
|
109
|
+
{ kind: 'interval', intervalMs: -1 },
|
|
110
|
+
{ kind: 'interval', intervalMs: 1.5 },
|
|
111
|
+
{ kind: 'provider-event', sourceType: 'stock.quote', sourceConfig: [] },
|
|
112
|
+
];
|
|
113
|
+
const invalidConditions = [
|
|
114
|
+
{ kind: 'unknown' },
|
|
115
|
+
{ kind: 'expression', expression: '' },
|
|
116
|
+
{ kind: 'approved-script', scriptId: 'script:1', approvedVersionId: 'version:1', edge: 'falling' },
|
|
117
|
+
];
|
|
118
|
+
for (const input of invalidActivations)
|
|
119
|
+
strict_1.default.throws(() => (0, automations_js_1.normalizeAutomationActivation)(input));
|
|
120
|
+
for (const input of invalidConditions)
|
|
121
|
+
strict_1.default.throws(() => (0, automations_js_1.normalizeAutomationCondition)(input));
|
|
122
|
+
});
|
|
123
|
+
(0, node_test_1.default)('rejects malformed routes and invalid numeric bounds', () => {
|
|
124
|
+
const cases = [
|
|
125
|
+
definition({ delivery: { platform: 'local', route: { type: 'local.thread' } } }),
|
|
126
|
+
definition({ delivery: { platform: 'local', route: { type: '', channelId: 'ws' } } }),
|
|
127
|
+
definition({ policies: { concurrency: 'skip-if-running', cooldownMs: -1 } }),
|
|
128
|
+
definition({ consecutiveEvaluationFailures: 1.5 }),
|
|
129
|
+
];
|
|
130
|
+
for (const input of cases)
|
|
131
|
+
strict_1.default.throws(() => (0, automations_js_1.normalizeAutomationDefinition)(input));
|
|
132
|
+
});
|
|
133
|
+
(0, node_test_1.default)('rejects falsy and non-string execution modes while preserving string aliases', () => {
|
|
134
|
+
for (const executionMode of [undefined, null, '', ' ', false, 0]) {
|
|
135
|
+
strict_1.default.throws(() => (0, automations_js_1.normalizeAutomationDefinition)(definition({
|
|
136
|
+
action: { kind: 'agent-prompt', promptTemplate: 'analyze', executionMode },
|
|
137
|
+
})), /executionMode/);
|
|
138
|
+
}
|
|
139
|
+
const normalized = (0, automations_js_1.normalizeAutomationDefinition)(definition({
|
|
140
|
+
action: { kind: 'agent-prompt', promptTemplate: 'analyze', executionMode: 'SIDE THREAD' },
|
|
141
|
+
}));
|
|
142
|
+
strict_1.default.equal(normalized.action.executionMode, 'side-thread');
|
|
143
|
+
});
|
|
144
|
+
(0, node_test_1.default)('validates canonical timestamps', () => {
|
|
145
|
+
const invalidDefinitions = [
|
|
146
|
+
definition({ createdAt: 'today' }),
|
|
147
|
+
definition({ updatedAt: '2026-07-05' }),
|
|
148
|
+
definition({ lastEvaluationAt: 'not-a-date' }),
|
|
149
|
+
definition({ lastTriggeredAt: '2026-13-05T00:00:00.000Z' }),
|
|
150
|
+
];
|
|
151
|
+
for (const input of invalidDefinitions)
|
|
152
|
+
strict_1.default.throws(() => (0, automations_js_1.normalizeAutomationDefinition)(input), /timestamp/);
|
|
153
|
+
strict_1.default.throws(() => (0, automations_js_1.normalizeAutomationActivation)({ kind: 'once', runAt: '2026-07-05' }), /timestamp/);
|
|
154
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
7
|
+
const node_test_1 = __importDefault(require("node:test"));
|
|
8
|
+
const automation_page_model_js_1 = require("../../src/pages/Automation/automation-page-model.js");
|
|
9
|
+
const base = {
|
|
10
|
+
id: 'automation:1', workspaceId: 'workspace:1', title: 'Check API', enabled: true,
|
|
11
|
+
health: 'healthy', activation: { kind: 'cron', expression: '*/5 * * * *', timezone: 'UTC' },
|
|
12
|
+
condition: { kind: 'always' }, action: { kind: 'agent-prompt', promptTemplate: 'check', executionMode: 'side-thread' },
|
|
13
|
+
delivery: { platform: 'local', route: { type: 'local.thread', channelId: 'workspace:1' } },
|
|
14
|
+
policies: { concurrency: 'skip-if-running', cooldownMs: 0 }, consecutiveEvaluationFailures: 0,
|
|
15
|
+
createdAt: '2026-07-12T00:00:00.000Z', updatedAt: '2026-07-12T00:00:00.000Z',
|
|
16
|
+
};
|
|
17
|
+
(0, node_test_1.default)('derives active, paused, and blocked display states without client-side transitions', () => {
|
|
18
|
+
strict_1.default.equal((0, automation_page_model_js_1.deriveAutomationDisplayStatus)(base), 'active');
|
|
19
|
+
strict_1.default.equal((0, automation_page_model_js_1.deriveAutomationDisplayStatus)({ ...base, enabled: false }), 'paused');
|
|
20
|
+
strict_1.default.equal((0, automation_page_model_js_1.deriveAutomationDisplayStatus)({ ...base, health: 'blocked' }), 'blocked');
|
|
21
|
+
});
|
|
22
|
+
(0, node_test_1.default)('renders only server-authorized script approval actions for every stage', () => {
|
|
23
|
+
const statuses = ['draft', 'pending_test_approval', 'test_authorized', 'testing', 'tested', 'pending_approval', 'approved', 'rejected', 'revoked'];
|
|
24
|
+
strict_1.default.deepEqual(statuses.map((status) => (0, automation_page_model_js_1.approvalActionForVersion)({ status, pendingTestApprovalId: 'test', pendingApprovalId: 'enable' })), [
|
|
25
|
+
null, 'authorize-test', 'run-test', null, 'request-enable', 'approve-enable', 'revoke', null, null,
|
|
26
|
+
]);
|
|
27
|
+
});
|
|
28
|
+
(0, node_test_1.default)('requires resolving the server-issued pending approval before applying a script decision', () => {
|
|
29
|
+
strict_1.default.deepEqual((0, automation_page_model_js_1.approvalResolutionForVersion)({ status: 'pending_test_approval', pendingTestApprovalId: 'test:1' }, 'approved'), { approvalId: 'test:1', decision: 'approved' });
|
|
30
|
+
strict_1.default.deepEqual((0, automation_page_model_js_1.approvalResolutionForVersion)({ status: 'pending_approval', pendingApprovalId: 'enable:1' }, 'rejected'), { approvalId: 'enable:1', decision: 'rejected' });
|
|
31
|
+
strict_1.default.equal((0, automation_page_model_js_1.approvalResolutionForVersion)({ status: 'tested' }, 'approved'), null);
|
|
32
|
+
});
|
|
33
|
+
(0, node_test_1.default)('groups and filters legacy origins and presents evaluations/runs safely', () => {
|
|
34
|
+
const rows = (0, automation_page_model_js_1.filterAutomationRows)([
|
|
35
|
+
{ ...base, id: 'native', originKind: 'native' },
|
|
36
|
+
{ ...base, id: 'cron', originKind: 'scheduled-job' },
|
|
37
|
+
{ ...base, id: 'monitor', originKind: 'automation-monitor' },
|
|
38
|
+
], { origin: 'scheduled-job', query: 'api' });
|
|
39
|
+
strict_1.default.deepEqual(rows.map((row) => row.id), ['cron']);
|
|
40
|
+
strict_1.default.equal((0, automation_page_model_js_1.originLabel)('automation-monitor'), 'Monitor');
|
|
41
|
+
strict_1.default.match((0, automation_page_model_js_1.formatEvaluation)({ status: 'finished', conditionOutcome: 'error', triggerDecision: 'not_evaluated', errorCategory: 'sandbox_unavailable' }), /sandbox unavailable/i);
|
|
42
|
+
strict_1.default.match((0, automation_page_model_js_1.formatRun)({ status: 'failed' }), /failed/i);
|
|
43
|
+
});
|
|
44
|
+
(0, node_test_1.default)('redacts secret names while retaining a useful identifier', () => {
|
|
45
|
+
strict_1.default.equal((0, automation_page_model_js_1.redactSecretName)('env://PROVIDER_API_TOKEN'), 'PROV…OKEN');
|
|
46
|
+
strict_1.default.equal((0, automation_page_model_js_1.redactSecretName)('env://KEY'), '***');
|
|
47
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
7
|
+
const node_test_1 = __importDefault(require("node:test"));
|
|
8
|
+
const automation_action_executor_js_1 = require("../../services/local-ai-core/src/automation/automation-action-executor.js");
|
|
9
|
+
function definition(platform = 'lark') {
|
|
10
|
+
return {
|
|
11
|
+
id: 'automation-1',
|
|
12
|
+
workspaceId: 'workspace-1',
|
|
13
|
+
title: 'Safe prompt',
|
|
14
|
+
enabled: true,
|
|
15
|
+
health: 'healthy',
|
|
16
|
+
activation: { kind: 'once', runAt: '2026-07-05T08:00:00.000Z' },
|
|
17
|
+
condition: { kind: 'always' },
|
|
18
|
+
action: { kind: 'agent-prompt', promptTemplate: 'run', executionMode: 'side-thread' },
|
|
19
|
+
delivery: { platform, route: { type: 'channel.chat', channelId: 'chat-1' } },
|
|
20
|
+
policies: { concurrency: 'skip-if-running', cooldownMs: 0 },
|
|
21
|
+
consecutiveEvaluationFailures: 0,
|
|
22
|
+
createdAt: '2026-07-05T07:00:00.000Z',
|
|
23
|
+
updatedAt: '2026-07-05T07:00:00.000Z',
|
|
24
|
+
originKind: 'native',
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const evaluation = {
|
|
28
|
+
id: 'evaluation-1',
|
|
29
|
+
automationId: 'automation-1',
|
|
30
|
+
status: 'finished',
|
|
31
|
+
activationKind: 'once',
|
|
32
|
+
startedAt: '2026-07-05T08:00:00.000Z',
|
|
33
|
+
finishedAt: '2026-07-05T08:00:00.000Z',
|
|
34
|
+
conditionOutcome: 'matched',
|
|
35
|
+
triggerDecision: 'triggered',
|
|
36
|
+
};
|
|
37
|
+
(0, node_test_1.default)('prompt rendering uses only own data properties and safely serializes objects', () => {
|
|
38
|
+
const circular = {};
|
|
39
|
+
circular.self = circular;
|
|
40
|
+
const inherited = Object.create({ constructor: 'unsafe', toString: 'unsafe', prototype: 'unsafe' });
|
|
41
|
+
Object.defineProperty(inherited, 'constructor', { enumerable: true, value: 'also unsafe' });
|
|
42
|
+
const object = Object.create({ toJSON: () => { throw new Error('must not run'); } });
|
|
43
|
+
object.value = 42;
|
|
44
|
+
Object.defineProperty(object, 'nestedGetter', { enumerable: true, get: () => { throw new Error('must not run'); } });
|
|
45
|
+
inherited.object = object;
|
|
46
|
+
inherited.circular = circular;
|
|
47
|
+
Object.defineProperty(inherited, 'getter', { enumerable: true, get: () => { throw new Error('must not run'); } });
|
|
48
|
+
strict_1.default.equal((0, automation_action_executor_js_1.renderAutomationPrompt)('{{constructor}}|{{toString}}|{{prototype}}|{{getter}}|{{object}}|{{circular}}', inherited), '||||{"value":42}|[Unserializable]');
|
|
49
|
+
});
|
|
50
|
+
(0, node_test_1.default)('action executor closes an opened bridge when ACP send fails', async () => {
|
|
51
|
+
let closed = false;
|
|
52
|
+
const executor = new automation_action_executor_js_1.AutomationActionExecutor({
|
|
53
|
+
store: {
|
|
54
|
+
getPlatformThreadBinding: () => undefined,
|
|
55
|
+
getRun: () => ({ status: 'completed' }),
|
|
56
|
+
},
|
|
57
|
+
getWorkspaceRouter: () => ({
|
|
58
|
+
listThreads: async () => [],
|
|
59
|
+
createThread: async () => ({ id: 'thread-1' }),
|
|
60
|
+
getThreadSessionKey: () => 'session-1',
|
|
61
|
+
sendThreadMessage: async () => { throw new Error('send failed'); },
|
|
62
|
+
}),
|
|
63
|
+
getChannelRuntime: () => ({
|
|
64
|
+
platform: 'lark',
|
|
65
|
+
registerScheduledThreadBridge: async () => () => { closed = true; },
|
|
66
|
+
onBridgeEvent: async () => undefined,
|
|
67
|
+
}),
|
|
68
|
+
});
|
|
69
|
+
await strict_1.default.rejects(() => executor.execute({
|
|
70
|
+
automation: definition(),
|
|
71
|
+
evaluation,
|
|
72
|
+
promptVariables: {},
|
|
73
|
+
}), /send failed/);
|
|
74
|
+
strict_1.default.equal(closed, true);
|
|
75
|
+
});
|