forge-openclaw-plugin 0.2.18 → 0.2.19

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 (56) hide show
  1. package/README.md +36 -4
  2. package/dist/assets/{board-2KevHCI0.js → board-8L3uX7_O.js} +2 -2
  3. package/dist/assets/{board-2KevHCI0.js.map → board-8L3uX7_O.js.map} +1 -1
  4. package/dist/assets/index-Cj1IBH_w.js +36 -0
  5. package/dist/assets/index-Cj1IBH_w.js.map +1 -0
  6. package/dist/assets/index-DQT6EbuS.css +1 -0
  7. package/dist/assets/{motion-q19HPmWs.js → motion-1GAqqi8M.js} +2 -2
  8. package/dist/assets/{motion-q19HPmWs.js.map → motion-1GAqqi8M.js.map} +1 -1
  9. package/dist/assets/{table-BDMHBY4a.js → table-DBGlgRjk.js} +2 -2
  10. package/dist/assets/{table-BDMHBY4a.js.map → table-DBGlgRjk.js.map} +1 -1
  11. package/dist/assets/{ui-CQ_AsFs8.js → ui-iTluWjC4.js} +2 -2
  12. package/dist/assets/{ui-CQ_AsFs8.js.map → ui-iTluWjC4.js.map} +1 -1
  13. package/dist/assets/{vendor-5HifrnRK.js → vendor-BvM2F9Dp.js} +139 -84
  14. package/dist/assets/vendor-BvM2F9Dp.js.map +1 -0
  15. package/dist/assets/{viz-CQzkRnTu.js → viz-CNeunkfu.js} +2 -2
  16. package/dist/assets/{viz-CQzkRnTu.js.map → viz-CNeunkfu.js.map} +1 -1
  17. package/dist/index.html +8 -8
  18. package/dist/openclaw/parity.js +1 -0
  19. package/dist/openclaw/routes.js +7 -0
  20. package/dist/openclaw/tools.js +183 -16
  21. package/dist/server/app.js +2509 -263
  22. package/dist/server/managers/platform/secrets-manager.js +44 -1
  23. package/dist/server/managers/runtime.js +3 -1
  24. package/dist/server/openapi.js +2037 -172
  25. package/dist/server/repositories/calendar.js +1101 -0
  26. package/dist/server/repositories/deleted-entities.js +10 -2
  27. package/dist/server/repositories/notes.js +161 -28
  28. package/dist/server/repositories/projects.js +45 -13
  29. package/dist/server/repositories/rewards.js +114 -6
  30. package/dist/server/repositories/settings.js +47 -5
  31. package/dist/server/repositories/task-runs.js +46 -10
  32. package/dist/server/repositories/tasks.js +25 -9
  33. package/dist/server/repositories/weekly-reviews.js +109 -0
  34. package/dist/server/repositories/work-adjustments.js +105 -0
  35. package/dist/server/services/calendar-runtime.js +1301 -0
  36. package/dist/server/services/entity-crud.js +94 -3
  37. package/dist/server/services/projects.js +32 -8
  38. package/dist/server/services/reviews.js +15 -1
  39. package/dist/server/services/work-time.js +27 -0
  40. package/dist/server/types.js +934 -49
  41. package/openclaw.plugin.json +1 -1
  42. package/package.json +1 -1
  43. package/server/migrations/006_work_adjustments.sql +14 -0
  44. package/server/migrations/007_weekly_review_closures.sql +17 -0
  45. package/server/migrations/008_calendar_execution.sql +147 -0
  46. package/server/migrations/009_true_calendar_events.sql +195 -0
  47. package/server/migrations/010_calendar_selection_state.sql +6 -0
  48. package/server/migrations/011_calendar_timezone_backfill.sql +11 -0
  49. package/server/migrations/012_work_block_ranges.sql +7 -0
  50. package/server/migrations/013_microsoft_local_auth_settings.sql +8 -0
  51. package/server/migrations/014_note_tags_and_ephemeral.sql +8 -0
  52. package/skills/forge-openclaw/SKILL.md +117 -11
  53. package/dist/assets/index-CDYW4WDH.js +0 -36
  54. package/dist/assets/index-CDYW4WDH.js.map +0 -1
  55. package/dist/assets/index-yroQr6YZ.css +0 -1
  56. package/dist/assets/vendor-5HifrnRK.js.map +0 -1
@@ -1,5 +1,11 @@
1
1
  import { z } from "zod";
2
- export const taskStatusSchema = z.enum(["backlog", "focus", "in_progress", "blocked", "done"]);
2
+ export const taskStatusSchema = z.enum([
3
+ "backlog",
4
+ "focus",
5
+ "in_progress",
6
+ "blocked",
7
+ "done"
8
+ ]);
3
9
  export const taskPrioritySchema = z.enum(["low", "medium", "high", "critical"]);
4
10
  export const taskEffortSchema = z.enum(["light", "deep", "marathon"]);
5
11
  export const taskEnergySchema = z.enum(["low", "steady", "high"]);
@@ -8,13 +14,68 @@ export const goalHorizonSchema = z.enum(["quarter", "year", "lifetime"]);
8
14
  export const projectStatusSchema = z.enum(["active", "paused", "completed"]);
9
15
  export const tagKindSchema = z.enum(["value", "category", "execution"]);
10
16
  export const taskDueFilterSchema = z.enum(["overdue", "today", "week"]);
11
- export const taskRunStatusSchema = z.enum(["active", "completed", "released", "timed_out"]);
17
+ export const taskRunStatusSchema = z.enum([
18
+ "active",
19
+ "completed",
20
+ "released",
21
+ "timed_out"
22
+ ]);
12
23
  export const taskTimerModeSchema = z.enum(["planned", "unlimited"]);
13
- export const timeAccountingModeSchema = z.enum(["split", "parallel", "primary_only"]);
24
+ export const timeAccountingModeSchema = z.enum([
25
+ "split",
26
+ "parallel",
27
+ "primary_only"
28
+ ]);
14
29
  export const habitFrequencySchema = z.enum(["daily", "weekly"]);
15
30
  export const habitPolaritySchema = z.enum(["positive", "negative"]);
16
31
  export const habitStatusSchema = z.enum(["active", "paused", "archived"]);
17
32
  export const habitCheckInStatusSchema = z.enum(["done", "missed"]);
33
+ export const calendarProviderSchema = z.enum([
34
+ "google",
35
+ "apple",
36
+ "caldav",
37
+ "microsoft"
38
+ ]);
39
+ export const calendarConnectionStatusSchema = z.enum([
40
+ "connected",
41
+ "needs_attention",
42
+ "error"
43
+ ]);
44
+ export const calendarOwnershipSchema = z.enum(["external", "forge"]);
45
+ export const calendarEventOriginSchema = z.enum([
46
+ "native",
47
+ "google",
48
+ "apple",
49
+ "caldav",
50
+ "microsoft",
51
+ "derived"
52
+ ]);
53
+ export const calendarAvailabilitySchema = z.enum(["busy", "free"]);
54
+ export const calendarEventStatusSchema = z.enum([
55
+ "confirmed",
56
+ "tentative",
57
+ "cancelled"
58
+ ]);
59
+ export const workBlockKindSchema = z.enum([
60
+ "main_activity",
61
+ "secondary_activity",
62
+ "third_activity",
63
+ "rest",
64
+ "holiday",
65
+ "custom"
66
+ ]);
67
+ export const calendarTimeboxStatusSchema = z.enum([
68
+ "planned",
69
+ "active",
70
+ "completed",
71
+ "cancelled"
72
+ ]);
73
+ export const calendarTimeboxSourceSchema = z.enum([
74
+ "manual",
75
+ "suggested",
76
+ "live_run"
77
+ ]);
78
+ export const workAdjustmentEntityTypeSchema = z.enum(["task", "project"]);
18
79
  export const activityEntityTypeSchema = z.enum([
19
80
  "task",
20
81
  "habit",
@@ -38,19 +99,69 @@ export const activityEntityTypeSchema = z.enum([
38
99
  "approval_request",
39
100
  "agent_action",
40
101
  "reward",
41
- "session"
102
+ "session",
103
+ "calendar_connection",
104
+ "calendar",
105
+ "calendar_event",
106
+ "work_block",
107
+ "work_block_template",
108
+ "task_timebox"
109
+ ]);
110
+ export const activitySourceSchema = z.enum([
111
+ "ui",
112
+ "openclaw",
113
+ "agent",
114
+ "system"
115
+ ]);
116
+ export const agentTrustLevelSchema = z.enum([
117
+ "standard",
118
+ "trusted",
119
+ "autonomous"
120
+ ]);
121
+ export const autonomyModeSchema = z.enum([
122
+ "approval_required",
123
+ "scoped_write",
124
+ "autonomous"
125
+ ]);
126
+ export const approvalModeSchema = z.enum([
127
+ "approval_by_default",
128
+ "high_impact_only",
129
+ "none"
42
130
  ]);
43
- export const activitySourceSchema = z.enum(["ui", "openclaw", "agent", "system"]);
44
- export const agentTrustLevelSchema = z.enum(["standard", "trusted", "autonomous"]);
45
- export const autonomyModeSchema = z.enum(["approval_required", "scoped_write", "autonomous"]);
46
- export const approvalModeSchema = z.enum(["approval_by_default", "high_impact_only", "none"]);
47
131
  export const insightOriginSchema = z.enum(["system", "user", "agent"]);
48
- export const insightStatusSchema = z.enum(["open", "accepted", "dismissed", "snoozed", "applied", "expired"]);
49
- export const insightVisibilitySchema = z.enum(["visible", "pending_review", "archived"]);
50
- export const insightFeedbackTypeSchema = z.enum(["accepted", "dismissed", "applied", "snoozed"]);
51
- export const approvalRequestStatusSchema = z.enum(["pending", "approved", "rejected", "cancelled", "executed"]);
132
+ export const insightStatusSchema = z.enum([
133
+ "open",
134
+ "accepted",
135
+ "dismissed",
136
+ "snoozed",
137
+ "applied",
138
+ "expired"
139
+ ]);
140
+ export const insightVisibilitySchema = z.enum([
141
+ "visible",
142
+ "pending_review",
143
+ "archived"
144
+ ]);
145
+ export const insightFeedbackTypeSchema = z.enum([
146
+ "accepted",
147
+ "dismissed",
148
+ "applied",
149
+ "snoozed"
150
+ ]);
151
+ export const approvalRequestStatusSchema = z.enum([
152
+ "pending",
153
+ "approved",
154
+ "rejected",
155
+ "cancelled",
156
+ "executed"
157
+ ]);
52
158
  export const actionRiskLevelSchema = z.enum(["low", "medium", "high"]);
53
- export const agentActionStatusSchema = z.enum(["pending_approval", "approved", "rejected", "executed"]);
159
+ export const agentActionStatusSchema = z.enum([
160
+ "pending_approval",
161
+ "approved",
162
+ "rejected",
163
+ "executed"
164
+ ]);
54
165
  export const crudEntityTypeSchema = z.enum([
55
166
  "goal",
56
167
  "project",
@@ -59,6 +170,9 @@ export const crudEntityTypeSchema = z.enum([
59
170
  "tag",
60
171
  "note",
61
172
  "insight",
173
+ "calendar_event",
174
+ "work_block_template",
175
+ "task_timebox",
62
176
  "psyche_value",
63
177
  "behavior_pattern",
64
178
  "behavior",
@@ -97,7 +211,12 @@ export const rewardRuleFamilySchema = z.enum([
97
211
  export const appLocaleSchema = z.enum(["en", "fr"]);
98
212
  const trimmedString = z.string().trim();
99
213
  const nonEmptyTrimmedString = trimmedString.min(1);
100
- const rewardConfigValueSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
214
+ const rewardConfigValueSchema = z.union([
215
+ z.string(),
216
+ z.number(),
217
+ z.boolean(),
218
+ z.null()
219
+ ]);
101
220
  function isValidDateOnly(value) {
102
221
  if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
103
222
  return false;
@@ -111,9 +230,15 @@ function isValidDateOnly(value) {
111
230
  candidate.getUTCMonth() === month - 1 &&
112
231
  candidate.getUTCDate() === day);
113
232
  }
233
+ function isValidDateTime(value) {
234
+ return !Number.isNaN(Date.parse(value));
235
+ }
114
236
  const dateOnlySchema = trimmedString.refine(isValidDateOnly, {
115
237
  message: "Expected a valid date in YYYY-MM-DD format"
116
238
  });
239
+ const dateTimeSchema = trimmedString.refine(isValidDateTime, {
240
+ message: "Expected a valid ISO date-time string"
241
+ });
117
242
  const uniqueStringArraySchema = z
118
243
  .array(nonEmptyTrimmedString)
119
244
  .superRefine((values, context) => {
@@ -130,6 +255,31 @@ const uniqueStringArraySchema = z
130
255
  seen.add(value);
131
256
  });
132
257
  });
258
+ const integerMinuteSchema = z
259
+ .number()
260
+ .int()
261
+ .min(0)
262
+ .max(24 * 60);
263
+ export const calendarSchedulingRulesSchema = z.object({
264
+ allowWorkBlockKinds: z.array(workBlockKindSchema).default([]),
265
+ blockWorkBlockKinds: z.array(workBlockKindSchema).default([]),
266
+ allowCalendarIds: uniqueStringArraySchema.default([]),
267
+ blockCalendarIds: uniqueStringArraySchema.default([]),
268
+ allowEventTypes: uniqueStringArraySchema.default([]),
269
+ blockEventTypes: uniqueStringArraySchema.default([]),
270
+ allowEventKeywords: uniqueStringArraySchema.default([]),
271
+ blockEventKeywords: uniqueStringArraySchema.default([]),
272
+ allowAvailability: z.array(calendarAvailabilitySchema).default([]),
273
+ blockAvailability: z.array(calendarAvailabilitySchema).default([])
274
+ });
275
+ export const calendarContextConflictSchema = z.object({
276
+ kind: z.enum(["external_event", "work_block"]),
277
+ id: z.string(),
278
+ title: z.string(),
279
+ reason: z.string(),
280
+ startsAt: z.string(),
281
+ endsAt: z.string()
282
+ });
133
283
  export const tagSchema = z.object({
134
284
  id: z.string(),
135
285
  name: nonEmptyTrimmedString,
@@ -140,15 +290,65 @@ export const tagSchema = z.object({
140
290
  export const taskTimeSummarySchema = z.object({
141
291
  totalTrackedSeconds: z.number().int().nonnegative(),
142
292
  totalCreditedSeconds: z.number().nonnegative(),
293
+ liveTrackedSeconds: z.number().int().nonnegative().default(0),
294
+ liveCreditedSeconds: z.number().nonnegative().default(0),
295
+ manualAdjustedSeconds: z.number().int().default(0),
143
296
  activeRunCount: z.number().int().nonnegative(),
144
297
  hasCurrentRun: z.boolean(),
145
298
  currentRunId: z.string().nullable()
146
299
  });
300
+ export const workAdjustmentSchema = z.object({
301
+ id: z.string(),
302
+ entityType: workAdjustmentEntityTypeSchema,
303
+ entityId: z.string(),
304
+ requestedDeltaMinutes: z
305
+ .number()
306
+ .int()
307
+ .refine((value) => value !== 0, {
308
+ message: "requestedDeltaMinutes must not be zero"
309
+ }),
310
+ appliedDeltaMinutes: z.number().int(),
311
+ note: z.string(),
312
+ actor: z.string().nullable(),
313
+ source: activitySourceSchema,
314
+ createdAt: z.string()
315
+ });
316
+ export const workAdjustmentTargetSummarySchema = z.object({
317
+ entityType: workAdjustmentEntityTypeSchema,
318
+ entityId: z.string(),
319
+ title: z.string(),
320
+ time: taskTimeSummarySchema
321
+ });
322
+ export const workAdjustmentResultSchema = z.object({
323
+ adjustment: workAdjustmentSchema,
324
+ target: workAdjustmentTargetSummarySchema,
325
+ reward: z.lazy(() => rewardLedgerEventSchema).nullable(),
326
+ metrics: z.lazy(() => xpMetricsPayloadSchema)
327
+ });
147
328
  export const noteLinkSchema = z.object({
148
329
  entityType: crudEntityTypeSchema,
149
330
  entityId: nonEmptyTrimmedString,
150
331
  anchorKey: trimmedString.nullable().default(null)
151
332
  });
333
+ const noteTagSchema = nonEmptyTrimmedString.max(80);
334
+ const uniqueNoteTagArraySchema = z
335
+ .array(noteTagSchema)
336
+ .max(24)
337
+ .superRefine((values, context) => {
338
+ const seen = new Set();
339
+ values.forEach((value, index) => {
340
+ const normalized = value.toLowerCase();
341
+ if (seen.has(normalized)) {
342
+ context.addIssue({
343
+ code: z.ZodIssueCode.custom,
344
+ path: [index],
345
+ message: `Duplicate note tag '${value}' is not allowed`
346
+ });
347
+ return;
348
+ }
349
+ seen.add(normalized);
350
+ });
351
+ });
152
352
  export const noteSchema = z.object({
153
353
  id: z.string(),
154
354
  contentMarkdown: nonEmptyTrimmedString,
@@ -157,7 +357,9 @@ export const noteSchema = z.object({
157
357
  source: activitySourceSchema,
158
358
  createdAt: z.string(),
159
359
  updatedAt: z.string(),
160
- links: z.array(noteLinkSchema).min(1)
360
+ links: z.array(noteLinkSchema).min(1),
361
+ tags: uniqueNoteTagArraySchema.default([]),
362
+ destroyAt: dateTimeSchema.nullable().default(null)
161
363
  });
162
364
  export const noteSummarySchema = z.object({
163
365
  count: z.number().int().nonnegative(),
@@ -185,6 +387,18 @@ export const projectSchema = z.object({
185
387
  status: projectStatusSchema,
186
388
  targetPoints: z.number().int().nonnegative(),
187
389
  themeColor: z.string(),
390
+ schedulingRules: calendarSchedulingRulesSchema.default({
391
+ allowWorkBlockKinds: [],
392
+ blockWorkBlockKinds: [],
393
+ allowCalendarIds: [],
394
+ blockCalendarIds: [],
395
+ allowEventTypes: [],
396
+ blockEventTypes: [],
397
+ allowEventKeywords: [],
398
+ blockEventKeywords: [],
399
+ allowAvailability: [],
400
+ blockAvailability: []
401
+ }),
188
402
  createdAt: z.string(),
189
403
  updatedAt: z.string()
190
404
  });
@@ -202,6 +416,14 @@ export const taskSchema = z.object({
202
416
  energy: taskEnergySchema,
203
417
  points: z.number().int().nonnegative(),
204
418
  sortOrder: z.number().int().nonnegative(),
419
+ plannedDurationSeconds: z
420
+ .number()
421
+ .int()
422
+ .min(60)
423
+ .max(86_400)
424
+ .nullable()
425
+ .default(null),
426
+ schedulingRules: calendarSchedulingRulesSchema.nullable().default(null),
205
427
  completedAt: z.string().nullable(),
206
428
  createdAt: z.string(),
207
429
  updatedAt: z.string(),
@@ -209,6 +431,9 @@ export const taskSchema = z.object({
209
431
  time: taskTimeSummarySchema.default({
210
432
  totalTrackedSeconds: 0,
211
433
  totalCreditedSeconds: 0,
434
+ liveTrackedSeconds: 0,
435
+ liveCreditedSeconds: 0,
436
+ manualAdjustedSeconds: 0,
212
437
  activeRunCount: 0,
213
438
  hasCurrentRun: false,
214
439
  currentRunId: null
@@ -235,8 +460,192 @@ export const taskRunSchema = z.object({
235
460
  completedAt: z.string().nullable(),
236
461
  releasedAt: z.string().nullable(),
237
462
  timedOutAt: z.string().nullable(),
463
+ overrideReason: trimmedString.nullable().default(null),
238
464
  updatedAt: z.string()
239
465
  });
466
+ export const calendarConnectionSchema = z.object({
467
+ id: z.string(),
468
+ provider: calendarProviderSchema,
469
+ label: nonEmptyTrimmedString,
470
+ accountLabel: trimmedString,
471
+ status: calendarConnectionStatusSchema,
472
+ config: z.record(z.string(), z.union([z.string(), z.number(), z.boolean(), z.null()])),
473
+ forgeCalendarId: z.string().nullable(),
474
+ lastSyncedAt: z.string().nullable(),
475
+ lastSyncError: trimmedString.nullable(),
476
+ createdAt: z.string(),
477
+ updatedAt: z.string()
478
+ });
479
+ export const calendarDiscoveryCalendarSchema = z.object({
480
+ url: nonEmptyTrimmedString.url(),
481
+ displayName: nonEmptyTrimmedString,
482
+ description: trimmedString,
483
+ color: z.string(),
484
+ timezone: trimmedString,
485
+ isPrimary: z.boolean(),
486
+ canWrite: z.boolean(),
487
+ selectedByDefault: z.boolean(),
488
+ isForgeCandidate: z.boolean()
489
+ });
490
+ export const calendarDiscoveryPayloadSchema = z.object({
491
+ provider: calendarProviderSchema,
492
+ accountLabel: trimmedString,
493
+ serverUrl: nonEmptyTrimmedString.url(),
494
+ principalUrl: z.string().nullable(),
495
+ homeUrl: z.string().nullable(),
496
+ calendars: z.array(calendarDiscoveryCalendarSchema)
497
+ });
498
+ export const calendarSchema = z.object({
499
+ id: z.string(),
500
+ connectionId: z.string(),
501
+ remoteId: nonEmptyTrimmedString,
502
+ title: nonEmptyTrimmedString,
503
+ description: trimmedString,
504
+ color: z.string(),
505
+ timezone: nonEmptyTrimmedString,
506
+ isPrimary: z.boolean(),
507
+ canWrite: z.boolean(),
508
+ selectedForSync: z.boolean(),
509
+ forgeManaged: z.boolean(),
510
+ lastSyncedAt: z.string().nullable(),
511
+ createdAt: z.string(),
512
+ updatedAt: z.string()
513
+ });
514
+ export const calendarEventSourceSchema = z.object({
515
+ id: z.string(),
516
+ provider: calendarProviderSchema,
517
+ connectionId: z.string().nullable(),
518
+ calendarId: z.string().nullable(),
519
+ remoteCalendarId: trimmedString.nullable(),
520
+ remoteEventId: nonEmptyTrimmedString,
521
+ remoteUid: trimmedString.nullable(),
522
+ recurrenceInstanceId: trimmedString.nullable(),
523
+ isMasterRecurring: z.boolean(),
524
+ remoteHref: trimmedString.nullable(),
525
+ remoteEtag: trimmedString.nullable(),
526
+ syncState: z.enum([
527
+ "pending_create",
528
+ "pending_update",
529
+ "pending_delete",
530
+ "synced",
531
+ "error",
532
+ "deleted"
533
+ ]),
534
+ lastSyncedAt: z.string().nullable(),
535
+ createdAt: z.string(),
536
+ updatedAt: z.string()
537
+ });
538
+ export const calendarEventLinkSchema = z.object({
539
+ id: z.string(),
540
+ entityType: crudEntityTypeSchema,
541
+ entityId: nonEmptyTrimmedString,
542
+ relationshipType: nonEmptyTrimmedString.default("context"),
543
+ createdAt: z.string(),
544
+ updatedAt: z.string()
545
+ });
546
+ export const calendarEventSchema = z.object({
547
+ id: z.string(),
548
+ connectionId: z.string().nullable(),
549
+ calendarId: z.string().nullable(),
550
+ remoteId: trimmedString.nullable(),
551
+ ownership: calendarOwnershipSchema,
552
+ originType: calendarEventOriginSchema,
553
+ status: calendarEventStatusSchema,
554
+ title: nonEmptyTrimmedString,
555
+ description: trimmedString,
556
+ location: trimmedString,
557
+ startAt: z.string(),
558
+ endAt: z.string(),
559
+ timezone: nonEmptyTrimmedString,
560
+ isAllDay: z.boolean(),
561
+ availability: calendarAvailabilitySchema,
562
+ eventType: trimmedString,
563
+ categories: z.array(z.string()).default([]),
564
+ sourceMappings: z.array(calendarEventSourceSchema).default([]),
565
+ links: z.array(calendarEventLinkSchema).default([]),
566
+ remoteUpdatedAt: z.string().nullable(),
567
+ deletedAt: z.string().nullable(),
568
+ createdAt: z.string(),
569
+ updatedAt: z.string()
570
+ });
571
+ export const workBlockTemplateSchema = z
572
+ .object({
573
+ id: z.string(),
574
+ title: nonEmptyTrimmedString,
575
+ kind: workBlockKindSchema,
576
+ color: z.string(),
577
+ timezone: nonEmptyTrimmedString,
578
+ weekDays: z.array(z.number().int().min(0).max(6)).min(1).max(7),
579
+ startMinute: integerMinuteSchema,
580
+ endMinute: integerMinuteSchema,
581
+ startsOn: dateOnlySchema.nullable().default(null),
582
+ endsOn: dateOnlySchema.nullable().default(null),
583
+ blockingState: z.enum(["allowed", "blocked"]),
584
+ createdAt: z.string(),
585
+ updatedAt: z.string()
586
+ })
587
+ .superRefine((value, context) => {
588
+ if (value.endMinute <= value.startMinute) {
589
+ context.addIssue({
590
+ code: z.ZodIssueCode.custom,
591
+ path: ["endMinute"],
592
+ message: "endMinute must be greater than startMinute"
593
+ });
594
+ }
595
+ if (value.startsOn && value.endsOn && value.endsOn < value.startsOn) {
596
+ context.addIssue({
597
+ code: z.ZodIssueCode.custom,
598
+ path: ["endsOn"],
599
+ message: "endsOn must be on or after startsOn"
600
+ });
601
+ }
602
+ });
603
+ export const workBlockInstanceSchema = z.object({
604
+ id: z.string(),
605
+ templateId: z.string(),
606
+ dateKey: dateOnlySchema,
607
+ startAt: z.string(),
608
+ endAt: z.string(),
609
+ title: nonEmptyTrimmedString,
610
+ kind: workBlockKindSchema,
611
+ color: z.string(),
612
+ blockingState: z.enum(["allowed", "blocked"]),
613
+ calendarEventId: z.string().nullable(),
614
+ createdAt: z.string(),
615
+ updatedAt: z.string()
616
+ });
617
+ export const taskTimeboxSchema = z.object({
618
+ id: z.string(),
619
+ taskId: z.string(),
620
+ projectId: z.string().nullable(),
621
+ connectionId: z.string().nullable(),
622
+ calendarId: z.string().nullable(),
623
+ remoteEventId: z.string().nullable(),
624
+ linkedTaskRunId: z.string().nullable(),
625
+ status: calendarTimeboxStatusSchema,
626
+ source: calendarTimeboxSourceSchema,
627
+ title: nonEmptyTrimmedString,
628
+ startsAt: z.string(),
629
+ endsAt: z.string(),
630
+ overrideReason: trimmedString.nullable(),
631
+ createdAt: z.string(),
632
+ updatedAt: z.string()
633
+ });
634
+ export const calendarOverviewPayloadSchema = z.object({
635
+ generatedAt: z.string(),
636
+ providers: z.array(z.object({
637
+ provider: calendarProviderSchema,
638
+ label: z.string(),
639
+ supportsDedicatedForgeCalendar: z.boolean(),
640
+ connectionHelp: z.string()
641
+ })),
642
+ connections: z.array(calendarConnectionSchema),
643
+ calendars: z.array(calendarSchema),
644
+ events: z.array(calendarEventSchema),
645
+ workBlockTemplates: z.array(workBlockTemplateSchema),
646
+ workBlockInstances: z.array(workBlockInstanceSchema),
647
+ timeboxes: z.array(taskTimeboxSchema)
648
+ });
240
649
  export const habitCheckInSchema = z.object({
241
650
  id: z.string(),
242
651
  habitId: z.string(),
@@ -308,7 +717,12 @@ export const dashboardStatsSchema = z.object({
308
717
  overdueTasks: z.number().int().nonnegative(),
309
718
  dueThisWeek: z.number().int().nonnegative()
310
719
  });
311
- export const executionBucketToneSchema = z.enum(["urgent", "accent", "neutral", "success"]);
720
+ export const executionBucketToneSchema = z.enum([
721
+ "urgent",
722
+ "accent",
723
+ "neutral",
724
+ "success"
725
+ ]);
312
726
  export const dashboardExecutionBucketSchema = z.object({
313
727
  id: z.enum(["overdue", "due_soon", "focus_now", "recently_completed"]),
314
728
  label: z.string(),
@@ -340,7 +754,12 @@ export const gamificationProfileSchema = z.object({
340
754
  topGoalId: z.string().nullable(),
341
755
  topGoalTitle: z.string().nullable()
342
756
  });
343
- export const achievementTierSchema = z.enum(["bronze", "silver", "gold", "platinum"]);
757
+ export const achievementTierSchema = z.enum([
758
+ "bronze",
759
+ "silver",
760
+ "gold",
761
+ "platinum"
762
+ ]);
344
763
  export const achievementSignalSchema = z.object({
345
764
  id: z.string(),
346
765
  title: z.string(),
@@ -434,7 +853,16 @@ export const todayQuestSchema = z.object({
434
853
  completed: z.boolean()
435
854
  });
436
855
  export const todayTimelineBucketSchema = z.object({
437
- id: z.enum(["completed", "active", "upcoming", "deferred", "in_progress", "ready", "blocked", "done"]),
856
+ id: z.enum([
857
+ "completed",
858
+ "active",
859
+ "upcoming",
860
+ "deferred",
861
+ "in_progress",
862
+ "ready",
863
+ "blocked",
864
+ "done"
865
+ ]),
438
866
  label: z.string(),
439
867
  tasks: z.array(taskSchema)
440
868
  });
@@ -450,7 +878,9 @@ export const todayContextSchema = z.object({
450
878
  dueHabits: z.array(habitSchema),
451
879
  dailyQuests: z.array(todayQuestSchema),
452
880
  milestoneRewards: z.array(milestoneRewardSchema),
453
- recentHabitRewards: z.array(z.lazy(() => rewardLedgerEventSchema)).default([]),
881
+ recentHabitRewards: z
882
+ .array(z.lazy(() => rewardLedgerEventSchema))
883
+ .default([]),
454
884
  momentum: z.object({
455
885
  streakDays: z.number().int().nonnegative(),
456
886
  momentumScore: z.number().int().min(0).max(100),
@@ -539,6 +969,9 @@ export const weeklyReviewCalibrationSchema = z.object({
539
969
  export const weeklyReviewPayloadSchema = z.object({
540
970
  generatedAt: z.string(),
541
971
  windowLabel: z.string(),
972
+ weekKey: z.string(),
973
+ weekStartDate: z.string(),
974
+ weekEndDate: z.string(),
542
975
  momentumSummary: z.object({
543
976
  totalXp: z.number().int().nonnegative(),
544
977
  focusHours: z.number().int().nonnegative(),
@@ -552,8 +985,31 @@ export const weeklyReviewPayloadSchema = z.object({
552
985
  title: z.string(),
553
986
  summary: z.string(),
554
987
  rewardXp: z.number().int().nonnegative()
988
+ }),
989
+ completion: z.object({
990
+ finalized: z.boolean(),
991
+ finalizedAt: z.string().nullable(),
992
+ finalizedBy: z.string().nullable()
555
993
  })
556
994
  });
995
+ export const weeklyReviewClosureSchema = z.object({
996
+ id: z.string(),
997
+ weekKey: z.string(),
998
+ weekStartDate: z.string(),
999
+ weekEndDate: z.string(),
1000
+ windowLabel: z.string(),
1001
+ actor: z.string().nullable(),
1002
+ source: activitySourceSchema,
1003
+ rewardId: z.string(),
1004
+ activityEventId: z.string(),
1005
+ createdAt: z.string()
1006
+ });
1007
+ export const finalizeWeeklyReviewResultSchema = z.object({
1008
+ review: weeklyReviewPayloadSchema,
1009
+ closure: weeklyReviewClosureSchema,
1010
+ reward: z.lazy(() => rewardLedgerEventSchema),
1011
+ metrics: z.lazy(() => xpMetricsPayloadSchema)
1012
+ });
557
1013
  export const notificationPreferencesSchema = z.object({
558
1014
  goalDriftAlerts: z.boolean(),
559
1015
  dailyQuestReminders: z.boolean(),
@@ -564,6 +1020,17 @@ export const executionSettingsSchema = z.object({
564
1020
  maxActiveTasks: z.number().int().min(1).max(8),
565
1021
  timeAccountingMode: timeAccountingModeSchema
566
1022
  });
1023
+ export const microsoftCalendarAuthSettingsSchema = z.object({
1024
+ clientId: z.string(),
1025
+ tenantId: z.string(),
1026
+ redirectUri: z.string(),
1027
+ usesClientSecret: z.literal(false),
1028
+ readOnly: z.literal(true),
1029
+ authMode: z.literal("public_client_pkce"),
1030
+ isConfigured: z.boolean(),
1031
+ isReadyForSignIn: z.boolean(),
1032
+ setupMessage: z.string()
1033
+ });
567
1034
  export const agentTokenSummarySchema = z.object({
568
1035
  id: z.string(),
569
1036
  label: z.string(),
@@ -746,13 +1213,27 @@ export const updateRewardRuleSchema = z.object({
746
1213
  export const createManualRewardGrantSchema = z.object({
747
1214
  entityType: rewardableEntityTypeSchema,
748
1215
  entityId: nonEmptyTrimmedString,
749
- deltaXp: z.number().int().refine((value) => value !== 0, {
1216
+ deltaXp: z
1217
+ .number()
1218
+ .int()
1219
+ .refine((value) => value !== 0, {
750
1220
  message: "deltaXp must not be zero"
751
1221
  }),
752
1222
  reasonTitle: nonEmptyTrimmedString,
753
1223
  reasonSummary: trimmedString.default(""),
754
1224
  metadata: z.record(z.string(), rewardConfigValueSchema).default({})
755
1225
  });
1226
+ export const createWorkAdjustmentSchema = z.object({
1227
+ entityType: workAdjustmentEntityTypeSchema,
1228
+ entityId: nonEmptyTrimmedString,
1229
+ deltaMinutes: z
1230
+ .number()
1231
+ .int()
1232
+ .refine((value) => value !== 0, {
1233
+ message: "deltaMinutes must not be zero"
1234
+ }),
1235
+ note: trimmedString.default("")
1236
+ });
756
1237
  export const settingsPayloadSchema = z.object({
757
1238
  profile: z.object({
758
1239
  operatorName: z.string(),
@@ -771,6 +1252,9 @@ export const settingsPayloadSchema = z.object({
771
1252
  tokenCount: z.number().int().nonnegative(),
772
1253
  psycheAuthRequired: z.boolean()
773
1254
  }),
1255
+ calendarProviders: z.object({
1256
+ microsoft: microsoftCalendarAuthSettingsSchema
1257
+ }),
774
1258
  agents: z.array(agentIdentitySchema),
775
1259
  agentTokens: z.array(agentTokenSummarySchema)
776
1260
  });
@@ -796,28 +1280,103 @@ export const createNoteLinkSchema = z.object({
796
1280
  entityId: nonEmptyTrimmedString,
797
1281
  anchorKey: trimmedString.nullable().default(null)
798
1282
  });
1283
+ const repeatedTrimmedStringQuerySchema = z.preprocess((value) => {
1284
+ if (value === undefined || value === null || value === "") {
1285
+ return [];
1286
+ }
1287
+ return Array.isArray(value) ? value : [value];
1288
+ }, z.array(trimmedString));
1289
+ const repeatedUnknownQuerySchema = z.preprocess((value) => {
1290
+ if (value === undefined || value === null || value === "") {
1291
+ return [];
1292
+ }
1293
+ return Array.isArray(value) ? value : [value];
1294
+ }, z.array(z.unknown()));
1295
+ const noteLinkedEntityFilterSchema = z.object({
1296
+ entityType: crudEntityTypeSchema,
1297
+ entityId: nonEmptyTrimmedString
1298
+ });
1299
+ function parseLinkedEntityQueryValue(raw) {
1300
+ const separatorIndex = raw.indexOf(":");
1301
+ if (separatorIndex <= 0 || separatorIndex >= raw.length - 1) {
1302
+ throw new Error("Expected linked entity filters in entityType:entityId format");
1303
+ }
1304
+ return {
1305
+ entityType: raw.slice(0, separatorIndex),
1306
+ entityId: raw.slice(separatorIndex + 1)
1307
+ };
1308
+ }
799
1309
  export const createNoteSchema = z.object({
800
1310
  contentMarkdown: nonEmptyTrimmedString,
801
1311
  author: trimmedString.nullable().default(null),
802
- links: z.array(createNoteLinkSchema).min(1)
1312
+ links: z.array(createNoteLinkSchema).min(1),
1313
+ tags: uniqueNoteTagArraySchema.default([]),
1314
+ destroyAt: dateTimeSchema.nullable().default(null)
803
1315
  });
804
1316
  export const nestedCreateNoteSchema = z.object({
805
1317
  contentMarkdown: nonEmptyTrimmedString,
806
1318
  author: trimmedString.nullable().default(null),
807
- links: z.array(createNoteLinkSchema).default([])
1319
+ links: z.array(createNoteLinkSchema).default([]),
1320
+ tags: uniqueNoteTagArraySchema.default([]),
1321
+ destroyAt: dateTimeSchema.nullable().default(null)
808
1322
  });
809
1323
  export const updateNoteSchema = z.object({
810
1324
  contentMarkdown: nonEmptyTrimmedString.optional(),
811
1325
  author: trimmedString.nullable().optional(),
812
- links: z.array(createNoteLinkSchema).min(1).optional()
1326
+ links: z.array(createNoteLinkSchema).min(1).optional(),
1327
+ tags: uniqueNoteTagArraySchema.optional(),
1328
+ destroyAt: dateTimeSchema.nullable().optional()
813
1329
  });
814
- export const notesListQuerySchema = z.object({
1330
+ export const notesListQuerySchema = z
1331
+ .object({
815
1332
  linkedEntityType: crudEntityTypeSchema.optional(),
816
1333
  linkedEntityId: nonEmptyTrimmedString.optional(),
817
1334
  anchorKey: trimmedString.nullable().optional(),
818
1335
  author: trimmedString.optional(),
819
1336
  query: trimmedString.optional(),
1337
+ linkedTo: repeatedUnknownQuerySchema.transform((values, context) => values.map((value, index) => {
1338
+ try {
1339
+ if (typeof value === "string") {
1340
+ return noteLinkedEntityFilterSchema.parse(parseLinkedEntityQueryValue(value));
1341
+ }
1342
+ return noteLinkedEntityFilterSchema.parse(value);
1343
+ }
1344
+ catch (error) {
1345
+ context.addIssue({
1346
+ code: z.ZodIssueCode.custom,
1347
+ path: ["linkedTo", index],
1348
+ message: error instanceof Error ? error.message : "Invalid linkedTo filter"
1349
+ });
1350
+ return {
1351
+ entityType: "goal",
1352
+ entityId: "__invalid__"
1353
+ };
1354
+ }
1355
+ })),
1356
+ tags: repeatedTrimmedStringQuerySchema,
1357
+ textTerms: repeatedTrimmedStringQuerySchema,
1358
+ updatedFrom: dateOnlySchema.optional(),
1359
+ updatedTo: dateOnlySchema.optional(),
820
1360
  limit: z.coerce.number().int().positive().max(200).optional()
1361
+ })
1362
+ .superRefine((value, context) => {
1363
+ if (value.linkedEntityType !== undefined &&
1364
+ value.linkedEntityId === undefined) {
1365
+ context.addIssue({
1366
+ code: z.ZodIssueCode.custom,
1367
+ path: ["linkedEntityId"],
1368
+ message: "linkedEntityId is required when linkedEntityType is provided"
1369
+ });
1370
+ }
1371
+ if (value.updatedFrom &&
1372
+ value.updatedTo &&
1373
+ value.updatedTo < value.updatedFrom) {
1374
+ context.addIssue({
1375
+ code: z.ZodIssueCode.custom,
1376
+ path: ["updatedTo"],
1377
+ message: "updatedTo must be on or after updatedFrom"
1378
+ });
1379
+ }
821
1380
  });
822
1381
  export const taskListQuerySchema = z.object({
823
1382
  status: taskStatusSchema.optional(),
@@ -846,6 +1405,254 @@ export const projectListQuerySchema = z.object({
846
1405
  status: projectStatusSchema.optional(),
847
1406
  limit: z.coerce.number().int().positive().max(100).optional()
848
1407
  });
1408
+ export const calendarOverviewQuerySchema = z.object({
1409
+ from: z.string().datetime().optional(),
1410
+ to: z.string().datetime().optional()
1411
+ });
1412
+ export const createCalendarConnectionSchema = z.discriminatedUnion("provider", [
1413
+ z.object({
1414
+ provider: z.literal("google"),
1415
+ label: nonEmptyTrimmedString,
1416
+ username: nonEmptyTrimmedString,
1417
+ clientId: nonEmptyTrimmedString,
1418
+ clientSecret: nonEmptyTrimmedString,
1419
+ refreshToken: nonEmptyTrimmedString,
1420
+ selectedCalendarUrls: z.array(nonEmptyTrimmedString.url()).min(1),
1421
+ forgeCalendarUrl: nonEmptyTrimmedString.url().nullable().optional(),
1422
+ createForgeCalendar: z.boolean().optional().default(false)
1423
+ }),
1424
+ z.object({
1425
+ provider: z.literal("apple"),
1426
+ label: nonEmptyTrimmedString,
1427
+ username: nonEmptyTrimmedString,
1428
+ password: nonEmptyTrimmedString,
1429
+ selectedCalendarUrls: z.array(nonEmptyTrimmedString.url()).min(1),
1430
+ forgeCalendarUrl: nonEmptyTrimmedString.url().nullable().optional(),
1431
+ createForgeCalendar: z.boolean().optional().default(false)
1432
+ }),
1433
+ z.object({
1434
+ provider: z.literal("caldav"),
1435
+ label: nonEmptyTrimmedString,
1436
+ serverUrl: nonEmptyTrimmedString.url(),
1437
+ username: nonEmptyTrimmedString,
1438
+ password: nonEmptyTrimmedString,
1439
+ selectedCalendarUrls: z.array(nonEmptyTrimmedString.url()).min(1),
1440
+ forgeCalendarUrl: nonEmptyTrimmedString.url().nullable().optional(),
1441
+ createForgeCalendar: z.boolean().optional().default(false)
1442
+ }),
1443
+ z.object({
1444
+ provider: z.literal("microsoft"),
1445
+ label: nonEmptyTrimmedString,
1446
+ authSessionId: nonEmptyTrimmedString,
1447
+ selectedCalendarUrls: z.array(nonEmptyTrimmedString.url()).min(1)
1448
+ })
1449
+ ]);
1450
+ export const discoverCalendarConnectionSchema = z.discriminatedUnion("provider", [
1451
+ z.object({
1452
+ provider: z.literal("google"),
1453
+ username: nonEmptyTrimmedString,
1454
+ clientId: nonEmptyTrimmedString,
1455
+ clientSecret: nonEmptyTrimmedString,
1456
+ refreshToken: nonEmptyTrimmedString
1457
+ }),
1458
+ z.object({
1459
+ provider: z.literal("apple"),
1460
+ username: nonEmptyTrimmedString,
1461
+ password: nonEmptyTrimmedString
1462
+ }),
1463
+ z.object({
1464
+ provider: z.literal("caldav"),
1465
+ serverUrl: nonEmptyTrimmedString.url(),
1466
+ username: nonEmptyTrimmedString,
1467
+ password: nonEmptyTrimmedString
1468
+ })
1469
+ ]);
1470
+ export const startMicrosoftCalendarOauthSchema = z.object({
1471
+ label: nonEmptyTrimmedString.optional()
1472
+ });
1473
+ export const testMicrosoftCalendarOauthConfigurationSchema = z.object({
1474
+ clientId: nonEmptyTrimmedString.regex(/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/, "Microsoft client IDs must use the standard app registration GUID format."),
1475
+ tenantId: trimmedString.default("common"),
1476
+ redirectUri: nonEmptyTrimmedString.url()
1477
+ });
1478
+ export const microsoftCalendarOauthSessionSchema = z.object({
1479
+ sessionId: nonEmptyTrimmedString,
1480
+ status: z.enum(["pending", "authorized", "error", "consumed", "expired"]),
1481
+ authUrl: z.string().url().nullable(),
1482
+ accountLabel: z.string().nullable(),
1483
+ error: z.string().nullable(),
1484
+ discovery: calendarDiscoveryPayloadSchema.nullable()
1485
+ });
1486
+ export const updateCalendarConnectionSchema = z.object({
1487
+ label: nonEmptyTrimmedString.optional(),
1488
+ selectedCalendarUrls: z.array(nonEmptyTrimmedString.url()).optional()
1489
+ });
1490
+ const workBlockTemplateMutationShape = {
1491
+ title: nonEmptyTrimmedString,
1492
+ kind: workBlockKindSchema.default("custom"),
1493
+ color: z
1494
+ .string()
1495
+ .regex(/^#[0-9a-fA-F]{6}$/)
1496
+ .default("#60a5fa"),
1497
+ timezone: nonEmptyTrimmedString.default("UTC"),
1498
+ weekDays: z.array(z.number().int().min(0).max(6)).min(1).max(7),
1499
+ startMinute: integerMinuteSchema,
1500
+ endMinute: integerMinuteSchema,
1501
+ startsOn: dateOnlySchema.nullable().optional(),
1502
+ endsOn: dateOnlySchema.nullable().optional(),
1503
+ blockingState: z.enum(["allowed", "blocked"]).default("blocked")
1504
+ };
1505
+ export const createWorkBlockTemplateSchema = z
1506
+ .object(workBlockTemplateMutationShape)
1507
+ .superRefine((value, context) => {
1508
+ if (value.endMinute <= value.startMinute) {
1509
+ context.addIssue({
1510
+ code: z.ZodIssueCode.custom,
1511
+ path: ["endMinute"],
1512
+ message: "endMinute must be greater than startMinute"
1513
+ });
1514
+ }
1515
+ if (value.startsOn && value.endsOn && value.endsOn < value.startsOn) {
1516
+ context.addIssue({
1517
+ code: z.ZodIssueCode.custom,
1518
+ path: ["endsOn"],
1519
+ message: "endsOn must be on or after startsOn"
1520
+ });
1521
+ }
1522
+ });
1523
+ export const updateWorkBlockTemplateSchema = z
1524
+ .object({
1525
+ title: nonEmptyTrimmedString.optional(),
1526
+ kind: workBlockKindSchema.optional(),
1527
+ color: z
1528
+ .string()
1529
+ .regex(/^#[0-9a-fA-F]{6}$/)
1530
+ .optional(),
1531
+ timezone: nonEmptyTrimmedString.optional(),
1532
+ weekDays: z.array(z.number().int().min(0).max(6)).min(1).max(7).optional(),
1533
+ startMinute: integerMinuteSchema.optional(),
1534
+ endMinute: integerMinuteSchema.optional(),
1535
+ startsOn: dateOnlySchema.nullable().optional(),
1536
+ endsOn: dateOnlySchema.nullable().optional(),
1537
+ blockingState: z.enum(["allowed", "blocked"]).optional()
1538
+ })
1539
+ .superRefine((value, context) => {
1540
+ if (value.startMinute !== undefined &&
1541
+ value.endMinute !== undefined &&
1542
+ value.endMinute <= value.startMinute) {
1543
+ context.addIssue({
1544
+ code: z.ZodIssueCode.custom,
1545
+ path: ["endMinute"],
1546
+ message: "endMinute must be greater than startMinute"
1547
+ });
1548
+ }
1549
+ if (value.startsOn !== undefined &&
1550
+ value.endsOn !== undefined &&
1551
+ value.startsOn &&
1552
+ value.endsOn &&
1553
+ value.endsOn < value.startsOn) {
1554
+ context.addIssue({
1555
+ code: z.ZodIssueCode.custom,
1556
+ path: ["endsOn"],
1557
+ message: "endsOn must be on or after startsOn"
1558
+ });
1559
+ }
1560
+ });
1561
+ export const createTaskTimeboxSchema = z
1562
+ .object({
1563
+ taskId: nonEmptyTrimmedString,
1564
+ projectId: nonEmptyTrimmedString.nullable().optional(),
1565
+ title: nonEmptyTrimmedString,
1566
+ startsAt: z.string().datetime(),
1567
+ endsAt: z.string().datetime(),
1568
+ source: calendarTimeboxSourceSchema.default("manual"),
1569
+ status: calendarTimeboxStatusSchema.default("planned"),
1570
+ overrideReason: trimmedString.nullable().default(null)
1571
+ })
1572
+ .superRefine((value, context) => {
1573
+ if (Date.parse(value.endsAt) <= Date.parse(value.startsAt)) {
1574
+ context.addIssue({
1575
+ code: z.ZodIssueCode.custom,
1576
+ path: ["endsAt"],
1577
+ message: "endsAt must be after startsAt"
1578
+ });
1579
+ }
1580
+ });
1581
+ export const updateTaskTimeboxSchema = z.object({
1582
+ title: nonEmptyTrimmedString.optional(),
1583
+ startsAt: z.string().datetime().optional(),
1584
+ endsAt: z.string().datetime().optional(),
1585
+ status: calendarTimeboxStatusSchema.optional(),
1586
+ overrideReason: trimmedString.nullable().optional()
1587
+ });
1588
+ export const recommendTaskTimeboxesSchema = z.object({
1589
+ taskId: nonEmptyTrimmedString,
1590
+ from: z.string().datetime().optional(),
1591
+ to: z.string().datetime().optional(),
1592
+ limit: z.coerce.number().int().positive().max(12).optional()
1593
+ });
1594
+ export const updateCalendarEventSchema = z
1595
+ .object({
1596
+ title: nonEmptyTrimmedString.optional(),
1597
+ description: trimmedString.optional(),
1598
+ location: trimmedString.optional(),
1599
+ startAt: z.string().datetime().optional(),
1600
+ endAt: z.string().datetime().optional(),
1601
+ timezone: nonEmptyTrimmedString.optional(),
1602
+ isAllDay: z.boolean().optional(),
1603
+ availability: calendarAvailabilitySchema.optional(),
1604
+ eventType: trimmedString.optional(),
1605
+ categories: z.array(trimmedString).optional(),
1606
+ preferredCalendarId: nonEmptyTrimmedString.nullable().optional(),
1607
+ links: z
1608
+ .array(z.object({
1609
+ entityType: crudEntityTypeSchema,
1610
+ entityId: nonEmptyTrimmedString,
1611
+ relationshipType: nonEmptyTrimmedString.default("context")
1612
+ }))
1613
+ .optional()
1614
+ })
1615
+ .superRefine((value, context) => {
1616
+ if (value.startAt !== undefined &&
1617
+ value.endAt !== undefined &&
1618
+ Date.parse(value.endAt) <= Date.parse(value.startAt)) {
1619
+ context.addIssue({
1620
+ code: z.ZodIssueCode.custom,
1621
+ path: ["endAt"],
1622
+ message: "endAt must be after startAt"
1623
+ });
1624
+ }
1625
+ });
1626
+ export const createCalendarEventSchema = z
1627
+ .object({
1628
+ title: nonEmptyTrimmedString,
1629
+ description: trimmedString.default(""),
1630
+ location: trimmedString.default(""),
1631
+ startAt: z.string().datetime(),
1632
+ endAt: z.string().datetime(),
1633
+ timezone: nonEmptyTrimmedString.default("UTC"),
1634
+ isAllDay: z.boolean().default(false),
1635
+ availability: calendarAvailabilitySchema.default("busy"),
1636
+ eventType: trimmedString.default(""),
1637
+ categories: z.array(trimmedString).default([]),
1638
+ preferredCalendarId: nonEmptyTrimmedString.nullable().optional(),
1639
+ links: z
1640
+ .array(z.object({
1641
+ entityType: crudEntityTypeSchema,
1642
+ entityId: nonEmptyTrimmedString,
1643
+ relationshipType: nonEmptyTrimmedString.default("context")
1644
+ }))
1645
+ .default([])
1646
+ })
1647
+ .superRefine((value, context) => {
1648
+ if (Date.parse(value.endAt) <= Date.parse(value.startAt)) {
1649
+ context.addIssue({
1650
+ code: z.ZodIssueCode.custom,
1651
+ path: ["endAt"],
1652
+ message: "endAt must be after startAt"
1653
+ });
1654
+ }
1655
+ });
849
1656
  export const habitListQuerySchema = z.object({
850
1657
  status: habitStatusSchema.optional(),
851
1658
  polarity: habitPolaritySchema.optional(),
@@ -858,7 +1665,10 @@ export const createGoalSchema = z.object({
858
1665
  horizon: goalHorizonSchema.default("year"),
859
1666
  status: goalStatusSchema.default("active"),
860
1667
  targetPoints: z.number().int().min(25).max(10000).default(400),
861
- themeColor: z.string().regex(/^#[0-9a-fA-F]{6}$/).default("#c8a46b"),
1668
+ themeColor: z
1669
+ .string()
1670
+ .regex(/^#[0-9a-fA-F]{6}$/)
1671
+ .default("#c8a46b"),
862
1672
  tagIds: uniqueStringArraySchema.default([]),
863
1673
  notes: z.array(nestedCreateNoteSchema).default([])
864
1674
  });
@@ -866,7 +1676,10 @@ export const updateGoalSchema = createGoalSchema.partial();
866
1676
  export const createTagSchema = z.object({
867
1677
  name: nonEmptyTrimmedString,
868
1678
  kind: tagKindSchema.default("category"),
869
- color: z.string().regex(/^#[0-9a-fA-F]{6}$/).default("#71717a"),
1679
+ color: z
1680
+ .string()
1681
+ .regex(/^#[0-9a-fA-F]{6}$/)
1682
+ .default("#71717a"),
870
1683
  description: trimmedString.default("")
871
1684
  });
872
1685
  export const updateTagSchema = createTagSchema.partial();
@@ -876,7 +1689,22 @@ export const createProjectSchema = z.object({
876
1689
  description: trimmedString.default(""),
877
1690
  status: projectStatusSchema.default("active"),
878
1691
  targetPoints: z.number().int().min(25).max(10000).default(240),
879
- themeColor: z.string().regex(/^#[0-9a-fA-F]{6}$/).default("#c0c1ff"),
1692
+ themeColor: z
1693
+ .string()
1694
+ .regex(/^#[0-9a-fA-F]{6}$/)
1695
+ .default("#c0c1ff"),
1696
+ schedulingRules: calendarSchedulingRulesSchema.default({
1697
+ allowWorkBlockKinds: [],
1698
+ blockWorkBlockKinds: [],
1699
+ allowCalendarIds: [],
1700
+ blockCalendarIds: [],
1701
+ allowEventTypes: [],
1702
+ blockEventTypes: [],
1703
+ allowEventKeywords: [],
1704
+ blockEventKeywords: [],
1705
+ allowAvailability: [],
1706
+ blockAvailability: []
1707
+ }),
880
1708
  notes: z.array(nestedCreateNoteSchema).default([])
881
1709
  });
882
1710
  export const updateProjectSchema = createProjectSchema.partial();
@@ -892,6 +1720,14 @@ export const taskMutationShape = {
892
1720
  effort: taskEffortSchema.default("deep"),
893
1721
  energy: taskEnergySchema.default("steady"),
894
1722
  points: z.number().int().min(5).max(500).default(40),
1723
+ plannedDurationSeconds: z
1724
+ .number()
1725
+ .int()
1726
+ .min(60)
1727
+ .max(86_400)
1728
+ .nullable()
1729
+ .default(null),
1730
+ schedulingRules: calendarSchedulingRulesSchema.nullable().default(null),
895
1731
  sortOrder: z.number().int().nonnegative().optional(),
896
1732
  tagIds: uniqueStringArraySchema.default([]),
897
1733
  notes: z.array(nestedCreateNoteSchema).default([])
@@ -918,7 +1754,9 @@ const habitMutationShape = {
918
1754
  rewardXp: z.number().int().min(1).max(100).default(12),
919
1755
  penaltyXp: z.number().int().min(1).max(100).default(8)
920
1756
  };
921
- export const createHabitSchema = z.object(habitMutationShape).superRefine((value, context) => {
1757
+ export const createHabitSchema = z
1758
+ .object(habitMutationShape)
1759
+ .superRefine((value, context) => {
922
1760
  if (value.frequency === "weekly" && value.weekDays.length === 0) {
923
1761
  context.addIssue({
924
1762
  code: z.ZodIssueCode.custom,
@@ -927,7 +1765,8 @@ export const createHabitSchema = z.object(habitMutationShape).superRefine((value
927
1765
  });
928
1766
  }
929
1767
  });
930
- export const updateHabitSchema = z.object({
1768
+ export const updateHabitSchema = z
1769
+ .object({
931
1770
  title: nonEmptyTrimmedString.optional(),
932
1771
  description: trimmedString.optional(),
933
1772
  status: habitStatusSchema.optional(),
@@ -947,8 +1786,11 @@ export const updateHabitSchema = z.object({
947
1786
  linkedBehaviorId: nonEmptyTrimmedString.nullable().optional(),
948
1787
  rewardXp: z.number().int().min(1).max(100).optional(),
949
1788
  penaltyXp: z.number().int().min(1).max(100).optional()
950
- }).superRefine((value, context) => {
951
- if (value.frequency === "weekly" && value.weekDays !== undefined && value.weekDays.length === 0) {
1789
+ })
1790
+ .superRefine((value, context) => {
1791
+ if (value.frequency === "weekly" &&
1792
+ value.weekDays !== undefined &&
1793
+ value.weekDays.length === 0) {
952
1794
  context.addIssue({
953
1795
  code: z.ZodIssueCode.custom,
954
1796
  path: ["weekDays"],
@@ -968,6 +1810,14 @@ export const updateTaskSchema = z.object({
968
1810
  effort: taskEffortSchema.optional(),
969
1811
  energy: taskEnergySchema.optional(),
970
1812
  points: z.number().int().min(5).max(500).optional(),
1813
+ plannedDurationSeconds: z
1814
+ .number()
1815
+ .int()
1816
+ .min(60)
1817
+ .max(86_400)
1818
+ .nullable()
1819
+ .optional(),
1820
+ schedulingRules: calendarSchedulingRulesSchema.nullable().optional(),
971
1821
  sortOrder: z.number().int().nonnegative().optional(),
972
1822
  tagIds: uniqueStringArraySchema.optional(),
973
1823
  notes: z.array(nestedCreateNoteSchema).optional()
@@ -978,22 +1828,33 @@ export const tagSuggestionRequestSchema = z.object({
978
1828
  goalId: nonEmptyTrimmedString.nullable().default(null),
979
1829
  selectedTagIds: uniqueStringArraySchema.default([])
980
1830
  });
981
- export const taskRunClaimSchema = z.object({
1831
+ export const taskRunClaimSchema = z
1832
+ .object({
982
1833
  actor: nonEmptyTrimmedString,
983
1834
  timerMode: taskTimerModeSchema.default("unlimited"),
984
- plannedDurationSeconds: z.coerce.number().int().min(60).max(86_400).nullable().default(null),
1835
+ plannedDurationSeconds: z.coerce
1836
+ .number()
1837
+ .int()
1838
+ .min(60)
1839
+ .max(86_400)
1840
+ .nullable()
1841
+ .default(null),
985
1842
  isCurrent: z.coerce.boolean().default(true),
986
1843
  leaseTtlSeconds: z.coerce.number().int().min(1).max(14400).default(900),
987
- note: trimmedString.default("")
988
- }).superRefine((value, context) => {
989
- if (value.timerMode === "planned" && value.plannedDurationSeconds === null) {
1844
+ note: trimmedString.default(""),
1845
+ overrideReason: trimmedString.optional()
1846
+ })
1847
+ .superRefine((value, context) => {
1848
+ if (value.timerMode === "planned" &&
1849
+ value.plannedDurationSeconds === null) {
990
1850
  context.addIssue({
991
1851
  code: z.ZodIssueCode.custom,
992
1852
  path: ["plannedDurationSeconds"],
993
1853
  message: "plannedDurationSeconds is required when timerMode is planned"
994
1854
  });
995
1855
  }
996
- if (value.timerMode === "unlimited" && value.plannedDurationSeconds !== null) {
1856
+ if (value.timerMode === "unlimited" &&
1857
+ value.plannedDurationSeconds !== null) {
997
1858
  context.addIssue({
998
1859
  code: z.ZodIssueCode.custom,
999
1860
  path: ["plannedDurationSeconds"],
@@ -1004,7 +1865,8 @@ export const taskRunClaimSchema = z.object({
1004
1865
  export const taskRunHeartbeatSchema = z.object({
1005
1866
  actor: nonEmptyTrimmedString.optional(),
1006
1867
  leaseTtlSeconds: z.coerce.number().int().min(1).max(14400).default(900),
1007
- note: trimmedString.optional()
1868
+ note: trimmedString.optional(),
1869
+ overrideReason: trimmedString.optional()
1008
1870
  });
1009
1871
  export const taskRunFinishSchema = z.object({
1010
1872
  actor: nonEmptyTrimmedString.optional(),
@@ -1034,6 +1896,17 @@ export const updateSettingsSchema = z.object({
1034
1896
  security: z
1035
1897
  .object({
1036
1898
  psycheAuthRequired: z.boolean().optional()
1899
+ })
1900
+ .optional(),
1901
+ calendarProviders: z
1902
+ .object({
1903
+ microsoft: z
1904
+ .object({
1905
+ clientId: trimmedString.optional(),
1906
+ tenantId: trimmedString.optional(),
1907
+ redirectUri: trimmedString.optional()
1908
+ })
1909
+ .optional()
1037
1910
  })
1038
1911
  .optional()
1039
1912
  });
@@ -1101,7 +1974,9 @@ export const resolveApprovalRequestSchema = z.object({
1101
1974
  export const createSessionEventSchema = z.object({
1102
1975
  sessionId: nonEmptyTrimmedString,
1103
1976
  eventType: nonEmptyTrimmedString,
1104
- metrics: z.record(z.string(), z.union([z.string(), z.number(), z.boolean(), z.null()])).default({})
1977
+ metrics: z
1978
+ .record(z.string(), z.union([z.string(), z.number(), z.boolean(), z.null()]))
1979
+ .default({})
1105
1980
  });
1106
1981
  export const removeActivityEventSchema = z.object({
1107
1982
  reason: trimmedString.default("Removed from the visible archive.")
@@ -1116,41 +1991,50 @@ const crudEntityLinkSchema = z.object({
1116
1991
  });
1117
1992
  export const batchCreateEntitiesSchema = z.object({
1118
1993
  atomic: z.boolean().default(false),
1119
- operations: z.array(z.object({
1994
+ operations: z
1995
+ .array(z.object({
1120
1996
  entityType: crudEntityTypeSchema,
1121
1997
  clientRef: trimmedString.optional(),
1122
1998
  data: z.record(z.string(), z.unknown())
1123
- })).min(1)
1999
+ }))
2000
+ .min(1)
1124
2001
  });
1125
2002
  export const batchUpdateEntitiesSchema = z.object({
1126
2003
  atomic: z.boolean().default(false),
1127
- operations: z.array(z.object({
2004
+ operations: z
2005
+ .array(z.object({
1128
2006
  entityType: crudEntityTypeSchema,
1129
2007
  id: nonEmptyTrimmedString,
1130
2008
  clientRef: trimmedString.optional(),
1131
2009
  patch: z.record(z.string(), z.unknown())
1132
- })).min(1)
2010
+ }))
2011
+ .min(1)
1133
2012
  });
1134
2013
  export const batchDeleteEntitiesSchema = z.object({
1135
2014
  atomic: z.boolean().default(false),
1136
- operations: z.array(z.object({
2015
+ operations: z
2016
+ .array(z.object({
1137
2017
  entityType: crudEntityTypeSchema,
1138
2018
  id: nonEmptyTrimmedString,
1139
2019
  clientRef: trimmedString.optional(),
1140
2020
  mode: deleteModeSchema.default("soft"),
1141
2021
  reason: trimmedString.default("")
1142
- })).min(1)
2022
+ }))
2023
+ .min(1)
1143
2024
  });
1144
2025
  export const batchRestoreEntitiesSchema = z.object({
1145
2026
  atomic: z.boolean().default(false),
1146
- operations: z.array(z.object({
2027
+ operations: z
2028
+ .array(z.object({
1147
2029
  entityType: crudEntityTypeSchema,
1148
2030
  id: nonEmptyTrimmedString,
1149
2031
  clientRef: trimmedString.optional()
1150
- })).min(1)
2032
+ }))
2033
+ .min(1)
1151
2034
  });
1152
2035
  export const batchSearchEntitiesSchema = z.object({
1153
- searches: z.array(z.object({
2036
+ searches: z
2037
+ .array(z.object({
1154
2038
  entityTypes: z.array(crudEntityTypeSchema).optional(),
1155
2039
  query: trimmedString.optional(),
1156
2040
  ids: uniqueStringArraySchema.optional(),
@@ -1159,7 +2043,8 @@ export const batchSearchEntitiesSchema = z.object({
1159
2043
  includeDeleted: z.boolean().default(false),
1160
2044
  limit: z.number().int().positive().max(200).default(25),
1161
2045
  clientRef: trimmedString.optional()
1162
- })).min(1)
2046
+ }))
2047
+ .min(1)
1163
2048
  });
1164
2049
  export const uncompleteTaskSchema = z.object({
1165
2050
  status: taskStatusSchema.exclude(["done"]).default("focus")