@exaudeus/workrail 3.24.4 → 3.25.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.
Files changed (42) hide show
  1. package/dist/cli/commands/index.d.ts +1 -0
  2. package/dist/cli/commands/index.js +3 -1
  3. package/dist/cli/commands/version.d.ts +6 -0
  4. package/dist/cli/commands/version.js +14 -0
  5. package/dist/cli.js +90 -0
  6. package/dist/console/assets/index-8dh0Psu-.css +1 -0
  7. package/dist/console/assets/{index-TMfptYpQ.js → index-HhtarvD5.js} +10 -10
  8. package/dist/console/index.html +2 -2
  9. package/dist/daemon/pi-mono-loader.d.ts +5 -0
  10. package/dist/daemon/pi-mono-loader.js +65 -0
  11. package/dist/daemon/workflow-runner.d.ts +41 -0
  12. package/dist/daemon/workflow-runner.js +573 -0
  13. package/dist/infrastructure/session/HttpServer.js +2 -2
  14. package/dist/manifest.json +111 -31
  15. package/dist/mcp/handlers/v2-execution/start.d.ts +2 -1
  16. package/dist/mcp/handlers/v2-execution/start.js +4 -3
  17. package/dist/mcp/server.js +1 -1
  18. package/dist/trigger/index.d.ts +5 -0
  19. package/dist/trigger/index.js +8 -0
  20. package/dist/trigger/trigger-listener.d.ts +30 -0
  21. package/dist/trigger/trigger-listener.js +166 -0
  22. package/dist/trigger/trigger-router.d.ts +32 -0
  23. package/dist/trigger/trigger-router.js +185 -0
  24. package/dist/trigger/trigger-store.d.ts +31 -0
  25. package/dist/trigger/trigger-store.js +457 -0
  26. package/dist/trigger/types.d.ts +46 -0
  27. package/dist/trigger/types.js +6 -0
  28. package/dist/v2/infra/in-memory/daemon-registry/index.d.ts +14 -0
  29. package/dist/v2/infra/in-memory/daemon-registry/index.js +32 -0
  30. package/dist/v2/infra/in-memory/keyed-async-queue/index.d.ts +5 -0
  31. package/dist/v2/infra/in-memory/keyed-async-queue/index.js +32 -0
  32. package/dist/v2/usecases/console-routes.d.ts +3 -1
  33. package/dist/v2/usecases/console-routes.js +102 -1
  34. package/dist/v2/usecases/console-service.d.ts +2 -0
  35. package/dist/v2/usecases/console-service.js +18 -2
  36. package/dist/v2/usecases/console-types.d.ts +2 -0
  37. package/package.json +3 -1
  38. package/workflows/coding-task-workflow-agentic.lean.v2.json +1 -1
  39. package/workflows/workflow-for-workflows.json +4 -2
  40. package/workflows/workflow-for-workflows.v2.json +4 -2
  41. package/dist/console/assets/index-BXRk3te_.css +0 -1
  42. package/workflows/rich-object-contribution.json +0 -258
@@ -0,0 +1,185 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.TriggerRouter = void 0;
37
+ exports.interpolateGoalTemplate = interpolateGoalTemplate;
38
+ const crypto = __importStar(require("node:crypto"));
39
+ const index_js_1 = require("../v2/infra/in-memory/keyed-async-queue/index.js");
40
+ function interpolateGoalTemplate(template, staticGoal, payload) {
41
+ const TOKEN_RE = /\{\{([^}]+)\}\}/g;
42
+ const tokens = [];
43
+ let match;
44
+ while ((match = TOKEN_RE.exec(template)) !== null) {
45
+ if (match[1] !== undefined)
46
+ tokens.push(match[1]);
47
+ }
48
+ if (tokens.length === 0)
49
+ return template;
50
+ const resolved = new Map();
51
+ for (const token of tokens) {
52
+ const value = extractDotPath(payload, token);
53
+ if (value === undefined || value === null) {
54
+ return staticGoal;
55
+ }
56
+ resolved.set(token, String(value));
57
+ }
58
+ return template.replace(TOKEN_RE, (_, token) => resolved.get(token) ?? staticGoal);
59
+ }
60
+ function extractDotPath(obj, rawPath) {
61
+ let path = rawPath.trim();
62
+ if (path.startsWith('$.'))
63
+ path = path.slice(2);
64
+ else if (path.startsWith('$'))
65
+ path = path.slice(1);
66
+ const segments = path.split('.');
67
+ let current = obj;
68
+ for (const segment of segments) {
69
+ if (segment.includes('[')) {
70
+ console.warn(`[TriggerRouter] contextMapping path "${rawPath}" contains array indexing ` +
71
+ `(segment: "${segment}"). Array indexing is not supported in MVP. ` +
72
+ `The extracted value will be undefined.`);
73
+ return undefined;
74
+ }
75
+ if (current === null || typeof current !== 'object') {
76
+ return undefined;
77
+ }
78
+ current = current[segment];
79
+ }
80
+ return current;
81
+ }
82
+ function applyContextMapping(payload, entries) {
83
+ const context = {};
84
+ for (const entry of entries) {
85
+ const value = extractDotPath(payload, entry.payloadPath);
86
+ if (value === undefined) {
87
+ if (entry.required) {
88
+ console.warn(`[TriggerRouter] Required contextMapping key "${entry.workflowContextKey}" ` +
89
+ `(path: "${entry.payloadPath}") resolved to undefined. ` +
90
+ `The workflow context will be missing this variable.`);
91
+ }
92
+ continue;
93
+ }
94
+ context[entry.workflowContextKey] = value;
95
+ }
96
+ return context;
97
+ }
98
+ function validateHmac(rawBody, secret, headerValue) {
99
+ const expected = crypto
100
+ .createHmac('sha256', secret)
101
+ .update(rawBody)
102
+ .digest('hex');
103
+ const received = headerValue.startsWith('sha256=')
104
+ ? headerValue.slice(7)
105
+ : headerValue;
106
+ if (expected.length !== received.length) {
107
+ return false;
108
+ }
109
+ return crypto.timingSafeEqual(Buffer.from(expected, 'utf8'), Buffer.from(received, 'utf8'));
110
+ }
111
+ class TriggerRouter {
112
+ constructor(index, ctx, apiKey, runWorkflowFn) {
113
+ this.index = index;
114
+ this.ctx = ctx;
115
+ this.apiKey = apiKey;
116
+ this.runWorkflowFn = runWorkflowFn;
117
+ this.queue = new index_js_1.KeyedAsyncQueue();
118
+ }
119
+ route(event) {
120
+ const trigger = this.index.get(event.triggerId);
121
+ if (!trigger) {
122
+ return {
123
+ _tag: 'error',
124
+ error: { kind: 'not_found', triggerId: event.triggerId },
125
+ };
126
+ }
127
+ if (trigger.hmacSecret) {
128
+ const signature = event.signature;
129
+ if (!signature) {
130
+ return { _tag: 'error', error: { kind: 'hmac_invalid' } };
131
+ }
132
+ if (!validateHmac(event.rawBody, trigger.hmacSecret, signature)) {
133
+ return { _tag: 'error', error: { kind: 'hmac_invalid' } };
134
+ }
135
+ }
136
+ let workflowContext;
137
+ if (trigger.contextMapping?.mappings.length) {
138
+ workflowContext = applyContextMapping(event.payload, trigger.contextMapping.mappings);
139
+ }
140
+ else {
141
+ workflowContext = { payload: event.payload };
142
+ }
143
+ const goal = trigger.goalTemplate
144
+ ? interpolateGoalTemplate(trigger.goalTemplate, trigger.goal, event.payload)
145
+ : trigger.goal;
146
+ const workflowTrigger = {
147
+ workflowId: trigger.workflowId,
148
+ goal,
149
+ workspacePath: trigger.workspacePath,
150
+ context: workflowContext,
151
+ ...(trigger.referenceUrls !== undefined ? { referenceUrls: trigger.referenceUrls } : {}),
152
+ ...(trigger.agentConfig !== undefined ? { agentConfig: trigger.agentConfig } : {}),
153
+ };
154
+ void this.queue.enqueue(trigger.id, async () => {
155
+ const result = await this.runWorkflowFn(workflowTrigger, this.ctx, this.apiKey);
156
+ if (result._tag === 'success') {
157
+ console.log(`[TriggerRouter] Workflow completed: triggerId=${trigger.id} ` +
158
+ `workflowId=${trigger.workflowId} stopReason=${result.stopReason}`);
159
+ }
160
+ else {
161
+ console.log(`[TriggerRouter] Workflow failed: triggerId=${trigger.id} ` +
162
+ `workflowId=${trigger.workflowId} error=${result.message} stopReason=${result.stopReason}`);
163
+ }
164
+ });
165
+ return { _tag: 'enqueued', triggerId: trigger.id };
166
+ }
167
+ dispatch(workflowTrigger) {
168
+ void this.queue.enqueue(workflowTrigger.workflowId, async () => {
169
+ const result = await this.runWorkflowFn(workflowTrigger, this.ctx, this.apiKey);
170
+ if (result._tag === 'success') {
171
+ console.log(`[TriggerRouter] Dispatch completed: workflowId=${workflowTrigger.workflowId} ` +
172
+ `stopReason=${result.stopReason}`);
173
+ }
174
+ else {
175
+ console.log(`[TriggerRouter] Dispatch failed: workflowId=${workflowTrigger.workflowId} ` +
176
+ `error=${result.message} stopReason=${result.stopReason}`);
177
+ }
178
+ });
179
+ return workflowTrigger.workflowId;
180
+ }
181
+ listTriggers() {
182
+ return [...this.index.values()];
183
+ }
184
+ }
185
+ exports.TriggerRouter = TriggerRouter;
@@ -0,0 +1,31 @@
1
+ import type { Result } from '../runtime/result.js';
2
+ import { type TriggerConfig, type TriggerDefinition } from './types.js';
3
+ export type TriggerStoreError = {
4
+ readonly kind: 'parse_error';
5
+ readonly message: string;
6
+ readonly lineNumber?: number;
7
+ } | {
8
+ readonly kind: 'missing_secret';
9
+ readonly envVarName: string;
10
+ readonly triggerId: string;
11
+ } | {
12
+ readonly kind: 'missing_field';
13
+ readonly field: string;
14
+ readonly triggerId: string;
15
+ } | {
16
+ readonly kind: 'unknown_provider';
17
+ readonly provider: string;
18
+ readonly triggerId: string;
19
+ } | {
20
+ readonly kind: 'file_not_found';
21
+ readonly filePath: string;
22
+ } | {
23
+ readonly kind: 'io_error';
24
+ readonly message: string;
25
+ } | {
26
+ readonly kind: 'duplicate_id';
27
+ readonly triggerId: string;
28
+ };
29
+ export declare function loadTriggerConfig(yamlContent: string, env?: Record<string, string | undefined>): Result<TriggerConfig, TriggerStoreError>;
30
+ export declare function loadTriggerConfigFromFile(workspacePath: string, env?: Record<string, string | undefined>): Promise<Result<TriggerConfig, TriggerStoreError>>;
31
+ export declare function buildTriggerIndex(config: TriggerConfig): Result<Map<string, TriggerDefinition>, TriggerStoreError>;
@@ -0,0 +1,457 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.loadTriggerConfig = loadTriggerConfig;
37
+ exports.loadTriggerConfigFromFile = loadTriggerConfigFromFile;
38
+ exports.buildTriggerIndex = buildTriggerIndex;
39
+ const path = __importStar(require("node:path"));
40
+ const fs = __importStar(require("node:fs/promises"));
41
+ const result_js_1 = require("../runtime/result.js");
42
+ const types_js_1 = require("./types.js");
43
+ const SUPPORTED_PROVIDERS = new Set(['generic']);
44
+ function unquoteYamlScalar(raw) {
45
+ const s = raw.trim();
46
+ if ((s.startsWith('"') && s.endsWith('"')) ||
47
+ (s.startsWith("'") && s.endsWith("'"))) {
48
+ return s.slice(1, -1);
49
+ }
50
+ return s;
51
+ }
52
+ function parseScalar(raw, lineNum) {
53
+ const s = raw.trim();
54
+ if (s.startsWith('[') || s.startsWith('{')) {
55
+ return (0, result_js_1.err)({
56
+ kind: 'parse_error',
57
+ message: `Inline arrays and objects are not supported. Use block style. At line ${lineNum}.`,
58
+ lineNumber: lineNum,
59
+ });
60
+ }
61
+ return (0, result_js_1.ok)(unquoteYamlScalar(s));
62
+ }
63
+ function parseTriggersYaml(content) {
64
+ const lines = content.split('\n');
65
+ const triggers = [];
66
+ let lineIndex = 0;
67
+ const skipBlankAndComments = () => {
68
+ while (lineIndex < lines.length) {
69
+ const l = lines[lineIndex];
70
+ if (l !== undefined && (l.trim() === '' || l.trim().startsWith('#'))) {
71
+ lineIndex++;
72
+ }
73
+ else {
74
+ break;
75
+ }
76
+ }
77
+ };
78
+ skipBlankAndComments();
79
+ if (lineIndex >= lines.length || !lines[lineIndex]?.trim().startsWith('triggers:')) {
80
+ return (0, result_js_1.err)({
81
+ kind: 'parse_error',
82
+ message: `Expected "triggers:" as the root key at line ${lineIndex + 1}.`,
83
+ lineNumber: lineIndex + 1,
84
+ });
85
+ }
86
+ lineIndex++;
87
+ while (lineIndex < lines.length) {
88
+ const line = lines[lineIndex];
89
+ if (line === undefined)
90
+ break;
91
+ const trimmed = line.trim();
92
+ if (trimmed === '' || trimmed.startsWith('#')) {
93
+ lineIndex++;
94
+ continue;
95
+ }
96
+ if (!line.match(/^[ ]{2,}- /)) {
97
+ return (0, result_js_1.err)({
98
+ kind: 'parse_error',
99
+ message: `Expected a list item starting with " - " at line ${lineIndex + 1}. Got: "${trimmed}"`,
100
+ lineNumber: lineIndex + 1,
101
+ });
102
+ }
103
+ const trigger = {};
104
+ const itemIndent = line.indexOf('-');
105
+ const afterDash = line.slice(itemIndent + 1).trim();
106
+ if (afterDash) {
107
+ const colonIdx = afterDash.indexOf(':');
108
+ if (colonIdx === -1) {
109
+ return (0, result_js_1.err)({
110
+ kind: 'parse_error',
111
+ message: `Missing colon in key-value pair at line ${lineIndex + 1}: "${afterDash}"`,
112
+ lineNumber: lineIndex + 1,
113
+ });
114
+ }
115
+ const key = afterDash.slice(0, colonIdx).trim();
116
+ const rawValue = afterDash.slice(colonIdx + 1).trim();
117
+ if (rawValue !== '') {
118
+ const valueResult = parseScalar(rawValue, lineIndex + 1);
119
+ if (valueResult.kind === 'err')
120
+ return valueResult;
121
+ setTriggerField(trigger, key, valueResult.value);
122
+ }
123
+ }
124
+ lineIndex++;
125
+ while (lineIndex < lines.length) {
126
+ const kvLine = lines[lineIndex];
127
+ if (kvLine === undefined)
128
+ break;
129
+ const kTrimmed = kvLine.trim();
130
+ if (kTrimmed === '' || kTrimmed.startsWith('#')) {
131
+ lineIndex++;
132
+ continue;
133
+ }
134
+ const lineIndent = kvLine.search(/\S/);
135
+ if (lineIndent <= itemIndent) {
136
+ break;
137
+ }
138
+ const colonIdx = kTrimmed.indexOf(':');
139
+ if (colonIdx === -1) {
140
+ return (0, result_js_1.err)({
141
+ kind: 'parse_error',
142
+ message: `Missing colon in key-value pair at line ${lineIndex + 1}: "${kTrimmed}"`,
143
+ lineNumber: lineIndex + 1,
144
+ });
145
+ }
146
+ const key = kTrimmed.slice(0, colonIdx).trim();
147
+ const rawValue = kTrimmed.slice(colonIdx + 1).trim();
148
+ if (key === 'contextMapping') {
149
+ lineIndex++;
150
+ const contextMapping = {};
151
+ while (lineIndex < lines.length) {
152
+ const cmLine = lines[lineIndex];
153
+ if (cmLine === undefined)
154
+ break;
155
+ const cmTrimmed = cmLine.trim();
156
+ if (cmTrimmed === '' || cmTrimmed.startsWith('#')) {
157
+ lineIndex++;
158
+ continue;
159
+ }
160
+ const cmIndent = cmLine.search(/\S/);
161
+ if (cmIndent <= lineIndent)
162
+ break;
163
+ const cmColonIdx = cmTrimmed.indexOf(':');
164
+ if (cmColonIdx === -1) {
165
+ return (0, result_js_1.err)({
166
+ kind: 'parse_error',
167
+ message: `Missing colon in contextMapping entry at line ${lineIndex + 1}: "${cmTrimmed}"`,
168
+ lineNumber: lineIndex + 1,
169
+ });
170
+ }
171
+ const cmKey = cmTrimmed.slice(0, cmColonIdx).trim();
172
+ const cmRawValue = cmTrimmed.slice(cmColonIdx + 1).trim();
173
+ const cmValueResult = parseScalar(cmRawValue, lineIndex + 1);
174
+ if (cmValueResult.kind === 'err')
175
+ return cmValueResult;
176
+ contextMapping[cmKey] = cmValueResult.value;
177
+ lineIndex++;
178
+ }
179
+ trigger.contextMapping = contextMapping;
180
+ continue;
181
+ }
182
+ if (key === 'agentConfig') {
183
+ lineIndex++;
184
+ const agentConfig = {};
185
+ while (lineIndex < lines.length) {
186
+ const acLine = lines[lineIndex];
187
+ if (acLine === undefined)
188
+ break;
189
+ const acTrimmed = acLine.trim();
190
+ if (acTrimmed === '' || acTrimmed.startsWith('#')) {
191
+ lineIndex++;
192
+ continue;
193
+ }
194
+ const acIndent = acLine.search(/\S/);
195
+ if (acIndent <= lineIndent)
196
+ break;
197
+ const acColonIdx = acTrimmed.indexOf(':');
198
+ if (acColonIdx === -1) {
199
+ return (0, result_js_1.err)({
200
+ kind: 'parse_error',
201
+ message: `Missing colon in agentConfig entry at line ${lineIndex + 1}: "${acTrimmed}"`,
202
+ lineNumber: lineIndex + 1,
203
+ });
204
+ }
205
+ const acKey = acTrimmed.slice(0, acColonIdx).trim();
206
+ const acRawValue = acTrimmed.slice(acColonIdx + 1).trim();
207
+ if (acRawValue !== '') {
208
+ const acValueResult = parseScalar(acRawValue, lineIndex + 1);
209
+ if (acValueResult.kind === 'err')
210
+ return acValueResult;
211
+ if (acKey === 'model')
212
+ agentConfig.model = acValueResult.value;
213
+ }
214
+ lineIndex++;
215
+ }
216
+ trigger.agentConfig = agentConfig;
217
+ continue;
218
+ }
219
+ if (key === 'onComplete') {
220
+ lineIndex++;
221
+ const onComplete = {};
222
+ while (lineIndex < lines.length) {
223
+ const ocLine = lines[lineIndex];
224
+ if (ocLine === undefined)
225
+ break;
226
+ const ocTrimmed = ocLine.trim();
227
+ if (ocTrimmed === '' || ocTrimmed.startsWith('#')) {
228
+ lineIndex++;
229
+ continue;
230
+ }
231
+ const ocIndent = ocLine.search(/\S/);
232
+ if (ocIndent <= lineIndent)
233
+ break;
234
+ const ocColonIdx = ocTrimmed.indexOf(':');
235
+ if (ocColonIdx === -1) {
236
+ return (0, result_js_1.err)({
237
+ kind: 'parse_error',
238
+ message: `Missing colon in onComplete entry at line ${lineIndex + 1}: "${ocTrimmed}"`,
239
+ lineNumber: lineIndex + 1,
240
+ });
241
+ }
242
+ const ocKey = ocTrimmed.slice(0, ocColonIdx).trim();
243
+ const ocRawValue = ocTrimmed.slice(ocColonIdx + 1).trim();
244
+ if (ocRawValue !== '') {
245
+ const ocValueResult = parseScalar(ocRawValue, lineIndex + 1);
246
+ if (ocValueResult.kind === 'err')
247
+ return ocValueResult;
248
+ switch (ocKey) {
249
+ case 'runOn':
250
+ onComplete.runOn = ocValueResult.value;
251
+ break;
252
+ case 'workflowId':
253
+ onComplete.workflowId = ocValueResult.value;
254
+ break;
255
+ case 'goal':
256
+ onComplete.goal = ocValueResult.value;
257
+ break;
258
+ default: break;
259
+ }
260
+ }
261
+ lineIndex++;
262
+ }
263
+ trigger.onComplete = onComplete;
264
+ continue;
265
+ }
266
+ if (rawValue === '') {
267
+ lineIndex++;
268
+ continue;
269
+ }
270
+ const valueResult = parseScalar(rawValue, lineIndex + 1);
271
+ if (valueResult.kind === 'err')
272
+ return valueResult;
273
+ setTriggerField(trigger, key, valueResult.value);
274
+ lineIndex++;
275
+ }
276
+ triggers.push(trigger);
277
+ }
278
+ return (0, result_js_1.ok)(triggers);
279
+ }
280
+ function setTriggerField(trigger, key, value) {
281
+ switch (key) {
282
+ case 'id':
283
+ trigger.id = value;
284
+ break;
285
+ case 'provider':
286
+ trigger.provider = value;
287
+ break;
288
+ case 'workflowId':
289
+ trigger.workflowId = value;
290
+ break;
291
+ case 'workspacePath':
292
+ trigger.workspacePath = value;
293
+ break;
294
+ case 'goal':
295
+ trigger.goal = value;
296
+ break;
297
+ case 'hmacSecret':
298
+ trigger.hmacSecret = value;
299
+ break;
300
+ case 'goalTemplate':
301
+ trigger.goalTemplate = value;
302
+ break;
303
+ case 'referenceUrls':
304
+ trigger.referenceUrls = value;
305
+ break;
306
+ default:
307
+ break;
308
+ }
309
+ }
310
+ function resolveSecret(value, triggerId, env) {
311
+ if (!value.startsWith('$')) {
312
+ return (0, result_js_1.ok)(value);
313
+ }
314
+ const envVarName = value.slice(1);
315
+ const resolved = env[envVarName];
316
+ if (resolved === undefined || resolved === '') {
317
+ return (0, result_js_1.err)({ kind: 'missing_secret', envVarName, triggerId });
318
+ }
319
+ return (0, result_js_1.ok)(resolved);
320
+ }
321
+ function assembleContextMapping(raw) {
322
+ if (!raw)
323
+ return undefined;
324
+ const mappings = Object.entries(raw).map(([workflowContextKey, payloadPath]) => ({
325
+ workflowContextKey,
326
+ payloadPath,
327
+ }));
328
+ return { mappings };
329
+ }
330
+ function validateAndResolveTrigger(raw, env) {
331
+ const rawId = raw.id?.trim() ?? '';
332
+ if (!rawId) {
333
+ return (0, result_js_1.err)({ kind: 'missing_field', field: 'id', triggerId: '(unknown)' });
334
+ }
335
+ const requiredStringFields = [
336
+ 'provider',
337
+ 'workflowId',
338
+ 'workspacePath',
339
+ 'goal',
340
+ ];
341
+ for (const field of requiredStringFields) {
342
+ const v = raw[field];
343
+ if (!v?.trim()) {
344
+ return (0, result_js_1.err)({ kind: 'missing_field', field, triggerId: rawId });
345
+ }
346
+ }
347
+ const provider = raw.provider.trim();
348
+ if (!SUPPORTED_PROVIDERS.has(provider)) {
349
+ return (0, result_js_1.err)({ kind: 'unknown_provider', provider, triggerId: rawId });
350
+ }
351
+ let hmacSecret;
352
+ if (raw.hmacSecret?.trim()) {
353
+ const secretResult = resolveSecret(raw.hmacSecret.trim(), rawId, env);
354
+ if (secretResult.kind === 'err')
355
+ return secretResult;
356
+ hmacSecret = secretResult.value;
357
+ }
358
+ const goalTemplate = raw.goalTemplate?.trim();
359
+ const referenceUrlsRaw = raw.referenceUrls?.trim();
360
+ const referenceUrls = referenceUrlsRaw
361
+ ? referenceUrlsRaw.split(/\s+/).filter(Boolean)
362
+ : undefined;
363
+ if (referenceUrls) {
364
+ for (const url of referenceUrls) {
365
+ try {
366
+ const parsed = new URL(url);
367
+ if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
368
+ return (0, result_js_1.err)({ kind: 'missing_field', field: `referenceUrls (non-HTTP URL rejected: ${url})`, triggerId: raw.id ?? '?' });
369
+ }
370
+ }
371
+ catch {
372
+ return (0, result_js_1.err)({ kind: 'missing_field', field: `referenceUrls (invalid URL: ${url})`, triggerId: raw.id ?? '?' });
373
+ }
374
+ }
375
+ }
376
+ const agentConfig = raw.agentConfig?.model?.trim()
377
+ ? { model: raw.agentConfig.model.trim() }
378
+ : undefined;
379
+ let onComplete;
380
+ if (raw.onComplete) {
381
+ const rawRunOn = raw.onComplete.runOn?.trim();
382
+ if (rawRunOn && rawRunOn !== 'success') {
383
+ console.warn(`[TriggerStore] UNSUPPORTED: onComplete.runOn='${rawRunOn}' is not implemented yet. ` +
384
+ `This trigger will NOT execute a completion hook on ${rawRunOn}. ` +
385
+ `Only runOn: 'success' is planned for a future release.`);
386
+ }
387
+ if (rawRunOn === 'success' || rawRunOn === 'failure' || rawRunOn === 'always') {
388
+ onComplete = {
389
+ runOn: rawRunOn,
390
+ ...(raw.onComplete.workflowId?.trim() ? { workflowId: raw.onComplete.workflowId.trim() } : {}),
391
+ ...(raw.onComplete.goal?.trim() ? { goal: raw.onComplete.goal.trim() } : {}),
392
+ };
393
+ }
394
+ }
395
+ const trigger = {
396
+ id: (0, types_js_1.asTriggerId)(rawId),
397
+ provider,
398
+ workflowId: raw.workflowId.trim(),
399
+ workspacePath: raw.workspacePath.trim(),
400
+ goal: raw.goal.trim(),
401
+ ...(hmacSecret !== undefined ? { hmacSecret } : {}),
402
+ ...(raw.contextMapping !== undefined
403
+ ? { contextMapping: assembleContextMapping(raw.contextMapping) }
404
+ : {}),
405
+ ...(goalTemplate ? { goalTemplate } : {}),
406
+ ...(referenceUrls !== undefined && referenceUrls.length > 0 ? { referenceUrls } : {}),
407
+ ...(agentConfig !== undefined ? { agentConfig } : {}),
408
+ ...(onComplete !== undefined ? { onComplete } : {}),
409
+ };
410
+ return (0, result_js_1.ok)(trigger);
411
+ }
412
+ function loadTriggerConfig(yamlContent, env = process.env) {
413
+ const parsedResult = parseTriggersYaml(yamlContent);
414
+ if (parsedResult.kind === 'err')
415
+ return parsedResult;
416
+ const validTriggers = [];
417
+ const validationErrors = [];
418
+ for (const rawTrigger of parsedResult.value) {
419
+ const triggerResult = validateAndResolveTrigger(rawTrigger, env);
420
+ if (triggerResult.kind === 'err') {
421
+ console.warn(`[TriggerStore] Skipping invalid trigger: ${JSON.stringify(triggerResult.error)}`);
422
+ validationErrors.push(triggerResult.error);
423
+ continue;
424
+ }
425
+ validTriggers.push(triggerResult.value);
426
+ }
427
+ if (validationErrors.length > 0) {
428
+ console.warn(`[TriggerStore] Loaded ${validTriggers.length} valid trigger(s), ` +
429
+ `skipped ${validationErrors.length} invalid trigger(s).`);
430
+ }
431
+ return (0, result_js_1.ok)({ triggers: validTriggers });
432
+ }
433
+ async function loadTriggerConfigFromFile(workspacePath, env = process.env) {
434
+ const filePath = path.join(workspacePath, 'triggers.yml');
435
+ let content;
436
+ try {
437
+ content = await fs.readFile(filePath, 'utf8');
438
+ }
439
+ catch (e) {
440
+ const error = e;
441
+ if (error.code === 'ENOENT') {
442
+ return (0, result_js_1.err)({ kind: 'file_not_found', filePath });
443
+ }
444
+ return (0, result_js_1.err)({ kind: 'io_error', message: error.message ?? String(e) });
445
+ }
446
+ return loadTriggerConfig(content, env);
447
+ }
448
+ function buildTriggerIndex(config) {
449
+ const index = new Map();
450
+ for (const trigger of config.triggers) {
451
+ if (index.has(trigger.id)) {
452
+ return (0, result_js_1.err)({ kind: 'duplicate_id', triggerId: trigger.id });
453
+ }
454
+ index.set(trigger.id, trigger);
455
+ }
456
+ return (0, result_js_1.ok)(index);
457
+ }