forge-openclaw-plugin 0.2.27 → 0.2.29

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 (39) hide show
  1. package/README.md +2 -1
  2. package/dist/assets/{board-C6jCchjI.js → board-q8cfwaAW.js} +2 -2
  3. package/dist/assets/{board-C6jCchjI.js.map → board-q8cfwaAW.js.map} +1 -1
  4. package/dist/assets/index-C6PCeHD_.css +1 -0
  5. package/dist/assets/index-bfHIqj0-.js +85 -0
  6. package/dist/assets/index-bfHIqj0-.js.map +1 -0
  7. package/dist/assets/{motion-DFHrH2rd.js → motion-DHfqFntt.js} +2 -2
  8. package/dist/assets/{motion-DFHrH2rd.js.map → motion-DHfqFntt.js.map} +1 -1
  9. package/dist/assets/{table-ZL7Di_u3.js → table-DLweENXt.js} +2 -2
  10. package/dist/assets/{table-ZL7Di_u3.js.map → table-DLweENXt.js.map} +1 -1
  11. package/dist/assets/{ui-CKNPpz7q.js → ui-BV0OYxkH.js} +2 -2
  12. package/dist/assets/{ui-CKNPpz7q.js.map → ui-BV0OYxkH.js.map} +1 -1
  13. package/dist/assets/{vendor-DoNZuFhn.js → vendor-OwcH20PM.js} +204 -204
  14. package/dist/assets/vendor-OwcH20PM.js.map +1 -0
  15. package/dist/index.html +7 -7
  16. package/dist/server/server/migrations/044_macos_local_calendar_provider.sql +21 -0
  17. package/dist/server/server/src/app.js +331 -14
  18. package/dist/server/server/src/openapi.js +828 -3
  19. package/dist/server/server/src/repositories/calendar.js +295 -12
  20. package/dist/server/server/src/repositories/tasks.js +36 -17
  21. package/dist/server/server/src/services/calendar-runtime.js +613 -32
  22. package/dist/server/server/src/services/life-force-model.js +20 -0
  23. package/dist/server/server/src/services/life-force.js +1333 -97
  24. package/dist/server/server/src/services/macos-calendar-helper.js +748 -0
  25. package/dist/server/server/src/types.js +67 -3
  26. package/dist/server/src/lib/api-error.js +2 -0
  27. package/dist/server/src/lib/api.js +39 -2
  28. package/dist/server/src/lib/calendar-name-deduper.js +2 -0
  29. package/dist/server/src/lib/snapshot-normalizer.js +2 -0
  30. package/openclaw.plugin.json +1 -1
  31. package/package.json +1 -1
  32. package/server/migrations/044_macos_local_calendar_provider.sql +21 -0
  33. package/skills/forge-openclaw/SKILL.md +38 -5
  34. package/skills/forge-openclaw/entity_conversation_playbooks.md +326 -5
  35. package/skills/forge-openclaw/psyche_entity_playbooks.md +57 -0
  36. package/dist/assets/index-DVvS8iiU.css +0 -1
  37. package/dist/assets/index-zYB-9Dfo.js +0 -85
  38. package/dist/assets/index-zYB-9Dfo.js.map +0 -1
  39. package/dist/assets/vendor-DoNZuFhn.js.map +0 -1
@@ -45,7 +45,8 @@ export const calendarProviderSchema = z.enum([
45
45
  "google",
46
46
  "apple",
47
47
  "caldav",
48
- "microsoft"
48
+ "microsoft",
49
+ "macos_local"
49
50
  ]);
50
51
  export const calendarConnectionStatusSchema = z.enum([
51
52
  "connected",
@@ -59,8 +60,16 @@ export const calendarEventOriginSchema = z.enum([
59
60
  "apple",
60
61
  "caldav",
61
62
  "microsoft",
63
+ "macos_local",
62
64
  "derived"
63
65
  ]);
66
+ export const macosCalendarAccessStatusSchema = z.enum([
67
+ "not_determined",
68
+ "denied",
69
+ "restricted",
70
+ "full_access",
71
+ "unavailable"
72
+ ]);
64
73
  export const calendarAvailabilitySchema = z.enum(["busy", "free"]);
65
74
  export const calendarEventStatusSchema = z.enum([
66
75
  "confirmed",
@@ -578,6 +587,7 @@ export const lifeForcePayloadSchema = z.object({
578
587
  spentTodayAp: z.number(),
579
588
  remainingAp: z.number(),
580
589
  forecastAp: z.number(),
590
+ plannedRemainingAp: z.number(),
581
591
  targetBandMinAp: z.number().min(0),
582
592
  targetBandMaxAp: z.number().min(0),
583
593
  instantCapacityApPerHour: z.number().min(0),
@@ -591,6 +601,7 @@ export const lifeForcePayloadSchema = z.object({
591
601
  stats: z.array(lifeForceStatStateSchema),
592
602
  currentCurve: z.array(lifeForceCurvePointSchema),
593
603
  activeDrains: z.array(lifeForceDrainEntrySchema),
604
+ plannedDrains: z.array(lifeForceDrainEntrySchema),
594
605
  warnings: z.array(lifeForceWarningSchema),
595
606
  recommendations: z.array(trimmedString),
596
607
  topTaskIdsNeedingSplit: z.array(z.string()),
@@ -790,7 +801,13 @@ export const calendarDiscoveryCalendarSchema = z.object({
790
801
  isPrimary: z.boolean(),
791
802
  canWrite: z.boolean(),
792
803
  selectedByDefault: z.boolean(),
793
- isForgeCandidate: z.boolean()
804
+ isForgeCandidate: z.boolean(),
805
+ sourceId: trimmedString.nullable().default(null),
806
+ sourceTitle: trimmedString.nullable().default(null),
807
+ sourceType: trimmedString.nullable().default(null),
808
+ calendarType: trimmedString.nullable().default(null),
809
+ hostCalendarId: trimmedString.nullable().default(null),
810
+ canonicalKey: trimmedString.nullable().default(null)
794
811
  });
795
812
  export const calendarDiscoveryPayloadSchema = z.object({
796
813
  provider: calendarProviderSchema,
@@ -800,6 +817,19 @@ export const calendarDiscoveryPayloadSchema = z.object({
800
817
  homeUrl: z.string().nullable(),
801
818
  calendars: z.array(calendarDiscoveryCalendarSchema)
802
819
  });
820
+ export const macosLocalCalendarSourceSchema = z.object({
821
+ sourceId: nonEmptyTrimmedString,
822
+ sourceTitle: trimmedString,
823
+ sourceType: trimmedString,
824
+ accountLabel: trimmedString,
825
+ accountIdentityKey: trimmedString,
826
+ calendars: z.array(calendarDiscoveryCalendarSchema)
827
+ });
828
+ export const macosLocalCalendarDiscoveryPayloadSchema = z.object({
829
+ status: macosCalendarAccessStatusSchema,
830
+ requestedAt: z.string(),
831
+ sources: z.array(macosLocalCalendarSourceSchema)
832
+ });
803
833
  export const calendarSchema = z.object({
804
834
  id: z.string(),
805
835
  connectionId: z.string(),
@@ -812,6 +842,12 @@ export const calendarSchema = z.object({
812
842
  canWrite: z.boolean(),
813
843
  selectedForSync: z.boolean(),
814
844
  forgeManaged: z.boolean(),
845
+ sourceId: trimmedString.nullable().default(null),
846
+ sourceTitle: trimmedString.nullable().default(null),
847
+ sourceType: trimmedString.nullable().default(null),
848
+ calendarType: trimmedString.nullable().default(null),
849
+ hostCalendarId: trimmedString.nullable().default(null),
850
+ canonicalKey: trimmedString.nullable().default(null),
815
851
  lastSyncedAt: z.string().nullable(),
816
852
  createdAt: z.string(),
817
853
  updatedAt: z.string()
@@ -887,6 +923,7 @@ export const calendarEventSchema = z.object({
887
923
  categories: z.array(z.string()).default([]),
888
924
  sourceMappings: z.array(calendarEventSourceSchema).default([]),
889
925
  links: z.array(calendarEventLinkSchema).default([]),
926
+ actionProfile: actionProfileSchema.nullable().default(null),
890
927
  remoteUpdatedAt: z.string().nullable(),
891
928
  deletedAt: z.string().nullable(),
892
929
  createdAt: z.string(),
@@ -906,6 +943,7 @@ export const workBlockTemplateSchema = z
906
943
  startsOn: dateOnlySchema.nullable().default(null),
907
944
  endsOn: dateOnlySchema.nullable().default(null),
908
945
  blockingState: z.enum(["allowed", "blocked"]),
946
+ actionProfile: actionProfileSchema.nullable().default(null),
909
947
  createdAt: z.string(),
910
948
  updatedAt: z.string(),
911
949
  ...ownershipShape
@@ -937,6 +975,7 @@ export const workBlockInstanceSchema = z.object({
937
975
  color: z.string(),
938
976
  blockingState: z.enum(["allowed", "blocked"]),
939
977
  calendarEventId: z.string().nullable(),
978
+ actionProfile: actionProfileSchema.nullable().default(null),
940
979
  createdAt: z.string(),
941
980
  updatedAt: z.string()
942
981
  });
@@ -954,6 +993,7 @@ export const taskTimeboxSchema = z.object({
954
993
  startsAt: z.string(),
955
994
  endsAt: z.string(),
956
995
  overrideReason: trimmedString.nullable(),
996
+ actionProfile: actionProfileSchema.nullable().default(null),
957
997
  createdAt: z.string(),
958
998
  updatedAt: z.string(),
959
999
  ...ownershipShape
@@ -2352,6 +2392,15 @@ export const createCalendarConnectionSchema = z.discriminatedUnion("provider", [
2352
2392
  label: nonEmptyTrimmedString,
2353
2393
  authSessionId: nonEmptyTrimmedString,
2354
2394
  selectedCalendarUrls: z.array(nonEmptyTrimmedString.url()).min(1)
2395
+ }),
2396
+ z.object({
2397
+ provider: z.literal("macos_local"),
2398
+ label: nonEmptyTrimmedString,
2399
+ sourceId: nonEmptyTrimmedString,
2400
+ selectedCalendarUrls: z.array(nonEmptyTrimmedString.url()).min(1),
2401
+ forgeCalendarUrl: nonEmptyTrimmedString.url().nullable().optional(),
2402
+ createForgeCalendar: z.boolean().optional().default(false),
2403
+ replaceConnectionIds: z.array(nonEmptyTrimmedString).optional().default([])
2355
2404
  })
2356
2405
  ]);
2357
2406
  export const discoverCalendarConnectionSchema = z.discriminatedUnion("provider", [
@@ -2416,7 +2465,11 @@ const workBlockTemplateMutationShape = {
2416
2465
  userId: nonEmptyTrimmedString.nullable().optional()
2417
2466
  };
2418
2467
  export const createWorkBlockTemplateSchema = z
2419
- .object(workBlockTemplateMutationShape)
2468
+ .object({
2469
+ ...workBlockTemplateMutationShape,
2470
+ activityPresetKey: trimmedString.nullable().optional(),
2471
+ customSustainRateApPerHour: z.number().min(0).nullable().optional()
2472
+ })
2420
2473
  .superRefine((value, context) => {
2421
2474
  if (value.endMinute <= value.startMinute) {
2422
2475
  context.addIssue({
@@ -2448,6 +2501,8 @@ export const updateWorkBlockTemplateSchema = z
2448
2501
  startsOn: dateOnlySchema.nullable().optional(),
2449
2502
  endsOn: dateOnlySchema.nullable().optional(),
2450
2503
  blockingState: z.enum(["allowed", "blocked"]).optional(),
2504
+ activityPresetKey: trimmedString.nullable().optional(),
2505
+ customSustainRateApPerHour: z.number().min(0).nullable().optional(),
2451
2506
  userId: nonEmptyTrimmedString.nullable().optional()
2452
2507
  })
2453
2508
  .superRefine((value, context) => {
@@ -2482,6 +2537,8 @@ export const createTaskTimeboxSchema = z
2482
2537
  source: calendarTimeboxSourceSchema.default("manual"),
2483
2538
  status: calendarTimeboxStatusSchema.default("planned"),
2484
2539
  overrideReason: trimmedString.nullable().default(null),
2540
+ activityPresetKey: trimmedString.nullable().optional(),
2541
+ customSustainRateApPerHour: z.number().min(0).nullable().optional(),
2485
2542
  userId: nonEmptyTrimmedString.nullable().optional()
2486
2543
  })
2487
2544
  .superRefine((value, context) => {
@@ -2499,6 +2556,8 @@ export const updateTaskTimeboxSchema = z.object({
2499
2556
  endsAt: z.string().datetime().optional(),
2500
2557
  status: calendarTimeboxStatusSchema.optional(),
2501
2558
  overrideReason: trimmedString.nullable().optional(),
2559
+ activityPresetKey: trimmedString.nullable().optional(),
2560
+ customSustainRateApPerHour: z.number().min(0).nullable().optional(),
2502
2561
  userId: nonEmptyTrimmedString.nullable().optional()
2503
2562
  });
2504
2563
  export const recommendTaskTimeboxesSchema = z.object({
@@ -2530,6 +2589,8 @@ export const updateCalendarEventSchema = z
2530
2589
  availability: calendarAvailabilitySchema.optional(),
2531
2590
  eventType: trimmedString.optional(),
2532
2591
  categories: z.array(trimmedString).optional(),
2592
+ activityPresetKey: trimmedString.nullable().optional(),
2593
+ customSustainRateApPerHour: z.number().min(0).nullable().optional(),
2533
2594
  preferredCalendarId: nonEmptyTrimmedString.nullable().optional(),
2534
2595
  userId: nonEmptyTrimmedString.nullable().optional(),
2535
2596
  links: z
@@ -2582,6 +2643,8 @@ export const createCalendarEventSchema = z
2582
2643
  availability: calendarAvailabilitySchema.default("busy"),
2583
2644
  eventType: trimmedString.default(""),
2584
2645
  categories: z.array(trimmedString).default([]),
2646
+ activityPresetKey: trimmedString.nullable().optional(),
2647
+ customSustainRateApPerHour: z.number().min(0).nullable().optional(),
2585
2648
  preferredCalendarId: nonEmptyTrimmedString.nullable().optional(),
2586
2649
  userId: nonEmptyTrimmedString.nullable().optional(),
2587
2650
  links: z
@@ -2814,6 +2877,7 @@ export const updateTaskSchema = z.object({
2814
2877
  title: nonEmptyTrimmedString.optional(),
2815
2878
  description: trimmedString.optional(),
2816
2879
  status: taskStatusSchema.optional(),
2880
+ completedAt: dateTimeSchema.optional(),
2817
2881
  priority: taskPrioritySchema.optional(),
2818
2882
  owner: nonEmptyTrimmedString.optional(),
2819
2883
  userId: nonEmptyTrimmedString.nullable().optional(),
@@ -3,6 +3,7 @@ export class ForgeApiError extends Error {
3
3
  code;
4
4
  details;
5
5
  requestPath;
6
+ response;
6
7
  constructor(input) {
7
8
  super(input.message);
8
9
  this.name = "ForgeApiError";
@@ -10,6 +11,7 @@ export class ForgeApiError extends Error {
10
11
  this.code = input.code;
11
12
  this.details = input.details ?? [];
12
13
  this.requestPath = input.requestPath;
14
+ this.response = input.response ?? null;
13
15
  }
14
16
  }
15
17
  export function describeApiError(error) {
@@ -130,7 +130,12 @@ async function request(path, init) {
130
130
  ? body
131
131
  : `Request failed: ${response.status}`,
132
132
  requestPath: path,
133
- details
133
+ details,
134
+ response: typeof body === "string"
135
+ ? body
136
+ : body && typeof body === "object"
137
+ ? body
138
+ : null
134
139
  });
135
140
  }
136
141
  return body;
@@ -161,7 +166,12 @@ async function requestBlob(path, init) {
161
166
  ? maybeBody.message
162
167
  : `Request failed: ${response.status}`,
163
168
  requestPath: path,
164
- details: []
169
+ details: [],
170
+ response: typeof body === "string"
171
+ ? body
172
+ : body && typeof body === "object"
173
+ ? body
174
+ : null
165
175
  });
166
176
  }
167
177
  const disposition = response.headers.get("content-disposition");
@@ -1109,6 +1119,33 @@ export function discoverCalendarConnection(input) {
1109
1119
  discovery: dedupeCalendarDiscoveryPayload(response.discovery)
1110
1120
  }));
1111
1121
  }
1122
+ export function getMacOSLocalCalendarStatus() {
1123
+ return request("/api/v1/calendar/macos-local/status");
1124
+ }
1125
+ export function requestMacOSLocalCalendarAccess() {
1126
+ return request("/api/v1/calendar/macos-local/request-access", {
1127
+ method: "POST"
1128
+ });
1129
+ }
1130
+ export function discoverMacOSLocalCalendarSources() {
1131
+ return request("/api/v1/calendar/macos-local/discovery").then((response) => ({
1132
+ ...response,
1133
+ discovery: {
1134
+ ...response.discovery,
1135
+ sources: response.discovery.sources.map((source) => ({
1136
+ ...source,
1137
+ calendars: dedupeCalendarDiscoveryPayload({
1138
+ provider: "macos_local",
1139
+ accountLabel: source.accountLabel,
1140
+ serverUrl: "forge-macos-local://eventkit/",
1141
+ principalUrl: null,
1142
+ homeUrl: null,
1143
+ calendars: source.calendars
1144
+ }).calendars
1145
+ }))
1146
+ }
1147
+ }));
1148
+ }
1112
1149
  export function startGoogleCalendarOauth(input) {
1113
1150
  return request("/api/v1/calendar/oauth/google/start", {
1114
1151
  method: "POST",
@@ -16,6 +16,8 @@ function shortCalendarProviderLabel(provider) {
16
16
  return "Apple";
17
17
  case "microsoft":
18
18
  return "Microsoft";
19
+ case "macos_local":
20
+ return "Mac";
19
21
  case "caldav":
20
22
  default:
21
23
  return "CalDAV";
@@ -394,6 +394,7 @@ export function normalizeForgeSnapshot(raw) {
394
394
  spentTodayAp: 0,
395
395
  remainingAp: 200,
396
396
  forecastAp: 0,
397
+ plannedRemainingAp: 0,
397
398
  targetBandMinAp: 170,
398
399
  targetBandMaxAp: 200,
399
400
  instantCapacityApPerHour: 0,
@@ -407,6 +408,7 @@ export function normalizeForgeSnapshot(raw) {
407
408
  stats: [],
408
409
  currentCurve: [],
409
410
  activeDrains: [],
411
+ plannedDrains: [],
410
412
  warnings: [],
411
413
  recommendations: [],
412
414
  topTaskIdsNeedingSplit: [],
@@ -2,7 +2,7 @@
2
2
  "id": "forge-openclaw-plugin",
3
3
  "name": "Forge",
4
4
  "description": "Curated OpenClaw adapter for the Forge collaboration API, UI entrypoint, and localhost auto-start runtime.",
5
- "version": "0.2.27",
5
+ "version": "0.2.29",
6
6
  "skills": [
7
7
  "./skills"
8
8
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forge-openclaw-plugin",
3
- "version": "0.2.27",
3
+ "version": "0.2.29",
4
4
  "description": "Curated OpenClaw adapter for the Forge collaboration API, UI entrypoint, and localhost auto-start runtime.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -0,0 +1,21 @@
1
+ ALTER TABLE calendar_calendars
2
+ ADD COLUMN source_id TEXT;
3
+
4
+ ALTER TABLE calendar_calendars
5
+ ADD COLUMN source_title TEXT;
6
+
7
+ ALTER TABLE calendar_calendars
8
+ ADD COLUMN source_type TEXT;
9
+
10
+ ALTER TABLE calendar_calendars
11
+ ADD COLUMN calendar_type TEXT;
12
+
13
+ ALTER TABLE calendar_calendars
14
+ ADD COLUMN host_calendar_id TEXT;
15
+
16
+ ALTER TABLE calendar_calendars
17
+ ADD COLUMN canonical_key TEXT;
18
+
19
+ UPDATE calendar_calendars
20
+ SET canonical_key = remote_id
21
+ WHERE canonical_key IS NULL OR TRIM(canonical_key) = '';
@@ -56,9 +56,16 @@ Entity conversation rule:
56
56
  - Before you ask, decide the exact missing thing you need and how that answer will help you name, place, or save the record.
57
57
  - Prefer a progression of:
58
58
  concrete example or intent -> working name -> purpose or meaning -> placement in Forge -> operational details -> linked context.
59
+ - Use those same playbooks for action-heavy non-Psyche flows such as `work_adjustment`, `preference_judgment`, `preference_signal`, and specialized `movement`, `life_force`, or `workbench` requests so the conversation starts from what the user is trying to understand, change, add, update, link, or run before you choose the route.
60
+ - When the operation is not already explicit, identify the job first:
61
+ add, update, review, compare, navigate, link, or run. Skip that meta question
62
+ when the action is already obvious from the user's wording.
59
63
  - For emotionally meaningful non-Psyche records such as goals, habits, and notes, reflect the meaning before you ask for structure.
60
64
  - When the user is vague, ask for one small concrete example, stake, or desired outcome before you ask them to name the record.
61
65
  - When the user is clear, say what the record seems to be becoming and ask only for the last missing detail.
66
+ - When the user wants to review, compare, inspect, or navigate an existing Forge
67
+ record, ask what they are trying to understand first and prefer the read path before
68
+ you reopen create or update intake.
62
69
  - When updating an entity, start with what is changing, what should stay true, and what prompted the update now.
63
70
  - When enough is clear, briefly summarize what you heard in the user's own language before asking for the last missing structural detail.
64
71
  - The quick intake prompts later in this file are fallback checkpoints, not a script to read aloud.
@@ -260,8 +267,9 @@ Minimum field: `label`
260
267
  Usually useful: `description`
261
268
  Ask:
262
269
 
263
- 1. What should this event type be called?
264
- 2. What kind of incident does it represent?
270
+ 1. What kind of repeated moment or incident do you want future reports to name the same way?
271
+ 2. What would count as inside this category, and what should stay outside it?
272
+ 3. If the meaning is clear but the wording is not, would you like me to suggest a concise label?
265
273
 
266
274
  `emotion_definition`
267
275
  Use for a reusable emotion vocabulary entry.
@@ -269,9 +277,9 @@ Minimum field: `label`
269
277
  Usually useful: `description`, `category`
270
278
  Ask:
271
279
 
272
- 1. What emotion label do you want to reuse?
273
- 2. How would you describe it?
274
- 3. Does it belong to a broader category?
280
+ 1. When this feeling is present, what tells you it is this feeling and not a nearby one?
281
+ 2. What would you want a later trigger report to mean when it uses this label?
282
+ 3. If the felt meaning is clear but the wording is not, would you like me to suggest a concise label or broader category?
275
283
 
276
284
  Use these rules when choosing tools.
277
285
 
@@ -291,9 +299,34 @@ Use the wiki tools for file-first memory work:
291
299
  Use the health tools for review and reflective enrichment, not as the default CRUD architecture:
292
300
  `forge_get_sleep_overview`, `forge_get_sports_overview`, `forge_update_sleep_session`, `forge_update_workout_session`
293
301
 
302
+ Use the dedicated domain routes for specialized surfaces that are not simple batch entities:
303
+
304
+ - Movement lives under `/api/v1/movement/*`. Treat it as a dedicated timeline of `stays` and `trips`, not as generic batch CRUD. A `stay` means the user remained in the same place for a span of time. A `trip` means the user traveled between places. Use the movement routes when the user wants to understand time in place, travel behavior, specific stays or trips, known places, or selected-span aggregates such as "how long was I at home in the past 2 weeks?" or "when did I travel last month?".
305
+ - Movement user actions are: query movement behavior, add a place or manual stay/trip overlay, update an existing stay/trip/place, or link a specific movement item to another Forge entity. Keep the explanation user-facing: where they stayed, when they traveled, what changed, and what this movement should be linked to.
306
+ - Movement read lanes map cleanly to the dedicated routes:
307
+ `/api/v1/movement/day`, `/api/v1/movement/month`, `/api/v1/movement/all-time`,
308
+ `/api/v1/movement/timeline`, `/api/v1/movement/places`,
309
+ `/api/v1/movement/selection`, and `/api/v1/movement/trips/:id`.
310
+ - When the user is filling a missing-data gap, the default write path is a user-defined overlay box, not a raw stay or trip patch. Use `POST /api/v1/movement/user-boxes/preflight` if you need to confirm overlap or snap to the nearest missing interval, then `POST /api/v1/movement/user-boxes` with `kind: "stay"` or `kind: "trip"`.
311
+ - Use `PATCH /api/v1/movement/stays/:id` or `PATCH /api/v1/movement/trips/:id` only when the user is editing an existing recorded stay or recorded trip. Do not use those routes to fill a missing span.
312
+ - If the user says something as explicit as "that missing block was me staying home", do not reopen broad intake. Confirm the interval or place only if it is still ambiguous, then create the overlay and read the timeline back.
313
+ - Life Force lives under `/api/v1/life-force*`. Use `GET /api/v1/life-force` for the current energy overview, `PATCH /api/v1/life-force/profile` for durable profile changes, `PUT /api/v1/life-force/templates/:weekday` for weekday curve edits, and `POST /api/v1/life-force/fatigue-signals` for real-time tired or recovered signals.
314
+ - Workbench lives under `/api/v1/workbench/*`. Use those dedicated routes for flow catalog reads, flow CRUD, runs, published outputs, node results, and latest-node-output reads instead of trying to force Workbench through the batch entity routes.
315
+ - Workbench lane hints:
316
+ use `/api/v1/workbench/flows` for flow catalog and CRUD,
317
+ `/api/v1/workbench/flows/:id/run` or `/api/v1/workbench/run` for execution,
318
+ `/api/v1/workbench/flows/:id/output` for published outputs, and the run/node routes
319
+ under `/api/v1/workbench/flows/:id` for run history and node-level inspection.
320
+ - If you are unsure which specialized route family applies, check `forge_get_agent_onboarding` and use its `entityRouteModel.specializedDomainSurfaces` section before guessing.
321
+
294
322
  Use live work tools for `task_run`:
295
323
  `forge_log_work`, `forge_start_task_run`, `forge_heartbeat_task_run`, `forge_focus_task_run`, `forge_complete_task_run`, `forge_release_task_run`
296
324
 
325
+ Use `forge_adjust_work_minutes` for `work_adjustment` when the user wants a truthful signed minute correction on an existing task or project rather than a fake live run or a retroactive new task.
326
+
327
+ Use the dedicated Preferences action tools for `preference_judgment` and `preference_signal`:
328
+ `forge_submit_preferences_judgment`, `forge_submit_preferences_signal`, `forge_update_preferences_score`
329
+
297
330
  Use `forge_post_insight` for `insight`.
298
331
  Use the calendar tools for provider sync and planning:
299
332
  `forge_get_calendar_overview`, `forge_connect_calendar_provider`, `forge_sync_calendar_connection`, `forge_create_work_block_template`, `forge_recommend_task_timeboxes`, `forge_create_task_timebox`