@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,264 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const node_test_1 = __importDefault(require("node:test"));
|
|
7
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
8
|
+
const automation_trigger_engine_js_1 = require("../../services/local-ai-core/src/automation/automation-trigger-engine.js");
|
|
9
|
+
const automation_condition_engine_js_1 = require("../../services/local-ai-core/src/automation/automation-condition-engine.js");
|
|
10
|
+
const cron_js_1 = require("../../services/local-ai-core/src/scheduler/cron.js");
|
|
11
|
+
const at = (value) => new Date(value);
|
|
12
|
+
(0, node_test_1.default)('activation engines calculate deterministic next and due instants', () => {
|
|
13
|
+
const cases = [
|
|
14
|
+
{
|
|
15
|
+
name: 'cron',
|
|
16
|
+
activation: { kind: 'cron', expression: '*/15 * * * *', timezone: 'UTC' },
|
|
17
|
+
after: at('2026-07-05T10:07:31.000Z'),
|
|
18
|
+
expectedNext: '2026-07-05T10:15:00.000Z',
|
|
19
|
+
dueAt: '2026-07-05T10:15:00.000Z',
|
|
20
|
+
notDueAt: '2026-07-05T10:14:59.999Z',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'once before runAt',
|
|
24
|
+
activation: { kind: 'once', runAt: '2026-07-05T11:00:00.000Z' },
|
|
25
|
+
after: at('2026-07-05T10:00:00.000Z'),
|
|
26
|
+
expectedNext: '2026-07-05T11:00:00.000Z',
|
|
27
|
+
dueAt: '2026-07-05T11:00:00.000Z',
|
|
28
|
+
notDueAt: '2026-07-05T10:59:59.999Z',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'once after runAt does not repeat',
|
|
32
|
+
activation: { kind: 'once', runAt: '2026-07-05T11:00:00.000Z' },
|
|
33
|
+
after: at('2026-07-05T11:00:00.000Z'),
|
|
34
|
+
expectedNext: null,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'interval remains anchored to the Unix epoch',
|
|
38
|
+
activation: { kind: 'interval', intervalMs: 60_000 },
|
|
39
|
+
after: at('2026-07-05T10:00:30.000Z'),
|
|
40
|
+
expectedNext: '2026-07-05T10:01:00.000Z',
|
|
41
|
+
dueAt: '2026-07-05T10:01:00.000Z',
|
|
42
|
+
notDueAt: '2026-07-05T10:00:59.999Z',
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
for (const item of cases) {
|
|
46
|
+
const next = (0, automation_trigger_engine_js_1.nextActivationAt)(item.activation, item.after);
|
|
47
|
+
strict_1.default.equal(next?.toISOString() ?? null, item.expectedNext, item.name);
|
|
48
|
+
if (item.dueAt && item.notDueAt && item.expectedNext) {
|
|
49
|
+
strict_1.default.equal((0, automation_trigger_engine_js_1.isActivationDue)(item.activation, at(item.notDueAt), item.expectedNext), false, item.name);
|
|
50
|
+
strict_1.default.equal((0, automation_trigger_engine_js_1.isActivationDue)(item.activation, at(item.dueAt), item.expectedNext), true, item.name);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
(0, node_test_1.default)('provider-event activations are external and never timer-due', () => {
|
|
55
|
+
const activation = {
|
|
56
|
+
kind: 'provider-event',
|
|
57
|
+
sourceType: 'stock.quote',
|
|
58
|
+
sourceConfig: {},
|
|
59
|
+
};
|
|
60
|
+
const now = at('2026-07-05T10:00:00.000Z');
|
|
61
|
+
strict_1.default.equal((0, automation_trigger_engine_js_1.nextActivationAt)(activation, now), null);
|
|
62
|
+
strict_1.default.equal((0, automation_trigger_engine_js_1.isActivationDue)(activation, now, '2026-07-05T09:00:00.000Z'), false);
|
|
63
|
+
strict_1.default.equal((0, automation_trigger_engine_js_1.missedActivationAt)(activation, undefined, now), null);
|
|
64
|
+
});
|
|
65
|
+
(0, node_test_1.default)('cron timezone limitations are explicit', () => {
|
|
66
|
+
strict_1.default.throws(() => (0, automation_trigger_engine_js_1.nextActivationAt)({ kind: 'cron', expression: '0 9 * * *', timezone: 'Asia/Shanghai' }, at('2026-07-05T00:00:00.000Z')), /only UTC cron timezones/i);
|
|
67
|
+
});
|
|
68
|
+
(0, node_test_1.default)('cron compilation is strict and all activation entry points reject invalid expressions', () => {
|
|
69
|
+
const invalidExpressions = [
|
|
70
|
+
'* * * *',
|
|
71
|
+
'* * * * * *',
|
|
72
|
+
'*/0 * * * *',
|
|
73
|
+
'*/x * * * *',
|
|
74
|
+
'*/1.5 * * * *',
|
|
75
|
+
'*/-1 * * * *',
|
|
76
|
+
'1,,2 * * * *',
|
|
77
|
+
'10-5 * * * *',
|
|
78
|
+
'60 * * * *',
|
|
79
|
+
'* 24 * * *',
|
|
80
|
+
'* * 0 * *',
|
|
81
|
+
'* * * 13 *',
|
|
82
|
+
'* * * * 7',
|
|
83
|
+
'bogus * * * *',
|
|
84
|
+
];
|
|
85
|
+
for (const expression of invalidExpressions) {
|
|
86
|
+
strict_1.default.throws(() => (0, cron_js_1.compileCronExpression)(expression), /invalid cron/i, expression);
|
|
87
|
+
}
|
|
88
|
+
const activation = { kind: 'cron', expression: '*/0 * * * *', timezone: 'UTC' };
|
|
89
|
+
const now = at('2026-07-05T10:00:00.000Z');
|
|
90
|
+
for (const invoke of [
|
|
91
|
+
() => (0, automation_trigger_engine_js_1.nextActivationAt)(activation, now),
|
|
92
|
+
() => (0, automation_trigger_engine_js_1.isActivationDue)(activation, now, now.toISOString()),
|
|
93
|
+
() => (0, automation_trigger_engine_js_1.missedActivationAt)(activation, undefined, now),
|
|
94
|
+
]) {
|
|
95
|
+
strict_1.default.throws(invoke, /invalid cron/i);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
(0, node_test_1.default)('compiled cron search is bounded for annual and impossible schedules', () => {
|
|
99
|
+
const annual = (0, cron_js_1.findNextCronMatchUtc)((0, cron_js_1.compileCronExpression)('0 0 1 1 *'), at('2026-01-01T00:00:00.000Z'));
|
|
100
|
+
strict_1.default.equal(annual.date?.toISOString(), '2027-01-01T00:00:00.000Z');
|
|
101
|
+
strict_1.default.ok(annual.inspectedDays <= 367, `annual search inspected ${annual.inspectedDays} days`);
|
|
102
|
+
const impossible = (0, cron_js_1.findNextCronMatchUtc)((0, cron_js_1.compileCronExpression)('0 0 31 2 *'), at('2026-01-01T00:00:00.000Z'));
|
|
103
|
+
strict_1.default.equal(impossible.date, null);
|
|
104
|
+
strict_1.default.ok(impossible.inspectedDays <= 146_098, `impossible search inspected ${impossible.inspectedDays} days`);
|
|
105
|
+
});
|
|
106
|
+
(0, node_test_1.default)('cron matching keeps legacy day-of-month AND day-of-week semantics', () => {
|
|
107
|
+
const expression = '0 9 5 * 1';
|
|
108
|
+
strict_1.default.equal((0, cron_js_1.cronMatchesDate)(expression, new Date(2026, 0, 5, 9)), true);
|
|
109
|
+
strict_1.default.equal((0, cron_js_1.cronMatchesDate)(expression, new Date(2026, 0, 12, 9)), false);
|
|
110
|
+
strict_1.default.equal((0, cron_js_1.cronMatchesDate)(expression, new Date(2026, 3, 5, 9)), false);
|
|
111
|
+
});
|
|
112
|
+
(0, node_test_1.default)('legacy cron matching fails closed for malformed expressions', () => {
|
|
113
|
+
const now = at('2026-07-05T10:00:00.000Z');
|
|
114
|
+
strict_1.default.equal((0, cron_js_1.cronMatchesDate)('invalid', now), false);
|
|
115
|
+
strict_1.default.equal((0, cron_js_1.cronMatchesDate)('*/0 * * * *', now), false);
|
|
116
|
+
});
|
|
117
|
+
(0, node_test_1.default)('cron next activation is exclusive at exact boundaries', () => {
|
|
118
|
+
const activation = { kind: 'cron', expression: '0 * * * *', timezone: 'UTC' };
|
|
119
|
+
strict_1.default.equal((0, automation_trigger_engine_js_1.nextActivationAt)(activation, at('2026-07-05T10:00:00.000Z'))?.toISOString(), '2026-07-05T11:00:00.000Z');
|
|
120
|
+
});
|
|
121
|
+
(0, node_test_1.default)('cron search preserves UTC years from 0 through 99', () => {
|
|
122
|
+
const activation = { kind: 'cron', expression: '0 0 * * *', timezone: 'UTC' };
|
|
123
|
+
strict_1.default.equal((0, automation_trigger_engine_js_1.nextActivationAt)(activation, at('0050-01-01T00:00:00.000Z'))?.toISOString(), '0050-01-02T00:00:00.000Z');
|
|
124
|
+
});
|
|
125
|
+
(0, node_test_1.default)('previous cron search stops at its exclusive lower bound', () => {
|
|
126
|
+
const result = (0, cron_js_1.findPreviousCronMatchUtc)((0, cron_js_1.compileCronExpression)('0 0 31 2 *'), at('2026-07-05T10:00:00.000Z'), at('2026-07-04T10:00:00.000Z'));
|
|
127
|
+
strict_1.default.equal(result.date, null);
|
|
128
|
+
strict_1.default.ok(result.inspectedDays <= 2, `bounded search inspected ${result.inspectedDays} days`);
|
|
129
|
+
});
|
|
130
|
+
(0, node_test_1.default)('cron search does not return dates outside the JavaScript Date range', () => {
|
|
131
|
+
const activation = { kind: 'cron', expression: '* * * * *', timezone: 'UTC' };
|
|
132
|
+
strict_1.default.throws(() => (0, automation_trigger_engine_js_1.nextActivationAt)(activation, new Date(8_640_000_000_000_000)), /no UTC cron activation exists/i);
|
|
133
|
+
});
|
|
134
|
+
(0, node_test_1.default)('restart recovery returns only the most recent missed activation', () => {
|
|
135
|
+
const now = at('2026-07-05T10:05:30.000Z');
|
|
136
|
+
const cases = [
|
|
137
|
+
['cron', { kind: 'cron', expression: '* * * * *', timezone: 'UTC' }, '2026-07-05T10:00:30.000Z', '2026-07-05T10:05:00.000Z'],
|
|
138
|
+
['once', { kind: 'once', runAt: '2026-07-05T10:02:00.000Z' }, '2026-07-05T10:00:30.000Z', '2026-07-05T10:02:00.000Z'],
|
|
139
|
+
['interval', { kind: 'interval', intervalMs: 60_000 }, '2026-07-05T10:00:30.000Z', '2026-07-05T10:05:00.000Z'],
|
|
140
|
+
['nothing before first check', { kind: 'once', runAt: '2026-07-05T10:02:00.000Z' }, undefined, null],
|
|
141
|
+
];
|
|
142
|
+
for (const [name, activation, lastCheckedAt, expected] of cases) {
|
|
143
|
+
strict_1.default.equal((0, automation_trigger_engine_js_1.missedActivationAt)(activation, lastCheckedAt, now)?.toISOString() ?? null, expected, name);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
(0, node_test_1.default)('activation helpers validate dates and activations before schedule state shortcuts', () => {
|
|
147
|
+
const invalidOnce = { kind: 'once', runAt: '2026-02-31T10:00:00.000Z' };
|
|
148
|
+
const localOnce = { kind: 'once', runAt: '2026-07-05 10:00:00' };
|
|
149
|
+
const invalidIntervals = [
|
|
150
|
+
{ kind: 'interval', intervalMs: 0 },
|
|
151
|
+
{ kind: 'interval', intervalMs: 1.5 },
|
|
152
|
+
];
|
|
153
|
+
const now = at('2026-07-05T10:00:00.000Z');
|
|
154
|
+
for (const activation of [invalidOnce, localOnce, ...invalidIntervals]) {
|
|
155
|
+
strict_1.default.throws(() => (0, automation_trigger_engine_js_1.nextActivationAt)(activation, now), /automation/i);
|
|
156
|
+
strict_1.default.throws(() => (0, automation_trigger_engine_js_1.isActivationDue)(activation, now, now.toISOString()), /automation/i);
|
|
157
|
+
strict_1.default.throws(() => (0, automation_trigger_engine_js_1.missedActivationAt)(activation, undefined, now), /automation/i);
|
|
158
|
+
}
|
|
159
|
+
strict_1.default.throws(() => (0, automation_trigger_engine_js_1.isActivationDue)({ kind: 'interval', intervalMs: 60_000 }, now, 'not-a-timestamp'), /valid ISO timestamp/i);
|
|
160
|
+
const maximumInterval = { kind: 'interval', intervalMs: Number.MAX_SAFE_INTEGER };
|
|
161
|
+
strict_1.default.equal((0, automation_trigger_engine_js_1.nextActivationAt)(maximumInterval, new Date(-1))?.toISOString(), '1970-01-01T00:00:00.000Z');
|
|
162
|
+
strict_1.default.equal((0, automation_trigger_engine_js_1.isActivationDue)(maximumInterval, now, now.toISOString()), true);
|
|
163
|
+
strict_1.default.equal((0, automation_trigger_engine_js_1.missedActivationAt)(maximumInterval, undefined, now), null);
|
|
164
|
+
strict_1.default.throws(() => (0, automation_trigger_engine_js_1.nextActivationAt)(maximumInterval, now), /valid date/i);
|
|
165
|
+
const valid = { kind: 'interval', intervalMs: 60_000 };
|
|
166
|
+
for (const invoke of [
|
|
167
|
+
() => (0, automation_trigger_engine_js_1.nextActivationAt)(valid, new Date(Number.NaN)),
|
|
168
|
+
() => (0, automation_trigger_engine_js_1.isActivationDue)(valid, new Date(Number.NaN)),
|
|
169
|
+
() => (0, automation_trigger_engine_js_1.missedActivationAt)(valid, now.toISOString(), new Date(Number.NaN)),
|
|
170
|
+
]) {
|
|
171
|
+
strict_1.default.throws(invoke, /valid date/i);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
(0, node_test_1.default)('activation and condition engines do not mutate caller inputs', () => {
|
|
175
|
+
const activation = Object.freeze({ kind: 'interval', intervalMs: 60_000 });
|
|
176
|
+
const payload = Object.freeze({ price: 101 });
|
|
177
|
+
const condition = Object.freeze({ kind: 'expression', expression: 'price > 100' });
|
|
178
|
+
const activationSnapshot = JSON.stringify(activation);
|
|
179
|
+
const payloadSnapshot = JSON.stringify(payload);
|
|
180
|
+
(0, automation_trigger_engine_js_1.nextActivationAt)(activation, at('2026-07-05T10:00:30.000Z'));
|
|
181
|
+
(0, automation_condition_engine_js_1.decideCondition)({ condition, payload, previous: false });
|
|
182
|
+
strict_1.default.equal(JSON.stringify(activation), activationSnapshot);
|
|
183
|
+
strict_1.default.equal(JSON.stringify(payload), payloadSnapshot);
|
|
184
|
+
});
|
|
185
|
+
(0, node_test_1.default)('trigger state machine detects rising edges and re-arms after false', () => {
|
|
186
|
+
const cases = [
|
|
187
|
+
{ name: 'first successful true', previous: undefined, matched: true, outcome: 'matched', decision: 'triggered', next: true },
|
|
188
|
+
{ name: 'false to true', previous: false, matched: true, outcome: 'matched', decision: 'triggered', next: true },
|
|
189
|
+
{ name: 'true to true', previous: true, matched: true, outcome: 'matched', decision: 'not_rising', next: true },
|
|
190
|
+
{ name: 'false re-arms', previous: true, matched: false, outcome: 'not_matched', decision: 'not_rising', next: false },
|
|
191
|
+
{ name: 'cooldown suppresses rising edge', previous: false, matched: true, coolingDown: true, outcome: 'matched', decision: 'skipped_cooldown', next: true },
|
|
192
|
+
{ name: 'running action suppresses rising edge', previous: false, matched: true, actionRunning: true, outcome: 'matched', decision: 'skipped_action_running', next: true },
|
|
193
|
+
];
|
|
194
|
+
for (const item of cases) {
|
|
195
|
+
strict_1.default.deepEqual((0, automation_condition_engine_js_1.decideTrigger)(item), {
|
|
196
|
+
conditionOutcome: item.outcome,
|
|
197
|
+
triggerDecision: item.decision,
|
|
198
|
+
nextMatch: item.next,
|
|
199
|
+
}, item.name);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
(0, node_test_1.default)('evaluation admission explicitly skips concurrent evaluation', () => {
|
|
203
|
+
strict_1.default.deepEqual((0, automation_condition_engine_js_1.admitConditionEvaluation)(false), { admitted: true });
|
|
204
|
+
strict_1.default.deepEqual((0, automation_condition_engine_js_1.admitConditionEvaluation)(true), {
|
|
205
|
+
admitted: false,
|
|
206
|
+
conditionOutcome: 'skipped',
|
|
207
|
+
triggerDecision: 'skipped_concurrent',
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
(0, node_test_1.default)('condition errors preserve the previous successful match', () => {
|
|
211
|
+
const result = (0, automation_condition_engine_js_1.decideCondition)({
|
|
212
|
+
condition: { kind: 'expression', expression: 'not valid' },
|
|
213
|
+
payload: {},
|
|
214
|
+
previous: true,
|
|
215
|
+
});
|
|
216
|
+
strict_1.default.equal(result.kind, 'decision');
|
|
217
|
+
if (result.kind !== 'decision')
|
|
218
|
+
strict_1.default.fail('expected a trigger decision');
|
|
219
|
+
strict_1.default.equal(result.conditionOutcome, 'error');
|
|
220
|
+
strict_1.default.equal(result.triggerDecision, 'not_evaluated');
|
|
221
|
+
strict_1.default.equal(result.nextMatch, true);
|
|
222
|
+
strict_1.default.match(result.error ?? '', /unsupported/i);
|
|
223
|
+
});
|
|
224
|
+
(0, node_test_1.default)('overlap skips without executing the condition evaluator', () => {
|
|
225
|
+
let calls = 0;
|
|
226
|
+
const result = (0, automation_condition_engine_js_1.decideCondition)({
|
|
227
|
+
condition: { kind: 'always' },
|
|
228
|
+
previous: false,
|
|
229
|
+
evaluationRunning: true,
|
|
230
|
+
}, () => {
|
|
231
|
+
calls += 1;
|
|
232
|
+
return { kind: 'evaluated', matched: true };
|
|
233
|
+
});
|
|
234
|
+
strict_1.default.equal(calls, 0);
|
|
235
|
+
strict_1.default.deepEqual(result, {
|
|
236
|
+
kind: 'decision',
|
|
237
|
+
conditionOutcome: 'skipped',
|
|
238
|
+
triggerDecision: 'skipped_concurrent',
|
|
239
|
+
nextMatch: false,
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
(0, node_test_1.default)('condition evaluation supports always and delegates restricted expressions', () => {
|
|
243
|
+
strict_1.default.deepEqual((0, automation_condition_engine_js_1.evaluateCondition)({ kind: 'always' }), { kind: 'evaluated', matched: true });
|
|
244
|
+
strict_1.default.deepEqual((0, automation_condition_engine_js_1.evaluateCondition)({ kind: 'expression', expression: 'price >= 100 && symbol == "AAPL"' }, { price: 101, symbol: 'AAPL' }), { kind: 'evaluated', matched: true });
|
|
245
|
+
strict_1.default.deepEqual((0, automation_condition_engine_js_1.evaluateCondition)({ kind: 'expression', expression: 'quote.price >= 100' }, { quote: { price: 101 } }), { kind: 'evaluated', matched: true });
|
|
246
|
+
strict_1.default.throws(() => (0, automation_condition_engine_js_1.evaluateCondition)({ kind: 'expression', expression: 'globalThis.process' }, {}), /unsupported/i);
|
|
247
|
+
});
|
|
248
|
+
(0, node_test_1.default)('approved-script conditions return a typed Task 9 delegation request', () => {
|
|
249
|
+
const condition = {
|
|
250
|
+
kind: 'approved-script',
|
|
251
|
+
scriptId: 'script:1',
|
|
252
|
+
approvedVersionId: 'version:2',
|
|
253
|
+
edge: 'rising',
|
|
254
|
+
};
|
|
255
|
+
strict_1.default.deepEqual((0, automation_condition_engine_js_1.evaluateCondition)(condition, { price: 101 }), {
|
|
256
|
+
kind: 'script-delegation',
|
|
257
|
+
request: {
|
|
258
|
+
scriptId: 'script:1',
|
|
259
|
+
approvedVersionId: 'version:2',
|
|
260
|
+
edge: 'rising',
|
|
261
|
+
payload: { price: 101 },
|
|
262
|
+
},
|
|
263
|
+
});
|
|
264
|
+
});
|