forge-openclaw-plugin 0.2.15 → 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 (67) hide show
  1. package/README.md +39 -4
  2. package/dist/assets/{board-C_m78kvK.js → board-8L3uX7_O.js} +2 -2
  3. package/dist/assets/{board-C_m78kvK.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-CpZvZumD.js → motion-1GAqqi8M.js} +2 -2
  8. package/dist/assets/{motion-CpZvZumD.js.map → motion-1GAqqi8M.js.map} +1 -1
  9. package/dist/assets/{table-DtyXTw03.js → table-DBGlgRjk.js} +2 -2
  10. package/dist/assets/{table-DtyXTw03.js.map → table-DBGlgRjk.js.map} +1 -1
  11. package/dist/assets/{ui-BXbpiKyS.js → ui-iTluWjC4.js} +2 -2
  12. package/dist/assets/{ui-BXbpiKyS.js.map → ui-iTluWjC4.js.map} +1 -1
  13. package/dist/assets/{vendor-QBH6qVEe.js → vendor-BvM2F9Dp.js} +151 -81
  14. package/dist/assets/vendor-BvM2F9Dp.js.map +1 -0
  15. package/dist/assets/{viz-w-IMeueL.js → viz-CNeunkfu.js} +2 -2
  16. package/dist/assets/{viz-w-IMeueL.js.map → viz-CNeunkfu.js.map} +1 -1
  17. package/dist/index.html +8 -8
  18. package/dist/openclaw/local-runtime.js +142 -9
  19. package/dist/openclaw/parity.js +1 -0
  20. package/dist/openclaw/plugin-entry-shared.js +7 -1
  21. package/dist/openclaw/routes.js +7 -0
  22. package/dist/openclaw/tools.js +198 -16
  23. package/dist/server/app.js +2615 -251
  24. package/dist/server/managers/platform/secrets-manager.js +44 -1
  25. package/dist/server/managers/runtime.js +3 -1
  26. package/dist/server/openapi.js +2212 -170
  27. package/dist/server/repositories/calendar.js +1101 -0
  28. package/dist/server/repositories/deleted-entities.js +10 -2
  29. package/dist/server/repositories/habits.js +358 -0
  30. package/dist/server/repositories/notes.js +161 -28
  31. package/dist/server/repositories/projects.js +45 -13
  32. package/dist/server/repositories/rewards.js +176 -6
  33. package/dist/server/repositories/settings.js +47 -5
  34. package/dist/server/repositories/task-runs.js +46 -10
  35. package/dist/server/repositories/tasks.js +25 -9
  36. package/dist/server/repositories/weekly-reviews.js +109 -0
  37. package/dist/server/repositories/work-adjustments.js +105 -0
  38. package/dist/server/services/calendar-runtime.js +1301 -0
  39. package/dist/server/services/context.js +16 -6
  40. package/dist/server/services/dashboard.js +6 -3
  41. package/dist/server/services/entity-crud.js +116 -3
  42. package/dist/server/services/gamification.js +66 -18
  43. package/dist/server/services/insights.js +2 -1
  44. package/dist/server/services/projects.js +32 -8
  45. package/dist/server/services/reviews.js +17 -2
  46. package/dist/server/services/work-time.js +27 -0
  47. package/dist/server/types.js +1069 -45
  48. package/openclaw.plugin.json +1 -1
  49. package/package.json +1 -1
  50. package/server/migrations/003_habits.sql +30 -0
  51. package/server/migrations/004_habit_links.sql +8 -0
  52. package/server/migrations/005_habit_psyche_links.sql +24 -0
  53. package/server/migrations/006_work_adjustments.sql +14 -0
  54. package/server/migrations/007_weekly_review_closures.sql +17 -0
  55. package/server/migrations/008_calendar_execution.sql +147 -0
  56. package/server/migrations/009_true_calendar_events.sql +195 -0
  57. package/server/migrations/010_calendar_selection_state.sql +6 -0
  58. package/server/migrations/011_calendar_timezone_backfill.sql +11 -0
  59. package/server/migrations/012_work_block_ranges.sql +7 -0
  60. package/server/migrations/013_microsoft_local_auth_settings.sql +8 -0
  61. package/server/migrations/014_note_tags_and_ephemeral.sql +8 -0
  62. package/skills/forge-openclaw/SKILL.md +130 -10
  63. package/skills/forge-openclaw/cron_jobs.md +395 -0
  64. package/dist/assets/index-BWtLtXwb.js +0 -36
  65. package/dist/assets/index-BWtLtXwb.js.map +0 -1
  66. package/dist/assets/index-Dp5GXY_z.css +0 -1
  67. package/dist/assets/vendor-QBH6qVEe.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,11 +14,71 @@ 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
+ ]);
29
+ export const habitFrequencySchema = z.enum(["daily", "weekly"]);
30
+ export const habitPolaritySchema = z.enum(["positive", "negative"]);
31
+ export const habitStatusSchema = z.enum(["active", "paused", "archived"]);
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"]);
14
79
  export const activityEntityTypeSchema = z.enum([
15
80
  "task",
81
+ "habit",
16
82
  "goal",
17
83
  "project",
18
84
  "domain",
@@ -33,26 +99,80 @@ export const activityEntityTypeSchema = z.enum([
33
99
  "approval_request",
34
100
  "agent_action",
35
101
  "reward",
36
- "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"
37
130
  ]);
38
- export const activitySourceSchema = z.enum(["ui", "openclaw", "agent", "system"]);
39
- export const agentTrustLevelSchema = z.enum(["standard", "trusted", "autonomous"]);
40
- export const autonomyModeSchema = z.enum(["approval_required", "scoped_write", "autonomous"]);
41
- export const approvalModeSchema = z.enum(["approval_by_default", "high_impact_only", "none"]);
42
131
  export const insightOriginSchema = z.enum(["system", "user", "agent"]);
43
- export const insightStatusSchema = z.enum(["open", "accepted", "dismissed", "snoozed", "applied", "expired"]);
44
- export const insightVisibilitySchema = z.enum(["visible", "pending_review", "archived"]);
45
- export const insightFeedbackTypeSchema = z.enum(["accepted", "dismissed", "applied", "snoozed"]);
46
- 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
+ ]);
47
158
  export const actionRiskLevelSchema = z.enum(["low", "medium", "high"]);
48
- 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
+ ]);
49
165
  export const crudEntityTypeSchema = z.enum([
50
166
  "goal",
51
167
  "project",
52
168
  "task",
169
+ "habit",
53
170
  "tag",
54
171
  "note",
55
172
  "insight",
173
+ "calendar_event",
174
+ "work_block_template",
175
+ "task_timebox",
56
176
  "psyche_value",
57
177
  "behavior_pattern",
58
178
  "behavior",
@@ -63,6 +183,22 @@ export const crudEntityTypeSchema = z.enum([
63
183
  "emotion_definition",
64
184
  "trigger_report"
65
185
  ]);
186
+ export const rewardableEntityTypeSchema = z.enum([
187
+ "system",
188
+ "goal",
189
+ "project",
190
+ "task",
191
+ "habit",
192
+ "tag",
193
+ "note",
194
+ "insight",
195
+ "psyche_value",
196
+ "behavior_pattern",
197
+ "behavior",
198
+ "belief_entry",
199
+ "mode_profile",
200
+ "trigger_report"
201
+ ]);
66
202
  export const deleteModeSchema = z.enum(["soft", "hard"]);
67
203
  export const rewardRuleFamilySchema = z.enum([
68
204
  "completion",
@@ -75,7 +211,12 @@ export const rewardRuleFamilySchema = z.enum([
75
211
  export const appLocaleSchema = z.enum(["en", "fr"]);
76
212
  const trimmedString = z.string().trim();
77
213
  const nonEmptyTrimmedString = trimmedString.min(1);
78
- 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
+ ]);
79
220
  function isValidDateOnly(value) {
80
221
  if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
81
222
  return false;
@@ -89,9 +230,15 @@ function isValidDateOnly(value) {
89
230
  candidate.getUTCMonth() === month - 1 &&
90
231
  candidate.getUTCDate() === day);
91
232
  }
233
+ function isValidDateTime(value) {
234
+ return !Number.isNaN(Date.parse(value));
235
+ }
92
236
  const dateOnlySchema = trimmedString.refine(isValidDateOnly, {
93
237
  message: "Expected a valid date in YYYY-MM-DD format"
94
238
  });
239
+ const dateTimeSchema = trimmedString.refine(isValidDateTime, {
240
+ message: "Expected a valid ISO date-time string"
241
+ });
95
242
  const uniqueStringArraySchema = z
96
243
  .array(nonEmptyTrimmedString)
97
244
  .superRefine((values, context) => {
@@ -108,6 +255,31 @@ const uniqueStringArraySchema = z
108
255
  seen.add(value);
109
256
  });
110
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
+ });
111
283
  export const tagSchema = z.object({
112
284
  id: z.string(),
113
285
  name: nonEmptyTrimmedString,
@@ -118,15 +290,65 @@ export const tagSchema = z.object({
118
290
  export const taskTimeSummarySchema = z.object({
119
291
  totalTrackedSeconds: z.number().int().nonnegative(),
120
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),
121
296
  activeRunCount: z.number().int().nonnegative(),
122
297
  hasCurrentRun: z.boolean(),
123
298
  currentRunId: z.string().nullable()
124
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
+ });
125
328
  export const noteLinkSchema = z.object({
126
329
  entityType: crudEntityTypeSchema,
127
330
  entityId: nonEmptyTrimmedString,
128
331
  anchorKey: trimmedString.nullable().default(null)
129
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
+ });
130
352
  export const noteSchema = z.object({
131
353
  id: z.string(),
132
354
  contentMarkdown: nonEmptyTrimmedString,
@@ -135,7 +357,9 @@ export const noteSchema = z.object({
135
357
  source: activitySourceSchema,
136
358
  createdAt: z.string(),
137
359
  updatedAt: z.string(),
138
- links: z.array(noteLinkSchema).min(1)
360
+ links: z.array(noteLinkSchema).min(1),
361
+ tags: uniqueNoteTagArraySchema.default([]),
362
+ destroyAt: dateTimeSchema.nullable().default(null)
139
363
  });
140
364
  export const noteSummarySchema = z.object({
141
365
  count: z.number().int().nonnegative(),
@@ -163,6 +387,18 @@ export const projectSchema = z.object({
163
387
  status: projectStatusSchema,
164
388
  targetPoints: z.number().int().nonnegative(),
165
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
+ }),
166
402
  createdAt: z.string(),
167
403
  updatedAt: z.string()
168
404
  });
@@ -180,6 +416,14 @@ export const taskSchema = z.object({
180
416
  energy: taskEnergySchema,
181
417
  points: z.number().int().nonnegative(),
182
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),
183
427
  completedAt: z.string().nullable(),
184
428
  createdAt: z.string(),
185
429
  updatedAt: z.string(),
@@ -187,6 +431,9 @@ export const taskSchema = z.object({
187
431
  time: taskTimeSummarySchema.default({
188
432
  totalTrackedSeconds: 0,
189
433
  totalCreditedSeconds: 0,
434
+ liveTrackedSeconds: 0,
435
+ liveCreditedSeconds: 0,
436
+ manualAdjustedSeconds: 0,
190
437
  activeRunCount: 0,
191
438
  hasCurrentRun: false,
192
439
  currentRunId: null
@@ -213,8 +460,234 @@ export const taskRunSchema = z.object({
213
460
  completedAt: z.string().nullable(),
214
461
  releasedAt: z.string().nullable(),
215
462
  timedOutAt: z.string().nullable(),
463
+ overrideReason: trimmedString.nullable().default(null),
464
+ updatedAt: z.string()
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(),
216
569
  updatedAt: z.string()
217
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
+ });
649
+ export const habitCheckInSchema = z.object({
650
+ id: z.string(),
651
+ habitId: z.string(),
652
+ dateKey: z.string(),
653
+ status: habitCheckInStatusSchema,
654
+ note: z.string(),
655
+ deltaXp: z.number().int(),
656
+ createdAt: z.string(),
657
+ updatedAt: z.string()
658
+ });
659
+ export const habitSchema = z.object({
660
+ id: z.string(),
661
+ title: nonEmptyTrimmedString,
662
+ description: trimmedString,
663
+ status: habitStatusSchema,
664
+ polarity: habitPolaritySchema,
665
+ frequency: habitFrequencySchema,
666
+ targetCount: z.number().int().positive(),
667
+ weekDays: z.array(z.number().int().min(0).max(6)).default([]),
668
+ linkedGoalIds: uniqueStringArraySchema.default([]),
669
+ linkedProjectIds: uniqueStringArraySchema.default([]),
670
+ linkedTaskIds: uniqueStringArraySchema.default([]),
671
+ linkedValueIds: uniqueStringArraySchema.default([]),
672
+ linkedPatternIds: uniqueStringArraySchema.default([]),
673
+ linkedBehaviorIds: uniqueStringArraySchema.default([]),
674
+ linkedBeliefIds: uniqueStringArraySchema.default([]),
675
+ linkedModeIds: uniqueStringArraySchema.default([]),
676
+ linkedReportIds: uniqueStringArraySchema.default([]),
677
+ linkedBehaviorId: z.string().nullable(),
678
+ linkedBehaviorTitle: z.string().nullable(),
679
+ linkedBehaviorTitles: z.array(z.string()).default([]),
680
+ rewardXp: z.number().int().positive(),
681
+ penaltyXp: z.number().int().positive(),
682
+ createdAt: z.string(),
683
+ updatedAt: z.string(),
684
+ lastCheckInAt: z.string().nullable(),
685
+ lastCheckInStatus: habitCheckInStatusSchema.nullable(),
686
+ streakCount: z.number().int().nonnegative(),
687
+ completionRate: z.number().min(0).max(100),
688
+ dueToday: z.boolean(),
689
+ checkIns: z.array(habitCheckInSchema).default([])
690
+ });
218
691
  export const activityEventSchema = z.object({
219
692
  id: z.string(),
220
693
  entityType: activityEntityTypeSchema,
@@ -244,7 +717,12 @@ export const dashboardStatsSchema = z.object({
244
717
  overdueTasks: z.number().int().nonnegative(),
245
718
  dueThisWeek: z.number().int().nonnegative()
246
719
  });
247
- export const executionBucketToneSchema = z.enum(["urgent", "accent", "neutral", "success"]);
720
+ export const executionBucketToneSchema = z.enum([
721
+ "urgent",
722
+ "accent",
723
+ "neutral",
724
+ "success"
725
+ ]);
248
726
  export const dashboardExecutionBucketSchema = z.object({
249
727
  id: z.enum(["overdue", "due_soon", "focus_now", "recently_completed"]),
250
728
  label: z.string(),
@@ -276,7 +754,12 @@ export const gamificationProfileSchema = z.object({
276
754
  topGoalId: z.string().nullable(),
277
755
  topGoalTitle: z.string().nullable()
278
756
  });
279
- export const achievementTierSchema = z.enum(["bronze", "silver", "gold", "platinum"]);
757
+ export const achievementTierSchema = z.enum([
758
+ "bronze",
759
+ "silver",
760
+ "gold",
761
+ "platinum"
762
+ ]);
280
763
  export const achievementSignalSchema = z.object({
281
764
  id: z.string(),
282
765
  title: z.string(),
@@ -314,6 +797,7 @@ export const dashboardPayloadSchema = z.object({
314
797
  goals: z.array(dashboardGoalSchema),
315
798
  projects: z.array(projectSummarySchema),
316
799
  tasks: z.array(taskSchema),
800
+ habits: z.array(habitSchema),
317
801
  tags: z.array(tagSchema),
318
802
  suggestedTags: z.array(tagSchema),
319
803
  owners: z.array(z.string()),
@@ -354,6 +838,7 @@ export const overviewContextSchema = z.object({
354
838
  projects: z.array(projectSummarySchema),
355
839
  activeGoals: z.array(dashboardGoalSchema),
356
840
  topTasks: z.array(taskSchema),
841
+ dueHabits: z.array(habitSchema),
357
842
  recentEvidence: z.array(activityEventSchema),
358
843
  achievements: z.array(achievementSignalSchema),
359
844
  domainBalance: z.array(contextDomainBalanceSchema),
@@ -368,7 +853,16 @@ export const todayQuestSchema = z.object({
368
853
  completed: z.boolean()
369
854
  });
370
855
  export const todayTimelineBucketSchema = z.object({
371
- 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
+ ]),
372
866
  label: z.string(),
373
867
  tasks: z.array(taskSchema)
374
868
  });
@@ -381,8 +875,12 @@ export const todayContextSchema = z.object({
381
875
  sessionLabel: z.string()
382
876
  }),
383
877
  timeline: z.array(todayTimelineBucketSchema),
878
+ dueHabits: z.array(habitSchema),
384
879
  dailyQuests: z.array(todayQuestSchema),
385
880
  milestoneRewards: z.array(milestoneRewardSchema),
881
+ recentHabitRewards: z
882
+ .array(z.lazy(() => rewardLedgerEventSchema))
883
+ .default([]),
386
884
  momentum: z.object({
387
885
  streakDays: z.number().int().nonnegative(),
388
886
  momentumScore: z.number().int().min(0).max(100),
@@ -471,6 +969,9 @@ export const weeklyReviewCalibrationSchema = z.object({
471
969
  export const weeklyReviewPayloadSchema = z.object({
472
970
  generatedAt: z.string(),
473
971
  windowLabel: z.string(),
972
+ weekKey: z.string(),
973
+ weekStartDate: z.string(),
974
+ weekEndDate: z.string(),
474
975
  momentumSummary: z.object({
475
976
  totalXp: z.number().int().nonnegative(),
476
977
  focusHours: z.number().int().nonnegative(),
@@ -484,8 +985,31 @@ export const weeklyReviewPayloadSchema = z.object({
484
985
  title: z.string(),
485
986
  summary: z.string(),
486
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()
487
993
  })
488
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
+ });
489
1013
  export const notificationPreferencesSchema = z.object({
490
1014
  goalDriftAlerts: z.boolean(),
491
1015
  dailyQuestReminders: z.boolean(),
@@ -496,6 +1020,17 @@ export const executionSettingsSchema = z.object({
496
1020
  maxActiveTasks: z.number().int().min(1).max(8),
497
1021
  timeAccountingMode: timeAccountingModeSchema
498
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
+ });
499
1034
  export const agentTokenSummarySchema = z.object({
500
1035
  id: z.string(),
501
1036
  label: z.string(),
@@ -656,6 +1191,7 @@ export const operatorContextPayloadSchema = z.object({
656
1191
  generatedAt: z.string(),
657
1192
  activeProjects: z.array(projectSummarySchema),
658
1193
  focusTasks: z.array(taskSchema),
1194
+ dueHabits: z.array(habitSchema),
659
1195
  currentBoard: z.object({
660
1196
  backlog: z.array(taskSchema),
661
1197
  focus: z.array(taskSchema),
@@ -675,15 +1211,29 @@ export const updateRewardRuleSchema = z.object({
675
1211
  config: z.record(z.string(), rewardConfigValueSchema).optional()
676
1212
  });
677
1213
  export const createManualRewardGrantSchema = z.object({
678
- entityType: nonEmptyTrimmedString,
1214
+ entityType: rewardableEntityTypeSchema,
679
1215
  entityId: nonEmptyTrimmedString,
680
- deltaXp: z.number().int().refine((value) => value !== 0, {
1216
+ deltaXp: z
1217
+ .number()
1218
+ .int()
1219
+ .refine((value) => value !== 0, {
681
1220
  message: "deltaXp must not be zero"
682
1221
  }),
683
1222
  reasonTitle: nonEmptyTrimmedString,
684
1223
  reasonSummary: trimmedString.default(""),
685
1224
  metadata: z.record(z.string(), rewardConfigValueSchema).default({})
686
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
+ });
687
1237
  export const settingsPayloadSchema = z.object({
688
1238
  profile: z.object({
689
1239
  operatorName: z.string(),
@@ -702,6 +1252,9 @@ export const settingsPayloadSchema = z.object({
702
1252
  tokenCount: z.number().int().nonnegative(),
703
1253
  psycheAuthRequired: z.boolean()
704
1254
  }),
1255
+ calendarProviders: z.object({
1256
+ microsoft: microsoftCalendarAuthSettingsSchema
1257
+ }),
705
1258
  agents: z.array(agentIdentitySchema),
706
1259
  agentTokens: z.array(agentTokenSummarySchema)
707
1260
  });
@@ -727,28 +1280,103 @@ export const createNoteLinkSchema = z.object({
727
1280
  entityId: nonEmptyTrimmedString,
728
1281
  anchorKey: trimmedString.nullable().default(null)
729
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
+ }
730
1309
  export const createNoteSchema = z.object({
731
1310
  contentMarkdown: nonEmptyTrimmedString,
732
1311
  author: trimmedString.nullable().default(null),
733
- links: z.array(createNoteLinkSchema).min(1)
1312
+ links: z.array(createNoteLinkSchema).min(1),
1313
+ tags: uniqueNoteTagArraySchema.default([]),
1314
+ destroyAt: dateTimeSchema.nullable().default(null)
734
1315
  });
735
1316
  export const nestedCreateNoteSchema = z.object({
736
1317
  contentMarkdown: nonEmptyTrimmedString,
737
1318
  author: trimmedString.nullable().default(null),
738
- links: z.array(createNoteLinkSchema).default([])
1319
+ links: z.array(createNoteLinkSchema).default([]),
1320
+ tags: uniqueNoteTagArraySchema.default([]),
1321
+ destroyAt: dateTimeSchema.nullable().default(null)
739
1322
  });
740
1323
  export const updateNoteSchema = z.object({
741
1324
  contentMarkdown: nonEmptyTrimmedString.optional(),
742
1325
  author: trimmedString.nullable().optional(),
743
- links: z.array(createNoteLinkSchema).min(1).optional()
1326
+ links: z.array(createNoteLinkSchema).min(1).optional(),
1327
+ tags: uniqueNoteTagArraySchema.optional(),
1328
+ destroyAt: dateTimeSchema.nullable().optional()
744
1329
  });
745
- export const notesListQuerySchema = z.object({
1330
+ export const notesListQuerySchema = z
1331
+ .object({
746
1332
  linkedEntityType: crudEntityTypeSchema.optional(),
747
1333
  linkedEntityId: nonEmptyTrimmedString.optional(),
748
1334
  anchorKey: trimmedString.nullable().optional(),
749
1335
  author: trimmedString.optional(),
750
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(),
751
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
+ }
752
1380
  });
753
1381
  export const taskListQuerySchema = z.object({
754
1382
  status: taskStatusSchema.optional(),
@@ -777,13 +1405,270 @@ export const projectListQuerySchema = z.object({
777
1405
  status: projectStatusSchema.optional(),
778
1406
  limit: z.coerce.number().int().positive().max(100).optional()
779
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
+ });
1656
+ export const habitListQuerySchema = z.object({
1657
+ status: habitStatusSchema.optional(),
1658
+ polarity: habitPolaritySchema.optional(),
1659
+ dueToday: z.coerce.boolean().optional(),
1660
+ limit: z.coerce.number().int().positive().max(100).optional()
1661
+ });
780
1662
  export const createGoalSchema = z.object({
781
1663
  title: nonEmptyTrimmedString,
782
1664
  description: trimmedString.default(""),
783
1665
  horizon: goalHorizonSchema.default("year"),
784
1666
  status: goalStatusSchema.default("active"),
785
1667
  targetPoints: z.number().int().min(25).max(10000).default(400),
786
- 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"),
787
1672
  tagIds: uniqueStringArraySchema.default([]),
788
1673
  notes: z.array(nestedCreateNoteSchema).default([])
789
1674
  });
@@ -791,7 +1676,10 @@ export const updateGoalSchema = createGoalSchema.partial();
791
1676
  export const createTagSchema = z.object({
792
1677
  name: nonEmptyTrimmedString,
793
1678
  kind: tagKindSchema.default("category"),
794
- 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"),
795
1683
  description: trimmedString.default("")
796
1684
  });
797
1685
  export const updateTagSchema = createTagSchema.partial();
@@ -801,7 +1689,22 @@ export const createProjectSchema = z.object({
801
1689
  description: trimmedString.default(""),
802
1690
  status: projectStatusSchema.default("active"),
803
1691
  targetPoints: z.number().int().min(25).max(10000).default(240),
804
- 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
+ }),
805
1708
  notes: z.array(nestedCreateNoteSchema).default([])
806
1709
  });
807
1710
  export const updateProjectSchema = createProjectSchema.partial();
@@ -817,11 +1720,84 @@ export const taskMutationShape = {
817
1720
  effort: taskEffortSchema.default("deep"),
818
1721
  energy: taskEnergySchema.default("steady"),
819
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),
820
1731
  sortOrder: z.number().int().nonnegative().optional(),
821
1732
  tagIds: uniqueStringArraySchema.default([]),
822
1733
  notes: z.array(nestedCreateNoteSchema).default([])
823
1734
  };
824
1735
  export const createTaskSchema = z.object(taskMutationShape);
1736
+ const habitMutationShape = {
1737
+ title: nonEmptyTrimmedString,
1738
+ description: trimmedString.default(""),
1739
+ status: habitStatusSchema.default("active"),
1740
+ polarity: habitPolaritySchema.default("positive"),
1741
+ frequency: habitFrequencySchema.default("daily"),
1742
+ targetCount: z.number().int().min(1).max(14).default(1),
1743
+ weekDays: z.array(z.number().int().min(0).max(6)).max(7).default([]),
1744
+ linkedGoalIds: uniqueStringArraySchema.default([]),
1745
+ linkedProjectIds: uniqueStringArraySchema.default([]),
1746
+ linkedTaskIds: uniqueStringArraySchema.default([]),
1747
+ linkedValueIds: uniqueStringArraySchema.default([]),
1748
+ linkedPatternIds: uniqueStringArraySchema.default([]),
1749
+ linkedBehaviorIds: uniqueStringArraySchema.default([]),
1750
+ linkedBeliefIds: uniqueStringArraySchema.default([]),
1751
+ linkedModeIds: uniqueStringArraySchema.default([]),
1752
+ linkedReportIds: uniqueStringArraySchema.default([]),
1753
+ linkedBehaviorId: nonEmptyTrimmedString.nullable().default(null),
1754
+ rewardXp: z.number().int().min(1).max(100).default(12),
1755
+ penaltyXp: z.number().int().min(1).max(100).default(8)
1756
+ };
1757
+ export const createHabitSchema = z
1758
+ .object(habitMutationShape)
1759
+ .superRefine((value, context) => {
1760
+ if (value.frequency === "weekly" && value.weekDays.length === 0) {
1761
+ context.addIssue({
1762
+ code: z.ZodIssueCode.custom,
1763
+ path: ["weekDays"],
1764
+ message: "Select at least one weekday for weekly habits"
1765
+ });
1766
+ }
1767
+ });
1768
+ export const updateHabitSchema = z
1769
+ .object({
1770
+ title: nonEmptyTrimmedString.optional(),
1771
+ description: trimmedString.optional(),
1772
+ status: habitStatusSchema.optional(),
1773
+ polarity: habitPolaritySchema.optional(),
1774
+ frequency: habitFrequencySchema.optional(),
1775
+ targetCount: z.number().int().min(1).max(14).optional(),
1776
+ weekDays: z.array(z.number().int().min(0).max(6)).max(7).optional(),
1777
+ linkedGoalIds: uniqueStringArraySchema.optional(),
1778
+ linkedProjectIds: uniqueStringArraySchema.optional(),
1779
+ linkedTaskIds: uniqueStringArraySchema.optional(),
1780
+ linkedValueIds: uniqueStringArraySchema.optional(),
1781
+ linkedPatternIds: uniqueStringArraySchema.optional(),
1782
+ linkedBehaviorIds: uniqueStringArraySchema.optional(),
1783
+ linkedBeliefIds: uniqueStringArraySchema.optional(),
1784
+ linkedModeIds: uniqueStringArraySchema.optional(),
1785
+ linkedReportIds: uniqueStringArraySchema.optional(),
1786
+ linkedBehaviorId: nonEmptyTrimmedString.nullable().optional(),
1787
+ rewardXp: z.number().int().min(1).max(100).optional(),
1788
+ penaltyXp: z.number().int().min(1).max(100).optional()
1789
+ })
1790
+ .superRefine((value, context) => {
1791
+ if (value.frequency === "weekly" &&
1792
+ value.weekDays !== undefined &&
1793
+ value.weekDays.length === 0) {
1794
+ context.addIssue({
1795
+ code: z.ZodIssueCode.custom,
1796
+ path: ["weekDays"],
1797
+ message: "Select at least one weekday for weekly habits"
1798
+ });
1799
+ }
1800
+ });
825
1801
  export const updateTaskSchema = z.object({
826
1802
  title: nonEmptyTrimmedString.optional(),
827
1803
  description: trimmedString.optional(),
@@ -834,6 +1810,14 @@ export const updateTaskSchema = z.object({
834
1810
  effort: taskEffortSchema.optional(),
835
1811
  energy: taskEnergySchema.optional(),
836
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(),
837
1821
  sortOrder: z.number().int().nonnegative().optional(),
838
1822
  tagIds: uniqueStringArraySchema.optional(),
839
1823
  notes: z.array(nestedCreateNoteSchema).optional()
@@ -844,22 +1828,33 @@ export const tagSuggestionRequestSchema = z.object({
844
1828
  goalId: nonEmptyTrimmedString.nullable().default(null),
845
1829
  selectedTagIds: uniqueStringArraySchema.default([])
846
1830
  });
847
- export const taskRunClaimSchema = z.object({
1831
+ export const taskRunClaimSchema = z
1832
+ .object({
848
1833
  actor: nonEmptyTrimmedString,
849
1834
  timerMode: taskTimerModeSchema.default("unlimited"),
850
- 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),
851
1842
  isCurrent: z.coerce.boolean().default(true),
852
1843
  leaseTtlSeconds: z.coerce.number().int().min(1).max(14400).default(900),
853
- note: trimmedString.default("")
854
- }).superRefine((value, context) => {
855
- 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) {
856
1850
  context.addIssue({
857
1851
  code: z.ZodIssueCode.custom,
858
1852
  path: ["plannedDurationSeconds"],
859
1853
  message: "plannedDurationSeconds is required when timerMode is planned"
860
1854
  });
861
1855
  }
862
- if (value.timerMode === "unlimited" && value.plannedDurationSeconds !== null) {
1856
+ if (value.timerMode === "unlimited" &&
1857
+ value.plannedDurationSeconds !== null) {
863
1858
  context.addIssue({
864
1859
  code: z.ZodIssueCode.custom,
865
1860
  path: ["plannedDurationSeconds"],
@@ -870,7 +1865,8 @@ export const taskRunClaimSchema = z.object({
870
1865
  export const taskRunHeartbeatSchema = z.object({
871
1866
  actor: nonEmptyTrimmedString.optional(),
872
1867
  leaseTtlSeconds: z.coerce.number().int().min(1).max(14400).default(900),
873
- note: trimmedString.optional()
1868
+ note: trimmedString.optional(),
1869
+ overrideReason: trimmedString.optional()
874
1870
  });
875
1871
  export const taskRunFinishSchema = z.object({
876
1872
  actor: nonEmptyTrimmedString.optional(),
@@ -880,6 +1876,11 @@ export const taskRunFinishSchema = z.object({
880
1876
  export const taskRunFocusSchema = z.object({
881
1877
  actor: nonEmptyTrimmedString.optional()
882
1878
  });
1879
+ export const createHabitCheckInSchema = z.object({
1880
+ dateKey: dateOnlySchema.default(new Date().toISOString().slice(0, 10)),
1881
+ status: habitCheckInStatusSchema,
1882
+ note: trimmedString.default("")
1883
+ });
883
1884
  export const updateSettingsSchema = z.object({
884
1885
  profile: z
885
1886
  .object({
@@ -895,6 +1896,17 @@ export const updateSettingsSchema = z.object({
895
1896
  security: z
896
1897
  .object({
897
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()
898
1910
  })
899
1911
  .optional()
900
1912
  });
@@ -962,7 +1974,9 @@ export const resolveApprovalRequestSchema = z.object({
962
1974
  export const createSessionEventSchema = z.object({
963
1975
  sessionId: nonEmptyTrimmedString,
964
1976
  eventType: nonEmptyTrimmedString,
965
- 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({})
966
1980
  });
967
1981
  export const removeActivityEventSchema = z.object({
968
1982
  reason: trimmedString.default("Removed from the visible archive.")
@@ -977,41 +1991,50 @@ const crudEntityLinkSchema = z.object({
977
1991
  });
978
1992
  export const batchCreateEntitiesSchema = z.object({
979
1993
  atomic: z.boolean().default(false),
980
- operations: z.array(z.object({
1994
+ operations: z
1995
+ .array(z.object({
981
1996
  entityType: crudEntityTypeSchema,
982
1997
  clientRef: trimmedString.optional(),
983
1998
  data: z.record(z.string(), z.unknown())
984
- })).min(1)
1999
+ }))
2000
+ .min(1)
985
2001
  });
986
2002
  export const batchUpdateEntitiesSchema = z.object({
987
2003
  atomic: z.boolean().default(false),
988
- operations: z.array(z.object({
2004
+ operations: z
2005
+ .array(z.object({
989
2006
  entityType: crudEntityTypeSchema,
990
2007
  id: nonEmptyTrimmedString,
991
2008
  clientRef: trimmedString.optional(),
992
2009
  patch: z.record(z.string(), z.unknown())
993
- })).min(1)
2010
+ }))
2011
+ .min(1)
994
2012
  });
995
2013
  export const batchDeleteEntitiesSchema = z.object({
996
2014
  atomic: z.boolean().default(false),
997
- operations: z.array(z.object({
2015
+ operations: z
2016
+ .array(z.object({
998
2017
  entityType: crudEntityTypeSchema,
999
2018
  id: nonEmptyTrimmedString,
1000
2019
  clientRef: trimmedString.optional(),
1001
2020
  mode: deleteModeSchema.default("soft"),
1002
2021
  reason: trimmedString.default("")
1003
- })).min(1)
2022
+ }))
2023
+ .min(1)
1004
2024
  });
1005
2025
  export const batchRestoreEntitiesSchema = z.object({
1006
2026
  atomic: z.boolean().default(false),
1007
- operations: z.array(z.object({
2027
+ operations: z
2028
+ .array(z.object({
1008
2029
  entityType: crudEntityTypeSchema,
1009
2030
  id: nonEmptyTrimmedString,
1010
2031
  clientRef: trimmedString.optional()
1011
- })).min(1)
2032
+ }))
2033
+ .min(1)
1012
2034
  });
1013
2035
  export const batchSearchEntitiesSchema = z.object({
1014
- searches: z.array(z.object({
2036
+ searches: z
2037
+ .array(z.object({
1015
2038
  entityTypes: z.array(crudEntityTypeSchema).optional(),
1016
2039
  query: trimmedString.optional(),
1017
2040
  ids: uniqueStringArraySchema.optional(),
@@ -1020,7 +2043,8 @@ export const batchSearchEntitiesSchema = z.object({
1020
2043
  includeDeleted: z.boolean().default(false),
1021
2044
  limit: z.number().int().positive().max(200).default(25),
1022
2045
  clientRef: trimmedString.optional()
1023
- })).min(1)
2046
+ }))
2047
+ .min(1)
1024
2048
  });
1025
2049
  export const uncompleteTaskSchema = z.object({
1026
2050
  status: taskStatusSchema.exclude(["done"]).default("focus")