forge-openclaw-plugin 0.2.60 → 0.2.65

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 (59) hide show
  1. package/README.md +121 -51
  2. package/dist/assets/{board-B1V3M__K.js → board-DUwMfZvN.js} +1 -1
  3. package/dist/assets/index-B9VOpR7r.css +1 -0
  4. package/dist/assets/index-DoHjjze2.js +90 -0
  5. package/dist/assets/{motion-CltSTItx.js → motion-Crg3QyXD.js} +1 -1
  6. package/dist/assets/{table-B-VrSFx8.js → table-CTlDeYRs.js} +1 -1
  7. package/dist/assets/{ui-DUqM4jkt.js → ui-CJPaElbj.js} +1 -1
  8. package/dist/assets/{vendor-C0otBhgu.js → vendor-BdrT2htV.js} +217 -207
  9. package/dist/companion-iroh/darwin-arm64/forge-companion-iroh +0 -0
  10. package/dist/companion-iroh/darwin-x64/forge-companion-iroh +0 -0
  11. package/dist/companion-iroh/linux-x64/forge-companion-iroh +0 -0
  12. package/dist/companion-iroh-src/Cargo.lock +4559 -0
  13. package/dist/companion-iroh-src/Cargo.toml +37 -0
  14. package/dist/companion-iroh-src/src/lib.rs +279 -0
  15. package/dist/companion-iroh-src/src/main.rs +478 -0
  16. package/dist/companion-iroh-src/src/protocol.rs +129 -0
  17. package/dist/gamification-previews/dark-fantasy-item-trophy-tasks-anvil-marathon.webp +0 -0
  18. package/dist/gamification-previews/dark-fantasy-item-trophy-xp-levels-the-first-heat.webp +0 -0
  19. package/dist/gamification-previews/dark-fantasy-item-unlock-streaks-molten-crown-fire.webp +0 -0
  20. package/dist/gamification-previews/dark-fantasy-mascot.webp +0 -0
  21. package/dist/gamification-previews/dramatic-smithie-item-trophy-tasks-anvil-marathon.webp +0 -0
  22. package/dist/gamification-previews/dramatic-smithie-item-trophy-xp-levels-the-first-heat.webp +0 -0
  23. package/dist/gamification-previews/dramatic-smithie-item-unlock-streaks-molten-crown-fire.webp +0 -0
  24. package/dist/gamification-previews/dramatic-smithie-mascot.webp +0 -0
  25. package/dist/gamification-previews/mind-locksmith-item-trophy-tasks-anvil-marathon.webp +0 -0
  26. package/dist/gamification-previews/mind-locksmith-item-trophy-xp-levels-the-first-heat.webp +0 -0
  27. package/dist/gamification-previews/mind-locksmith-item-unlock-streaks-molten-crown-fire.webp +0 -0
  28. package/dist/gamification-previews/mind-locksmith-mascot.webp +0 -0
  29. package/dist/index.html +7 -7
  30. package/dist/openclaw/parity.js +27 -0
  31. package/dist/openclaw/plugin-entry-shared.js +2 -2
  32. package/dist/openclaw/plugin-sdk-types.d.ts +2 -1
  33. package/dist/openclaw/routes.d.ts +4 -0
  34. package/dist/openclaw/routes.js +112 -3
  35. package/dist/openclaw/tools.js +32 -4
  36. package/dist/server/server/migrations/059_data_backup_retention.sql +2 -0
  37. package/dist/server/server/src/app.js +288 -61
  38. package/dist/server/server/src/data-management-types.js +2 -0
  39. package/dist/server/server/src/discovery-advertiser.js +13 -0
  40. package/dist/server/server/src/health.js +58 -3
  41. package/dist/server/server/src/movement.js +16 -1
  42. package/dist/server/server/src/openapi.js +410 -9
  43. package/dist/server/server/src/repositories/rewards.js +60 -0
  44. package/dist/server/server/src/services/companion-iroh.js +425 -0
  45. package/dist/server/server/src/services/data-management.js +32 -2
  46. package/dist/server/server/src/services/doctor.js +762 -0
  47. package/dist/server/server/src/services/gamification.js +75 -3
  48. package/dist/server/server/src/services/life-force.js +166 -25
  49. package/dist/server/server/src/web.js +88 -12
  50. package/dist/server/src/lib/api.js +9 -0
  51. package/dist/server/src/lib/gamification-catalog.js +1 -1
  52. package/openclaw.plugin.json +85 -3
  53. package/package.json +10 -6
  54. package/server/migrations/059_data_backup_retention.sql +2 -0
  55. package/skills/forge-openclaw/SKILL.md +80 -19
  56. package/skills/forge-openclaw/entity_conversation_playbooks.md +283 -25
  57. package/skills/forge-openclaw/psyche_entity_playbooks.md +82 -0
  58. package/dist/assets/index-BwKAPo98.css +0 -1
  59. package/dist/assets/index-Dy7c-dRY.js +0 -90
@@ -26,6 +26,7 @@ import { createProject, updateProject } from "./repositories/projects.js";
26
26
  import { createPreferenceCatalog, createPreferenceCatalogItem, createPreferenceContext, createPreferenceItem, createPreferenceItemFromEntity, deletePreferenceCatalog, deletePreferenceCatalogItem, deletePreferenceContext, deletePreferenceItem, getPreferenceCatalogById, getPreferenceCatalogItemById, getPreferenceContextById, getPreferenceItemById, getPreferenceWorkspace, listPreferenceCatalogItems, listPreferenceCatalogs, listPreferenceContexts, listPreferenceItems, mergePreferenceContexts, startPreferenceGame, submitAbsoluteSignal, submitPairwiseJudgment, updatePreferenceCatalog, updatePreferenceCatalogItem, updatePreferenceContext, updatePreferenceItem, updatePreferenceScore } from "./repositories/preferences.js";
27
27
  import { createStrategy, getStrategyById, listStrategies, updateStrategy } from "./repositories/strategies.js";
28
28
  import { buildKnowledgeGraph, buildKnowledgeGraphFocus } from "./services/knowledge-graph.js";
29
+ import { applyForgeDoctorFixes, buildForgeDoctorReport } from "./services/doctor.js";
29
30
  import { createManualRewardGrant, getRewardRuleById, listRewardLedger, listRewardRules, recordWorkAdjustmentReward, recordSessionEvent, updateRewardRule } from "./repositories/rewards.js";
30
31
  import { markGamificationCelebrationSeen } from "./repositories/gamification.js";
31
32
  import { getSettingsFileStatus, listAgentIdentities, getSettings, isPsycheAuthRequired, mirrorSettingsFileFromCurrentState, updateSettings, verifyAgentToken } from "./repositories/settings.js";
@@ -63,7 +64,8 @@ import { buildOpenApiDocument } from "./openapi.js";
63
64
  import { registerWebRoutes } from "./web.js";
64
65
  import { createManagerRuntime } from "./managers/runtime.js";
65
66
  import { isManagerError } from "./managers/type-guards.js";
66
- import { createCompanionPairingSession, createCompanionPairingSessionSchema, createSleepSession, createSleepSessionSchema, createWorkoutSession, createWorkoutSessionSchema, deleteSleepSession, deleteWorkoutSession, getCompanionPairingSessionById, getCompanionOverview, getFitnessViewData, getSleepSessionById, getSleepSessionDetailById, getSleepTimelineOverlaysForRange, getSleepViewData, getVitalsViewData, getWorkoutSessionById, ingestMobileHealthSync, mobileHealthSyncSchema, patchCompanionPairingSourceState, patchCompanionPairingSourceStateSchema, companionSourceKeySchema, requireValidPairing, revokeAllCompanionPairingSessions, revokeAllCompanionPairingSessionsSchema, revokeCompanionPairingSession, updateMobileCompanionSourceState, updateMobileCompanionSourceStateSchema, verifyCompanionPairing, verifyCompanionPairingSchema, updateSleepMetadata, updateSleepMetadataSchema, updateWorkoutMetadata, updateWorkoutMetadataSchema } from "./health.js";
67
+ import { buildCompanionPairingTransport, getCompanionIrohStatus, stopCompanionIroh } from "./services/companion-iroh.js";
68
+ import { createCompanionPairingSession, createCompanionPairingSessionSchema, createSleepSession, createSleepSessionSchema, createWorkoutSession, createWorkoutSessionSchema, deleteSleepSession, deleteWorkoutSession, getCompanionPairingSessionById, getCompanionOverview, getFitnessViewData, getSleepSessionById, getSleepSessionDetailById, getSleepTimelineOverlaysForRange, getSleepViewData, getVitalsViewData, getWorkoutSessionById, heartbeatCompanionPairing, heartbeatCompanionPairingSchema, ingestMobileHealthSync, mobileHealthSyncSchema, patchCompanionPairingSourceState, patchCompanionPairingSourceStateSchema, companionSourceKeySchema, requireValidPairing, revokeAllCompanionPairingSessions, revokeAllCompanionPairingSessionsSchema, revokeCompanionPairingSession, updateMobileCompanionSourceState, updateMobileCompanionSourceStateSchema, verifyCompanionPairing, verifyCompanionPairingSchema, updateSleepMetadata, updateSleepMetadataSchema, updateWorkoutMetadata, updateWorkoutMetadataSchema } from "./health.js";
67
69
  import { analyzeMovementUserBoxPreflight, createMovementUserBox, createMovementPlace, deleteMovementUserBox, getMovementAllTimeSummary, getMovementBoxDetail, getMovementDayDetail, getMovementMobileBootstrap, getMovementTimeline, getMovementSelectionAggregate, getMovementSettings, getMovementTripDetail, getMovementMonthSummary, invalidateAutomaticMovementBox, listMovementPlaces, movementAutomaticBoxInvalidateSchema, movementMobileBootstrapSchema, movementMobilePlaceMutationSchema, movementMobileStayPatchSchema, movementMobileUserBoxCreateSchema, movementMobileUserBoxPreflightSchema, movementMobileUserBoxPatchSchema, movementMobileAutomaticBoxInvalidateSchema, movementMobileTimelineSchema, movementPlaceMutationSchema, movementPlacePatchSchema, movementSelectionAggregateSchema, movementStayPatchSchema, movementTripPatchSchema, movementUserBoxCreateSchema, movementUserBoxPreflightSchema, movementUserBoxPatchSchema, movementSettingsPatchSchema, movementTimelineQuerySchema, movementTripPointPatchSchema, deleteMovementStay, deleteMovementTrip, deleteMovementTripPoint, updateMovementPlace, updateMovementSettings, updateMovementStay, updateMovementTrip, updateMovementUserBox, updateMovementTripPoint, resolveMovementTimelineSegmentForBox } from "./movement.js";
68
70
  import { getScreenTimeAllTimeSummary, getScreenTimeDayDetail, getScreenTimeMonthSummary, getScreenTimeSettings, screenTimeSettingsPatchSchema, updateScreenTimeSettings } from "./screen-time.js";
69
71
  import { assertWatchReady, buildWatchBootstrap, ingestWatchCaptureBatch, mobileWatchBootstrapSchema, mobileWatchCaptureBatchSchema, mobileWatchHabitCheckInSchema } from "./watch-mobile.js";
@@ -153,6 +155,18 @@ function buildApiBaseUrl(request) {
153
155
  const basePath = forwardedPrefix.replace(/\/$/, "");
154
156
  return `${request.protocol}://${host}${basePath}/api/v1`;
155
157
  }
158
+ function buildUiBaseUrlFromApiBaseUrl(apiBaseUrl) {
159
+ try {
160
+ const url = new URL(apiBaseUrl);
161
+ url.pathname = "/forge/";
162
+ url.search = "";
163
+ url.hash = "";
164
+ return url.toString();
165
+ }
166
+ catch {
167
+ return apiBaseUrl;
168
+ }
169
+ }
156
170
  function readSingleForwardedHeader(value) {
157
171
  if (Array.isArray(value)) {
158
172
  return value[0]?.split(",")[0]?.trim() || null;
@@ -1956,7 +1970,7 @@ function buildPreferredMutationPath(entityType) {
1956
1970
  case "wiki_page":
1957
1971
  return "Use /api/v1/wiki/pages with POST or PATCH for page CRUD.";
1958
1972
  case "calendar_connection":
1959
- return "Use /api/v1/calendar/connections plus provider-specific setup flows.";
1973
+ return "Use /api/v1/calendar/discovery or /api/v1/calendar/macos-local/discovery before setup when needed; use /api/v1/calendar/connections with POST, PATCH, DELETE, rediscovery, and sync for connection lifecycle work.";
1960
1974
  case "task_run":
1961
1975
  return "Use the task-run action routes to start, heartbeat, focus, complete, or release live work.";
1962
1976
  case "questionnaire_run":
@@ -1991,7 +2005,7 @@ function buildPreferredMutationTool(entityType) {
1991
2005
  case "wiki_page":
1992
2006
  return "forge_upsert_wiki_page";
1993
2007
  case "calendar_connection":
1994
- return "forge_connect_calendar_provider | forge_sync_calendar_connection";
2008
+ return "forge_connect_calendar_provider | forge_sync_calendar_connection | mirrored calendar connection routes";
1995
2009
  case "task_run":
1996
2010
  return "forge_start_task_run | forge_heartbeat_task_run | forge_focus_task_run | forge_complete_task_run | forge_release_task_run";
1997
2011
  case "questionnaire_run":
@@ -2802,6 +2816,7 @@ const AGENT_ONBOARDING_CONVERSATION_RULES = [
2802
2816
  "Once the route family is clear, say it plainly enough that another agent could follow the same path without guessing.",
2803
2817
  "For Movement specifically, treat missing-data corrections as user-defined overlay boxes unless the user is editing an already-recorded stay or trip. When the user already gave a clear instruction like 'that missing block was home', act after only the last ambiguity is resolved.",
2804
2818
  "For action workflows such as task_run, work_adjustment, questionnaire_run, preference_judgment, preference_signal, and self_observation, keep the question focused on the missing action payload and do not downgrade the request into generic batch CRUD.",
2819
+ "For read-model-only health surfaces such as sleep_overview and sports_overview, use the dedicated overview reads first when the user wants review, pattern interpretation, recovery context, or training-load context. Move to sleep_session or workout_session writes only after one specific stored session needs enrichment.",
2805
2820
  "For normal stored Preferences and questionnaire records, use batch CRUD by default; switch to dedicated action routes only for judgments, signals, run answers, clone/draft/publish lifecycle, or visual comparison gameplay.",
2806
2821
  "When the user wants to remember a book, article, paper, source, concept, person, conversation, project reference, recurring explanation, or personal manual, consider wiki_page before note or self_observation.",
2807
2822
  "For meaning-bearing updates, especially in Psyche, briefly say what feels newly true before you ask for the one structural detail that still changes the save."
@@ -3018,6 +3033,30 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
3018
3033
  "Ask about tags only if they help later retrieval."
3019
3034
  ]
3020
3035
  },
3036
+ {
3037
+ focus: "sleep_overview",
3038
+ openingQuestion: "What are you trying to understand from your sleep picture right now?",
3039
+ coachingGoal: "Review recent nights, regularity, score, stages, and recovery context before deciding whether one sleep_session needs enrichment.",
3040
+ askSequence: [
3041
+ "Ask what practical sleep question the user wants the overview to answer.",
3042
+ "Ask which night or date range matters only if the scope is not already clear.",
3043
+ "Use the dedicated sleep overview read before asking the user to reconstruct metrics from memory.",
3044
+ "Reflect the pattern or decision the user is trying to understand.",
3045
+ "Move to a sleep_session write only when one specific night needs reflective context, tags, notes, or links."
3046
+ ]
3047
+ },
3048
+ {
3049
+ focus: "sports_overview",
3050
+ openingQuestion: "What are you trying to understand from your workout picture right now?",
3051
+ coachingGoal: "Review workouts, training load, effort, activity types, and recovery context before deciding whether one workout_session needs enrichment.",
3052
+ askSequence: [
3053
+ "Ask what practical training or recovery question the user wants the overview to answer.",
3054
+ "Ask which workout or date range matters only if the scope is not already clear.",
3055
+ "Use the dedicated sports overview read before asking the user to reconstruct metrics from memory.",
3056
+ "Reflect the decision the user is trying to make from the training picture.",
3057
+ "Move to a workout_session write only when one specific workout needs reflective context, tags, notes, or links."
3058
+ ]
3059
+ },
3021
3060
  {
3022
3061
  focus: "preference_catalog",
3023
3062
  openingQuestion: "What decision or taste question should this catalog help with?",
@@ -3122,10 +3161,12 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
3122
3161
  askSequence: [
3123
3162
  "Ask what they are trying to make clearer, repair, or preserve about where they were before you narrow to the exact movement lane.",
3124
3163
  "Ask whether the user is trying to query behavior, add something manually, update an existing movement item, or link movement to another Forge entity.",
3164
+ "Treat day, month, all-time, timeline, trip detail, and selection as internal route lanes. With the user, ask for the useful time window, place, selected span, stay, or trip instead of listing route choices.",
3125
3165
  "Ask whether the focus is a stay, a trip, a place, a timeline window, or a selected span.",
3126
3166
  "Ask for the time window, place, or movement item that makes the question concrete.",
3127
3167
  "Ask what they are trying to notice, preserve, or answer through that movement context.",
3128
3168
  "Choose the dedicated day, month, all-time, timeline, places, trip-detail, or selection route once the question shape is clear.",
3169
+ "Use allTime for whole-history aggregates, selection for a bounded selected-span aggregate, and tripDetail only when a concrete trip id is known.",
3129
3170
  "If the truth of one uncertain span is still unclear, read the timeline or saved-box detail before you mutate it.",
3130
3171
  "Skip the meta lane question when the user already named the exact correction or review target and only one ambiguity remains.",
3131
3172
  "If the request is filling a missing-data gap, use a user-defined movement box rather than a raw stay or trip patch.",
@@ -3142,6 +3183,8 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
3142
3183
  askSequence: [
3143
3184
  "Ask what feels off, important, or worth tracking in their energy picture before you reduce it to one life-force lane.",
3144
3185
  "Ask whether the job is overview, profile change, weekday-template change, or fatigue signaling.",
3186
+ "Treat overview, profile, weekday-template, and fatigue-signal as internal route lanes. With the user, ask whether this is a current read, a durable assumption, a repeated weekday rhythm, or a right-now state instead of reciting route names.",
3187
+ "Use routeKey overview for the current read; it maps to GET /api/v1/life-force, not /api/v1/life-force/overview.",
3145
3188
  "Ask what part of the current energy picture feels most important or inaccurate.",
3146
3189
  "Ask what should stay true if they are changing profile or template assumptions.",
3147
3190
  "Ask whether the user is describing a stable weekly shape or just how today feels when the lane is still blurred.",
@@ -3159,6 +3202,8 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
3159
3202
  askSequence: [
3160
3203
  "Ask what they are trying to learn, repair, publish, or run through Workbench before you narrow to flow discovery, editing, execution, or results.",
3161
3204
  "Ask whether the job is flow discovery, one flow edit, execution, run history, published output, node-level inspection, or latest-node-output lookup.",
3205
+ "Treat saved-flow catalog, box catalog, run history, run detail, node result, latest node output, and published output as internal read lanes. With the user, ask whether they need the saved flow, its input contract, one run, one node, or the public result instead of listing route keys.",
3206
+ "Use listFlows for the saved flow catalog and boxCatalog for available input-box contracts; do not collapse both into a vague catalog read.",
3162
3207
  "Ask which flow, slug, run, or node the request is about.",
3163
3208
  "Ask whether they need the stable flow contract, one run result, one published output, one node result, or the latest node output.",
3164
3209
  "If the user already named the flow and action clearly, skip the meta lane question and ask only for the missing run, node, or output scope.",
@@ -3172,26 +3217,29 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
3172
3217
  {
3173
3218
  focus: "event_type",
3174
3219
  openingQuestion: "What kind of moment keeps happening that you want future reports to name the same way each time?",
3175
- coachingGoal: "Create a reusable incident category that will actually help future reports stay consistent.",
3220
+ coachingGoal: "Bridge into Psyche-quality questioning for a reusable incident category without flattening the lived episode into cold taxonomy.",
3176
3221
  askSequence: [
3177
- "Ask what kind of moment or incident this label should capture in lived terms.",
3178
- "Reflect the repeated moment back in plain language before narrowing the wording.",
3179
- "Ask how narrow or broad it should be.",
3180
- "Ask what would count as inside versus outside the category if that boundary is still fuzzy.",
3181
- "Offer a concise label if the lived meaning is clearer than the wording.",
3182
- "Ask for a short description only if the label could be ambiguous later."
3222
+ "Treat event_type as Psyche taxonomy: use the event_type Psyche coaching playbook when the user is exploring meaning, and keep batch CRUD as the storage path.",
3223
+ "Ask what kind of emotionally meaningful moment keeps recurring and why naming it consistently would help future trigger reports.",
3224
+ "Reflect the repeated moment back in plain language by naming the emotional or relational stake before narrowing the wording.",
3225
+ "Ask for one recent example if the boundary is still abstract.",
3226
+ "Clarify what belongs inside this event type and what should stay outside it.",
3227
+ "Offer one concise candidate label once the repeated moment is clear.",
3228
+ "Link it to trigger reports, beliefs, patterns, modes, or emotion definitions only after the category itself feels accurate."
3183
3229
  ]
3184
3230
  },
3185
3231
  {
3186
3232
  focus: "emotion_definition",
3187
3233
  openingQuestion: "When this feeling is present, what tells you it is this feeling and not a nearby one?",
3188
- coachingGoal: "Create a reusable emotion label with enough clarity to use consistently later.",
3234
+ coachingGoal: "Bridge into Psyche-quality questioning for a reusable emotion label by its lived signature, not by a dictionary definition.",
3189
3235
  askSequence: [
3190
- "Ask what this feeling is like in lived terms when the user says it.",
3191
- "Reflect the felt signature back in plain language before you settle the label.",
3236
+ "Treat emotion_definition as Psyche taxonomy: use the emotion_definition Psyche coaching playbook when the user is exploring the feeling, and keep batch CRUD as the storage path.",
3237
+ "Ask when this feeling was present recently and what made it recognizable.",
3238
+ "Reflect the felt signature back in plain language before asking for category or label polish.",
3192
3239
  "Ask what distinguishes it from nearby emotions if that matters.",
3193
- "Offer a concise label if the felt meaning is clearer than the wording.",
3194
- "Ask for a broader category only if it will help later browsing or reporting."
3240
+ "Ask what the feeling tends to signal, protect, warn about, long for, or demand.",
3241
+ "Offer one concise definition in the user's language and invite correction.",
3242
+ "Link it to trigger reports, modes, beliefs, or patterns only after the definition feels steady."
3195
3243
  ]
3196
3244
  }
3197
3245
  ];
@@ -3201,9 +3249,10 @@ const AGENT_ONBOARDING_PSYCHE_PLAYBOOKS = [
3201
3249
  useWhen: "Use for a lived direction, quality of being, or way of showing up that matters to the user and should guide actions rather than just describe an outcome.",
3202
3250
  coachingGoal: "Clarify the value as a chosen direction, distinguish it from a goal, and gather one concrete way the user wants to embody it now.",
3203
3251
  askSequence: [
3204
- "Start with what matters and why it matters now.",
3205
- "Ask for one concrete example of what living this value would look like in ordinary life.",
3206
- "Separate the value direction from any specific outcome or achievement goal.",
3252
+ "Start with one ordinary recent moment where the value felt alive, absent, or painfully important.",
3253
+ "Ask what mattered in that moment and why it matters now.",
3254
+ "Reflect the direction in the user's own language before separating it from any specific outcome or achievement goal.",
3255
+ "Ask what someone would see this week if the user lived this value a little more.",
3207
3256
  "Notice tensions, barriers, or situations where the value gets lost.",
3208
3257
  "Name one small committed action that would move toward the value."
3209
3258
  ],
@@ -3218,6 +3267,7 @@ const AGENT_ONBOARDING_PSYCHE_PLAYBOOKS = [
3218
3267
  "linkedTaskIds"
3219
3268
  ],
3220
3269
  exampleQuestions: [
3270
+ "Where did this value show up recently, even faintly?",
3221
3271
  "What feels deeply important about this to you?",
3222
3272
  "If you were living this value a little more this week, what would someone be able to see?",
3223
3273
  "What goal or area of life does this value belong to most clearly?",
@@ -3322,12 +3372,12 @@ const AGENT_ONBOARDING_PSYCHE_PLAYBOOKS = [
3322
3372
  useWhen: "Use for a belief, rule, or self-statement that keeps showing up in reactions, especially when the user can phrase it as a sentence.",
3323
3373
  coachingGoal: "Turn implicit self-talk or a likely schema theme into one explicit belief statement that can be tested and linked to patterns, reports, and modes without forcing the user into a debate too early.",
3324
3374
  askSequence: [
3325
- "Reflect the likely belief in the user's own words and ask for confirmation or correction.",
3326
- "Decide whether it is absolute or conditional.",
3327
- "Estimate how true it feels from 0 to 100.",
3328
- "Collect evidence for and evidence against.",
3329
- "Notice where the belief may have been learned or reinforced.",
3330
- "Offer a more flexible alternative belief.",
3375
+ "Anchor the belief in one recent moment or reaction before abstracting it.",
3376
+ "Reflect the likely belief sentence in the user's own words and ask for confirmation or correction.",
3377
+ "Condense it into one saveable statement only after the wording feels accurate.",
3378
+ "Decide whether it is absolute or conditional after the sentence lands.",
3379
+ "Estimate how true it feels from 0 to 100 only if that helps the user understand its grip or guide later review.",
3380
+ "Explore evidence, origin, and a flexible alternative only if the user wants to examine or soften the belief now.",
3331
3381
  "Link a schemaId only when a real schema catalog match is known."
3332
3382
  ],
3333
3383
  requiredForCreate: ["statement", "beliefType"],
@@ -3343,6 +3393,7 @@ const AGENT_ONBOARDING_PSYCHE_PLAYBOOKS = [
3343
3393
  "linkedModeIds"
3344
3394
  ],
3345
3395
  exampleQuestions: [
3396
+ "When did this belief show up most recently?",
3346
3397
  "If we turned that reaction into one sentence, what would it sound like?",
3347
3398
  "When that reaction hits, what does it start telling you?",
3348
3399
  "Is it more of an always/never belief, or an if-then rule?",
@@ -3355,6 +3406,7 @@ const AGENT_ONBOARDING_PSYCHE_PLAYBOOKS = [
3355
3406
  "Schema catalog entries are reference concepts; belief_entry is the user-owned record.",
3356
3407
  "If no schema catalog match is known, omit schemaId rather than inventing one.",
3357
3408
  "Do not argue the user out of the belief. Reflect it, understand its function, and then collaboratively test for flexibility.",
3409
+ "Do not rush to confidence, evidence, or flexible alternatives before the user feels the belief has been captured.",
3358
3410
  "When the wording is nearly there, ask whether it feels true enough before you move into confidence, evidence, or alternative-belief details.",
3359
3411
  "A useful hypothesis should name the rule or prediction the moment seems to activate and then invite correction before saving it as the belief sentence."
3360
3412
  ]
@@ -3400,6 +3452,7 @@ const AGENT_ONBOARDING_PSYCHE_PLAYBOOKS = [
3400
3452
  "Mode profiles are durable parts descriptions.",
3401
3453
  "Mode guide sessions are the guided reasoning process that can lead toward a mode profile.",
3402
3454
  "Do not overpathologize. The point is to understand the part's job and cost, then increase choice.",
3455
+ "Do not start by asking for the mode family; choose the family after the lived description, protective job, fear, or burden is visible enough.",
3403
3456
  "If the user asks to understand the mode first, start from a recent moment and ask what the part is trying to do before you name it.",
3404
3457
  "When enough evidence is present, offer one tentative hypothesis about the mode's protective job, fear, or burden before choosing the family label."
3405
3458
  ]
@@ -3436,12 +3489,13 @@ const AGENT_ONBOARDING_PSYCHE_PLAYBOOKS = [
3436
3489
  useWhen: "Use for one specific emotionally meaningful incident that should be mapped from situation through emotions, thoughts, behaviors, consequences, and next moves.",
3437
3490
  coachingGoal: "Help the user build a clear incident chain with enough structure to learn from one episode while staying grounded and not rushing past the user's felt experience.",
3438
3491
  askSequence: [
3439
- "Name the incident briefly and anchor it in one concrete sequence.",
3440
- "Describe what happened in the situation.",
3441
- "Capture emotions and intensity.",
3492
+ "Start with the situation and felt stake: what happened, and why did it hit enough to save.",
3493
+ "Name the incident briefly only after the concrete sequence is clear.",
3494
+ "Capture emotions, body state, and intensity before moving into interpretation.",
3442
3495
  "Capture thoughts, meanings, or belief-linked interpretations.",
3443
- "Capture behaviors and immediate coping moves.",
3444
- "Capture short-term and long-term consequences.",
3496
+ "Capture behaviors, urges, and immediate coping moves.",
3497
+ "Capture what the move did short term and what it cost or changed later.",
3498
+ "Offer one careful hypothesis about the sequence only after situation, emotion, meaning, behavior, and consequence are partly visible.",
3445
3499
  "Identify next moves and linked patterns, beliefs, modes, values, or tasks."
3446
3500
  ],
3447
3501
  requiredForCreate: ["title"],
@@ -3473,6 +3527,7 @@ const AGENT_ONBOARDING_PSYCHE_PLAYBOOKS = [
3473
3527
  notes: [
3474
3528
  "Use eventTypeId only when a known event taxonomy item fits; otherwise use customEventType.",
3475
3529
  "Use emotionDefinitionId only when a known emotion definition fits; otherwise keep the raw label.",
3530
+ "Do not turn the report into a worksheet dump before the felt stake is clear; reflect what made the episode matter before asking for the full chain.",
3476
3531
  "If the user becomes overwhelmed, slow down, summarize, and return to one segment of the chain at a time instead of pushing for the full report in one turn.",
3477
3532
  "Only hypothesize about the incident sequence after the situation, emotion, meaning, behavior, and consequence are at least partly visible."
3478
3533
  ]
@@ -4230,8 +4285,12 @@ function buildAgentOnboardingPayload(request) {
4230
4285
  },
4231
4286
  calendar_connection: {
4232
4287
  list: "/api/v1/calendar/connections",
4288
+ discover: "/api/v1/calendar/discovery",
4289
+ discoverMacOSLocal: "/api/v1/calendar/macos-local/discovery",
4290
+ rediscover: "/api/v1/calendar/connections/:id/discovery",
4233
4291
  create: "/api/v1/calendar/connections",
4234
4292
  update: "/api/v1/calendar/connections/:id",
4293
+ sync: "/api/v1/calendar/connections/:id/sync",
4235
4294
  delete: "/api/v1/calendar/connections/:id"
4236
4295
  }
4237
4296
  },
@@ -4305,12 +4364,38 @@ function buildAgentOnboardingPayload(request) {
4305
4364
  specializedDomainSurfaces: {
4306
4365
  movement: {
4307
4366
  classification: "specialized_domain_surface",
4367
+ aliases: ["movement", "Movement"],
4308
4368
  summary: "Dedicated movement workspace API. Use these routes for stays, trips, time-in-place questions, visited places, trip detail, selection aggregates, user-defined overlays, and repair actions on already-recorded movement data.",
4309
4369
  routeSelectionQuestions: [
4310
4370
  "Is the user asking for a day, month, all-time, timeline, place, trip detail, or selected-span answer?",
4311
4371
  "Is this a missing-gap overlay, a saved-overlay repair, or an edit to one already-recorded stay, trip, or trip point?",
4312
4372
  "If the target is already known, what one time, place, or saved-object detail is still missing before acting?"
4313
4373
  ],
4374
+ methodRoutes: {
4375
+ day: "GET /api/v1/movement/day",
4376
+ month: "GET /api/v1/movement/month",
4377
+ allTime: "GET /api/v1/movement/all-time",
4378
+ timeline: "GET /api/v1/movement/timeline",
4379
+ places: "GET /api/v1/movement/places",
4380
+ boxDetail: "GET /api/v1/movement/boxes/:id",
4381
+ tripDetail: "GET /api/v1/movement/trips/:id",
4382
+ selection: "POST /api/v1/movement/selection",
4383
+ settings: "GET /api/v1/movement/settings",
4384
+ settingsUpdate: "PATCH /api/v1/movement/settings",
4385
+ placeCreate: "POST /api/v1/movement/places",
4386
+ placeUpdate: "PATCH /api/v1/movement/places/:id",
4387
+ userBoxCreate: "POST /api/v1/movement/user-boxes",
4388
+ userBoxPreflight: "POST /api/v1/movement/user-boxes/preflight",
4389
+ userBoxUpdate: "PATCH /api/v1/movement/user-boxes/:id",
4390
+ userBoxDelete: "DELETE /api/v1/movement/user-boxes/:id",
4391
+ automaticBoxInvalidate: "POST /api/v1/movement/automatic-boxes/:id/invalidate",
4392
+ stayUpdate: "PATCH /api/v1/movement/stays/:id",
4393
+ stayDelete: "DELETE /api/v1/movement/stays/:id",
4394
+ tripUpdate: "PATCH /api/v1/movement/trips/:id",
4395
+ tripDelete: "DELETE /api/v1/movement/trips/:id",
4396
+ tripPointUpdate: "PATCH /api/v1/movement/trips/:id/points/:pointId",
4397
+ tripPointDelete: "DELETE /api/v1/movement/trips/:id/points/:pointId"
4398
+ },
4314
4399
  readRoutes: {
4315
4400
  day: "/api/v1/movement/day",
4316
4401
  month: "/api/v1/movement/month",
@@ -4340,6 +4425,7 @@ function buildAgentOnboardingPayload(request) {
4340
4425
  },
4341
4426
  notes: [
4342
4427
  "Movement is not a normal batch CRUD entity family. It is a dedicated record of stays and trips: a stay means the user remained in the same place for a span of time, and a trip means they traveled between places.",
4428
+ "Route-selection questions are internal. User-facing questions should ask for the useful time window, place, selected span, stay, or trip instead of reciting day/month/all-time/timeline/selection route keys.",
4343
4429
  "Use /api/v1/movement/day, /month, /all-time, /timeline, or /selection when the user wants behavioral answers such as how long they stayed at home, when they traveled, which places dominated a period, or what happened across a selected span.",
4344
4430
  "Use the movement write routes when the user wants to add a place or manual overlay, update a specific stay or trip, repair one recorded movement span, or attach movement context to another Forge record. If the user is filling a missing-data gap, the usual write path is a user-defined overlay box rather than a raw stay or trip patch.",
4345
4431
  "If the user is revising or removing an existing correction, first identify whether the saved object is a user-defined box, automatic box, recorded stay, recorded trip, or trip point so the repair or delete path stays truthful.",
@@ -4348,12 +4434,19 @@ function buildAgentOnboardingPayload(request) {
4348
4434
  },
4349
4435
  lifeForce: {
4350
4436
  classification: "specialized_domain_surface",
4437
+ aliases: ["life_force", "life-force", "Life Force"],
4351
4438
  summary: "Dedicated life-force API. Use it to read the current energy budget, drains, recommendations, and warnings, then patch only the parts that are meant to be user-controlled.",
4352
4439
  routeSelectionQuestions: [
4353
4440
  "Is the user trying to understand the overview, change durable profile assumptions, change a weekday curve, or log a right-now fatigue signal?",
4354
4441
  "Are they describing a repeatable weekly shape or a one-off current state?",
4355
4442
  "If the lane is already clear, what one weekday, profile field, or signal detail is still missing?"
4356
4443
  ],
4444
+ methodRoutes: {
4445
+ overview: "GET /api/v1/life-force",
4446
+ profile: "PATCH /api/v1/life-force/profile",
4447
+ weekdayTemplate: "PUT /api/v1/life-force/templates/:weekday",
4448
+ fatigueSignal: "POST /api/v1/life-force/fatigue-signals"
4449
+ },
4357
4450
  readRoutes: {
4358
4451
  overview: "/api/v1/life-force"
4359
4452
  },
@@ -4364,6 +4457,41 @@ function buildAgentOnboardingPayload(request) {
4364
4457
  },
4365
4458
  notes: [
4366
4459
  "Life Force is a focused domain surface, not a batch CRUD entity type.",
4460
+ "Route-selection questions are internal. User-facing questions should ask whether this is a current read, durable assumption, repeated weekday rhythm, or right-now state instead of reciting overview/profile/template/signal route keys.",
4461
+ "Use GET /api/v1/life-force for the current overview payload with stats, drains, recommendations, and current-curve state.",
4462
+ "Patch the profile only for durable personal settings, update weekday templates only for the curve itself, and post fatigue signals for real-time tired or recovered observations.",
4463
+ "If the user says something like 'I always dip on Tuesdays after lunch', treat that as a weekday-template change rather than a one-off fatigue signal.",
4464
+ "If the user is asking what changed after a profile, template, or fatigue write, read the overview back so the effect stays visible.",
4465
+ "If the user already knows they want a profile change, weekday-template edit, or right-now fatigue signal, skip the broad lane question and ask only for the missing weekday, profile field, or signal detail."
4466
+ ]
4467
+ },
4468
+ life_force: {
4469
+ classification: "specialized_domain_surface",
4470
+ aliases: ["lifeForce", "life-force", "Life Force"],
4471
+ summary: "Alias for the dedicated Life Force API keyed to the entity-style name `life_force`. Use the same overview, profile, weekday-template, and fatigue-signal routes as `lifeForce`.",
4472
+ routeSelectionQuestions: [
4473
+ "Is the user trying to understand the overview, change durable profile assumptions, change a weekday curve, or log a right-now fatigue signal?",
4474
+ "Are they describing a repeatable weekly shape or a one-off current state?",
4475
+ "If the lane is already clear, what one weekday, profile field, or signal detail is still missing?"
4476
+ ],
4477
+ methodRoutes: {
4478
+ overview: "GET /api/v1/life-force",
4479
+ profile: "PATCH /api/v1/life-force/profile",
4480
+ weekdayTemplate: "PUT /api/v1/life-force/templates/:weekday",
4481
+ fatigueSignal: "POST /api/v1/life-force/fatigue-signals"
4482
+ },
4483
+ readRoutes: {
4484
+ overview: "/api/v1/life-force"
4485
+ },
4486
+ writeRoutes: {
4487
+ profile: "/api/v1/life-force/profile",
4488
+ weekdayTemplate: "/api/v1/life-force/templates/:weekday",
4489
+ fatigueSignal: "/api/v1/life-force/fatigue-signals"
4490
+ },
4491
+ notes: [
4492
+ "This `life_force` key exists so agents can look up the specialized route family by the entity catalog name without guessing that the canonical surface key is `lifeForce`.",
4493
+ "Life Force is a focused domain surface, not a batch CRUD entity type.",
4494
+ "Route-selection questions are internal. User-facing questions should ask whether this is a current read, durable assumption, repeated weekday rhythm, or right-now state instead of reciting overview/profile/template/signal route keys.",
4367
4495
  "Use GET /api/v1/life-force for the current overview payload with stats, drains, recommendations, and current-curve state.",
4368
4496
  "Patch the profile only for durable personal settings, update weekday templates only for the curve itself, and post fatigue signals for real-time tired or recovered observations.",
4369
4497
  "If the user says something like 'I always dip on Tuesdays after lunch', treat that as a weekday-template change rather than a one-off fatigue signal.",
@@ -4373,12 +4501,31 @@ function buildAgentOnboardingPayload(request) {
4373
4501
  },
4374
4502
  workbench: {
4375
4503
  classification: "specialized_domain_surface",
4504
+ aliases: ["workbench", "Workbench"],
4376
4505
  summary: "Dedicated graph-flow API. Use it for flow catalog reads, flow CRUD, execution, run history, published outputs, node results, and latest successful node outputs.",
4377
4506
  routeSelectionQuestions: [
4378
- "Is the job flow discovery, flow editing, execution, published output, run detail, node result, latest node output, or flow chat follow-up?",
4507
+ "Is the job flow discovery, flow editing, execution, run history, published output, run detail, node result, latest node output, or flow chat follow-up?",
4379
4508
  "Does the user need a stable public contract or one execution artifact?",
4380
4509
  "If the flow is already known, what one run, node, or output scope detail is still missing before acting?"
4381
4510
  ],
4511
+ methodRoutes: {
4512
+ listFlows: "GET /api/v1/workbench/flows",
4513
+ flowById: "GET /api/v1/workbench/flows/:id",
4514
+ flowBySlug: "GET /api/v1/workbench/flows/by-slug/:slug",
4515
+ publishedOutput: "GET /api/v1/workbench/flows/:id/output",
4516
+ runs: "GET /api/v1/workbench/flows/:id/runs",
4517
+ runDetail: "GET /api/v1/workbench/flows/:id/runs/:runId",
4518
+ runNodes: "GET /api/v1/workbench/flows/:id/runs/:runId/nodes",
4519
+ nodeResult: "GET /api/v1/workbench/flows/:id/runs/:runId/nodes/:nodeId",
4520
+ latestNodeOutput: "GET /api/v1/workbench/flows/:id/nodes/:nodeId/output",
4521
+ boxCatalog: "GET /api/v1/workbench/catalog/boxes",
4522
+ createFlow: "POST /api/v1/workbench/flows",
4523
+ updateFlow: "PATCH /api/v1/workbench/flows/:id",
4524
+ deleteFlow: "DELETE /api/v1/workbench/flows/:id",
4525
+ runFlow: "POST /api/v1/workbench/flows/:id/run",
4526
+ runByPayload: "POST /api/v1/workbench/run",
4527
+ chatFlow: "POST /api/v1/workbench/flows/:id/chat"
4528
+ },
4382
4529
  readRoutes: {
4383
4530
  listFlows: "/api/v1/workbench/flows",
4384
4531
  flowById: "/api/v1/workbench/flows/:id",
@@ -4401,6 +4548,7 @@ function buildAgentOnboardingPayload(request) {
4401
4548
  },
4402
4549
  notes: [
4403
4550
  "Workbench is a dedicated execution surface, not a batch CRUD entity family.",
4551
+ "Route-selection questions are internal. User-facing questions should ask whether the user needs the saved flow, its input contract, one run, one node, or the public result instead of reciting Workbench route keys.",
4404
4552
  "Use the flow routes when the agent needs stable public input contracts, published outputs, node-level results, or reusable execution history.",
4405
4553
  "If the user is still figuring out inputs or editable structure, read flow detail or box catalog before asking them to author a payload from memory.",
4406
4554
  "Prefer the dedicated output and node-result routes over reverse-engineering raw traces.",
@@ -4411,10 +4559,15 @@ function buildAgentOnboardingPayload(request) {
4411
4559
  },
4412
4560
  readModelOnlySurfaces: {
4413
4561
  sleepOverview: "/api/v1/health/sleep",
4562
+ sleep_overview: "/api/v1/health/sleep",
4414
4563
  sportsOverview: "/api/v1/health/fitness",
4564
+ sports_overview: "/api/v1/health/fitness",
4415
4565
  selfObservation: "/api/v1/psyche/self-observation/calendar",
4566
+ self_observation: "/api/v1/psyche/self-observation/calendar",
4416
4567
  calendarOverview: "/api/v1/calendar/overview",
4568
+ calendar_overview: "/api/v1/calendar/overview",
4417
4569
  operatorOverview: "/api/v1/operator/overview",
4570
+ operator_overview: "/api/v1/operator/overview",
4418
4571
  operatorContext: "/api/v1/operator/context"
4419
4572
  }
4420
4573
  },
@@ -4626,7 +4779,7 @@ function buildAgentOnboardingPayload(request) {
4626
4779
  saveSuggestionPlacement: "end_of_message",
4627
4780
  saveSuggestionTone: "gentle_optional",
4628
4781
  maxQuestionsPerTurn: 1,
4629
- psycheExplorationRule: "When a Psyche entity needs understanding first, begin with one exploratory question before any working formulation, replacement belief, suggested title, or save pitch. Keep the opening reflection to one or two short sentences, stay in plain prose instead of bullets or numbered lists, keep that first reply short, do not mention Forge search or save structure yet, avoid colons or list-shaped phrasing, prefer what/when/how over why until the experience is grounded, wait for the user's answer before offering a fuller formulation, ask permission before moving from charged exploration into naming or challenge when needed, make the next question help the user feel more able to name the experience rather than more examined, do not widen into adjacent entities until the current one has a working sentence the user recognizes, and once the lived experience is coherent stop deepening and help the user name it cleanly. When the user is updating a Psyche record because of one fresh episode, anchor in that episode before renaming the durable formulation, begin with the smallest part of the old wording that no longer fits, and do not reopen the full origin story unless the new understanding is truly structural. If the user accepts the wording, move toward the save instead of reopening deeper exploration.",
4782
+ psycheExplorationRule: "When a Psyche entity needs understanding first, begin with one exploratory question before any working formulation, replacement belief, suggested title, or save pitch. Keep the opening reflection to one or two short sentences, stay in plain prose instead of bullets or numbered lists, keep that first reply short, do not mention Forge search or save structure yet, avoid colons or list-shaped phrasing, prefer what/when/how over why until the experience is grounded, wait for the user's answer before offering a fuller formulation, ask permission before moving from charged exploration into naming or challenge when needed, make the next question help the user feel more able to name the experience rather than more examined, do not widen into adjacent entities until the current one has a working sentence the user recognizes, and once the lived experience is coherent stop deepening and help the user name it cleanly. After one concrete example is clear and a hypothesis lands or is corrected, translate it into a saveable record shape such as a belief sentence, functional loop, behavior, mode, trigger report, value, event type, or emotion definition; ask one accuracy question instead of reopening broad exploration, then use the shared batch entity routes after the user accepts the wording or explicitly asks to save. When the user is updating a Psyche record because of one fresh episode, anchor in that episode before renaming the durable formulation, begin with the smallest part of the old wording that no longer fits, and do not reopen the full origin story unless the new understanding is truly structural. If the user accepts the wording, move toward the save instead of reopening deeper exploration.",
4630
4783
  specializedSurfaceRule: "For Movement, Life Force, and Workbench, clarify the job first, then choose the dedicated route family internally and do not guess at a generic CRUD path. Use specializedDomainSurfaces.routeSelectionQuestions when they are present so the next follow-up selects the right route instead of asking generic questions. When available, use forge_call_movement_route, forge_call_life_force_route, or forge_call_workbench_route after the lane is clear. In user-facing language, talk about timeline, overlay, weekday template, published output, run detail, or node result rather than surfaces, payloads, read paths, mutation paths, or CRUD. If the truth of the current state is still uncertain, read the relevant dedicated view before you mutate it. When the user already named a precise correction or review target, confirm only the route-selecting detail that is still missing. After a concrete Movement, Life Force, or Workbench correction, read the relevant view back when the user is trying to understand the result rather than just store it. The canonical runtime routes stay under /api/v1/*, and the OpenClaw HTTP mirror exposes the same families under /forge/v1/movement, /forge/v1/life-force, and /forge/v1/workbench.",
4631
4784
  reviewShortcutRule: "When the user is reviewing or correcting an existing record, ask what practical question they want the read or correction to answer, then narrow the saved object, timeframe, or route family first. Do not reopen the whole intake unless the user is actually redefining the record.",
4632
4785
  readModelWriteRule: "Self-observation is note-backed and should be written through observed notes with frontmatter.observedAt only when a lightweight episode observation is the right container. Do not use it as the default bucket for Psyche material: prefer trigger_report for one emotionally meaningful episode, behavior_pattern for functional analysis of a recurring loop, behavior for one repeated move, belief_entry for a core sentence, mode_guide_session or mode_profile for a central part-state, and wiki_page for durable memory such as books, articles, concepts, sources, or personal manuals. Sleep and workout sessions stay on batch CRUD by default; use the reflective review helpers only when enriching one already-known record after review.",
@@ -4652,10 +4805,28 @@ function buildAgentOnboardingPayload(request) {
4652
4805
  searchRule: "forge_search_entities accepts searches as an array. Search before create or update when duplicate risk exists.",
4653
4806
  createRule: "Each create operation must include entityType and full data. entityType alone is not enough. This includes calendar_event, work_block_template, task_timebox, sleep_session, workout_session, preference CRUD entities, and questionnaire_instrument alongside the usual planning and Psyche entities.",
4654
4807
  updateRule: "Each update operation must include entityType, id, and patch. For projects, lifecycle changes are status patches: active to restart, paused to suspend, completed to finish. Keep task and project scheduling rules on those same patch payloads. Official habit outcomes can also be logged through forge_update_entities by patching the habit with checkIn: { status, dateKey?, note?, description? } instead of route-hunting. Calendar-event updates still run downstream provider projection sync, and manual health-session field edits belong on the batch route by default rather than on the reflective review helpers.",
4655
- specializedRouteToolRule: "forge_call_movement_route, forge_call_life_force_route, and forge_call_workbench_route expect { routeKey, pathParams?, query?, body? }. Choose routeKey from the tool schema or entityRouteModel.specializedDomainSurfaces, fill pathParams for placeholders such as id, weekday, slug, runId, nodeId, or pointId, use query for read filters and userIds, and use body only for POST, PATCH, or PUT route keys. Normal stored entities still use the shared batch entity tools.",
4808
+ specializedRouteToolRule: "forge_call_movement_route, forge_call_life_force_route, and forge_call_workbench_route expect { routeKey, pathParams?, query?, body? }. Choose routeKey from the tool schema or entityRouteModel.specializedDomainSurfaces, fill pathParams for placeholders such as id, weekday, slug, runId, nodeId, or pointId, use query for read filters and userIds, and use body only for POST, PATCH, or PUT route keys. The Life Force overview route key maps to GET /api/v1/life-force; do not invent /api/v1/life-force/overview. Normal stored entities still use the shared batch entity tools.",
4656
4809
  createExample: '{"operations":[{"entityType":"goal","data":{"title":"Create meaningfully"},"clientRef":"goal-create-1"},{"entityType":"goal","data":{"title":"Build a beautiful family"},"clientRef":"goal-create-2"}]}',
4657
4810
  updateExample: '{"operations":[{"entityType":"project","id":"project_123","patch":{"status":"paused","schedulingRules":{"blockWorkBlockKinds":["main_activity"],"allowWorkBlockKinds":["secondary_activity"]}},"clientRef":"project-suspend-1"},{"entityType":"habit","id":"habit_456","patch":{"checkIn":{"status":"missed","note":"Resisted the urge after dinner.","description":"85 sec reset"}},"clientRef":"habit-check-in-1"}]}',
4658
- specializedRouteToolExample: '{"routeKey":"weekdayTemplate","pathParams":{"weekday":"monday"},"body":{"points":[{"hour":13,"freeAp":-4}]}}'
4811
+ specializedRouteToolExample: '{"routeKey":"weekdayTemplate","pathParams":{"weekday":"monday"},"body":{"points":[{"hour":13,"freeAp":-4}]}}',
4812
+ specializedRouteToolExamples: {
4813
+ movementAllTime: '{"routeKey":"allTime","query":{"userIds":["user_operator"]}}',
4814
+ movementTimeline: '{"routeKey":"timeline","query":{"from":"2026-05-01T00:00:00.000Z","to":"2026-05-06T23:59:59.999Z","userIds":["user_operator"]}}',
4815
+ movementSelection: '{"routeKey":"selection","query":{"from":"2026-05-01T00:00:00.000Z","to":"2026-05-14T23:59:59.999Z","placeIds":["place_home"],"userIds":["user_operator"]}}',
4816
+ movementTripDetail: '{"routeKey":"tripDetail","pathParams":{"id":"trip_123"}}',
4817
+ movementMissingStayPreflight: '{"routeKey":"userBoxPreflight","body":{"kind":"stay","startedAt":"2026-05-06T13:00:00.000Z","endedAt":"2026-05-06T15:00:00.000Z","placeLabel":"Home","userId":"user_operator"}}',
4818
+ movementMissingStayCreate: '{"routeKey":"userBoxCreate","body":{"kind":"stay","startedAt":"2026-05-06T13:00:00.000Z","endedAt":"2026-05-06T15:00:00.000Z","placeLabel":"Home","userId":"user_operator","note":"Manual correction after reviewing the timeline."}}',
4819
+ lifeForceOverview: '{"routeKey":"overview"}',
4820
+ lifeForceProfile: '{"routeKey":"profile","body":{"baselineDailyAp":24,"recoveryNotes":"Clinic-admin days need a lower expected afternoon load."}}',
4821
+ lifeForceWeekdayTemplate: '{"routeKey":"weekdayTemplate","pathParams":{"weekday":"monday"},"body":{"points":[{"hour":13,"freeAp":-4}]}}',
4822
+ lifeForceFatigueSignal: '{"routeKey":"fatigueSignal","body":{"signal":"tired","intensity":7,"note":"Sharp post-lunch dip after clinic admin."}}',
4823
+ workbenchFlowCatalog: '{"routeKey":"listFlows","query":{"includeArchived":false}}',
4824
+ workbenchBoxCatalog: '{"routeKey":"boxCatalog"}',
4825
+ workbenchRunDetail: '{"routeKey":"runDetail","pathParams":{"id":"flow_research_digest","runId":"run_123"}}',
4826
+ workbenchPublishedOutput: '{"routeKey":"publishedOutput","pathParams":{"id":"flow_research_digest"}}',
4827
+ workbenchLatestNodeOutput: '{"routeKey":"latestNodeOutput","pathParams":{"id":"flow_research_digest","nodeId":"node_summary"}}',
4828
+ workbenchRunFlow: '{"routeKey":"runFlow","pathParams":{"id":"flow_research_digest"},"body":{"input":{"topic":"question flow quality"}}}'
4829
+ }
4659
4830
  }
4660
4831
  };
4661
4832
  }
@@ -6055,6 +6226,7 @@ export async function buildServer(options = {}) {
6055
6226
  clearInterval(cronSchedulerTimer);
6056
6227
  clearInterval(dataBackupTimer);
6057
6228
  taskRunWatchdog?.stop();
6229
+ await stopCompanionIroh();
6058
6230
  await managers.backgroundJobs.stop();
6059
6231
  });
6060
6232
  const enqueueWikiIngestJob = (jobId) => {
@@ -6377,6 +6549,7 @@ export async function buildServer(options = {}) {
6377
6549
  storageRoot: getEffectiveDataRoot(),
6378
6550
  dataDir: resolveDataDir(),
6379
6551
  databasePath: resolveDatabasePathForDataRoot(),
6552
+ port: runtimeConfig.port,
6380
6553
  basePath: runtimeConfig.basePath,
6381
6554
  devWebOrigin: process.env.FORGE_DEV_WEB_ORIGIN?.trim() || null
6382
6555
  };
@@ -6385,34 +6558,72 @@ export async function buildServer(options = {}) {
6385
6558
  backend: "forge-node-runtime",
6386
6559
  runtime
6387
6560
  });
6388
- const warnings = [];
6389
- if (!settingsFile.valid) {
6390
- warnings.push(`forge.json is invalid at ${settingsFile.path}. Forge ignored file precedence until the JSON is repaired or rewritten.`);
6391
- }
6392
- if (settingsFile.syncState === "applied_file_overrides") {
6393
- warnings.push("forge.json overrode one or more persisted database settings on this run.");
6394
- }
6395
- if (health.ok === false) {
6396
- warnings.push("The task-run watchdog reported degraded health.");
6397
- }
6398
6561
  return {
6399
- doctor: {
6400
- ok: health.ok && settingsFile.valid,
6401
- now: new Date().toISOString(),
6562
+ doctor: await buildForgeDoctorReport({
6563
+ settings,
6564
+ settingsFile,
6402
6565
  runtime,
6403
- health,
6566
+ health
6567
+ })
6568
+ };
6569
+ });
6570
+ app.post("/api/v1/doctor/fixes", async (request) => {
6571
+ requireScopedAccess(request.headers, ["write"], { route: "/api/v1/doctor/fixes" });
6572
+ const parsed = z
6573
+ .object({
6574
+ fixIds: z.array(z.string().min(1)).optional(),
6575
+ applyAllSafe: z.boolean().optional()
6576
+ })
6577
+ .parse(request.body ?? {});
6578
+ const initialSettings = getSettings();
6579
+ const initialSettingsFile = getSettingsFileStatus();
6580
+ const initialRuntime = {
6581
+ pid: process.pid,
6582
+ storageRoot: getEffectiveDataRoot(),
6583
+ dataDir: resolveDataDir(),
6584
+ databasePath: resolveDatabasePathForDataRoot(),
6585
+ port: runtimeConfig.port,
6586
+ basePath: runtimeConfig.basePath,
6587
+ devWebOrigin: process.env.FORGE_DEV_WEB_ORIGIN?.trim() || null
6588
+ };
6589
+ const initialHealth = buildHealthPayload(taskRunWatchdog, {
6590
+ apiVersion: "v1",
6591
+ backend: "forge-node-runtime",
6592
+ runtime: initialRuntime
6593
+ });
6594
+ const initialDoctor = await buildForgeDoctorReport({
6595
+ settings: initialSettings,
6596
+ settingsFile: initialSettingsFile,
6597
+ runtime: initialRuntime,
6598
+ health: initialHealth
6599
+ });
6600
+ const fixResult = applyForgeDoctorFixes(parsed, {
6601
+ integrityScore: initialDoctor.integrity.score
6602
+ });
6603
+ const settings = getSettings();
6604
+ const settingsFile = getSettingsFileStatus();
6605
+ const runtime = {
6606
+ pid: process.pid,
6607
+ storageRoot: getEffectiveDataRoot(),
6608
+ dataDir: resolveDataDir(),
6609
+ databasePath: resolveDatabasePathForDataRoot(),
6610
+ port: runtimeConfig.port,
6611
+ basePath: runtimeConfig.basePath,
6612
+ devWebOrigin: process.env.FORGE_DEV_WEB_ORIGIN?.trim() || null
6613
+ };
6614
+ const health = buildHealthPayload(taskRunWatchdog, {
6615
+ apiVersion: "v1",
6616
+ backend: "forge-node-runtime",
6617
+ runtime
6618
+ });
6619
+ return {
6620
+ ...fixResult,
6621
+ doctor: await buildForgeDoctorReport({
6622
+ settings,
6404
6623
  settingsFile,
6405
- settingsSummary: {
6406
- themePreference: settings.themePreference,
6407
- localePreference: settings.localePreference,
6408
- operatorName: settings.profile.operatorName,
6409
- maxActiveTasks: settings.execution.maxActiveTasks,
6410
- timeAccountingMode: settings.execution.timeAccountingMode,
6411
- psycheAuthRequired: settings.security.psycheAuthRequired,
6412
- webAppUrl: `http://127.0.0.1:${runtimeConfig.port}${runtimeConfig.basePath}`
6413
- },
6414
- warnings
6415
- }
6624
+ runtime,
6625
+ health
6626
+ })
6416
6627
  };
6417
6628
  });
6418
6629
  app.get("/api/v1/auth/operator-session", async (request, reply) => ({
@@ -6803,11 +7014,26 @@ export async function buildServer(options = {}) {
6803
7014
  requireOperatorSession(request.headers, {
6804
7015
  route: "/api/v1/health/pairing-sessions"
6805
7016
  });
6806
- reply.code(201);
6807
- return createCompanionPairingSession(buildApiBaseUrl({
7017
+ const parsed = createCompanionPairingSessionSchema.parse(request.body ?? {});
7018
+ const requestApiBaseUrl = buildApiBaseUrl({
6808
7019
  protocol: request.protocol,
6809
7020
  headers: request.headers
6810
- }), createCompanionPairingSessionSchema.parse(request.body ?? {}));
7021
+ });
7022
+ const pairingTransport = await buildCompanionPairingTransport({
7023
+ requestedMode: parsed.transportMode,
7024
+ requestApiBaseUrl,
7025
+ requestUiBaseUrl: buildUiBaseUrlFromApiBaseUrl(requestApiBaseUrl)
7026
+ });
7027
+ reply.code(201);
7028
+ return createCompanionPairingSession(pairingTransport, parsed);
7029
+ });
7030
+ app.get("/api/v1/health/companion-iroh", async (request) => {
7031
+ requireOperatorSession(request.headers, {
7032
+ route: "/api/v1/health/companion-iroh"
7033
+ });
7034
+ return {
7035
+ iroh: getCompanionIrohStatus()
7036
+ };
6811
7037
  });
6812
7038
  app.delete("/api/v1/health/pairing-sessions/:id", async (request, reply) => {
6813
7039
  const auth = requireOperatorSession(request.headers, {
@@ -6853,6 +7079,7 @@ export async function buildServer(options = {}) {
6853
7079
  app.post("/api/v1/mobile/pairing/verify", async (request) => ({
6854
7080
  pairing: verifyCompanionPairing(verifyCompanionPairingSchema.parse(request.body ?? {}))
6855
7081
  }));
7082
+ app.post("/api/v1/mobile/pairing/heartbeat", async (request) => heartbeatCompanionPairing(heartbeatCompanionPairingSchema.parse(request.body ?? {})));
6856
7083
  app.post("/api/v1/mobile/movement/bootstrap", async (request) => {
6857
7084
  const parsed = movementMobileBootstrapSchema.parse(request.body ?? {});
6858
7085
  const pairing = requireValidPairing(parsed.sessionId, parsed.pairingToken);