@exaudeus/workrail 3.24.4 → 3.26.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 (82) hide show
  1. package/dist/cli/commands/index.d.ts +6 -0
  2. package/dist/cli/commands/index.js +14 -1
  3. package/dist/cli/commands/version.d.ts +6 -0
  4. package/dist/cli/commands/version.js +14 -0
  5. package/dist/cli/commands/worktrain-await.d.ts +35 -0
  6. package/dist/cli/commands/worktrain-await.js +207 -0
  7. package/dist/cli/commands/worktrain-inbox.d.ts +23 -0
  8. package/dist/cli/commands/worktrain-inbox.js +82 -0
  9. package/dist/cli/commands/worktrain-init.d.ts +23 -0
  10. package/dist/cli/commands/worktrain-init.js +338 -0
  11. package/dist/cli/commands/worktrain-spawn.d.ts +28 -0
  12. package/dist/cli/commands/worktrain-spawn.js +106 -0
  13. package/dist/cli/commands/worktrain-tell.d.ts +25 -0
  14. package/dist/cli/commands/worktrain-tell.js +32 -0
  15. package/dist/cli-worktrain.d.ts +2 -0
  16. package/dist/cli-worktrain.js +169 -0
  17. package/dist/cli.js +100 -0
  18. package/dist/config/config-file.d.ts +2 -0
  19. package/dist/config/config-file.js +55 -0
  20. package/dist/console/assets/index-8dh0Psu-.css +1 -0
  21. package/dist/console/assets/{index-TMfptYpQ.js → index-HhtarvD5.js} +10 -10
  22. package/dist/console/index.html +2 -2
  23. package/dist/daemon/agent-loop.d.ts +90 -0
  24. package/dist/daemon/agent-loop.js +214 -0
  25. package/dist/daemon/pi-mono-loader.d.ts +0 -0
  26. package/dist/daemon/pi-mono-loader.js +1 -0
  27. package/dist/daemon/soul-template.d.ts +2 -0
  28. package/dist/daemon/soul-template.js +22 -0
  29. package/dist/daemon/workflow-runner.d.ts +63 -0
  30. package/dist/daemon/workflow-runner.js +689 -0
  31. package/dist/infrastructure/session/HttpServer.js +2 -2
  32. package/dist/manifest.json +226 -50
  33. package/dist/mcp/handlers/v2-execution/start.d.ts +2 -1
  34. package/dist/mcp/handlers/v2-execution/start.js +4 -3
  35. package/dist/mcp/output-schemas.d.ts +154 -154
  36. package/dist/mcp/server.js +1 -1
  37. package/dist/mcp/transports/bridge-entry.js +20 -2
  38. package/dist/mcp/transports/bridge-events.d.ts +34 -0
  39. package/dist/mcp/transports/bridge-events.js +24 -0
  40. package/dist/mcp/transports/fatal-exit.d.ts +5 -0
  41. package/dist/mcp/transports/fatal-exit.js +82 -0
  42. package/dist/mcp/transports/http-entry.js +3 -0
  43. package/dist/mcp/transports/stdio-entry.js +3 -7
  44. package/dist/mcp/v2/tools.d.ts +7 -7
  45. package/dist/trigger/delivery-action.d.ts +37 -0
  46. package/dist/trigger/delivery-action.js +204 -0
  47. package/dist/trigger/delivery-client.d.ts +11 -0
  48. package/dist/trigger/delivery-client.js +27 -0
  49. package/dist/trigger/index.d.ts +5 -0
  50. package/dist/trigger/index.js +8 -0
  51. package/dist/trigger/trigger-listener.d.ts +32 -0
  52. package/dist/trigger/trigger-listener.js +176 -0
  53. package/dist/trigger/trigger-router.d.ts +38 -0
  54. package/dist/trigger/trigger-router.js +343 -0
  55. package/dist/trigger/trigger-store.d.ts +39 -0
  56. package/dist/trigger/trigger-store.js +698 -0
  57. package/dist/trigger/types.d.ts +70 -0
  58. package/dist/trigger/types.js +10 -0
  59. package/dist/v2/durable-core/schemas/execution-snapshot/blocked-snapshot.d.ts +22 -22
  60. package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.d.ts +114 -114
  61. package/dist/v2/durable-core/schemas/export-bundle/index.d.ts +454 -454
  62. package/dist/v2/durable-core/schemas/session/blockers.d.ts +14 -14
  63. package/dist/v2/durable-core/schemas/session/events.d.ts +93 -93
  64. package/dist/v2/durable-core/schemas/session/gaps.d.ts +2 -2
  65. package/dist/v2/durable-core/schemas/session/validation-event.d.ts +4 -4
  66. package/dist/v2/infra/in-memory/daemon-registry/index.d.ts +14 -0
  67. package/dist/v2/infra/in-memory/daemon-registry/index.js +32 -0
  68. package/dist/v2/infra/in-memory/keyed-async-queue/index.d.ts +5 -0
  69. package/dist/v2/infra/in-memory/keyed-async-queue/index.js +32 -0
  70. package/dist/v2/usecases/console-routes.d.ts +3 -1
  71. package/dist/v2/usecases/console-routes.js +132 -1
  72. package/dist/v2/usecases/console-service.d.ts +2 -0
  73. package/dist/v2/usecases/console-service.js +18 -2
  74. package/dist/v2/usecases/console-types.d.ts +2 -0
  75. package/package.json +6 -2
  76. package/spec/workflow-tags.json +1 -0
  77. package/workflows/classify-task-workflow.json +68 -0
  78. package/workflows/coding-task-workflow-agentic.lean.v2.json +43 -13
  79. package/workflows/workflow-for-workflows.json +4 -2
  80. package/workflows/workflow-for-workflows.v2.json +4 -2
  81. package/dist/console/assets/index-BXRk3te_.css +0 -1
  82. package/workflows/rich-object-contribution.json +0 -258
@@ -0,0 +1,698 @@
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 os = __importStar(require("node:os"));
40
+ const path = __importStar(require("node:path"));
41
+ const fs = __importStar(require("node:fs/promises"));
42
+ const result_js_1 = require("../runtime/result.js");
43
+ const types_js_1 = require("./types.js");
44
+ const SUPPORTED_PROVIDERS = new Set(['generic', 'gitlab_poll']);
45
+ function unquoteYamlScalar(raw) {
46
+ const s = raw.trim();
47
+ if ((s.startsWith('"') && s.endsWith('"')) ||
48
+ (s.startsWith("'") && s.endsWith("'"))) {
49
+ return s.slice(1, -1);
50
+ }
51
+ return s;
52
+ }
53
+ function parseScalar(raw, lineNum) {
54
+ const s = raw.trim();
55
+ if (s.startsWith('[') || s.startsWith('{')) {
56
+ return (0, result_js_1.err)({
57
+ kind: 'parse_error',
58
+ message: `Inline arrays and objects are not supported. Use block style. At line ${lineNum}.`,
59
+ lineNumber: lineNum,
60
+ });
61
+ }
62
+ return (0, result_js_1.ok)(unquoteYamlScalar(s));
63
+ }
64
+ function parseTriggersYaml(content) {
65
+ const lines = content.split('\n');
66
+ const triggers = [];
67
+ let lineIndex = 0;
68
+ const skipBlankAndComments = () => {
69
+ while (lineIndex < lines.length) {
70
+ const l = lines[lineIndex];
71
+ if (l !== undefined && (l.trim() === '' || l.trim().startsWith('#'))) {
72
+ lineIndex++;
73
+ }
74
+ else {
75
+ break;
76
+ }
77
+ }
78
+ };
79
+ skipBlankAndComments();
80
+ if (lineIndex >= lines.length || !lines[lineIndex]?.trim().startsWith('triggers:')) {
81
+ return (0, result_js_1.err)({
82
+ kind: 'parse_error',
83
+ message: `Expected "triggers:" as the root key at line ${lineIndex + 1}.`,
84
+ lineNumber: lineIndex + 1,
85
+ });
86
+ }
87
+ lineIndex++;
88
+ while (lineIndex < lines.length) {
89
+ const line = lines[lineIndex];
90
+ if (line === undefined)
91
+ break;
92
+ const trimmed = line.trim();
93
+ if (trimmed === '' || trimmed.startsWith('#')) {
94
+ lineIndex++;
95
+ continue;
96
+ }
97
+ if (!line.match(/^[ ]{2,}- /)) {
98
+ return (0, result_js_1.err)({
99
+ kind: 'parse_error',
100
+ message: `Expected a list item starting with " - " at line ${lineIndex + 1}. Got: "${trimmed}"`,
101
+ lineNumber: lineIndex + 1,
102
+ });
103
+ }
104
+ const trigger = {};
105
+ const itemIndent = line.indexOf('-');
106
+ const afterDash = line.slice(itemIndent + 1).trim();
107
+ if (afterDash) {
108
+ const colonIdx = afterDash.indexOf(':');
109
+ if (colonIdx === -1) {
110
+ return (0, result_js_1.err)({
111
+ kind: 'parse_error',
112
+ message: `Missing colon in key-value pair at line ${lineIndex + 1}: "${afterDash}"`,
113
+ lineNumber: lineIndex + 1,
114
+ });
115
+ }
116
+ const key = afterDash.slice(0, colonIdx).trim();
117
+ const rawValue = afterDash.slice(colonIdx + 1).trim();
118
+ if (rawValue !== '') {
119
+ const valueResult = parseScalar(rawValue, lineIndex + 1);
120
+ if (valueResult.kind === 'err')
121
+ return valueResult;
122
+ setTriggerField(trigger, key, valueResult.value);
123
+ }
124
+ }
125
+ lineIndex++;
126
+ while (lineIndex < lines.length) {
127
+ const kvLine = lines[lineIndex];
128
+ if (kvLine === undefined)
129
+ break;
130
+ const kTrimmed = kvLine.trim();
131
+ if (kTrimmed === '' || kTrimmed.startsWith('#')) {
132
+ lineIndex++;
133
+ continue;
134
+ }
135
+ const lineIndent = kvLine.search(/\S/);
136
+ if (lineIndent <= itemIndent) {
137
+ break;
138
+ }
139
+ const colonIdx = kTrimmed.indexOf(':');
140
+ if (colonIdx === -1) {
141
+ return (0, result_js_1.err)({
142
+ kind: 'parse_error',
143
+ message: `Missing colon in key-value pair at line ${lineIndex + 1}: "${kTrimmed}"`,
144
+ lineNumber: lineIndex + 1,
145
+ });
146
+ }
147
+ const key = kTrimmed.slice(0, colonIdx).trim();
148
+ const rawValue = kTrimmed.slice(colonIdx + 1).trim();
149
+ if (key === 'contextMapping') {
150
+ lineIndex++;
151
+ const contextMapping = {};
152
+ while (lineIndex < lines.length) {
153
+ const cmLine = lines[lineIndex];
154
+ if (cmLine === undefined)
155
+ break;
156
+ const cmTrimmed = cmLine.trim();
157
+ if (cmTrimmed === '' || cmTrimmed.startsWith('#')) {
158
+ lineIndex++;
159
+ continue;
160
+ }
161
+ const cmIndent = cmLine.search(/\S/);
162
+ if (cmIndent <= lineIndent)
163
+ break;
164
+ const cmColonIdx = cmTrimmed.indexOf(':');
165
+ if (cmColonIdx === -1) {
166
+ return (0, result_js_1.err)({
167
+ kind: 'parse_error',
168
+ message: `Missing colon in contextMapping entry at line ${lineIndex + 1}: "${cmTrimmed}"`,
169
+ lineNumber: lineIndex + 1,
170
+ });
171
+ }
172
+ const cmKey = cmTrimmed.slice(0, cmColonIdx).trim();
173
+ const cmRawValue = cmTrimmed.slice(cmColonIdx + 1).trim();
174
+ const cmValueResult = parseScalar(cmRawValue, lineIndex + 1);
175
+ if (cmValueResult.kind === 'err')
176
+ return cmValueResult;
177
+ contextMapping[cmKey] = cmValueResult.value;
178
+ lineIndex++;
179
+ }
180
+ trigger.contextMapping = contextMapping;
181
+ continue;
182
+ }
183
+ if (key === 'agentConfig') {
184
+ lineIndex++;
185
+ const agentConfig = {};
186
+ while (lineIndex < lines.length) {
187
+ const acLine = lines[lineIndex];
188
+ if (acLine === undefined)
189
+ break;
190
+ const acTrimmed = acLine.trim();
191
+ if (acTrimmed === '' || acTrimmed.startsWith('#')) {
192
+ lineIndex++;
193
+ continue;
194
+ }
195
+ const acIndent = acLine.search(/\S/);
196
+ if (acIndent <= lineIndent)
197
+ break;
198
+ const acColonIdx = acTrimmed.indexOf(':');
199
+ if (acColonIdx === -1) {
200
+ return (0, result_js_1.err)({
201
+ kind: 'parse_error',
202
+ message: `Missing colon in agentConfig entry at line ${lineIndex + 1}: "${acTrimmed}"`,
203
+ lineNumber: lineIndex + 1,
204
+ });
205
+ }
206
+ const acKey = acTrimmed.slice(0, acColonIdx).trim();
207
+ const acRawValue = acTrimmed.slice(acColonIdx + 1).trim();
208
+ if (acRawValue !== '') {
209
+ const acValueResult = parseScalar(acRawValue, lineIndex + 1);
210
+ if (acValueResult.kind === 'err')
211
+ return acValueResult;
212
+ if (acKey === 'model')
213
+ agentConfig.model = acValueResult.value;
214
+ else if (acKey === 'maxSessionMinutes')
215
+ agentConfig.maxSessionMinutes = acValueResult.value;
216
+ else if (acKey === 'maxTurns')
217
+ agentConfig.maxTurns = acValueResult.value;
218
+ }
219
+ lineIndex++;
220
+ }
221
+ trigger.agentConfig = agentConfig;
222
+ continue;
223
+ }
224
+ if (key === 'onComplete') {
225
+ lineIndex++;
226
+ const onComplete = {};
227
+ while (lineIndex < lines.length) {
228
+ const ocLine = lines[lineIndex];
229
+ if (ocLine === undefined)
230
+ break;
231
+ const ocTrimmed = ocLine.trim();
232
+ if (ocTrimmed === '' || ocTrimmed.startsWith('#')) {
233
+ lineIndex++;
234
+ continue;
235
+ }
236
+ const ocIndent = ocLine.search(/\S/);
237
+ if (ocIndent <= lineIndent)
238
+ break;
239
+ const ocColonIdx = ocTrimmed.indexOf(':');
240
+ if (ocColonIdx === -1) {
241
+ return (0, result_js_1.err)({
242
+ kind: 'parse_error',
243
+ message: `Missing colon in onComplete entry at line ${lineIndex + 1}: "${ocTrimmed}"`,
244
+ lineNumber: lineIndex + 1,
245
+ });
246
+ }
247
+ const ocKey = ocTrimmed.slice(0, ocColonIdx).trim();
248
+ const ocRawValue = ocTrimmed.slice(ocColonIdx + 1).trim();
249
+ if (ocRawValue !== '') {
250
+ const ocValueResult = parseScalar(ocRawValue, lineIndex + 1);
251
+ if (ocValueResult.kind === 'err')
252
+ return ocValueResult;
253
+ switch (ocKey) {
254
+ case 'runOn':
255
+ onComplete.runOn = ocValueResult.value;
256
+ break;
257
+ case 'workflowId':
258
+ onComplete.workflowId = ocValueResult.value;
259
+ break;
260
+ case 'goal':
261
+ onComplete.goal = ocValueResult.value;
262
+ break;
263
+ default: break;
264
+ }
265
+ }
266
+ lineIndex++;
267
+ }
268
+ trigger.onComplete = onComplete;
269
+ continue;
270
+ }
271
+ if (key === 'source') {
272
+ lineIndex++;
273
+ const source = {};
274
+ while (lineIndex < lines.length) {
275
+ const srcLine = lines[lineIndex];
276
+ if (srcLine === undefined)
277
+ break;
278
+ const srcTrimmed = srcLine.trim();
279
+ if (srcTrimmed === '' || srcTrimmed.startsWith('#')) {
280
+ lineIndex++;
281
+ continue;
282
+ }
283
+ const srcIndent = srcLine.search(/\S/);
284
+ if (srcIndent <= lineIndent)
285
+ break;
286
+ const srcColonIdx = srcTrimmed.indexOf(':');
287
+ if (srcColonIdx === -1) {
288
+ return (0, result_js_1.err)({
289
+ kind: 'parse_error',
290
+ message: `Missing colon in source entry at line ${lineIndex + 1}: "${srcTrimmed}"`,
291
+ lineNumber: lineIndex + 1,
292
+ });
293
+ }
294
+ const srcKey = srcTrimmed.slice(0, srcColonIdx).trim();
295
+ const srcRawValue = srcTrimmed.slice(srcColonIdx + 1).trim();
296
+ if (srcRawValue !== '') {
297
+ const srcValueResult = parseScalar(srcRawValue, lineIndex + 1);
298
+ if (srcValueResult.kind === 'err')
299
+ return srcValueResult;
300
+ switch (srcKey) {
301
+ case 'baseUrl':
302
+ source.baseUrl = srcValueResult.value;
303
+ break;
304
+ case 'projectId':
305
+ source.projectId = srcValueResult.value;
306
+ break;
307
+ case 'token':
308
+ source.token = srcValueResult.value;
309
+ break;
310
+ case 'events':
311
+ source.events = srcValueResult.value;
312
+ break;
313
+ case 'pollIntervalSeconds':
314
+ source.pollIntervalSeconds = srcValueResult.value;
315
+ break;
316
+ default: break;
317
+ }
318
+ }
319
+ lineIndex++;
320
+ }
321
+ trigger.source = source;
322
+ continue;
323
+ }
324
+ if (rawValue === '') {
325
+ lineIndex++;
326
+ continue;
327
+ }
328
+ const valueResult = parseScalar(rawValue, lineIndex + 1);
329
+ if (valueResult.kind === 'err')
330
+ return valueResult;
331
+ setTriggerField(trigger, key, valueResult.value);
332
+ lineIndex++;
333
+ }
334
+ triggers.push(trigger);
335
+ }
336
+ return (0, result_js_1.ok)(triggers);
337
+ }
338
+ function setTriggerField(trigger, key, value) {
339
+ switch (key) {
340
+ case 'id':
341
+ trigger.id = value;
342
+ break;
343
+ case 'provider':
344
+ trigger.provider = value;
345
+ break;
346
+ case 'workflowId':
347
+ trigger.workflowId = value;
348
+ break;
349
+ case 'workspacePath':
350
+ trigger.workspacePath = value;
351
+ break;
352
+ case 'goal':
353
+ trigger.goal = value;
354
+ break;
355
+ case 'hmacSecret':
356
+ trigger.hmacSecret = value;
357
+ break;
358
+ case 'goalTemplate':
359
+ trigger.goalTemplate = value;
360
+ break;
361
+ case 'referenceUrls':
362
+ trigger.referenceUrls = value;
363
+ break;
364
+ case 'concurrencyMode':
365
+ trigger.concurrencyMode = value;
366
+ break;
367
+ case 'callbackUrl':
368
+ trigger.callbackUrl = value;
369
+ break;
370
+ case 'autoCommit':
371
+ trigger.autoCommit = value;
372
+ break;
373
+ case 'autoOpenPR':
374
+ trigger.autoOpenPR = value;
375
+ break;
376
+ case 'workspaceName':
377
+ trigger.workspaceName = value;
378
+ break;
379
+ case 'soulFile':
380
+ trigger.soulFile = value;
381
+ break;
382
+ default:
383
+ break;
384
+ }
385
+ }
386
+ function expandTildePath(p) {
387
+ return p.startsWith('~/') ? path.join(os.homedir(), p.slice(2)) : p;
388
+ }
389
+ function resolveSecret(value, triggerId, env) {
390
+ if (!value.startsWith('$')) {
391
+ return (0, result_js_1.ok)(value);
392
+ }
393
+ const envVarName = value.slice(1);
394
+ const resolved = env[envVarName];
395
+ if (resolved === undefined || resolved === '') {
396
+ return (0, result_js_1.err)({ kind: 'missing_secret', envVarName, triggerId });
397
+ }
398
+ return (0, result_js_1.ok)(resolved);
399
+ }
400
+ function assembleContextMapping(raw) {
401
+ if (!raw)
402
+ return undefined;
403
+ const mappings = Object.entries(raw).map(([workflowContextKey, payloadPath]) => ({
404
+ workflowContextKey,
405
+ payloadPath,
406
+ }));
407
+ return { mappings };
408
+ }
409
+ function validateAndResolveTrigger(raw, env, workspaces = {}) {
410
+ const rawId = raw.id?.trim() ?? '';
411
+ if (!rawId) {
412
+ return (0, result_js_1.err)({ kind: 'missing_field', field: 'id', triggerId: '(unknown)' });
413
+ }
414
+ const requiredStringFields = [
415
+ 'provider',
416
+ 'workflowId',
417
+ 'goal',
418
+ ];
419
+ for (const field of requiredStringFields) {
420
+ const v = raw[field];
421
+ if (!v?.trim()) {
422
+ return (0, result_js_1.err)({ kind: 'missing_field', field, triggerId: rawId });
423
+ }
424
+ }
425
+ const provider = raw.provider.trim();
426
+ if (!SUPPORTED_PROVIDERS.has(provider)) {
427
+ return (0, result_js_1.err)({ kind: 'unknown_provider', provider, triggerId: rawId });
428
+ }
429
+ let resolvedWorkspacePath;
430
+ let resolvedWorkspaceName;
431
+ let resolvedSoulFile;
432
+ const rawWorkspaceName = raw.workspaceName?.trim();
433
+ const rawWorkspacePath = raw.workspacePath?.trim();
434
+ const rawSoulFile = raw.soulFile?.trim();
435
+ if (rawWorkspaceName) {
436
+ if (!/^[a-zA-Z0-9_-]+$/.test(rawWorkspaceName)) {
437
+ return (0, result_js_1.err)({
438
+ kind: 'invalid_field_value',
439
+ field: `workspaceName (must match ^[a-zA-Z0-9_-]+$, got: "${rawWorkspaceName}")`,
440
+ triggerId: rawId,
441
+ });
442
+ }
443
+ const workspaceConfig = workspaces[rawWorkspaceName];
444
+ if (!workspaceConfig) {
445
+ return (0, result_js_1.err)({ kind: 'unknown_workspace', workspaceName: rawWorkspaceName, triggerId: rawId });
446
+ }
447
+ if (!path.isAbsolute(workspaceConfig.path)) {
448
+ return (0, result_js_1.err)({
449
+ kind: 'invalid_field_value',
450
+ field: `workspace "${rawWorkspaceName}".path (must be absolute, got: "${workspaceConfig.path}")`,
451
+ triggerId: rawId,
452
+ });
453
+ }
454
+ if (rawWorkspacePath) {
455
+ console.warn(`[TriggerStore] WARNING: trigger "${rawId}" has both workspaceName and workspacePath. ` +
456
+ `workspaceName takes precedence; workspacePath "${rawWorkspacePath}" is ignored.`);
457
+ }
458
+ resolvedWorkspacePath = workspaceConfig.path;
459
+ resolvedWorkspaceName = (0, types_js_1.asWorkspaceName)(rawWorkspaceName);
460
+ resolvedSoulFile = rawSoulFile ?? workspaceConfig.soulFile;
461
+ }
462
+ else {
463
+ if (!rawWorkspacePath) {
464
+ return (0, result_js_1.err)({ kind: 'missing_field', field: 'workspacePath', triggerId: rawId });
465
+ }
466
+ resolvedWorkspacePath = rawWorkspacePath;
467
+ resolvedSoulFile = rawSoulFile;
468
+ }
469
+ if (resolvedSoulFile) {
470
+ resolvedSoulFile = expandTildePath(resolvedSoulFile);
471
+ }
472
+ if (resolvedSoulFile && !path.isAbsolute(resolvedSoulFile)) {
473
+ return (0, result_js_1.err)({ kind: 'invalid_field_value', field: 'soulFile', triggerId: rawId });
474
+ }
475
+ let hmacSecret;
476
+ if (raw.hmacSecret?.trim()) {
477
+ const secretResult = resolveSecret(raw.hmacSecret.trim(), rawId, env);
478
+ if (secretResult.kind === 'err')
479
+ return secretResult;
480
+ hmacSecret = secretResult.value;
481
+ }
482
+ const goalTemplate = raw.goalTemplate?.trim();
483
+ const referenceUrlsRaw = raw.referenceUrls?.trim();
484
+ const referenceUrls = referenceUrlsRaw
485
+ ? referenceUrlsRaw.split(/\s+/).filter(Boolean)
486
+ : undefined;
487
+ if (referenceUrls) {
488
+ for (const url of referenceUrls) {
489
+ try {
490
+ const parsed = new URL(url);
491
+ if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
492
+ return (0, result_js_1.err)({ kind: 'invalid_field_value', field: `referenceUrls (non-HTTP URL rejected: ${url})`, triggerId: raw.id ?? '?' });
493
+ }
494
+ }
495
+ catch {
496
+ return (0, result_js_1.err)({ kind: 'invalid_field_value', field: `referenceUrls (invalid URL: ${url})`, triggerId: raw.id ?? '?' });
497
+ }
498
+ }
499
+ }
500
+ let callbackUrl;
501
+ if (raw.callbackUrl?.trim()) {
502
+ const rawCb = raw.callbackUrl.trim();
503
+ try {
504
+ const parsedCb = new URL(rawCb);
505
+ if (parsedCb.protocol !== 'https:' && parsedCb.protocol !== 'http:') {
506
+ return (0, result_js_1.err)({ kind: 'invalid_field_value', field: `callbackUrl (non-HTTP URL rejected: ${rawCb})`, triggerId: rawId });
507
+ }
508
+ }
509
+ catch {
510
+ return (0, result_js_1.err)({ kind: 'invalid_field_value', field: `callbackUrl (invalid URL: ${rawCb})`, triggerId: rawId });
511
+ }
512
+ callbackUrl = rawCb;
513
+ }
514
+ let agentConfig;
515
+ if (raw.agentConfig) {
516
+ const model = raw.agentConfig.model?.trim() || undefined;
517
+ let maxSessionMinutes;
518
+ if (raw.agentConfig.maxSessionMinutes !== undefined) {
519
+ const asNumber = Number(raw.agentConfig.maxSessionMinutes);
520
+ if (!Number.isInteger(asNumber) || asNumber <= 0) {
521
+ return (0, result_js_1.err)({
522
+ kind: 'invalid_field_value',
523
+ field: 'agentConfig.maxSessionMinutes (must be a positive integer)',
524
+ triggerId: rawId,
525
+ });
526
+ }
527
+ maxSessionMinutes = asNumber;
528
+ }
529
+ let maxTurns;
530
+ if (raw.agentConfig.maxTurns !== undefined) {
531
+ const asNumber = Number(raw.agentConfig.maxTurns);
532
+ if (!Number.isInteger(asNumber) || asNumber <= 0) {
533
+ return (0, result_js_1.err)({
534
+ kind: 'invalid_field_value',
535
+ field: 'agentConfig.maxTurns (must be a positive integer)',
536
+ triggerId: rawId,
537
+ });
538
+ }
539
+ maxTurns = asNumber;
540
+ }
541
+ if (model !== undefined || maxSessionMinutes !== undefined || maxTurns !== undefined) {
542
+ agentConfig = {
543
+ ...(model !== undefined ? { model } : {}),
544
+ ...(maxSessionMinutes !== undefined ? { maxSessionMinutes } : {}),
545
+ ...(maxTurns !== undefined ? { maxTurns } : {}),
546
+ };
547
+ }
548
+ }
549
+ const rawConcurrencyMode = raw.concurrencyMode?.trim();
550
+ if (rawConcurrencyMode !== undefined && rawConcurrencyMode !== 'serial' && rawConcurrencyMode !== 'parallel') {
551
+ return (0, result_js_1.err)({
552
+ kind: 'invalid_field_value',
553
+ field: `concurrencyMode (invalid value: "${rawConcurrencyMode}"; must be "serial" or "parallel")`,
554
+ triggerId: rawId,
555
+ });
556
+ }
557
+ const concurrencyMode = rawConcurrencyMode === 'parallel' ? 'parallel' : 'serial';
558
+ let onComplete;
559
+ if (raw.onComplete) {
560
+ const rawRunOn = raw.onComplete.runOn?.trim();
561
+ if (rawRunOn && rawRunOn !== 'success') {
562
+ console.warn(`[TriggerStore] UNSUPPORTED: onComplete.runOn='${rawRunOn}' is not implemented yet. ` +
563
+ `This trigger will NOT execute a completion hook on ${rawRunOn}. ` +
564
+ `Only runOn: 'success' is planned for a future release.`);
565
+ }
566
+ if (rawRunOn === 'success' || rawRunOn === 'failure' || rawRunOn === 'always') {
567
+ onComplete = {
568
+ runOn: rawRunOn,
569
+ ...(raw.onComplete.workflowId?.trim() ? { workflowId: raw.onComplete.workflowId.trim() } : {}),
570
+ ...(raw.onComplete.goal?.trim() ? { goal: raw.onComplete.goal.trim() } : {}),
571
+ };
572
+ }
573
+ }
574
+ const autoCommit = raw.autoCommit?.trim().toLowerCase() === 'true';
575
+ const autoOpenPR = raw.autoOpenPR?.trim().toLowerCase() === 'true';
576
+ if (autoOpenPR && !autoCommit) {
577
+ console.warn(`[TriggerStore] Warning: trigger "${rawId}" has autoOpenPR: true but autoCommit is not true. ` +
578
+ `A PR requires a commit -- delivery will be skipped unless autoCommit is also set to true.`);
579
+ }
580
+ let pollingSource;
581
+ if (provider === 'gitlab_poll') {
582
+ if (!raw.source) {
583
+ return (0, result_js_1.err)({ kind: 'missing_field', field: 'source', triggerId: rawId });
584
+ }
585
+ const src = raw.source;
586
+ const requiredSourceFields = [
587
+ 'baseUrl', 'projectId', 'token', 'events',
588
+ ];
589
+ for (const field of requiredSourceFields) {
590
+ if (!src[field]?.trim()) {
591
+ return (0, result_js_1.err)({ kind: 'missing_field', field: `source.${field}`, triggerId: rawId });
592
+ }
593
+ }
594
+ const tokenRaw = src.token.trim();
595
+ const tokenResult = resolveSecret(tokenRaw, rawId, env);
596
+ if (tokenResult.kind === 'err')
597
+ return tokenResult;
598
+ const eventsRaw = src.events.trim();
599
+ const events = eventsRaw.split(/\s+/).filter(Boolean);
600
+ if (events.length === 0) {
601
+ return (0, result_js_1.err)({ kind: 'missing_field', field: 'source.events (empty)', triggerId: rawId });
602
+ }
603
+ const intervalRaw = src.pollIntervalSeconds?.trim();
604
+ let pollIntervalSeconds = 60;
605
+ if (intervalRaw) {
606
+ const asNumber = Number(intervalRaw);
607
+ if (!Number.isInteger(asNumber) || asNumber <= 0) {
608
+ return (0, result_js_1.err)({
609
+ kind: 'invalid_field_value',
610
+ field: `source.pollIntervalSeconds (must be a positive integer, got: ${intervalRaw})`,
611
+ triggerId: rawId,
612
+ });
613
+ }
614
+ pollIntervalSeconds = asNumber;
615
+ }
616
+ pollingSource = {
617
+ baseUrl: src.baseUrl.trim(),
618
+ projectId: src.projectId.trim(),
619
+ token: tokenResult.value,
620
+ events,
621
+ pollIntervalSeconds,
622
+ };
623
+ }
624
+ else if (raw.source) {
625
+ console.warn(`[TriggerStore] WARNING: trigger '${rawId}' has provider='${provider}' but also ` +
626
+ `defines a source: block. The source: block is only used for provider='gitlab_poll'. ` +
627
+ `It will be ignored for this trigger.`);
628
+ }
629
+ const trigger = {
630
+ id: (0, types_js_1.asTriggerId)(rawId),
631
+ provider,
632
+ workflowId: raw.workflowId.trim(),
633
+ workspacePath: resolvedWorkspacePath,
634
+ goal: raw.goal.trim(),
635
+ concurrencyMode,
636
+ ...(hmacSecret !== undefined ? { hmacSecret } : {}),
637
+ ...(raw.contextMapping !== undefined
638
+ ? { contextMapping: assembleContextMapping(raw.contextMapping) }
639
+ : {}),
640
+ ...(goalTemplate ? { goalTemplate } : {}),
641
+ ...(referenceUrls !== undefined && referenceUrls.length > 0 ? { referenceUrls } : {}),
642
+ ...(agentConfig !== undefined ? { agentConfig } : {}),
643
+ ...(callbackUrl !== undefined ? { callbackUrl } : {}),
644
+ ...(onComplete !== undefined ? { onComplete } : {}),
645
+ ...(autoCommit ? { autoCommit } : {}),
646
+ ...(autoOpenPR ? { autoOpenPR } : {}),
647
+ ...(pollingSource !== undefined ? { pollingSource } : {}),
648
+ ...(resolvedWorkspaceName !== undefined ? { workspaceName: resolvedWorkspaceName } : {}),
649
+ ...(resolvedSoulFile ? { soulFile: resolvedSoulFile } : {}),
650
+ };
651
+ return (0, result_js_1.ok)(trigger);
652
+ }
653
+ function loadTriggerConfig(yamlContent, env = process.env, workspaces = {}) {
654
+ const parsedResult = parseTriggersYaml(yamlContent);
655
+ if (parsedResult.kind === 'err')
656
+ return parsedResult;
657
+ const validTriggers = [];
658
+ const validationErrors = [];
659
+ for (const rawTrigger of parsedResult.value) {
660
+ const triggerResult = validateAndResolveTrigger(rawTrigger, env, workspaces);
661
+ if (triggerResult.kind === 'err') {
662
+ console.warn(`[TriggerStore] Skipping invalid trigger: ${JSON.stringify(triggerResult.error)}`);
663
+ validationErrors.push(triggerResult.error);
664
+ continue;
665
+ }
666
+ validTriggers.push(triggerResult.value);
667
+ }
668
+ if (validationErrors.length > 0) {
669
+ console.warn(`[TriggerStore] Loaded ${validTriggers.length} valid trigger(s), ` +
670
+ `skipped ${validationErrors.length} invalid trigger(s).`);
671
+ }
672
+ return (0, result_js_1.ok)({ triggers: validTriggers });
673
+ }
674
+ async function loadTriggerConfigFromFile(workspacePath, env = process.env, workspaces = {}) {
675
+ const filePath = path.join(workspacePath, 'triggers.yml');
676
+ let content;
677
+ try {
678
+ content = await fs.readFile(filePath, 'utf8');
679
+ }
680
+ catch (e) {
681
+ const error = e;
682
+ if (error.code === 'ENOENT') {
683
+ return (0, result_js_1.err)({ kind: 'file_not_found', filePath });
684
+ }
685
+ return (0, result_js_1.err)({ kind: 'io_error', message: error.message ?? String(e) });
686
+ }
687
+ return loadTriggerConfig(content, env, workspaces);
688
+ }
689
+ function buildTriggerIndex(config) {
690
+ const index = new Map();
691
+ for (const trigger of config.triggers) {
692
+ if (index.has(trigger.id)) {
693
+ return (0, result_js_1.err)({ kind: 'duplicate_id', triggerId: trigger.id });
694
+ }
695
+ index.set(trigger.id, trigger);
696
+ }
697
+ return (0, result_js_1.ok)(index);
698
+ }