@kafca/agentdock 0.1.42 → 0.1.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/README.md +10 -0
  2. package/dist/renderer/assets/{Badge-D-WU4kUV.js → Badge-Qk9qHovH.js} +1 -1
  3. package/dist/renderer/assets/{Chat-BGkC5tAe.js → Chat-BK-fm0Xr.js} +1 -1
  4. package/dist/renderer/assets/{ChatMarkdown-CfbC1jRd.js → ChatMarkdown-BKdW8OON.js} +1 -1
  5. package/dist/renderer/assets/{Config-qQb4bX5D.js → Config-B93CQGvY.js} +2 -2
  6. package/dist/renderer/assets/CronList-Z3TvSRKP.js +1 -0
  7. package/dist/renderer/assets/{Dashboard-xn6cUy_n.js → Dashboard-Ch3QkWwf.js} +2 -2
  8. package/dist/renderer/assets/{EmptyState-BdKBBUpr.js → EmptyState-kD9yAwtw.js} +1 -1
  9. package/dist/renderer/assets/{KnowledgeDetail-Duk8IQP3.js → KnowledgeDetail-BAMCLrjz.js} +2 -2
  10. package/dist/renderer/assets/{KnowledgeHome-BfjX_4Sv.js → KnowledgeHome-ChvjOfrc.js} +2 -2
  11. package/dist/renderer/assets/{Logs-Bfq61QMn.js → Logs-Cv0nth3j.js} +1 -1
  12. package/dist/renderer/assets/{Modal-B3vlBmBr.js → Modal-DwY4lecS.js} +1 -1
  13. package/dist/renderer/assets/MonitorList-JEvMoWUm.js +1 -0
  14. package/dist/renderer/assets/{Page-CPsLOP3z.js → Page-CnB9ePsb.js} +1 -1
  15. package/dist/renderer/assets/{ProjectDetail-D-g46wyF.js → ProjectDetail-DJa3sMmU.js} +2 -2
  16. package/dist/renderer/assets/{ProjectList-BrQEKDQ8.js → ProjectList-D6CXumR_.js} +1 -1
  17. package/dist/renderer/assets/{Select-C4CFMQmF.js → Select-zfoksLGB.js} +1 -1
  18. package/dist/renderer/assets/SessionChat-DUTh4WoM.js +1 -0
  19. package/dist/renderer/assets/{SessionList-DPWIUbOg.js → SessionList-D9o-lUst.js} +1 -1
  20. package/dist/renderer/assets/{ThreadChat-BjtU-j9q.js → ThreadChat-BYXlT-Kc.js} +7 -7
  21. package/dist/renderer/assets/{Workspace-BcNmHuJx.js → Workspace-BrhSwRQ-.js} +1 -1
  22. package/dist/renderer/assets/{arrow-left-DPzSmidS.js → arrow-left-CNEAuElg.js} +1 -1
  23. package/dist/renderer/assets/{book-open-B0xhoRqZ.js → book-open-BNJMhNI-.js} +1 -1
  24. package/dist/renderer/assets/{check-BTJTyd0C.js → check-BnclClRU.js} +1 -1
  25. package/dist/renderer/assets/{chevron-down-B6jJXlhi.js → chevron-down-Bh53e0O-.js} +1 -1
  26. package/dist/renderer/assets/{circle-qRhJR9qR.js → circle-ClZTWCWO.js} +1 -1
  27. package/dist/renderer/assets/desktop-C5vBT0yM.js +1 -0
  28. package/dist/renderer/assets/desktop-C6q6wBae.js +5 -0
  29. package/dist/renderer/assets/en-D2SkCrhG.js +1 -0
  30. package/dist/renderer/assets/{funnel-CbYKGTn8.js → funnel-_5hsz0xI.js} +1 -1
  31. package/dist/renderer/assets/{heart-DjwWJfkz.js → heart-u4Q1aOqj.js} +1 -1
  32. package/dist/renderer/assets/{index-BWmHJRhe.js → index-B36eowj-.js} +39 -34
  33. package/dist/renderer/assets/{index-CyZs_2NJ.js → index-BNFx4uYv.js} +3 -3
  34. package/dist/renderer/assets/{pencil-1dfAivmj.js → pencil-DLh9QNmT.js} +1 -1
  35. package/dist/renderer/assets/{play-CB3w1G6S.js → play-BDTk9Scm.js} +1 -1
  36. package/dist/renderer/assets/{plug-Bvep5o3r.js → plug-BG0JHW6B.js} +1 -1
  37. package/dist/renderer/assets/{plus-DJVg9m09.js → plus-BSc8POsz.js} +1 -1
  38. package/dist/renderer/assets/projects-DwfZwxDW.js +1 -0
  39. package/dist/renderer/assets/{save-xjJn3qf2.js → save-D5AO_G6a.js} +1 -1
  40. package/dist/renderer/assets/{search-BK3UrQxM.js → search-CniPk7Ey.js} +1 -1
  41. package/dist/renderer/assets/{server-Dyd2eXJu.js → server-GsXyQAZm.js} +1 -1
  42. package/dist/renderer/assets/{session-utils-eXC0020s.js → session-utils-BDxzgTBC.js} +1 -1
  43. package/dist/renderer/assets/{sessions-WLYpkyUt.js → sessions-CLcKA1Tt.js} +1 -1
  44. package/dist/renderer/assets/{shield-check-C0N6aWzF.js → shield-check-4i0E3hmf.js} +1 -1
  45. package/dist/renderer/assets/{trash-2-BGMTNJA6.js → trash-2-DhtOCsO5.js} +1 -1
  46. package/dist/renderer/assets/{user-BcGMLyhu.js → user-BtC_pVXV.js} +1 -1
  47. package/dist/renderer/assets/{wifi-off-FhcQx0HK.js → wifi-off-BHPzPY_B.js} +1 -1
  48. package/dist/renderer/assets/zh-DIcdHycM.js +1 -0
  49. package/dist/renderer/index.html +1 -1
  50. package/dist-electron/packages/contracts/src/local-core.js +28 -0
  51. package/dist-electron/packages/core-sdk/src/index.js +32 -0
  52. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-backend.js +51 -0
  53. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-store.js +4 -1364
  54. package/dist-electron/services/local-ai-core/src/acp/store/agent-task-store.js +166 -0
  55. package/dist-electron/services/local-ai-core/src/acp/store/automation-monitor-store.js +231 -0
  56. package/dist-electron/services/local-ai-core/src/acp/store/local-core-acp-store.js +263 -0
  57. package/dist-electron/services/local-ai-core/src/acp/store/platform-store.js +186 -0
  58. package/dist-electron/services/local-ai-core/src/acp/store/scheduler-store.js +201 -0
  59. package/dist-electron/services/local-ai-core/src/acp/store/schema.js +275 -0
  60. package/dist-electron/services/local-ai-core/src/acp/store/security-store.js +266 -0
  61. package/dist-electron/services/local-ai-core/src/acp/store/thread-store.js +232 -0
  62. package/dist-electron/services/local-ai-core/src/acp/store/utils.js +47 -0
  63. package/dist-electron/services/local-ai-core/src/acp/store/workspace-registry-store.js +109 -0
  64. package/dist-electron/services/local-ai-core/src/automation/automation-conversation-executor.js +136 -0
  65. package/dist-electron/services/local-ai-core/src/automation/automation-monitor-repository.js +56 -0
  66. package/dist-electron/services/local-ai-core/src/automation/automation-monitor-service.js +359 -0
  67. package/dist-electron/services/local-ai-core/src/automation/condition-evaluator.js +77 -0
  68. package/dist-electron/services/local-ai-core/src/automation/mock-stock-provider.js +40 -0
  69. package/dist-electron/services/local-ai-core/src/automation/monitor-id.js +10 -0
  70. package/dist-electron/services/local-ai-core/src/channel/lark/cards.js +55 -0
  71. package/dist-electron/services/local-ai-core/src/channel/lark/config.js +2 -0
  72. package/dist-electron/services/local-ai-core/src/channel/lark/inbound.js +114 -0
  73. package/dist-electron/services/local-ai-core/src/channel/lark/local-core-lark-gateway.js +153 -94
  74. package/dist-electron/services/local-ai-core/src/channel/shared/session-command-runtime.js +53 -0
  75. package/dist-electron/services/local-ai-core/src/channel/shared/thread-routing.js +34 -0
  76. package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js +63 -51
  77. package/dist-electron/services/local-ai-core/src/cli/lac.js +196 -0
  78. package/dist-electron/services/local-ai-core/src/cli/monitor-cli-parsers.js +37 -0
  79. package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +43 -0
  80. package/dist-electron/services/local-ai-core/src/kernel/capability-registry.js +12 -0
  81. package/dist-electron/services/local-ai-core/src/plugins/builtin/monitor-stock-plugin.js +30 -0
  82. package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +41 -0
  83. package/dist-electron/services/local-ai-core/src/runtime/server-routes.js +31 -0
  84. package/dist-electron/services/local-ai-core/src/runtime/server.js +36 -0
  85. package/dist-electron/services/local-ai-core/src/scheduler/scheduled-bridge-session.js +25 -11
  86. package/dist-electron/services/local-ai-core/src/thread/session-command-service.js +275 -0
  87. package/dist-electron/services/local-ai-core/src/thread/slash-command-registry.js +27 -0
  88. package/dist-electron/services/local-ai-core/src/thread/thread-command-service.js +15 -15
  89. package/dist-electron/shared/desktop.js +21 -1
  90. package/dist-electron/tests/contracts/local-core-contracts.test.js +9 -0
  91. package/dist-electron/tests/electron/automation-monitor.test.js +38 -0
  92. package/dist-electron/tests/electron/lac-cli.test.js +146 -0
  93. package/dist-electron/tests/integration/channel-gateways.test.js +32 -0
  94. package/dist-electron/tests/integration/lark-gateway.test.js +1918 -0
  95. package/dist-electron/tests/integration/local-core-acp-progress.test.js +1772 -0
  96. package/dist-electron/tests/integration/local-core-refactor.test.js +216 -4566
  97. package/dist-electron/tests/integration/local-core-routes.test.js +204 -0
  98. package/dist-electron/tests/integration/weixin-gateway.test.js +927 -0
  99. package/package.json +1 -1
  100. package/dist/renderer/assets/CronList-CKhggzYk.js +0 -1
  101. package/dist/renderer/assets/SessionChat-06Jny5_G.js +0 -1
  102. package/dist/renderer/assets/desktop-DXmXjjUR.js +0 -1
  103. package/dist/renderer/assets/desktop-O8_6ZCVZ.js +0 -4
  104. package/dist/renderer/assets/en-CKhCF6St.js +0 -1
  105. package/dist/renderer/assets/projects-xnW0fTtq.js +0 -1
  106. package/dist/renderer/assets/zh-Dz5FLTBV.js +0 -1
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AutomationConversationExecutor = void 0;
4
+ exports.renderMonitorPrompt = renderMonitorPrompt;
5
+ const scheduled_bridge_session_js_1 = require("../scheduler/scheduled-bridge-session.js");
6
+ const scheduled_job_route_js_1 = require("../scheduler/scheduled-job-route.js");
7
+ const TERMINAL_RUN_STATES = new Set(['completed', 'failed', 'interrupted']);
8
+ const MONITOR_RUN_PERMISSION_MODE = 'bypassPermissions';
9
+ class AutomationConversationExecutor {
10
+ options;
11
+ constructor(options) {
12
+ this.options = options;
13
+ }
14
+ async execute(monitor, event, timeoutMs = 15 * 60 * 1000) {
15
+ const workspaceRouter = this.options.getWorkspaceRouter();
16
+ const threadId = await this.resolveThread(monitor);
17
+ const prompt = renderMonitorPrompt(monitor.promptTemplate, event, monitor);
18
+ const channelRuntime = monitor.platform === 'local' ? undefined : this.options.getChannelRuntime(monitor.platform);
19
+ const bridge = channelRuntime
20
+ ? await scheduled_bridge_session_js_1.ScheduledBridgeSession.open({
21
+ target: {
22
+ id: monitor.id,
23
+ workspaceId: monitor.workspaceId,
24
+ platform: monitor.platform,
25
+ route: monitor.route,
26
+ title: monitor.title,
27
+ promptTemplate: monitor.promptTemplate,
28
+ },
29
+ threadId,
30
+ workspaceRouter,
31
+ getChannelRuntime: () => channelRuntime,
32
+ noticeIcon: monitor.sourceType === 'stock.quote' ? '📈' : '🔔',
33
+ noticeTitle: monitor.title,
34
+ })
35
+ : undefined;
36
+ try {
37
+ const sendResult = await workspaceRouter.sendThreadMessage(threadId, prompt, {
38
+ permissionMode: MONITOR_RUN_PERMISSION_MODE,
39
+ runtimeEnv: this.buildRuntimeEnv(monitor),
40
+ });
41
+ await this.waitForRun(sendResult.runId, timeoutMs);
42
+ const thread = await workspaceRouter.getThread(threadId);
43
+ const replyText = [...thread.messages]
44
+ .reverse()
45
+ .find((message) => message.role === 'assistant' && message.kind === 'final')
46
+ ?.content;
47
+ return {
48
+ threadId,
49
+ runId: sendResult.runId,
50
+ replyText,
51
+ deliveryMode: bridge ? 'bridge-stream' : 'thread-only',
52
+ deliveryStatus: 'succeeded',
53
+ lastBridgeEventAt: bridge ? new Date().toISOString() : undefined,
54
+ };
55
+ }
56
+ finally {
57
+ await bridge?.close();
58
+ }
59
+ }
60
+ async resolveThread(monitor) {
61
+ const workspaceRouter = this.options.getWorkspaceRouter();
62
+ if (monitor.executionMode === 'same-thread') {
63
+ const binding = this.options.store.getPlatformThreadBinding(monitor.workspaceId, monitor.route.channelId, monitor.route.participantId || '', monitor.platform);
64
+ if (binding?.thread_id && await this.threadExists(binding.thread_id)) {
65
+ return binding.thread_id;
66
+ }
67
+ if (monitor.route.threadId && await this.threadExists(monitor.route.threadId)) {
68
+ return monitor.route.threadId;
69
+ }
70
+ }
71
+ const title = monitor.platform === 'local'
72
+ ? `[Monitor] ${monitor.title}`
73
+ : `[Monitor:${(0, scheduled_job_route_js_1.getChannelPlatformBase)(monitor.platform) || monitor.platform}] ${monitor.title}`;
74
+ const existing = (await workspaceRouter.listThreads(monitor.workspaceId))
75
+ .find((thread) => thread.title === title);
76
+ if (existing) {
77
+ return existing.id;
78
+ }
79
+ const created = await workspaceRouter.createThread(monitor.workspaceId, title);
80
+ return created.id;
81
+ }
82
+ async threadExists(threadId) {
83
+ try {
84
+ await this.options.getWorkspaceRouter().getThread(threadId);
85
+ return true;
86
+ }
87
+ catch {
88
+ return false;
89
+ }
90
+ }
91
+ buildRuntimeEnv(monitor) {
92
+ const basePlatform = (0, scheduled_job_route_js_1.getChannelPlatformBase)(monitor.platform);
93
+ const env = {};
94
+ if (basePlatform && basePlatform !== 'local') {
95
+ env.LOCAL_AI_PLATFORM = basePlatform;
96
+ env.LOCAL_AI_ROUTE_TYPE = (0, scheduled_job_route_js_1.routeTypeForPlatform)(monitor.platform);
97
+ }
98
+ const instanceId = monitor.route.instanceId || (0, scheduled_job_route_js_1.getChannelPlatformInstanceId)(monitor.platform);
99
+ if (instanceId) {
100
+ env.LOCAL_AI_PLATFORM_INSTANCE_ID = instanceId;
101
+ }
102
+ if (monitor.route.channelId) {
103
+ env.LOCAL_AI_CHAT_ID = monitor.route.channelId;
104
+ }
105
+ if (monitor.route.participantId) {
106
+ env.LOCAL_AI_PLATFORM_USER_ID = monitor.route.participantId;
107
+ }
108
+ return env;
109
+ }
110
+ async waitForRun(runId, timeoutMs) {
111
+ const startedAt = Date.now();
112
+ while (Date.now() - startedAt < timeoutMs) {
113
+ const run = this.options.store.getRun(runId);
114
+ if (run && TERMINAL_RUN_STATES.has(run.status)) {
115
+ if (run.status !== 'completed') {
116
+ throw new Error(`Monitor run finished with status ${run.status}`);
117
+ }
118
+ return run;
119
+ }
120
+ await new Promise((resolve) => setTimeout(resolve, 300));
121
+ }
122
+ throw new Error(`Timed out waiting for monitor run ${runId}`);
123
+ }
124
+ }
125
+ exports.AutomationConversationExecutor = AutomationConversationExecutor;
126
+ function renderMonitorPrompt(template, event, monitor) {
127
+ const values = {
128
+ title: monitor.title,
129
+ sourceType: event.sourceType,
130
+ subject: event.subject,
131
+ summary: event.summary || '',
132
+ timestamp: event.occurredAt,
133
+ ...event.payload,
134
+ };
135
+ return String(template || '').replace(/\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g, (_match, key) => String(values[key] ?? ''));
136
+ }
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AutomationMonitorRepository = void 0;
4
+ class AutomationMonitorRepository {
5
+ store;
6
+ constructor(store) {
7
+ this.store = store;
8
+ }
9
+ list(workspaceId) {
10
+ return this.store.listAutomationMonitors(workspaceId);
11
+ }
12
+ get(monitorId) {
13
+ return this.store.getAutomationMonitor(monitorId);
14
+ }
15
+ create(input) {
16
+ return this.store.createAutomationMonitor(input);
17
+ }
18
+ update(monitorId, input) {
19
+ return this.store.updateAutomationMonitor(monitorId, input);
20
+ }
21
+ updateState(monitorId, input) {
22
+ return this.store.updateAutomationMonitorState(monitorId, input);
23
+ }
24
+ delete(monitorId) {
25
+ return this.store.deleteAutomationMonitor(monitorId);
26
+ }
27
+ listRuns(monitorId) {
28
+ return this.store.listAutomationMonitorRuns(monitorId);
29
+ }
30
+ createRun(monitorId, status, input = {}) {
31
+ return this.store.createAutomationMonitorRun(monitorId, status, input);
32
+ }
33
+ updateRun(runId, input) {
34
+ return this.store.updateAutomationMonitorRun(runId, input);
35
+ }
36
+ getPlatformThreadBindingByThreadId(threadId) {
37
+ return this.store.getPlatformThreadBindingByThreadId(threadId);
38
+ }
39
+ toScheduledLike(monitor) {
40
+ return {
41
+ id: monitor.id,
42
+ workspaceId: monitor.workspaceId,
43
+ platform: monitor.platform,
44
+ route: monitor.route,
45
+ executionMode: monitor.executionMode,
46
+ triggerType: 'once',
47
+ promptTemplate: monitor.promptTemplate,
48
+ description: monitor.title,
49
+ enabled: monitor.enabled,
50
+ concurrencyPolicy: monitor.concurrencyPolicy,
51
+ createdAt: monitor.createdAt,
52
+ updatedAt: monitor.updatedAt,
53
+ };
54
+ }
55
+ }
56
+ exports.AutomationMonitorRepository = AutomationMonitorRepository;
@@ -0,0 +1,359 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AutomationMonitorService = void 0;
4
+ const scheduled_job_route_js_1 = require("../scheduler/scheduled-job-route.js");
5
+ const condition_evaluator_js_1 = require("./condition-evaluator.js");
6
+ const automation_conversation_executor_js_1 = require("./automation-conversation-executor.js");
7
+ const automation_monitor_repository_js_1 = require("./automation-monitor-repository.js");
8
+ class AutomationMonitorService {
9
+ options;
10
+ timer = null;
11
+ pollInFlight = false;
12
+ runningMonitors = new Set();
13
+ subscriptionHandles = new Map();
14
+ providers = new Map();
15
+ executor;
16
+ repository;
17
+ constructor(options) {
18
+ this.options = options;
19
+ for (const provider of options.providers) {
20
+ this.providers.set(provider.sourceType, provider);
21
+ }
22
+ this.repository = new automation_monitor_repository_js_1.AutomationMonitorRepository(options.store);
23
+ this.executor = new automation_conversation_executor_js_1.AutomationConversationExecutor({
24
+ store: options.store,
25
+ getWorkspaceRouter: options.getWorkspaceRouter,
26
+ getChannelRuntime: options.getChannelRuntime,
27
+ });
28
+ }
29
+ async start() {
30
+ await this.refreshSubscriptions();
31
+ await this.tick();
32
+ this.timer = setInterval(() => {
33
+ void this.tick();
34
+ }, 30_000);
35
+ }
36
+ async stop() {
37
+ if (this.timer) {
38
+ clearInterval(this.timer);
39
+ this.timer = null;
40
+ }
41
+ for (const handle of this.subscriptionHandles.values()) {
42
+ await handle.stop();
43
+ }
44
+ this.subscriptionHandles.clear();
45
+ }
46
+ listMonitors(workspaceId) {
47
+ return this.repository.list(workspaceId);
48
+ }
49
+ getMonitor(monitorId) {
50
+ const resolvedMonitorId = this.resolveMonitorId(monitorId);
51
+ return resolvedMonitorId ? this.repository.get(resolvedMonitorId) : undefined;
52
+ }
53
+ createMonitor(input) {
54
+ const resolved = this.resolveCreateInput(input);
55
+ const provider = this.providers.get(resolved.sourceType);
56
+ provider?.validateConfig?.(resolved.sourceConfig || {});
57
+ const monitor = this.repository.create(resolved);
58
+ this.emitMonitor(monitor);
59
+ void this.ensureSubscription(monitor);
60
+ return monitor;
61
+ }
62
+ updateMonitor(monitorId, input) {
63
+ const resolvedMonitorId = this.resolveRequiredMonitorId(monitorId);
64
+ const existing = this.repository.get(resolvedMonitorId);
65
+ if (existing && input.sourceConfig) {
66
+ this.providers.get(existing.sourceType)?.validateConfig?.(input.sourceConfig);
67
+ }
68
+ const monitor = this.repository.update(resolvedMonitorId, {
69
+ ...input,
70
+ ...(input.route ? { route: (0, scheduled_job_route_js_1.withoutThreadRoute)(input.route) } : {}),
71
+ });
72
+ this.emitMonitor(monitor);
73
+ void this.ensureSubscription(monitor);
74
+ return monitor;
75
+ }
76
+ deleteMonitor(monitorId) {
77
+ const resolvedMonitorId = this.resolveRequiredMonitorId(monitorId);
78
+ void this.stopSubscription(resolvedMonitorId);
79
+ return this.repository.delete(resolvedMonitorId);
80
+ }
81
+ listRuns(monitorId) {
82
+ return this.repository.listRuns(this.resolveRequiredMonitorId(monitorId));
83
+ }
84
+ async runMonitorNow(monitorId, event) {
85
+ const monitor = this.getRequiredMonitor(monitorId);
86
+ const snapshot = event || await this.pollMonitor(monitor, true);
87
+ if (!snapshot) {
88
+ const skipped = this.markSkipped(monitor, new Date().toISOString(), 'No event snapshot is available for this monitor.');
89
+ this.emitRun(skipped);
90
+ return skipped;
91
+ }
92
+ return this.executeMonitor(monitor, snapshot, true);
93
+ }
94
+ listMonitorsForThread(threadId) {
95
+ const binding = this.repository.getPlatformThreadBindingByThreadId(threadId);
96
+ return this.repository
97
+ .list()
98
+ .filter((monitor) => monitor.route.threadId === threadId ||
99
+ (binding ? (0, scheduled_job_route_js_1.scheduledJobMatchesPlatformBinding)(this.repository.toScheduledLike(monitor), binding) : false));
100
+ }
101
+ async tick() {
102
+ if (this.pollInFlight) {
103
+ return;
104
+ }
105
+ this.pollInFlight = true;
106
+ try {
107
+ for (const monitor of this.repository.list().filter((candidate) => candidate.enabled)) {
108
+ const provider = this.providers.get(monitor.sourceType);
109
+ if (provider?.startMonitor) {
110
+ continue;
111
+ }
112
+ const event = await this.pollMonitor(monitor, false);
113
+ if (!event) {
114
+ continue;
115
+ }
116
+ if (!(0, condition_evaluator_js_1.evaluateMonitorCondition)(monitor.condition, event)) {
117
+ this.repository.updateState(monitor.id, {
118
+ lastState: this.stateFromEvent(event, monitor.lastState),
119
+ });
120
+ continue;
121
+ }
122
+ if (this.isCoolingDown(monitor, event.occurredAt)) {
123
+ continue;
124
+ }
125
+ void this.executeMonitor(monitor, event, false);
126
+ }
127
+ }
128
+ finally {
129
+ this.pollInFlight = false;
130
+ }
131
+ }
132
+ async pollMonitor(monitor, manual) {
133
+ const provider = this.providers.get(monitor.sourceType);
134
+ if (!provider?.poll) {
135
+ if (manual) {
136
+ throw new Error(`No polling provider is available for monitor source "${monitor.sourceType}"`);
137
+ }
138
+ return null;
139
+ }
140
+ return provider.poll({
141
+ monitorId: monitor.id,
142
+ workspaceId: monitor.workspaceId,
143
+ sourceConfig: monitor.sourceConfig,
144
+ lastState: monitor.lastState,
145
+ });
146
+ }
147
+ async executeMonitor(monitor, event, manual) {
148
+ if (this.runningMonitors.has(monitor.id)) {
149
+ const skipped = this.markSkipped(monitor, event.occurredAt, 'Skipped because the previous monitor run is still active.', event);
150
+ this.emitRun(skipped);
151
+ return skipped;
152
+ }
153
+ this.runningMonitors.add(monitor.id);
154
+ const run = this.repository.createRun(monitor.id, 'queued', {
155
+ triggeredAt: event.occurredAt,
156
+ eventSnapshot: event,
157
+ deliveryStatus: 'pending',
158
+ });
159
+ this.emitRun(run);
160
+ try {
161
+ const running = this.repository.updateRun(run.id, {
162
+ status: 'running',
163
+ startedAt: new Date().toISOString(),
164
+ });
165
+ this.emitRun(running);
166
+ const result = await this.executor.execute(monitor, event);
167
+ const succeeded = this.repository.updateRun(run.id, {
168
+ status: 'succeeded',
169
+ finishedAt: new Date().toISOString(),
170
+ threadId: result.threadId,
171
+ runId: result.runId,
172
+ deliveryMode: result.deliveryMode,
173
+ deliveryStatus: result.deliveryStatus || 'succeeded',
174
+ deliveryError: result.deliveryError || '',
175
+ lastBridgeEventAt: result.lastBridgeEventAt,
176
+ error: '',
177
+ });
178
+ this.repository.updateState(monitor.id, {
179
+ lastState: this.stateFromEvent(event, monitor.lastState),
180
+ lastTriggeredAt: event.occurredAt,
181
+ lastStatus: 'succeeded',
182
+ lastError: '',
183
+ });
184
+ this.emitRun(succeeded);
185
+ const nextMonitor = this.repository.get(monitor.id);
186
+ if (nextMonitor)
187
+ this.emitMonitor(nextMonitor);
188
+ return succeeded;
189
+ }
190
+ catch (error) {
191
+ const message = error instanceof Error ? error.message : String(error);
192
+ const failed = this.repository.updateRun(run.id, {
193
+ status: 'failed',
194
+ finishedAt: new Date().toISOString(),
195
+ error: message,
196
+ deliveryStatus: 'failed',
197
+ deliveryError: message,
198
+ });
199
+ this.repository.updateState(monitor.id, {
200
+ lastState: this.stateFromEvent(event, monitor.lastState),
201
+ lastTriggeredAt: event.occurredAt,
202
+ lastStatus: 'failed',
203
+ lastError: message,
204
+ });
205
+ this.emitRun(failed);
206
+ const nextMonitor = this.repository.get(monitor.id);
207
+ if (nextMonitor)
208
+ this.emitMonitor(nextMonitor);
209
+ this.options.log?.(`automation monitor failed ${monitor.id}: ${message}`);
210
+ return failed;
211
+ }
212
+ finally {
213
+ this.runningMonitors.delete(monitor.id);
214
+ }
215
+ }
216
+ markSkipped(monitor, triggeredAt, error, eventSnapshot) {
217
+ return this.repository.createRun(monitor.id, 'skipped', {
218
+ triggeredAt,
219
+ error,
220
+ eventSnapshot,
221
+ deliveryStatus: 'skipped',
222
+ deliveryError: error,
223
+ });
224
+ }
225
+ stateFromEvent(event, previous) {
226
+ return {
227
+ ...(previous || {}),
228
+ lastEventId: event.id,
229
+ lastEventAt: event.occurredAt,
230
+ latestPrice: event.payload.latestPrice ?? previous?.latestPrice,
231
+ previousPrice: event.payload.previousPrice ?? previous?.previousPrice,
232
+ payload: event.payload,
233
+ };
234
+ }
235
+ isCoolingDown(monitor, occurredAt) {
236
+ if (!monitor.lastTriggeredAt || monitor.cooldownMs <= 0) {
237
+ return false;
238
+ }
239
+ return Date.parse(occurredAt) - Date.parse(monitor.lastTriggeredAt) < monitor.cooldownMs;
240
+ }
241
+ resolveCreateInput(input) {
242
+ if (input.platform && input.route) {
243
+ return {
244
+ ...input,
245
+ route: (0, scheduled_job_route_js_1.routeWithPlatformInstance)((0, scheduled_job_route_js_1.withoutThreadRoute)(input.route), input.platform),
246
+ };
247
+ }
248
+ const threadId = String(input.threadId || input.route?.threadId || '').trim();
249
+ if (threadId) {
250
+ const binding = this.repository.getPlatformThreadBindingByThreadId(threadId);
251
+ if (binding && binding.workspace_id === input.workspaceId) {
252
+ return {
253
+ ...input,
254
+ platform: binding.platform,
255
+ route: (0, scheduled_job_route_js_1.routeFromPlatformThreadBinding)(binding),
256
+ };
257
+ }
258
+ }
259
+ return {
260
+ ...input,
261
+ platform: 'local',
262
+ route: {
263
+ type: 'local.thread',
264
+ channelId: input.workspaceId,
265
+ },
266
+ };
267
+ }
268
+ getRequiredMonitor(monitorId) {
269
+ const monitor = this.getMonitor(monitorId);
270
+ if (!monitor) {
271
+ throw new Error(`Automation monitor not found: ${monitorId}`);
272
+ }
273
+ return monitor;
274
+ }
275
+ resolveMonitorId(monitorId) {
276
+ if (this.repository.get(monitorId)) {
277
+ return monitorId;
278
+ }
279
+ const matches = this.repository
280
+ .list()
281
+ .filter((monitor) => publicMonitorId(monitor.id) === monitorId);
282
+ if (matches.length === 0) {
283
+ return '';
284
+ }
285
+ if (matches.length > 1) {
286
+ throw new Error(`Automation monitor id is ambiguous: ${monitorId}`);
287
+ }
288
+ return matches[0].id;
289
+ }
290
+ resolveRequiredMonitorId(monitorId) {
291
+ const resolved = this.resolveMonitorId(monitorId);
292
+ if (!resolved) {
293
+ throw new Error(`Automation monitor not found: ${monitorId}`);
294
+ }
295
+ return resolved;
296
+ }
297
+ emitMonitor(monitor) {
298
+ this.options.eventBus.emit({ type: 'automation.monitor.updated', payload: monitor });
299
+ }
300
+ emitRun(run) {
301
+ this.options.eventBus.emit({ type: 'automation.monitor.run.updated', payload: run });
302
+ }
303
+ async refreshSubscriptions() {
304
+ const activeIds = new Set();
305
+ for (const monitor of this.repository.list().filter((candidate) => candidate.enabled)) {
306
+ const provider = this.providers.get(monitor.sourceType);
307
+ if (!provider?.startMonitor)
308
+ continue;
309
+ activeIds.add(monitor.id);
310
+ await this.ensureSubscription(monitor);
311
+ }
312
+ for (const monitorId of [...this.subscriptionHandles.keys()]) {
313
+ if (!activeIds.has(monitorId)) {
314
+ await this.stopSubscription(monitorId);
315
+ }
316
+ }
317
+ }
318
+ async ensureSubscription(monitor) {
319
+ const provider = this.providers.get(monitor.sourceType);
320
+ if (!monitor.enabled || !provider?.startMonitor) {
321
+ await this.stopSubscription(monitor.id);
322
+ return;
323
+ }
324
+ if (this.subscriptionHandles.has(monitor.id)) {
325
+ return;
326
+ }
327
+ const handle = await provider.startMonitor({
328
+ monitorId: monitor.id,
329
+ workspaceId: monitor.workspaceId,
330
+ sourceConfig: monitor.sourceConfig,
331
+ lastState: monitor.lastState,
332
+ emit: async (event) => {
333
+ const latest = this.repository.get(monitor.id);
334
+ if (!latest || !latest.enabled)
335
+ return;
336
+ if (!(0, condition_evaluator_js_1.evaluateMonitorCondition)(latest.condition, event)) {
337
+ this.repository.updateState(latest.id, { lastState: this.stateFromEvent(event, latest.lastState) });
338
+ return;
339
+ }
340
+ if (this.isCoolingDown(latest, event.occurredAt))
341
+ return;
342
+ await this.executeMonitor(latest, event, false);
343
+ },
344
+ });
345
+ this.subscriptionHandles.set(monitor.id, handle);
346
+ }
347
+ async stopSubscription(monitorId) {
348
+ const handle = this.subscriptionHandles.get(monitorId);
349
+ if (!handle)
350
+ return;
351
+ this.subscriptionHandles.delete(monitorId);
352
+ await handle.stop();
353
+ }
354
+ }
355
+ exports.AutomationMonitorService = AutomationMonitorService;
356
+ function publicMonitorId(monitorId) {
357
+ const normalized = monitorId.startsWith('monitor:') ? monitorId.slice('monitor:'.length) : monitorId;
358
+ return normalized.includes('-') ? normalized.split('-')[0] || normalized : normalized;
359
+ }
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.evaluateMonitorCondition = evaluateMonitorCondition;
4
+ exports.evaluateExpression = evaluateExpression;
5
+ exports.readMetric = readMetric;
6
+ function evaluateMonitorCondition(condition, event) {
7
+ if (condition.expression) {
8
+ return evaluateExpression(condition.expression, event);
9
+ }
10
+ const actual = readMetric(event, condition.metric);
11
+ const expected = condition.value;
12
+ switch (condition.operator) {
13
+ case '>':
14
+ return Number(actual) > Number(expected);
15
+ case '>=':
16
+ return Number(actual) >= Number(expected);
17
+ case '<':
18
+ return Number(actual) < Number(expected);
19
+ case '<=':
20
+ return Number(actual) <= Number(expected);
21
+ case '==':
22
+ return String(actual) === String(expected);
23
+ case '!=':
24
+ return String(actual) !== String(expected);
25
+ default:
26
+ return false;
27
+ }
28
+ }
29
+ function evaluateExpression(expression, event) {
30
+ const orParts = splitExpression(expression, '||');
31
+ return orParts.some((orPart) => splitExpression(orPart, '&&').every((andPart) => evaluateComparison(andPart.trim(), event)));
32
+ }
33
+ function evaluateComparison(expression, event) {
34
+ const match = expression.match(/^([a-zA-Z0-9_.-]+)\s*(>=|<=|==|!=|>|<)\s*(.+)$/);
35
+ if (!match) {
36
+ throw new Error(`Unsupported monitor condition expression: ${expression}`);
37
+ }
38
+ const rawValue = String(match[3] || '').trim().replace(/^["']|["']$/g, '');
39
+ const numeric = Number(rawValue);
40
+ return evaluateMonitorCondition({
41
+ metric: String(match[1] || '').trim(),
42
+ operator: match[2],
43
+ value: Number.isFinite(numeric) && rawValue !== '' ? numeric : rawValue,
44
+ }, event);
45
+ }
46
+ function splitExpression(expression, operator) {
47
+ return expression
48
+ .split(operator)
49
+ .map((part) => part.trim())
50
+ .filter(Boolean);
51
+ }
52
+ function readMetric(event, metric) {
53
+ const key = String(metric || '').trim();
54
+ if (!key) {
55
+ return undefined;
56
+ }
57
+ if (key === 'subject')
58
+ return event.subject;
59
+ if (key === 'sourceType')
60
+ return event.sourceType;
61
+ const payload = event.payload || {};
62
+ if (key === 'abs_change_percent') {
63
+ return Math.abs(Number(payload.change_percent ?? payload.changePercent ?? 0));
64
+ }
65
+ const normalizedKey = key.replace(/[A-Z]/g, (match) => `_${match.toLowerCase()}`);
66
+ if (Object.prototype.hasOwnProperty.call(payload, key)) {
67
+ return payload[key];
68
+ }
69
+ if (Object.prototype.hasOwnProperty.call(payload, normalizedKey)) {
70
+ return payload[normalizedKey];
71
+ }
72
+ return key.split('.').reduce((current, part) => {
73
+ if (!current || typeof current !== 'object')
74
+ return undefined;
75
+ return current[part];
76
+ }, payload);
77
+ }
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MockStockQuoteProvider = void 0;
4
+ class MockStockQuoteProvider {
5
+ sourceType = 'stock.quote';
6
+ modes = ['poll'];
7
+ validateConfig(config) {
8
+ const symbol = String(config.symbol || '').trim();
9
+ if (!symbol) {
10
+ throw new Error('stock.quote monitor requires sourceConfig.symbol.');
11
+ }
12
+ }
13
+ poll(input) {
14
+ this.validateConfig(input.sourceConfig);
15
+ const symbol = String(input.sourceConfig.symbol || '').trim().toUpperCase();
16
+ const latestPrice = Number(input.sourceConfig.price ?? input.sourceConfig.latestPrice ?? input.lastState?.latestPrice ?? 0);
17
+ if (!Number.isFinite(latestPrice) || latestPrice <= 0) {
18
+ return null;
19
+ }
20
+ const previousPrice = Number(input.lastState?.latestPrice ?? input.sourceConfig.previousPrice ?? latestPrice);
21
+ const changePercent = previousPrice > 0 ? ((latestPrice - previousPrice) / previousPrice) * 100 : 0;
22
+ const now = new Date().toISOString();
23
+ return {
24
+ id: `${input.monitorId}:${symbol}:${now}`,
25
+ sourceType: this.sourceType,
26
+ occurredAt: now,
27
+ subject: symbol,
28
+ summary: `${symbol} ${latestPrice} (${changePercent.toFixed(2)}%)`,
29
+ payload: {
30
+ symbol,
31
+ latestPrice,
32
+ previousPrice,
33
+ change_percent: changePercent,
34
+ changePercent,
35
+ timestamp: now,
36
+ },
37
+ };
38
+ }
39
+ }
40
+ exports.MockStockQuoteProvider = MockStockQuoteProvider;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toPublicAutomationMonitorId = toPublicAutomationMonitorId;
4
+ function toPublicAutomationMonitorId(monitorId) {
5
+ const normalized = monitorId.startsWith('monitor:') ? monitorId.slice('monitor:'.length) : monitorId;
6
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(normalized)) {
7
+ return normalized.split('-')[0] || normalized;
8
+ }
9
+ return normalized;
10
+ }