tide-commander 1.32.2 → 1.35.0
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/dist/assets/{BossLogsModal-2lqGyuCD.js → BossLogsModal-B_dgVF7L.js} +1 -1
- package/dist/assets/{BossSpawnModal-CR-ofDeB.js → BossSpawnModal-CSO1bYxA.js} +1 -1
- package/dist/assets/{ControlsModal-DQ8QVshA.js → ControlsModal-WMTTqbca.js} +1 -1
- package/dist/assets/{DockerLogsModal-D6yQTHy7.js → DockerLogsModal-THzhLHch.js} +1 -1
- package/dist/assets/{EmbeddedEditor-wechGxGl.js → EmbeddedEditor-DWLKJYav.js} +1 -1
- package/dist/assets/GmailOAuthSetup-DN9ceaS6.js +270 -0
- package/dist/assets/{GoogleOAuthSetup-BcpJEydM.js → GoogleOAuthSetup-bVST2EOB.js} +1 -1
- package/dist/assets/{IframeModal-E2E7NR08.js → IframeModal-BELsjvgi.js} +1 -1
- package/dist/assets/{IntegrationsPanel-CrS2QOFR.js → IntegrationsPanel-DwDr4BRt.js} +2 -2
- package/dist/assets/{LogViewerModal-BpIPZeFr.js → LogViewerModal-CMe04PO5.js} +1 -1
- package/dist/assets/MonitoringModal-CqSalNeY.js +1 -0
- package/dist/assets/{PM2LogsModal-CDk_2mi1.js → PM2LogsModal-CCmCDxVt.js} +1 -1
- package/dist/assets/{RestoreArchivedAreaModal-BmgBrk9J.js → RestoreArchivedAreaModal-IfzPidIv.js} +1 -1
- package/dist/assets/{SaveSnapshotModal-DWta9pcx.js → SaveSnapshotModal-DUhrVD5l.js} +1 -1
- package/dist/assets/{Scene2DCanvas-C3CcFsjU.js → Scene2DCanvas-Bl5DUC7w.js} +1 -1
- package/dist/assets/{SceneManager-mFUakRNl.js → SceneManager-BGO9tiaI.js} +1 -1
- package/dist/assets/{SkillsPanel-Br3h8GNh.js → SkillsPanel-CPFOI4Tl.js} +1 -1
- package/dist/assets/{SnapshotManager-CPYWfnPR.js → SnapshotManager-Cbu0tJBz.js} +1 -1
- package/dist/assets/{SpawnModal-DSDirR0j.js → SpawnModal-BqDbsYLY.js} +1 -1
- package/dist/assets/{SubordinateAssignmentModal-Daz67phV.js → SubordinateAssignmentModal-DOqkhL_L.js} +1 -1
- package/dist/assets/{SupervisorPanel-DoAl5e8W.js → SupervisorPanel-BvX-dlk_.js} +1 -1
- package/dist/assets/{TriggerManagerPanel-CVIHbqDt.js → TriggerManagerPanel-RUVFmKmf.js} +1 -1
- package/dist/assets/WorkflowEditorPanel-CwZpEqzM.js +42 -0
- package/dist/assets/browser-ponyfill-DZOWXZ4K.js +2 -0
- package/dist/assets/camera-D_KeL_pz.js +1 -0
- package/dist/assets/{index-I-I3pPPW.js → index-B-wV06cR.js} +1 -1
- package/dist/assets/index-BFguOWBW.js +2 -0
- package/dist/assets/{index-uXOqPsuU.js → index-C7gqY2AA.js} +1 -1
- package/dist/assets/{index-CE_GbjZ6.js → index-CiD1Rwaq.js} +1 -1
- package/dist/assets/index-D4nfDvz4.js +49 -0
- package/dist/assets/{index-CvMf5n2v.js → index-DDPUtz8-.js} +1 -1
- package/dist/assets/{index-Chrxgrys.js → index-EH8IBvSU.js} +1 -1
- package/dist/assets/{index-DWVQ48nQ.js → index-H0PzHVFw.js} +1 -1
- package/dist/assets/main-Cjm0d8dZ.js +152 -0
- package/dist/assets/main-DqC9_fF4.css +1 -0
- package/dist/assets/{prism-cpp-CcQnz8LL.js → prism-cpp-CK2Ly5dS.js} +1 -1
- package/dist/assets/{prism-csharp-DFIAaw4Y.js → prism-csharp-ByDDDiWW.js} +1 -1
- package/dist/assets/{prism-elixir-jP4m4T-8.js → prism-elixir-df27OMMQ.js} +1 -1
- package/dist/assets/{prism-haskell-BrMZM7_F.js → prism-haskell-Ce8aBmia.js} +1 -1
- package/dist/assets/{prism-java-BEsh8u4L.js → prism-java-CK6tws4L.js} +1 -1
- package/dist/assets/{prism-perl-ecHKp0bZ.js → prism-perl-UZfqnD51.js} +1 -1
- package/dist/assets/{prism-php-Ch-kk89U.js → prism-php-Dt9698bA.js} +1 -1
- package/dist/assets/{prism-ruby-zNpGDk6v.js → prism-ruby-CQBUuZIF.js} +1 -1
- package/dist/assets/{prism-scss-BeuXx0O0.js → prism-scss-CeN16CFC.js} +1 -1
- package/dist/assets/{vendor-react-uS-d4TUT.js → vendor-react--Eh9ivFN.js} +2 -2
- package/dist/assets/{web-IWJRtE3-.js → web-D1vWYL8u.js} +1 -1
- package/dist/assets/{web-DdRt5c0R.js → web-DUq3Undh.js} +1 -1
- package/dist/index.html +3 -3
- package/dist/src/packages/server/data/builtin-skills/boss-instructions.js +57 -66
- package/dist/src/packages/server/data/builtin-skills/index.js +2 -0
- package/dist/src/packages/server/data/builtin-skills/workflow-builder.js +253 -0
- package/dist/src/packages/server/data/builtin-skills/workflow-designer.js +157 -0
- package/dist/src/packages/server/data/event-queries.js +24 -0
- package/dist/src/packages/server/data/migrations/002_workflow_agent_binding.sql +14 -0
- package/dist/src/packages/server/data/migrations/003_matcher_executions.sql +19 -0
- package/dist/src/packages/server/data/migrations/004_matcher_message_source.sql +8 -0
- package/dist/src/packages/server/integrations/gmail/gmail-client.js +75 -15
- package/dist/src/packages/server/integrations/gmail/gmail-config.js +32 -2
- package/dist/src/packages/server/integrations/gmail/gmail-routes.js +5 -0
- package/dist/src/packages/server/integrations/gmail/index.js +23 -1
- package/dist/src/packages/server/integrations/jira/jira-client.js +11 -5
- package/dist/src/packages/server/integrations/jira/jira-routes.js +20 -3
- package/dist/src/packages/server/integrations/jira/jira-skill.js +110 -58
- package/dist/src/packages/server/routes/trigger-routes.js +22 -0
- package/dist/src/packages/server/routes/workflow-routes.js +86 -2
- package/dist/src/packages/server/services/boss-message-service.js +1 -1
- package/dist/src/packages/server/services/llm-matcher-service.js +50 -81
- package/dist/src/packages/server/services/trigger-service.js +195 -6
- package/dist/src/packages/server/services/workflow-executor.js +230 -0
- package/dist/src/packages/server/services/workflow-service.js +59 -13
- package/package.json +7 -7
- package/dist/assets/GmailOAuthSetup-B2GDjROU.js +0 -222
- package/dist/assets/MonitoringModal-CIF9MUm9.js +0 -1
- package/dist/assets/WorkflowEditorPanel-C4BRfmDM.js +0 -42
- package/dist/assets/browser-ponyfill-DIm4hKhx.js +0 -2
- package/dist/assets/camera-8crtHeRa.js +0 -1
- package/dist/assets/index--PCy0J0f.js +0 -49
- package/dist/assets/index-DOzx4Y9b.js +0 -2
- package/dist/assets/main-DQpuQfqS.css +0 -1
- package/dist/assets/main-DahZb6P4.js +0 -152
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { Router } from 'express';
|
|
7
7
|
import * as workflowService from '../services/workflow-service.js';
|
|
8
|
+
import * as workflowExecutor from '../services/workflow-executor.js';
|
|
8
9
|
import * as workflowChatService from '../services/workflow-chat-service.js';
|
|
9
10
|
const router = Router();
|
|
10
11
|
// ─── Query Helpers (same pattern as event-routes.ts) ───
|
|
@@ -125,7 +126,33 @@ router.patch('/instances/:id/cancel', (req, res) => {
|
|
|
125
126
|
}
|
|
126
127
|
res.json(instance);
|
|
127
128
|
});
|
|
128
|
-
// ───
|
|
129
|
+
// ─── Explicit Transition (agent-driven) ───
|
|
130
|
+
router.put('/instances/:id/transition', async (req, res) => {
|
|
131
|
+
try {
|
|
132
|
+
const { targetStateId, reason } = req.body;
|
|
133
|
+
if (!targetStateId) {
|
|
134
|
+
res.status(400).json({ error: 'targetStateId is required' });
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const instance = await workflowService.transitionTo(req.params.id, targetStateId, reason);
|
|
138
|
+
res.json(instance);
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
const msg = err instanceof Error ? err.message : 'Transition failed';
|
|
142
|
+
res.status(400).json({ error: msg });
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
router.get('/instances/:id/available-transitions', (req, res) => {
|
|
146
|
+
try {
|
|
147
|
+
const transitions = workflowService.getAvailableTransitions(req.params.id);
|
|
148
|
+
res.json({ transitions });
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
const msg = err instanceof Error ? err.message : 'Failed to get transitions';
|
|
152
|
+
res.status(400).json({ error: msg });
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
// ─── Legacy Manual Transition (event-based) ───
|
|
129
156
|
router.post('/instances/:id/transition', async (req, res) => {
|
|
130
157
|
try {
|
|
131
158
|
await workflowService.notifyEvent({
|
|
@@ -172,6 +199,63 @@ router.post('/instances/:id/event', async (req, res) => {
|
|
|
172
199
|
res.status(400).json({ error: msg });
|
|
173
200
|
}
|
|
174
201
|
});
|
|
202
|
+
// ─── Trigger Routing (workflow-executor) ───
|
|
203
|
+
router.post('/trigger', async (req, res) => {
|
|
204
|
+
try {
|
|
205
|
+
const { triggerId, triggerData, agentId } = req.body;
|
|
206
|
+
if (!triggerId) {
|
|
207
|
+
res.status(400).json({ error: 'triggerId is required' });
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
await workflowExecutor.handleTrigger({
|
|
211
|
+
triggerId,
|
|
212
|
+
triggerData: triggerData ?? {},
|
|
213
|
+
agentId,
|
|
214
|
+
});
|
|
215
|
+
res.json({ routed: true, triggerId });
|
|
216
|
+
}
|
|
217
|
+
catch (err) {
|
|
218
|
+
const msg = err instanceof Error ? err.message : 'Trigger routing failed';
|
|
219
|
+
res.status(500).json({ error: msg });
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
// ─── Agent Complete with Reasoning (workflow-executor) ───
|
|
223
|
+
router.post('/instances/:id/agent-complete', async (req, res) => {
|
|
224
|
+
try {
|
|
225
|
+
const { agentResponse, agentReasoning, agentSummary, variables, triggerId, transitionId } = req.body;
|
|
226
|
+
const instance = await workflowExecutor.transitionInstance(req.params.id, 'agent_complete', { agentResponse, agentReasoning, agentSummary, variables }, { triggerId, transitionId });
|
|
227
|
+
if (!instance) {
|
|
228
|
+
res.status(404).json({ error: 'Instance not found' });
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
res.json(instance);
|
|
232
|
+
}
|
|
233
|
+
catch (err) {
|
|
234
|
+
const msg = err instanceof Error ? err.message : 'Agent complete failed';
|
|
235
|
+
res.status(400).json({ error: msg });
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
// ─── Bind Agent to Instance (workflow-executor) ───
|
|
239
|
+
router.post('/instances/:id/bind-agent', (req, res) => {
|
|
240
|
+
try {
|
|
241
|
+
const { agentId, stateId } = req.body;
|
|
242
|
+
if (!agentId) {
|
|
243
|
+
res.status(400).json({ error: 'agentId is required' });
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const result = workflowExecutor.executeInstanceState(req.params.id, stateId || workflowService.getInstance(req.params.id)?.currentStateId || '', agentId);
|
|
247
|
+
res.json({
|
|
248
|
+
instanceId: result.instance.id,
|
|
249
|
+
stateId: result.state.id,
|
|
250
|
+
stateName: result.state.name,
|
|
251
|
+
currentStepId: result.currentStep?.id,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
catch (err) {
|
|
255
|
+
const msg = err instanceof Error ? err.message : 'Bind agent failed';
|
|
256
|
+
res.status(400).json({ error: msg });
|
|
257
|
+
}
|
|
258
|
+
});
|
|
175
259
|
// ============================================================================
|
|
176
260
|
// History & Audit (reads from SQLite)
|
|
177
261
|
// ============================================================================
|
|
@@ -193,7 +277,7 @@ router.get('/instances/:id/variables', (req, res) => {
|
|
|
193
277
|
res.json({ changes });
|
|
194
278
|
});
|
|
195
279
|
router.get('/instances/:id/reasoning', (req, res) => {
|
|
196
|
-
const steps =
|
|
280
|
+
const steps = workflowExecutor.getReasoningChain(req.params.id);
|
|
197
281
|
res.json({ steps });
|
|
198
282
|
});
|
|
199
283
|
// ============================================================================
|
|
@@ -12,7 +12,7 @@ import { buildBossContext } from './subordinate-context-service.js';
|
|
|
12
12
|
export function buildBossSystemPrompt(bossName, bossId) {
|
|
13
13
|
const agent = agentService.getAgent(bossId);
|
|
14
14
|
const customInstructions = agent?.customInstructions;
|
|
15
|
-
let prompt = `You are "${bossName}", a Boss Agent manager with ID \`${bossId}\`.
|
|
15
|
+
let prompt = `You are "${bossName}", a Boss Agent manager with ID \`${bossId}\`. Your default is to delegate tasks to your subordinates. Do small tasks yourself only when you already have the context to finish quickly (1-2 tool calls). For anything requiring exploration or substantial work, delegate to your team.
|
|
16
16
|
|
|
17
17
|
Your agent ID for notifications: ${bossId}`;
|
|
18
18
|
// Append agent-specific custom instructions
|
|
@@ -3,39 +3,57 @@
|
|
|
3
3
|
* Evaluates whether events match triggers using LLM-powered semantic matching,
|
|
4
4
|
* and extracts structured variables from unstructured event content.
|
|
5
5
|
*
|
|
6
|
-
* Uses Anthropic
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Uses the Anthropic SDK to call Claude (requires TC_ANTHROPIC_API_KEY environment variable).
|
|
7
|
+
* Haiku by default (fast, cheap classification).
|
|
8
|
+
* 15-second timeout — match treated as false on timeout (fail-safe).
|
|
9
9
|
*/
|
|
10
10
|
import Anthropic from '@anthropic-ai/sdk';
|
|
11
11
|
import { createLogger } from '../utils/logger.js';
|
|
12
12
|
const log = createLogger('LLMMatcher');
|
|
13
|
-
|
|
13
|
+
const TIMEOUT_MS = 15_000;
|
|
14
|
+
// Model mapping — use latest available model IDs
|
|
14
15
|
const MODEL_MAP = {
|
|
15
16
|
haiku: 'claude-haiku-4-5-20251001',
|
|
16
|
-
sonnet: 'claude-sonnet-4-6',
|
|
17
|
-
opus: 'claude-opus-4-6',
|
|
17
|
+
sonnet: 'claude-sonnet-4-6-20250514',
|
|
18
|
+
opus: 'claude-opus-4-6-20250514',
|
|
18
19
|
};
|
|
19
20
|
function resolveModel(model) {
|
|
20
21
|
if (!model)
|
|
21
22
|
return MODEL_MAP.haiku;
|
|
22
23
|
return MODEL_MAP[model] || model;
|
|
23
24
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Execute a prompt via the Anthropic SDK.
|
|
27
|
+
* Returns the raw text response.
|
|
28
|
+
*/
|
|
29
|
+
async function callAnthropicAPI(prompt, model) {
|
|
30
|
+
const startTime = Date.now();
|
|
31
|
+
const client = new Anthropic({
|
|
32
|
+
apiKey: process.env.TC_ANTHROPIC_API_KEY,
|
|
33
|
+
timeout: TIMEOUT_MS,
|
|
34
|
+
});
|
|
35
|
+
try {
|
|
36
|
+
const message = await client.messages.create({
|
|
37
|
+
model,
|
|
38
|
+
max_tokens: 1024,
|
|
39
|
+
messages: [{ role: 'user', content: prompt }],
|
|
40
|
+
});
|
|
41
|
+
const durationMs = Date.now() - startTime;
|
|
42
|
+
const text = message.content
|
|
43
|
+
.filter(block => block.type === 'text')
|
|
44
|
+
.map(block => block.text)
|
|
45
|
+
.join('\n');
|
|
46
|
+
return { text, durationMs };
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
throw err;
|
|
29
50
|
}
|
|
30
|
-
return client;
|
|
31
51
|
}
|
|
32
52
|
// ─── LLM Match ───
|
|
33
53
|
export async function llmMatch(formattedEvent, config) {
|
|
34
54
|
const startTime = Date.now();
|
|
35
55
|
const model = resolveModel(config.model);
|
|
36
|
-
const
|
|
37
|
-
const maxTokens = config.maxTokens ?? 150;
|
|
38
|
-
const systemPrompt = `You are an event classifier. Your job is to decide whether an incoming event matches a given condition.
|
|
56
|
+
const prompt = `You are an event classifier. Your job is to decide whether an incoming event matches a given condition.
|
|
39
57
|
|
|
40
58
|
EVENT:
|
|
41
59
|
---
|
|
@@ -55,18 +73,7 @@ Respond ONLY with valid JSON (no markdown, no explanation outside JSON):
|
|
|
55
73
|
"confidence": 0.0 to 1.0
|
|
56
74
|
}`;
|
|
57
75
|
try {
|
|
58
|
-
const
|
|
59
|
-
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
60
|
-
const response = await getClient().messages.create({
|
|
61
|
-
model,
|
|
62
|
-
max_tokens: maxTokens,
|
|
63
|
-
temperature,
|
|
64
|
-
messages: [{ role: 'user', content: systemPrompt }],
|
|
65
|
-
}, { signal: controller.signal });
|
|
66
|
-
clearTimeout(timeout);
|
|
67
|
-
const durationMs = Date.now() - startTime;
|
|
68
|
-
const text = response.content[0]?.type === 'text' ? response.content[0].text : '';
|
|
69
|
-
const tokensUsed = (response.usage?.input_tokens || 0) + (response.usage?.output_tokens || 0);
|
|
76
|
+
const { text, durationMs } = await callAnthropicAPI(prompt, model);
|
|
70
77
|
try {
|
|
71
78
|
// Extract JSON from response (handle potential markdown wrapping)
|
|
72
79
|
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
@@ -79,7 +86,7 @@ Respond ONLY with valid JSON (no markdown, no explanation outside JSON):
|
|
|
79
86
|
confidence: typeof parsed.confidence === 'number' ? parsed.confidence : (parsed.match ? 1.0 : 0.0),
|
|
80
87
|
durationMs,
|
|
81
88
|
model,
|
|
82
|
-
tokensUsed,
|
|
89
|
+
tokensUsed: 0,
|
|
83
90
|
};
|
|
84
91
|
}
|
|
85
92
|
catch (parseErr) {
|
|
@@ -90,32 +97,19 @@ Respond ONLY with valid JSON (no markdown, no explanation outside JSON):
|
|
|
90
97
|
confidence: 0,
|
|
91
98
|
durationMs,
|
|
92
99
|
model,
|
|
93
|
-
tokensUsed,
|
|
100
|
+
tokensUsed: 0,
|
|
94
101
|
};
|
|
95
102
|
}
|
|
96
103
|
}
|
|
97
104
|
catch (err) {
|
|
98
105
|
const durationMs = Date.now() - startTime;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
reason: 'LLM call timed out (5s)',
|
|
104
|
-
confidence: 0,
|
|
105
|
-
durationMs,
|
|
106
|
-
model,
|
|
107
|
-
tokensUsed: 0,
|
|
108
|
-
};
|
|
106
|
+
const reason = err instanceof Error ? err.message : 'unknown';
|
|
107
|
+
if (reason.includes('timed out') || reason.includes('timeout')) {
|
|
108
|
+
log.warn(`LLM match timed out after ${TIMEOUT_MS}ms`);
|
|
109
|
+
return { match: false, reason: `LLM call timed out (${TIMEOUT_MS}ms)`, confidence: 0, durationMs, model, tokensUsed: 0 };
|
|
109
110
|
}
|
|
110
111
|
log.error('LLM match error:', err);
|
|
111
|
-
return {
|
|
112
|
-
match: false,
|
|
113
|
-
reason: `LLM error: ${err instanceof Error ? err.message : 'unknown'}`,
|
|
114
|
-
confidence: 0,
|
|
115
|
-
durationMs,
|
|
116
|
-
model,
|
|
117
|
-
tokensUsed: 0,
|
|
118
|
-
};
|
|
112
|
+
return { match: false, reason: `LLM error: ${reason}`, confidence: 0, durationMs, model, tokensUsed: 0 };
|
|
119
113
|
}
|
|
120
114
|
}
|
|
121
115
|
// ─── LLM Variable Extraction ───
|
|
@@ -123,7 +117,7 @@ export async function llmExtractVariables(formattedEvent, config) {
|
|
|
123
117
|
const startTime = Date.now();
|
|
124
118
|
const model = resolveModel(config.model);
|
|
125
119
|
const variableList = config.variables.map(v => `- ${v}`).join('\n');
|
|
126
|
-
const
|
|
120
|
+
const prompt = `You are a data extractor. Extract specific variables from the event below.
|
|
127
121
|
|
|
128
122
|
EVENT:
|
|
129
123
|
---
|
|
@@ -147,18 +141,7 @@ Respond ONLY with valid JSON (no markdown, no explanation outside JSON):
|
|
|
147
141
|
"reason": "Brief explanation of how you extracted each value"
|
|
148
142
|
}`;
|
|
149
143
|
try {
|
|
150
|
-
const
|
|
151
|
-
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
152
|
-
const response = await getClient().messages.create({
|
|
153
|
-
model,
|
|
154
|
-
max_tokens: 300,
|
|
155
|
-
temperature: 0,
|
|
156
|
-
messages: [{ role: 'user', content: systemPrompt }],
|
|
157
|
-
}, { signal: controller.signal });
|
|
158
|
-
clearTimeout(timeout);
|
|
159
|
-
const durationMs = Date.now() - startTime;
|
|
160
|
-
const text = response.content[0]?.type === 'text' ? response.content[0].text : '';
|
|
161
|
-
const tokensUsed = (response.usage?.input_tokens || 0) + (response.usage?.output_tokens || 0);
|
|
144
|
+
const { text, durationMs } = await callAnthropicAPI(prompt, model);
|
|
162
145
|
try {
|
|
163
146
|
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
164
147
|
if (!jsonMatch)
|
|
@@ -169,17 +152,10 @@ Respond ONLY with valid JSON (no markdown, no explanation outside JSON):
|
|
|
169
152
|
for (const v of config.variables) {
|
|
170
153
|
variables[v] = parsed.variables?.[v] ?? '';
|
|
171
154
|
}
|
|
172
|
-
return {
|
|
173
|
-
variables,
|
|
174
|
-
reason: parsed.reason || '',
|
|
175
|
-
durationMs,
|
|
176
|
-
model,
|
|
177
|
-
tokensUsed,
|
|
178
|
-
};
|
|
155
|
+
return { variables, reason: parsed.reason || '', durationMs, model, tokensUsed: 0 };
|
|
179
156
|
}
|
|
180
157
|
catch (parseErr) {
|
|
181
158
|
log.error('Failed to parse LLM extract response:', text);
|
|
182
|
-
// Return empty variables on parse failure
|
|
183
159
|
const variables = {};
|
|
184
160
|
for (const v of config.variables) {
|
|
185
161
|
variables[v] = '';
|
|
@@ -187,9 +163,7 @@ Respond ONLY with valid JSON (no markdown, no explanation outside JSON):
|
|
|
187
163
|
return {
|
|
188
164
|
variables,
|
|
189
165
|
reason: `Parse error: ${parseErr instanceof Error ? parseErr.message : 'unknown'}`,
|
|
190
|
-
durationMs,
|
|
191
|
-
model,
|
|
192
|
-
tokensUsed,
|
|
166
|
+
durationMs, model, tokensUsed: 0,
|
|
193
167
|
};
|
|
194
168
|
}
|
|
195
169
|
}
|
|
@@ -199,17 +173,12 @@ Respond ONLY with valid JSON (no markdown, no explanation outside JSON):
|
|
|
199
173
|
for (const v of config.variables) {
|
|
200
174
|
variables[v] = '';
|
|
201
175
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
176
|
+
const reason = err instanceof Error ? err.message : 'unknown';
|
|
177
|
+
if (reason.includes('timed out') || reason.includes('timeout')) {
|
|
178
|
+
log.warn(`LLM extract timed out after ${TIMEOUT_MS}ms`);
|
|
179
|
+
return { variables, reason: `LLM call timed out (${TIMEOUT_MS}ms)`, durationMs, model, tokensUsed: 0 };
|
|
205
180
|
}
|
|
206
181
|
log.error('LLM extract error:', err);
|
|
207
|
-
return {
|
|
208
|
-
variables,
|
|
209
|
-
reason: `LLM error: ${err instanceof Error ? err.message : 'unknown'}`,
|
|
210
|
-
durationMs,
|
|
211
|
-
model,
|
|
212
|
-
tokensUsed: 0,
|
|
213
|
-
};
|
|
182
|
+
return { variables, reason: `LLM error: ${reason}`, durationMs, model, tokensUsed: 0 };
|
|
214
183
|
}
|
|
215
184
|
}
|
|
@@ -160,27 +160,54 @@ export function deleteTrigger(id) {
|
|
|
160
160
|
// ─── Event Evaluation ───
|
|
161
161
|
async function evaluateEvent(handler, event) {
|
|
162
162
|
const triggersOfType = Array.from(triggers.values()).filter(t => t.type === handler.triggerType && t.enabled);
|
|
163
|
+
// Extract source info from the event for per-message debugging
|
|
164
|
+
const sourceType = event.source;
|
|
165
|
+
const sourceId = extractSourceId(event);
|
|
166
|
+
const sourceTimestamp = event.timestamp;
|
|
163
167
|
for (const trigger of triggersOfType) {
|
|
164
168
|
try {
|
|
165
169
|
let matched = false;
|
|
170
|
+
let structuralPassed = true;
|
|
166
171
|
let llmResult;
|
|
167
172
|
let llmExtractResult;
|
|
173
|
+
const matcherExecutions = [];
|
|
174
|
+
const sourceInfo = { sourceType, sourceId, sourceTimestamp };
|
|
168
175
|
// Step 1: Structural matching
|
|
169
176
|
if (trigger.matchMode === 'structural' || trigger.matchMode === 'hybrid') {
|
|
170
177
|
const structuralResult = handler.structuralMatch(trigger, event);
|
|
178
|
+
matcherExecutions.push({
|
|
179
|
+
matcherType: 'structural',
|
|
180
|
+
matcherName: `${trigger.type}_structural`,
|
|
181
|
+
executedAt: Date.now(),
|
|
182
|
+
matched: structuralResult,
|
|
183
|
+
reason: structuralResult ? 'Structural match passed' : 'Structural match failed',
|
|
184
|
+
resultJson: { triggerType: trigger.type, matchMode: trigger.matchMode },
|
|
185
|
+
...sourceInfo,
|
|
186
|
+
});
|
|
171
187
|
if (trigger.matchMode === 'structural') {
|
|
172
188
|
matched = structuralResult;
|
|
173
189
|
}
|
|
174
190
|
else {
|
|
175
191
|
// hybrid: structural must pass before LLM is called
|
|
192
|
+
structuralPassed = structuralResult;
|
|
176
193
|
if (!structuralResult)
|
|
177
|
-
|
|
194
|
+
matched = false;
|
|
178
195
|
}
|
|
179
196
|
}
|
|
180
|
-
// Step 2: LLM matching
|
|
181
|
-
if (trigger.matchMode === 'llm' || trigger.matchMode === 'hybrid') {
|
|
197
|
+
// Step 2: LLM matching (skip if hybrid structural failed)
|
|
198
|
+
if (structuralPassed && (trigger.matchMode === 'llm' || trigger.matchMode === 'hybrid')) {
|
|
182
199
|
if (!trigger.llmMatch) {
|
|
183
200
|
log.warn(`Trigger ${trigger.name} has matchMode=${trigger.matchMode} but no llmMatch config`);
|
|
201
|
+
matcherExecutions.push({
|
|
202
|
+
matcherType: 'llm',
|
|
203
|
+
matcherName: 'llm_match',
|
|
204
|
+
executedAt: Date.now(),
|
|
205
|
+
matched: false,
|
|
206
|
+
reason: 'No llmMatch config defined',
|
|
207
|
+
...sourceInfo,
|
|
208
|
+
});
|
|
209
|
+
// Log non-match executions and skip to next trigger
|
|
210
|
+
logMatcherExecutions(null, trigger.id, matcherExecutions);
|
|
184
211
|
continue;
|
|
185
212
|
}
|
|
186
213
|
const formatted = handler.formatEventForLLM(event);
|
|
@@ -188,10 +215,28 @@ async function evaluateEvent(handler, event) {
|
|
|
188
215
|
// Check confidence threshold
|
|
189
216
|
const minConfidence = trigger.llmMatch.minConfidence ?? 0.0;
|
|
190
217
|
matched = llmResult.match && llmResult.confidence >= minConfidence;
|
|
218
|
+
matcherExecutions.push({
|
|
219
|
+
matcherType: 'llm',
|
|
220
|
+
matcherName: 'llm_match',
|
|
221
|
+
executedAt: Date.now(),
|
|
222
|
+
matched,
|
|
223
|
+
confidence: llmResult.confidence,
|
|
224
|
+
reason: llmResult.reason,
|
|
225
|
+
resultJson: {
|
|
226
|
+
model: llmResult.model,
|
|
227
|
+
tokensUsed: llmResult.tokensUsed,
|
|
228
|
+
durationMs: llmResult.durationMs,
|
|
229
|
+
minConfidence,
|
|
230
|
+
},
|
|
231
|
+
...sourceInfo,
|
|
232
|
+
});
|
|
191
233
|
}
|
|
192
|
-
|
|
234
|
+
// Log non-match executions directly (they won't go through fireTrigger)
|
|
235
|
+
if (!matched) {
|
|
236
|
+
logMatcherExecutions(null, trigger.id, matcherExecutions);
|
|
193
237
|
continue;
|
|
194
|
-
|
|
238
|
+
}
|
|
239
|
+
// Step 3: Variable extraction (only on match)
|
|
195
240
|
let variables;
|
|
196
241
|
if (trigger.extractionMode === 'llm' && trigger.llmExtract) {
|
|
197
242
|
const formatted = handler.formatEventForLLM(event);
|
|
@@ -199,15 +244,39 @@ async function evaluateEvent(handler, event) {
|
|
|
199
244
|
// Merge with structural variables as fallback
|
|
200
245
|
const structuralVars = handler.extractVariables(trigger, event);
|
|
201
246
|
variables = { ...structuralVars, ...llmExtractResult.variables };
|
|
247
|
+
matcherExecutions.push({
|
|
248
|
+
matcherType: 'extraction',
|
|
249
|
+
matcherName: 'llm_extract',
|
|
250
|
+
executedAt: Date.now(),
|
|
251
|
+
matched: Object.keys(llmExtractResult.variables).length > 0,
|
|
252
|
+
reason: llmExtractResult.reason,
|
|
253
|
+
resultJson: {
|
|
254
|
+
extractedVariables: llmExtractResult.variables,
|
|
255
|
+
model: llmExtractResult.model,
|
|
256
|
+
tokensUsed: llmExtractResult.tokensUsed,
|
|
257
|
+
durationMs: llmExtractResult.durationMs,
|
|
258
|
+
},
|
|
259
|
+
...sourceInfo,
|
|
260
|
+
});
|
|
202
261
|
}
|
|
203
262
|
else {
|
|
204
263
|
variables = handler.extractVariables(trigger, event);
|
|
264
|
+
matcherExecutions.push({
|
|
265
|
+
matcherType: 'extraction',
|
|
266
|
+
matcherName: 'structural_extract',
|
|
267
|
+
executedAt: Date.now(),
|
|
268
|
+
matched: Object.keys(variables).length > 0,
|
|
269
|
+
reason: `Extracted ${Object.keys(variables).length} variables`,
|
|
270
|
+
resultJson: { extractedVariables: variables },
|
|
271
|
+
...sourceInfo,
|
|
272
|
+
});
|
|
205
273
|
}
|
|
206
|
-
// Step 4: Fire the trigger
|
|
274
|
+
// Step 4: Fire the trigger (matcher executions linked after trigger_event created)
|
|
207
275
|
await fireTrigger(trigger.id, variables, {
|
|
208
276
|
rawPayload: event.data,
|
|
209
277
|
llmMatchResult: llmResult,
|
|
210
278
|
llmExtractResult: llmExtractResult,
|
|
279
|
+
matcherExecutions,
|
|
211
280
|
});
|
|
212
281
|
}
|
|
213
282
|
catch (err) {
|
|
@@ -218,6 +287,33 @@ async function evaluateEvent(handler, event) {
|
|
|
218
287
|
}
|
|
219
288
|
}
|
|
220
289
|
}
|
|
290
|
+
/**
|
|
291
|
+
* Extract a stable identifier from an external event's data.
|
|
292
|
+
* Each integration stores its message ID in a different field.
|
|
293
|
+
*/
|
|
294
|
+
function extractSourceId(event) {
|
|
295
|
+
if (!event.data || typeof event.data !== 'object')
|
|
296
|
+
return undefined;
|
|
297
|
+
const data = event.data;
|
|
298
|
+
// Slack: ts or thread_ts
|
|
299
|
+
if (data.ts)
|
|
300
|
+
return String(data.ts);
|
|
301
|
+
if (data.event_ts)
|
|
302
|
+
return String(data.event_ts);
|
|
303
|
+
// Email: messageId or id
|
|
304
|
+
if (data.messageId)
|
|
305
|
+
return String(data.messageId);
|
|
306
|
+
// Jira: issue key or id
|
|
307
|
+
if (data.issue && typeof data.issue === 'object') {
|
|
308
|
+
const issue = data.issue;
|
|
309
|
+
if (issue.key)
|
|
310
|
+
return String(issue.key);
|
|
311
|
+
}
|
|
312
|
+
// Webhook: try id field
|
|
313
|
+
if (data.id)
|
|
314
|
+
return String(data.id);
|
|
315
|
+
return undefined;
|
|
316
|
+
}
|
|
221
317
|
// ─── Test Match (dry run — no fire, no SQLite log) ───
|
|
222
318
|
export async function testMatch(triggerId, event) {
|
|
223
319
|
const trigger = triggers.get(triggerId);
|
|
@@ -229,9 +325,18 @@ export async function testMatch(triggerId, event) {
|
|
|
229
325
|
let structuralResult;
|
|
230
326
|
let llmResult;
|
|
231
327
|
let wouldFire = false;
|
|
328
|
+
const matcherExecutions = [];
|
|
232
329
|
// Structural matching
|
|
233
330
|
if (trigger.matchMode === 'structural' || trigger.matchMode === 'hybrid') {
|
|
234
331
|
structuralResult = effectiveHandler.structuralMatch(trigger, event);
|
|
332
|
+
matcherExecutions.push({
|
|
333
|
+
matcherType: 'structural',
|
|
334
|
+
matcherName: `${trigger.type}_structural`,
|
|
335
|
+
executedAt: Date.now(),
|
|
336
|
+
matched: structuralResult,
|
|
337
|
+
reason: structuralResult ? 'Structural match passed' : 'Structural match failed',
|
|
338
|
+
resultJson: { triggerType: trigger.type, matchMode: trigger.matchMode },
|
|
339
|
+
});
|
|
235
340
|
if (trigger.matchMode === 'structural') {
|
|
236
341
|
wouldFire = structuralResult;
|
|
237
342
|
}
|
|
@@ -241,6 +346,7 @@ export async function testMatch(triggerId, event) {
|
|
|
241
346
|
structuralMatch: structuralResult,
|
|
242
347
|
extractedVariables: {},
|
|
243
348
|
wouldFire: false,
|
|
349
|
+
matcherExecutions,
|
|
244
350
|
};
|
|
245
351
|
}
|
|
246
352
|
}
|
|
@@ -251,6 +357,20 @@ export async function testMatch(triggerId, event) {
|
|
|
251
357
|
llmResult = await llmMatch(formatted, trigger.llmMatch);
|
|
252
358
|
const minConfidence = trigger.llmMatch.minConfidence ?? 0.0;
|
|
253
359
|
wouldFire = llmResult.match && llmResult.confidence >= minConfidence;
|
|
360
|
+
matcherExecutions.push({
|
|
361
|
+
matcherType: 'llm',
|
|
362
|
+
matcherName: 'llm_match',
|
|
363
|
+
executedAt: Date.now(),
|
|
364
|
+
matched: wouldFire,
|
|
365
|
+
confidence: llmResult.confidence,
|
|
366
|
+
reason: llmResult.reason,
|
|
367
|
+
resultJson: {
|
|
368
|
+
model: llmResult.model,
|
|
369
|
+
tokensUsed: llmResult.tokensUsed,
|
|
370
|
+
durationMs: llmResult.durationMs,
|
|
371
|
+
minConfidence,
|
|
372
|
+
},
|
|
373
|
+
});
|
|
254
374
|
}
|
|
255
375
|
}
|
|
256
376
|
// Variable extraction (always run for test)
|
|
@@ -260,15 +380,37 @@ export async function testMatch(triggerId, event) {
|
|
|
260
380
|
const extractResult = await llmExtractVariables(formatted, trigger.llmExtract);
|
|
261
381
|
const structuralVars = effectiveHandler.extractVariables(trigger, event);
|
|
262
382
|
extractedVariables = { ...structuralVars, ...extractResult.variables };
|
|
383
|
+
matcherExecutions.push({
|
|
384
|
+
matcherType: 'extraction',
|
|
385
|
+
matcherName: 'llm_extract',
|
|
386
|
+
executedAt: Date.now(),
|
|
387
|
+
matched: Object.keys(extractResult.variables).length > 0,
|
|
388
|
+
reason: extractResult.reason,
|
|
389
|
+
resultJson: {
|
|
390
|
+
extractedVariables: extractResult.variables,
|
|
391
|
+
model: extractResult.model,
|
|
392
|
+
tokensUsed: extractResult.tokensUsed,
|
|
393
|
+
durationMs: extractResult.durationMs,
|
|
394
|
+
},
|
|
395
|
+
});
|
|
263
396
|
}
|
|
264
397
|
else {
|
|
265
398
|
extractedVariables = effectiveHandler.extractVariables(trigger, event);
|
|
399
|
+
matcherExecutions.push({
|
|
400
|
+
matcherType: 'extraction',
|
|
401
|
+
matcherName: 'structural_extract',
|
|
402
|
+
executedAt: Date.now(),
|
|
403
|
+
matched: Object.keys(extractedVariables).length > 0,
|
|
404
|
+
reason: `Extracted ${Object.keys(extractedVariables).length} variables`,
|
|
405
|
+
resultJson: { extractedVariables },
|
|
406
|
+
});
|
|
266
407
|
}
|
|
267
408
|
return {
|
|
268
409
|
structuralMatch: structuralResult,
|
|
269
410
|
llmMatch: llmResult,
|
|
270
411
|
extractedVariables,
|
|
271
412
|
wouldFire,
|
|
413
|
+
matcherExecutions,
|
|
272
414
|
};
|
|
273
415
|
}
|
|
274
416
|
// ─── Fire Trigger ───
|
|
@@ -305,6 +447,10 @@ export async function fireTrigger(id, variables, opts) {
|
|
|
305
447
|
error: null,
|
|
306
448
|
duration_ms: null,
|
|
307
449
|
});
|
|
450
|
+
// Log matcher executions linked to this trigger event
|
|
451
|
+
if (opts?.matcherExecutions && eventId > 0) {
|
|
452
|
+
logMatcherExecutions(eventId, trigger.id, opts.matcherExecutions);
|
|
453
|
+
}
|
|
308
454
|
}
|
|
309
455
|
catch (err) {
|
|
310
456
|
log.error('Failed to log trigger fire to SQLite:', err);
|
|
@@ -332,6 +478,16 @@ export async function fireTrigger(id, variables, opts) {
|
|
|
332
478
|
}
|
|
333
479
|
emit('trigger_fired', { triggerId: id, agentId: trigger.agentId, timestamp: startTime });
|
|
334
480
|
log.log(`Fired trigger ${trigger.name} -> agent ${trigger.agentId}`);
|
|
481
|
+
// Route to workflow instances that are waiting for this trigger
|
|
482
|
+
try {
|
|
483
|
+
const { handleTrigger } = await import('./workflow-executor.js');
|
|
484
|
+
await handleTrigger({
|
|
485
|
+
triggerId: id,
|
|
486
|
+
triggerData: variables,
|
|
487
|
+
agentId: trigger.agentId,
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
catch { /* workflow routing is best-effort */ }
|
|
335
491
|
}
|
|
336
492
|
catch (err) {
|
|
337
493
|
const errorMsg = err instanceof Error ? err.message : 'Unknown error';
|
|
@@ -434,6 +590,39 @@ export function getTriggerEvents(triggerId, limit = 50) {
|
|
|
434
590
|
export function getAllTriggerEvents(limit = 100) {
|
|
435
591
|
return queryMany('SELECT * FROM trigger_events ORDER BY fired_at DESC LIMIT ?', [limit]);
|
|
436
592
|
}
|
|
593
|
+
// ─── Matcher Execution Logging (debugging) ───
|
|
594
|
+
function logMatcherExecutions(triggerEventId, triggerId, executions) {
|
|
595
|
+
for (const exec of executions) {
|
|
596
|
+
try {
|
|
597
|
+
insertOne('matcher_executions', {
|
|
598
|
+
trigger_event_id: triggerEventId,
|
|
599
|
+
trigger_id: triggerId,
|
|
600
|
+
matcher_type: exec.matcherType,
|
|
601
|
+
matcher_name: exec.matcherName,
|
|
602
|
+
executed_at: exec.executedAt,
|
|
603
|
+
matched: exec.matched ? 1 : 0,
|
|
604
|
+
confidence: exec.confidence ?? null,
|
|
605
|
+
reason: exec.reason ?? null,
|
|
606
|
+
result_json: exec.resultJson ? JSON.stringify(exec.resultJson) : null,
|
|
607
|
+
source_type: exec.sourceType ?? null,
|
|
608
|
+
source_id: exec.sourceId ?? null,
|
|
609
|
+
source_timestamp: exec.sourceTimestamp ?? null,
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
catch (err) {
|
|
613
|
+
log.error(`Failed to log matcher execution: ${err}`);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
export function getMatchersByEvent(triggerEventId) {
|
|
618
|
+
return queryMany('SELECT * FROM matcher_executions WHERE trigger_event_id = ? ORDER BY executed_at ASC', [triggerEventId]);
|
|
619
|
+
}
|
|
620
|
+
export function getMatcherHistoryByTrigger(triggerId, limit = 100) {
|
|
621
|
+
return queryMany('SELECT * FROM matcher_executions WHERE trigger_id = ? ORDER BY executed_at DESC LIMIT ?', [triggerId, limit]);
|
|
622
|
+
}
|
|
623
|
+
export function getMatchersBySource(sourceType, sourceId) {
|
|
624
|
+
return queryMany('SELECT * FROM matcher_executions WHERE source_type = ? AND source_id = ? ORDER BY executed_at ASC', [sourceType, sourceId]);
|
|
625
|
+
}
|
|
437
626
|
// ─── Webhook Handler (basic built-in handler for webhook triggers) ───
|
|
438
627
|
function createWebhookHandler() {
|
|
439
628
|
return {
|