@yattalo/task-system 0.3.6 → 0.5.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 (77) hide show
  1. package/README.md +48 -0
  2. package/dashboard-app/assets/spa-entry-CnIKatv4.js +24 -0
  3. package/dashboard-app/assets/styles-CAIFwsCh.css +1 -0
  4. package/dashboard-app/index.html +14 -0
  5. package/dist/commands/agent.d.ts +2 -0
  6. package/dist/commands/agent.d.ts.map +1 -0
  7. package/dist/commands/agent.js +97 -0
  8. package/dist/commands/agent.js.map +1 -0
  9. package/dist/commands/context.d.ts +2 -0
  10. package/dist/commands/context.d.ts.map +1 -0
  11. package/dist/commands/context.js +151 -0
  12. package/dist/commands/context.js.map +1 -0
  13. package/dist/commands/dashboard.d.ts +2 -0
  14. package/dist/commands/dashboard.d.ts.map +1 -1
  15. package/dist/commands/dashboard.js +133 -6
  16. package/dist/commands/dashboard.js.map +1 -1
  17. package/dist/commands/day.d.ts +9 -0
  18. package/dist/commands/day.d.ts.map +1 -0
  19. package/dist/commands/day.js +98 -0
  20. package/dist/commands/day.js.map +1 -0
  21. package/dist/commands/drift.d.ts +2 -0
  22. package/dist/commands/drift.d.ts.map +1 -0
  23. package/dist/commands/drift.js +190 -0
  24. package/dist/commands/drift.js.map +1 -0
  25. package/dist/commands/init.d.ts.map +1 -1
  26. package/dist/commands/init.js +35 -1
  27. package/dist/commands/init.js.map +1 -1
  28. package/dist/generators/mgrep-setup.d.ts +6 -0
  29. package/dist/generators/mgrep-setup.d.ts.map +1 -0
  30. package/dist/generators/mgrep-setup.js +191 -0
  31. package/dist/generators/mgrep-setup.js.map +1 -0
  32. package/dist/generators/mgrep-skill.d.ts +6 -0
  33. package/dist/generators/mgrep-skill.d.ts.map +1 -0
  34. package/dist/generators/mgrep-skill.js +173 -0
  35. package/dist/generators/mgrep-skill.js.map +1 -0
  36. package/dist/generators/uca-functions.d.ts +8 -0
  37. package/dist/generators/uca-functions.d.ts.map +1 -0
  38. package/dist/generators/uca-functions.js +57 -0
  39. package/dist/generators/uca-functions.js.map +1 -0
  40. package/dist/generators/uca-reexports.d.ts +8 -0
  41. package/dist/generators/uca-reexports.d.ts.map +1 -0
  42. package/dist/generators/uca-reexports.js +112 -0
  43. package/dist/generators/uca-reexports.js.map +1 -0
  44. package/dist/generators/uca-schema.d.ts +8 -0
  45. package/dist/generators/uca-schema.d.ts.map +1 -0
  46. package/dist/generators/uca-schema.js +650 -0
  47. package/dist/generators/uca-schema.js.map +1 -0
  48. package/dist/index.js +19 -1
  49. package/dist/index.js.map +1 -1
  50. package/dist/presets/research.d.ts.map +1 -1
  51. package/dist/presets/research.js +10 -0
  52. package/dist/presets/research.js.map +1 -1
  53. package/dist/presets/software.d.ts.map +1 -1
  54. package/dist/presets/software.js +10 -0
  55. package/dist/presets/software.js.map +1 -1
  56. package/dist/utils/convex-run.d.ts +10 -0
  57. package/dist/utils/convex-run.d.ts.map +1 -0
  58. package/dist/utils/convex-run.js +34 -0
  59. package/dist/utils/convex-run.js.map +1 -0
  60. package/dist/utils/detect.d.ts.map +1 -1
  61. package/dist/utils/detect.js +15 -0
  62. package/dist/utils/detect.js.map +1 -1
  63. package/dist/utils/merge.d.ts.map +1 -1
  64. package/dist/utils/merge.js +2 -0
  65. package/dist/utils/merge.js.map +1 -1
  66. package/package.json +6 -4
  67. package/templates/uca/agents.ts +59 -0
  68. package/templates/uca/contextEntries.ts +125 -0
  69. package/templates/uca/cronManager.ts +255 -0
  70. package/templates/uca/cronUtils.ts +99 -0
  71. package/templates/uca/driftEvents.ts +106 -0
  72. package/templates/uca/heartbeats.ts +167 -0
  73. package/templates/uca/hooks.ts +430 -0
  74. package/templates/uca/memory.ts +326 -0
  75. package/templates/uca/sessionBridge.ts +238 -0
  76. package/templates/uca/skills.ts +284 -0
  77. package/templates/uca/ucaTasks.ts +500 -0
@@ -0,0 +1,430 @@
1
+ import { mutation, query } from "../_generated/server";
2
+ import { v } from "convex/values";
3
+
4
+ function makeId(prefix: string): string {
5
+ return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
6
+ }
7
+
8
+ function safeString(value: unknown): string {
9
+ if (typeof value === "string") return value;
10
+ if (value === null || value === undefined) return "";
11
+ try {
12
+ return JSON.stringify(value);
13
+ } catch {
14
+ return String(value);
15
+ }
16
+ }
17
+
18
+ function matchesRules(rules: any, payload: any): boolean {
19
+ if (!rules) return true;
20
+
21
+ if (rules.equals && typeof rules.equals === "object") {
22
+ for (const [key, expected] of Object.entries(rules.equals)) {
23
+ if ((payload as any)?.[key] !== expected) return false;
24
+ }
25
+ }
26
+
27
+ if (rules.contains && typeof rules.contains === "object") {
28
+ for (const [key, expected] of Object.entries(rules.contains)) {
29
+ const source = safeString((payload as any)?.[key]).toLowerCase();
30
+ if (!source.includes(String(expected).toLowerCase())) return false;
31
+ }
32
+ }
33
+
34
+ if (Array.isArray(rules.taskStatusIn)) {
35
+ const status = (payload as any)?.status;
36
+ if (!rules.taskStatusIn.includes(status)) return false;
37
+ }
38
+
39
+ return true;
40
+ }
41
+
42
+ async function findTaskByTaskId(ctx: any, taskId: string): Promise<any | null> {
43
+ return await ctx.db
44
+ .query("tasks")
45
+ .withIndex("by_taskId", (q: any) => q.eq("taskId", taskId))
46
+ .first();
47
+ }
48
+
49
+ async function ensureJobForTask(ctx: any, task: any, lane: "main" | "cron" | "subagent"): Promise<string> {
50
+ const existingJob = await ctx.db
51
+ .query("agentOpsJobs")
52
+ .withIndex("by_taskId", (q: any) => q.eq("taskId", task.taskId))
53
+ .first();
54
+ if (existingJob) return existingJob.jobId;
55
+
56
+ const now = Date.now();
57
+ const jobId = makeId(`job-${task.taskId.toLowerCase()}`);
58
+ await ctx.db.insert("agentOpsJobs", {
59
+ jobId,
60
+ taskId: task.taskId,
61
+ name: `${task.taskId} - ${task.title}`,
62
+ description: task.description,
63
+ agent: task.agent,
64
+ lane,
65
+ scheduleType: "manual",
66
+ cronExpression: undefined,
67
+ intervalMinutes: undefined,
68
+ enabled: true,
69
+ status: "active",
70
+ triggerEvent: undefined,
71
+ triggerFilter: undefined,
72
+ prompt: `Webhook-triggered job for ${task.taskId}`,
73
+ metadata: { source: "hooks" },
74
+ lastRunAt: undefined,
75
+ nextRunAt: undefined,
76
+ totalRuns: 0,
77
+ successRuns: 0,
78
+ failureRuns: 0,
79
+ createdAt: now,
80
+ updatedAt: now,
81
+ });
82
+
83
+ await ctx.db.patch(task._id, {
84
+ linkedJobId: jobId,
85
+ updatedAt: now,
86
+ });
87
+
88
+ return jobId;
89
+ }
90
+
91
+ async function createTaskFromEvent(ctx: any, event: any, endpoint: any, payload: any): Promise<any> {
92
+ const now = Date.now();
93
+ const providedTaskId = typeof payload?.taskId === "string" ? payload.taskId : undefined;
94
+ const taskId = providedTaskId ?? makeId(`EV-${event.eventType.toUpperCase()}`);
95
+
96
+ const existing = await findTaskByTaskId(ctx, taskId);
97
+ if (existing) return existing;
98
+
99
+ const task = {
100
+ taskId,
101
+ agent: endpoint.targetAgent ?? payload?.agent ?? "codex",
102
+ title: payload?.title ?? `Event ${event.eventType}`,
103
+ description:
104
+ payload?.description ??
105
+ [
106
+ `Generated from event ${event.eventType}`,
107
+ `Source: ${event.source}`,
108
+ `Event ID: ${event.eventId}`,
109
+ "",
110
+ safeString(payload),
111
+ ].join("\n"),
112
+ status: "todo",
113
+ priority: payload?.priority ?? "medium",
114
+ category: payload?.category ?? "backend",
115
+ phaseId: payload?.phaseId,
116
+ tags: ["webhook", "event-driven"],
117
+ createdAt: now,
118
+ updatedAt: now,
119
+ statusHistory: [
120
+ {
121
+ status: "todo",
122
+ timestamp: now,
123
+ agent: endpoint.targetAgent ?? payload?.agent ?? "codex",
124
+ note: `Created by webhook endpoint ${endpoint.webhookId}`,
125
+ },
126
+ ],
127
+ commits: [],
128
+ notes: `Triggered by event ${event.eventId}`,
129
+ } as any;
130
+
131
+ await ctx.db.insert("tasks", task);
132
+ return await findTaskByTaskId(ctx, taskId);
133
+ }
134
+
135
+ async function processEvent(ctx: any, event: any): Promise<{
136
+ routed: number;
137
+ createdTasks: string[];
138
+ createdJobs: string[];
139
+ }> {
140
+ const endpoints = await ctx.db
141
+ .query("webhookEndpoints")
142
+ .withIndex("by_enabled", (q: any) => q.eq("enabled", true))
143
+ .collect();
144
+
145
+ const matched = endpoints.filter(
146
+ (endpoint: any) =>
147
+ (endpoint.eventType === event.eventType || endpoint.eventType === "*") &&
148
+ matchesRules(endpoint.rules, event.payload),
149
+ );
150
+
151
+ const createdTasks: string[] = [];
152
+ const createdJobs: string[] = [];
153
+
154
+ for (const endpoint of matched) {
155
+ const deliveryId = makeId("delivery");
156
+ await ctx.db.insert("webhookDeliveries", {
157
+ deliveryId,
158
+ webhookId: endpoint.webhookId,
159
+ eventId: event.eventId,
160
+ status: "received",
161
+ payload: event.payload,
162
+ error: undefined,
163
+ receivedAt: Date.now(),
164
+ processedAt: undefined,
165
+ });
166
+
167
+ try {
168
+ let task = event.taskId ? await findTaskByTaskId(ctx, event.taskId) : null;
169
+ if (endpoint.createTask) {
170
+ task = await createTaskFromEvent(ctx, event, endpoint, event.payload);
171
+ if (task) createdTasks.push(task.taskId);
172
+ }
173
+
174
+ if (endpoint.createJob) {
175
+ const taskForJob = task ?? (event.taskId ? await findTaskByTaskId(ctx, event.taskId) : null);
176
+ if (taskForJob) {
177
+ const jobId = await ensureJobForTask(ctx, taskForJob, "main");
178
+ createdJobs.push(jobId);
179
+ }
180
+ }
181
+
182
+ await ctx.db.patch(endpoint._id, {
183
+ lastTriggeredAt: Date.now(),
184
+ updatedAt: Date.now(),
185
+ });
186
+
187
+ const delivery = await ctx.db
188
+ .query("webhookDeliveries")
189
+ .withIndex("by_deliveryId", (q: any) => q.eq("deliveryId", deliveryId))
190
+ .first();
191
+ if (delivery) {
192
+ await ctx.db.patch(delivery._id, {
193
+ status: "processed",
194
+ processedAt: Date.now(),
195
+ });
196
+ }
197
+ } catch (error: any) {
198
+ const delivery = await ctx.db
199
+ .query("webhookDeliveries")
200
+ .withIndex("by_deliveryId", (q: any) => q.eq("deliveryId", deliveryId))
201
+ .first();
202
+ if (delivery) {
203
+ await ctx.db.patch(delivery._id, {
204
+ status: "failed",
205
+ error: error?.message ?? String(error),
206
+ processedAt: Date.now(),
207
+ });
208
+ }
209
+ }
210
+ }
211
+
212
+ await ctx.db.patch(event._id, {
213
+ handled: true,
214
+ handledAt: Date.now(),
215
+ });
216
+
217
+ return {
218
+ routed: matched.length,
219
+ createdTasks: [...new Set(createdTasks)],
220
+ createdJobs: [...new Set(createdJobs)],
221
+ };
222
+ }
223
+
224
+ export const listEvents = query({
225
+ args: {
226
+ eventType: v.optional(v.string()),
227
+ handled: v.optional(v.boolean()),
228
+ limit: v.optional(v.number()),
229
+ },
230
+ handler: async (ctx, args) => {
231
+ let events = await ctx.db.query("agentEvents").collect();
232
+ if (args.eventType) events = events.filter((event) => event.eventType === args.eventType);
233
+ if (args.handled !== undefined) events = events.filter((event) => event.handled === args.handled);
234
+ events.sort((a, b) => b.createdAt - a.createdAt);
235
+ return args.limit ? events.slice(0, args.limit) : events;
236
+ },
237
+ });
238
+
239
+ export const listWebhooks = query({
240
+ args: {
241
+ enabled: v.optional(v.boolean()),
242
+ },
243
+ handler: async (ctx, { enabled }) => {
244
+ let endpoints = await ctx.db.query("webhookEndpoints").collect();
245
+ if (enabled !== undefined) endpoints = endpoints.filter((endpoint) => endpoint.enabled === enabled);
246
+ return endpoints.sort((a, b) => b.updatedAt - a.updatedAt);
247
+ },
248
+ });
249
+
250
+ export const registerWebhook = mutation({
251
+ args: {
252
+ webhookId: v.optional(v.string()),
253
+ name: v.string(),
254
+ path: v.string(),
255
+ secret: v.optional(v.string()),
256
+ enabled: v.optional(v.boolean()),
257
+ eventType: v.string(),
258
+ rules: v.optional(v.any()),
259
+ targetAgent: v.optional(v.string()),
260
+ createTask: v.optional(v.boolean()),
261
+ createJob: v.optional(v.boolean()),
262
+ createdBy: v.string(),
263
+ },
264
+ handler: async (ctx, args) => {
265
+ const now = Date.now();
266
+ const webhookId = args.webhookId ?? makeId("wh");
267
+
268
+ const existing = await ctx.db
269
+ .query("webhookEndpoints")
270
+ .withIndex("by_webhookId", (q: any) => q.eq("webhookId", webhookId))
271
+ .first();
272
+
273
+ if (existing) {
274
+ await ctx.db.patch(existing._id, {
275
+ name: args.name,
276
+ path: args.path,
277
+ secret: args.secret,
278
+ enabled: args.enabled ?? existing.enabled,
279
+ eventType: args.eventType,
280
+ rules: args.rules,
281
+ targetAgent: args.targetAgent,
282
+ createTask: args.createTask ?? existing.createTask,
283
+ createJob: args.createJob ?? existing.createJob,
284
+ updatedAt: now,
285
+ });
286
+ return { webhookId, updated: true };
287
+ }
288
+
289
+ await ctx.db.insert("webhookEndpoints", {
290
+ webhookId,
291
+ name: args.name,
292
+ path: args.path,
293
+ secret: args.secret,
294
+ enabled: args.enabled ?? true,
295
+ eventType: args.eventType,
296
+ rules: args.rules,
297
+ targetAgent: args.targetAgent,
298
+ createTask: args.createTask ?? true,
299
+ createJob: args.createJob ?? false,
300
+ createdBy: args.createdBy,
301
+ lastTriggeredAt: undefined,
302
+ createdAt: now,
303
+ updatedAt: now,
304
+ });
305
+
306
+ return { webhookId, created: true };
307
+ },
308
+ });
309
+
310
+ export const setWebhookEnabled = mutation({
311
+ args: {
312
+ webhookId: v.string(),
313
+ enabled: v.boolean(),
314
+ },
315
+ handler: async (ctx, { webhookId, enabled }) => {
316
+ const endpoint = await ctx.db
317
+ .query("webhookEndpoints")
318
+ .withIndex("by_webhookId", (q: any) => q.eq("webhookId", webhookId))
319
+ .first();
320
+ if (!endpoint) throw new Error(`Webhook ${webhookId} not found`);
321
+
322
+ await ctx.db.patch(endpoint._id, {
323
+ enabled,
324
+ updatedAt: Date.now(),
325
+ });
326
+ },
327
+ });
328
+
329
+ export const emitEvent = mutation({
330
+ args: {
331
+ eventId: v.optional(v.string()),
332
+ eventType: v.string(),
333
+ source: v.string(),
334
+ sourceId: v.optional(v.string()),
335
+ taskId: v.optional(v.string()),
336
+ payload: v.optional(v.any()),
337
+ },
338
+ handler: async (ctx, args) => {
339
+ const now = Date.now();
340
+ const eventId = args.eventId ?? makeId("evt");
341
+
342
+ const existing = await ctx.db
343
+ .query("agentEvents")
344
+ .withIndex("by_eventId", (q: any) => q.eq("eventId", eventId))
345
+ .first();
346
+ if (existing) {
347
+ return { eventId, duplicated: true };
348
+ }
349
+
350
+ await ctx.db.insert("agentEvents", {
351
+ eventId,
352
+ eventType: args.eventType,
353
+ source: args.source,
354
+ sourceId: args.sourceId,
355
+ taskId: args.taskId,
356
+ payload: args.payload,
357
+ handled: false,
358
+ handledAt: undefined,
359
+ createdAt: now,
360
+ });
361
+
362
+ const event = await ctx.db
363
+ .query("agentEvents")
364
+ .withIndex("by_eventId", (q: any) => q.eq("eventId", eventId))
365
+ .first();
366
+ if (!event) throw new Error(`Event ${eventId} not found after insert`);
367
+
368
+ const routing = await processEvent(ctx, event);
369
+ return { eventId, ...routing };
370
+ },
371
+ });
372
+
373
+ export const ingestWebhook = mutation({
374
+ args: {
375
+ path: v.string(),
376
+ secret: v.optional(v.string()),
377
+ payload: v.optional(v.any()),
378
+ headers: v.optional(v.any()),
379
+ },
380
+ handler: async (ctx, args) => {
381
+ const endpoint = await ctx.db
382
+ .query("webhookEndpoints")
383
+ .withIndex("by_path", (q: any) => q.eq("path", args.path))
384
+ .first();
385
+
386
+ if (!endpoint || !endpoint.enabled) {
387
+ return { accepted: false, reason: "endpoint_not_found_or_disabled" };
388
+ }
389
+
390
+ if (endpoint.secret && endpoint.secret !== args.secret) {
391
+ await ctx.db.insert("webhookDeliveries", {
392
+ deliveryId: makeId("delivery"),
393
+ webhookId: endpoint.webhookId,
394
+ eventId: undefined,
395
+ status: "rejected",
396
+ payload: args.payload,
397
+ error: "invalid_secret",
398
+ receivedAt: Date.now(),
399
+ processedAt: Date.now(),
400
+ });
401
+ return { accepted: false, reason: "invalid_secret" };
402
+ }
403
+
404
+ const eventId = makeId("evt-webhook");
405
+ await ctx.db.insert("agentEvents", {
406
+ eventId,
407
+ eventType: endpoint.eventType,
408
+ source: "webhook",
409
+ sourceId: endpoint.webhookId,
410
+ taskId: typeof args.payload?.taskId === "string" ? args.payload.taskId : undefined,
411
+ payload: {
412
+ ...(args.payload ?? {}),
413
+ _headers: args.headers,
414
+ _path: args.path,
415
+ },
416
+ handled: false,
417
+ handledAt: undefined,
418
+ createdAt: Date.now(),
419
+ });
420
+
421
+ const event = await ctx.db
422
+ .query("agentEvents")
423
+ .withIndex("by_eventId", (q: any) => q.eq("eventId", eventId))
424
+ .first();
425
+ if (!event) throw new Error(`Webhook event ${eventId} not found`);
426
+
427
+ const routing = await processEvent(ctx, event);
428
+ return { accepted: true, eventId, ...routing };
429
+ },
430
+ });