forge-openclaw-plugin 0.2.69 → 0.2.71

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 (34) hide show
  1. package/dist/assets/{board-BfqxFNiQ.js → board-B0TuXl4u.js} +1 -1
  2. package/dist/assets/index-DtT3Y-Bj.css +1 -0
  3. package/dist/assets/index-clNilMKr.js +91 -0
  4. package/dist/assets/{motion-C0ALlgho.js → motion-Dmjq6HPm.js} +1 -1
  5. package/dist/assets/{table-WcMjnJll.js → table-CKKimYN1.js} +1 -1
  6. package/dist/assets/{ui-B5I-3U91.js → ui-JBdCP1Qb.js} +1 -1
  7. package/dist/assets/{vendor-C56o26_3.js → vendor-fiXu5f59.js} +233 -228
  8. package/dist/index.html +7 -7
  9. package/dist/server/server/migrations/062_health_mobile_sync_sessions.sql +55 -0
  10. package/dist/server/server/migrations/063_psyche_flashcards.sql +31 -0
  11. package/dist/server/server/src/app.js +314 -20
  12. package/dist/server/server/src/health.js +525 -1
  13. package/dist/server/server/src/openapi.js +1 -0
  14. package/dist/server/server/src/psyche-types.js +54 -0
  15. package/dist/server/server/src/repositories/psyche.js +146 -1
  16. package/dist/server/server/src/services/entity-crud.js +27 -5
  17. package/dist/server/server/src/services/gamification.js +2 -0
  18. package/dist/server/server/src/services/knowledge-graph.js +87 -1
  19. package/dist/server/server/src/services/psyche-observation-calendar.js +1 -0
  20. package/dist/server/server/src/services/psyche.js +4 -1
  21. package/dist/server/server/src/types.js +3 -0
  22. package/dist/server/src/lib/api.js +43 -0
  23. package/dist/server/src/lib/entity-visuals.js +9 -0
  24. package/dist/server/src/lib/knowledge-graph-types.js +19 -0
  25. package/dist/server/src/lib/psyche-schemas.js +177 -0
  26. package/openclaw.plugin.json +1 -1
  27. package/package.json +2 -1
  28. package/server/migrations/062_health_mobile_sync_sessions.sql +55 -0
  29. package/server/migrations/063_psyche_flashcards.sql +31 -0
  30. package/skills/forge-openclaw/SKILL.md +30 -9
  31. package/skills/forge-openclaw/entity_conversation_playbooks.md +139 -7
  32. package/skills/forge-openclaw/psyche_entity_playbooks.md +64 -3
  33. package/dist/assets/index-BfLQnCNZ.js +0 -91
  34. package/dist/assets/index-DIapFz9v.css +0 -1
package/dist/index.html CHANGED
@@ -13,14 +13,14 @@
13
13
  />
14
14
  <link rel="icon" type="image/png" href="/forge/assets/favicon-BCHm9dUV.ico" />
15
15
  <link rel="alternate icon" href="/forge/assets/favicon-BCHm9dUV.ico" />
16
- <script type="module" crossorigin src="/forge/assets/index-BfLQnCNZ.js"></script>
17
- <link rel="modulepreload" crossorigin href="/forge/assets/vendor-C56o26_3.js">
18
- <link rel="modulepreload" crossorigin href="/forge/assets/board-BfqxFNiQ.js">
19
- <link rel="modulepreload" crossorigin href="/forge/assets/ui-B5I-3U91.js">
20
- <link rel="modulepreload" crossorigin href="/forge/assets/motion-C0ALlgho.js">
21
- <link rel="modulepreload" crossorigin href="/forge/assets/table-WcMjnJll.js">
16
+ <script type="module" crossorigin src="/forge/assets/index-clNilMKr.js"></script>
17
+ <link rel="modulepreload" crossorigin href="/forge/assets/vendor-fiXu5f59.js">
18
+ <link rel="modulepreload" crossorigin href="/forge/assets/board-B0TuXl4u.js">
19
+ <link rel="modulepreload" crossorigin href="/forge/assets/ui-JBdCP1Qb.js">
20
+ <link rel="modulepreload" crossorigin href="/forge/assets/motion-Dmjq6HPm.js">
21
+ <link rel="modulepreload" crossorigin href="/forge/assets/table-CKKimYN1.js">
22
22
  <link rel="stylesheet" crossorigin href="/forge/assets/vendor-B-Lq_OG3.css">
23
- <link rel="stylesheet" crossorigin href="/forge/assets/index-DIapFz9v.css">
23
+ <link rel="stylesheet" crossorigin href="/forge/assets/index-DtT3Y-Bj.css">
24
24
  </head>
25
25
  <body class="bg-canvas text-ink antialiased">
26
26
  <div id="root"></div>
@@ -0,0 +1,55 @@
1
+ CREATE TABLE IF NOT EXISTS health_mobile_sync_sessions (
2
+ id TEXT PRIMARY KEY,
3
+ pairing_session_id TEXT NOT NULL REFERENCES companion_pairing_sessions(id) ON DELETE CASCADE,
4
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
5
+ status TEXT NOT NULL DEFAULT 'running',
6
+ schema_version TEXT NOT NULL DEFAULT 'healthkit-sync-v2',
7
+ requested_families_json TEXT NOT NULL DEFAULT '[]',
8
+ source_metadata_json TEXT NOT NULL DEFAULT '{}',
9
+ expected_counts_json TEXT NOT NULL DEFAULT '{}',
10
+ received_counts_json TEXT NOT NULL DEFAULT '{}',
11
+ byte_totals_json TEXT NOT NULL DEFAULT '{}',
12
+ affected_workout_ids_json TEXT NOT NULL DEFAULT '[]',
13
+ error_json TEXT NOT NULL DEFAULT '{}',
14
+ started_at TEXT NOT NULL,
15
+ completed_at TEXT,
16
+ failed_at TEXT,
17
+ aborted_at TEXT,
18
+ expired_at TEXT,
19
+ created_at TEXT NOT NULL,
20
+ updated_at TEXT NOT NULL
21
+ );
22
+
23
+ CREATE INDEX IF NOT EXISTS idx_health_mobile_sync_sessions_pairing_status
24
+ ON health_mobile_sync_sessions(pairing_session_id, status, started_at DESC);
25
+
26
+ CREATE TABLE IF NOT EXISTS health_mobile_sync_chunks (
27
+ id TEXT PRIMARY KEY,
28
+ sync_session_id TEXT NOT NULL REFERENCES health_mobile_sync_sessions(id) ON DELETE CASCADE,
29
+ chunk_id TEXT NOT NULL,
30
+ sequence INTEGER NOT NULL,
31
+ family TEXT NOT NULL,
32
+ checksum_sha256 TEXT NOT NULL,
33
+ record_count INTEGER NOT NULL DEFAULT 0,
34
+ byte_count INTEGER NOT NULL DEFAULT 0,
35
+ payload_json TEXT NOT NULL DEFAULT '{}',
36
+ payload_summary_json TEXT NOT NULL DEFAULT '{}',
37
+ received_at TEXT NOT NULL,
38
+ applied_at TEXT,
39
+ created_at TEXT NOT NULL,
40
+ updated_at TEXT NOT NULL,
41
+ UNIQUE(sync_session_id, chunk_id)
42
+ );
43
+
44
+ CREATE INDEX IF NOT EXISTS idx_health_mobile_sync_chunks_session_sequence
45
+ ON health_mobile_sync_chunks(sync_session_id, sequence);
46
+
47
+ CREATE TABLE IF NOT EXISTS health_mobile_sync_family_cursors (
48
+ id TEXT PRIMARY KEY,
49
+ pairing_session_id TEXT NOT NULL REFERENCES companion_pairing_sessions(id) ON DELETE CASCADE,
50
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
51
+ family TEXT NOT NULL,
52
+ cursor_json TEXT NOT NULL DEFAULT '{}',
53
+ updated_at TEXT NOT NULL,
54
+ UNIQUE(pairing_session_id, family)
55
+ );
@@ -0,0 +1,31 @@
1
+ CREATE TABLE IF NOT EXISTS psyche_flashcards (
2
+ id TEXT PRIMARY KEY,
3
+ domain_id TEXT NOT NULL REFERENCES domains(id) ON DELETE CASCADE,
4
+ title TEXT NOT NULL DEFAULT '',
5
+ message TEXT NOT NULL,
6
+ trigger_sentence TEXT NOT NULL DEFAULT '',
7
+ trigger_situation TEXT NOT NULL DEFAULT '',
8
+ tags_json TEXT NOT NULL DEFAULT '[]',
9
+ background_color TEXT NOT NULL DEFAULT '#f8fafc',
10
+ text_color TEXT NOT NULL DEFAULT '#111827',
11
+ accent_color TEXT NOT NULL DEFAULT '#6ee7b7',
12
+ typography TEXT NOT NULL DEFAULT 'serif',
13
+ image_url TEXT NOT NULL DEFAULT '',
14
+ image_alt TEXT NOT NULL DEFAULT '',
15
+ layout TEXT NOT NULL DEFAULT 'centered',
16
+ visual_style TEXT NOT NULL DEFAULT 'calm',
17
+ linked_value_ids_json TEXT NOT NULL DEFAULT '[]',
18
+ linked_behavior_ids_json TEXT NOT NULL DEFAULT '[]',
19
+ linked_pattern_ids_json TEXT NOT NULL DEFAULT '[]',
20
+ linked_belief_ids_json TEXT NOT NULL DEFAULT '[]',
21
+ linked_mode_ids_json TEXT NOT NULL DEFAULT '[]',
22
+ linked_report_ids_json TEXT NOT NULL DEFAULT '[]',
23
+ created_at TEXT NOT NULL,
24
+ updated_at TEXT NOT NULL
25
+ );
26
+
27
+ CREATE INDEX IF NOT EXISTS idx_psyche_flashcards_domain_updated
28
+ ON psyche_flashcards(domain_id, updated_at DESC);
29
+
30
+ CREATE INDEX IF NOT EXISTS idx_psyche_flashcards_trigger_sentence
31
+ ON psyche_flashcards(trigger_sentence);
@@ -66,7 +66,7 @@ import { registerWebRoutes } from "./web.js";
66
66
  import { createManagerRuntime } from "./managers/runtime.js";
67
67
  import { isManagerError } from "./managers/type-guards.js";
68
68
  import { buildCompanionPairingTransport, getCompanionIrohStatus, stopCompanionIroh } from "./services/companion-iroh.js";
69
- import { createCompanionPairingSession, createCompanionPairingSessionSchema, createSleepSession, createSleepSessionSchema, createWorkoutSession, createWorkoutSessionSchema, deleteSleepSession, deleteWorkoutSession, getCompanionPairingSessionById, getCompanionOverview, getFitnessViewData, getSleepSessionById, getSleepSessionDetailById, getSleepTimelineOverlaysForRange, getSleepViewData, getVitalsViewData, getHealthZoneProfileForUser, getWorkoutSessionById, getWorkoutSessionDetailById, heartbeatCompanionPairing, heartbeatCompanionPairingSchema, healthZoneProfilePatchSchema, ingestMobileHealthSync, mobileHealthSyncSchema, patchHealthZoneProfileForUser, patchCompanionPairingSourceState, patchCompanionPairingSourceStateSchema, companionSourceKeySchema, requireValidPairing, revokeAllCompanionPairingSessions, revokeAllCompanionPairingSessionsSchema, revokeCompanionPairingSession, updateMobileCompanionSourceState, updateMobileCompanionSourceStateSchema, verifyCompanionPairing, verifyCompanionPairingSchema, updateSleepMetadata, updateSleepMetadataSchema, updateWorkoutMetadata, updateWorkoutMetadataSchema } from "./health.js";
69
+ import { createCompanionPairingSession, createCompanionPairingSessionSchema, createSleepSession, createSleepSessionSchema, createWorkoutSession, createWorkoutSessionSchema, deleteSleepSession, deleteWorkoutSession, getCompanionPairingSessionById, getCompanionOverview, getFitnessViewData, getSleepSessionById, getSleepSessionDetailById, getSleepTimelineOverlaysForRange, getSleepViewData, getVitalsViewData, getHealthZoneProfileForUser, getWorkoutSessionById, getWorkoutSessionDetailById, heartbeatCompanionPairing, heartbeatCompanionPairingSchema, healthZoneProfilePatchSchema, abortMobileHealthSyncSession, completeMobileHealthSyncSession, ingestMobileHealthSync, ingestMobileHealthSyncChunk, mobileHealthSyncChunkSchema, mobileHealthSyncSessionCompleteSchema, mobileHealthSyncSessionStartSchema, mobileHealthSyncSchema, patchHealthZoneProfileForUser, patchCompanionPairingSourceState, patchCompanionPairingSourceStateSchema, companionSourceKeySchema, requireValidPairing, startMobileHealthSyncSession, revokeAllCompanionPairingSessions, revokeAllCompanionPairingSessionsSchema, revokeCompanionPairingSession, updateMobileCompanionSourceState, updateMobileCompanionSourceStateSchema, verifyCompanionPairing, verifyCompanionPairingSchema, updateSleepMetadata, updateSleepMetadataSchema, updateWorkoutMetadata, updateWorkoutMetadataSchema } from "./health.js";
70
70
  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";
71
71
  import { getScreenTimeAllTimeSummary, getScreenTimeDayDetail, getScreenTimeMonthSummary, getScreenTimeSettings, screenTimeSettingsPatchSchema, updateScreenTimeSettings } from "./screen-time.js";
72
72
  import { assertWatchReady, buildWatchBootstrap, ingestWatchCaptureBatch, mobileWatchBootstrapSchema, mobileWatchCaptureBatchSchema, mobileWatchHabitCheckInSchema } from "./watch-mobile.js";
@@ -1739,6 +1739,160 @@ const AGENT_ONBOARDING_ENTITY_CATALOG_BASE = [
1739
1739
  }
1740
1740
  ]
1741
1741
  },
1742
+ {
1743
+ entityType: "flashcard",
1744
+ purpose: "A small therapeutic reminder card with a central message, optional title, tags, trigger cue, visual styling, and links to Psyche records.",
1745
+ minimumCreateFields: ["message"],
1746
+ relationshipRules: [
1747
+ "Flashcards are normal stored Psyche entities and should use shared batch CRUD routes.",
1748
+ "Use flashcards when the user wants a brief message to be shown back during an urge, trigger, mode, belief activation, or value reminder.",
1749
+ "Tags, triggerSentence, triggerSituation, and optional title make the card findable; the message is the main therapeutic content."
1750
+ ],
1751
+ searchHints: [
1752
+ "Search by tags, trigger wording, title, message, behavior, belief, pattern, mode, or urge language before creating a duplicate card.",
1753
+ "When the user says they feel an urge, search flashcards first if the urge or trigger may have a prepared card."
1754
+ ],
1755
+ examples: [
1756
+ '{"message":"This urge is a wave. You do not have to obey it; stay with your body for two minutes and choose again.","tags":["urge","sobriety"],"triggerSentence":"I feel the urge to drink","triggerSituation":"Late evening craving after shame or loneliness","visualStyle":"urgent","backgroundColor":"#111827","textColor":"#f8fafc","accentColor":"#6ee7b7"}'
1757
+ ],
1758
+ fieldGuide: [
1759
+ {
1760
+ name: "message",
1761
+ type: "string",
1762
+ required: true,
1763
+ description: "The main sentence or brief therapeutic reminder shown on the card."
1764
+ },
1765
+ {
1766
+ name: "title",
1767
+ type: "string",
1768
+ required: false,
1769
+ description: "Compact optional retrieval title; not the main card content.",
1770
+ defaultValue: ""
1771
+ },
1772
+ {
1773
+ name: "triggerSentence",
1774
+ type: "string",
1775
+ required: false,
1776
+ description: "A user phrase that should retrieve this card, such as an urge sentence.",
1777
+ defaultValue: ""
1778
+ },
1779
+ {
1780
+ name: "triggerSituation",
1781
+ type: "string",
1782
+ required: false,
1783
+ description: "Situation or cue where this card should be shown.",
1784
+ defaultValue: ""
1785
+ },
1786
+ {
1787
+ name: "tags",
1788
+ type: "string[]",
1789
+ required: false,
1790
+ description: "Retrieval tags such as urge, sobriety, shame, critic, or grounding.",
1791
+ defaultValue: []
1792
+ },
1793
+ {
1794
+ name: "backgroundColor",
1795
+ type: "string",
1796
+ required: false,
1797
+ description: "Card background color.",
1798
+ defaultValue: "#f8fafc"
1799
+ },
1800
+ {
1801
+ name: "textColor",
1802
+ type: "string",
1803
+ required: false,
1804
+ description: "Card text color.",
1805
+ defaultValue: "#111827"
1806
+ },
1807
+ {
1808
+ name: "accentColor",
1809
+ type: "string",
1810
+ required: false,
1811
+ description: "Card accent color.",
1812
+ defaultValue: "#6ee7b7"
1813
+ },
1814
+ {
1815
+ name: "typography",
1816
+ type: "serif|sans|mono|display",
1817
+ required: false,
1818
+ description: "Typography style.",
1819
+ enumValues: ["serif", "sans", "mono", "display"],
1820
+ defaultValue: "serif"
1821
+ },
1822
+ {
1823
+ name: "imageUrl",
1824
+ type: "string",
1825
+ required: false,
1826
+ description: "Optional image URL.",
1827
+ defaultValue: ""
1828
+ },
1829
+ {
1830
+ name: "imageAlt",
1831
+ type: "string",
1832
+ required: false,
1833
+ description: "Optional image alt text.",
1834
+ defaultValue: ""
1835
+ },
1836
+ {
1837
+ name: "layout",
1838
+ type: "centered|top_left|image_split|poster",
1839
+ required: false,
1840
+ description: "Flashcard layout style.",
1841
+ enumValues: ["centered", "top_left", "image_split", "poster"],
1842
+ defaultValue: "centered"
1843
+ },
1844
+ {
1845
+ name: "visualStyle",
1846
+ type: "calm|urgent|warm|clinical|playful",
1847
+ required: false,
1848
+ description: "Therapeutic visual tone.",
1849
+ enumValues: ["calm", "urgent", "warm", "clinical", "playful"],
1850
+ defaultValue: "calm"
1851
+ },
1852
+ {
1853
+ name: "linkedValueIds",
1854
+ type: "string[]",
1855
+ required: false,
1856
+ description: "Linked value ids.",
1857
+ defaultValue: []
1858
+ },
1859
+ {
1860
+ name: "linkedBehaviorIds",
1861
+ type: "string[]",
1862
+ required: false,
1863
+ description: "Linked behavior ids.",
1864
+ defaultValue: []
1865
+ },
1866
+ {
1867
+ name: "linkedPatternIds",
1868
+ type: "string[]",
1869
+ required: false,
1870
+ description: "Linked behavior pattern ids.",
1871
+ defaultValue: []
1872
+ },
1873
+ {
1874
+ name: "linkedBeliefIds",
1875
+ type: "string[]",
1876
+ required: false,
1877
+ description: "Linked belief ids.",
1878
+ defaultValue: []
1879
+ },
1880
+ {
1881
+ name: "linkedModeIds",
1882
+ type: "string[]",
1883
+ required: false,
1884
+ description: "Linked mode ids.",
1885
+ defaultValue: []
1886
+ },
1887
+ {
1888
+ name: "linkedReportIds",
1889
+ type: "string[]",
1890
+ required: false,
1891
+ description: "Linked trigger report ids.",
1892
+ defaultValue: []
1893
+ }
1894
+ ]
1895
+ },
1742
1896
  {
1743
1897
  entityType: "trigger_report",
1744
1898
  purpose: "A structured reflective incident report that ties situation, emotions, thoughts, behaviors, consequences, and next moves together.",
@@ -1933,6 +2087,7 @@ const AGENT_ONBOARDING_BATCH_ROUTE_BASES = {
1933
2087
  belief_entry: "/api/v1/psyche/beliefs",
1934
2088
  mode_profile: "/api/v1/psyche/modes",
1935
2089
  mode_guide_session: "/api/v1/psyche/mode-guides",
2090
+ flashcard: "/api/v1/entities/search",
1936
2091
  event_type: "/api/v1/psyche/event-types",
1937
2092
  emotion_definition: "/api/v1/psyche/emotions",
1938
2093
  trigger_report: "/api/v1/psyche/reports",
@@ -2809,7 +2964,7 @@ const AGENT_ONBOARDING_CONVERSATION_RULES = [
2809
2964
  "The opening question should help the user understand what they are actually trying to save, decide, review, or change, not make them perform the schema out loud.",
2810
2965
  "If the user already named the exact correction in usable language, confirm only the missing scope, timing, or route-selecting detail that still matters, then act.",
2811
2966
  "Keep API and architecture nouns out of user-facing questions unless the user asks about implementation. Do not ask the user about surfaces, route families, CRUD, payloads, mutation paths, or read paths; ask about the human object such as a wiki page, note, trigger report, behavior pattern, belief, mode, movement timeline, energy model, weekday pattern, flow, run, or node result.",
2812
- "Self-observation is not the default container for Psyche material. Use it only for a lightweight observed event note; prefer trigger_report for one emotionally meaningful episode, behavior_pattern for a recurring loop and functional analysis, behavior for one repeated move, belief_entry for a core sentence, mode_guide_session or mode_profile for an active part-state, and wiki_page for durable memory.",
2967
+ "Self-observation is not the default container for Psyche material. Use it only for a lightweight observed event note; prefer trigger_report for one emotionally meaningful episode, behavior_pattern for a recurring loop and functional analysis, behavior for one repeated move, belief_entry for a core sentence, mode_guide_session or mode_profile for an active part-state, flashcard for a brief rehearsable reminder to use during a trigger or urge, and wiki_page for durable memory.",
2813
2968
  "Do not bury schema work in self-observation. If the user is describing a schema theme, preserve it through a belief_entry, behavior_pattern, mode_profile, mode_guide_session, trigger_report, or wiki_page depending on whether it appears as a rule, loop, part-state, live exploration, one episode, or durable explanation.",
2814
2969
  "Do not minimize functional analysis, trigger chains, behavior patterns, modes, beliefs, or schema themes. After at least one concrete example is clear, offer one careful interpretive hypothesis when it would help the user understand what the reaction may be protecting, predicting, relieving, or costing.",
2815
2970
  "Phrase Psyche interpretive hypotheses as collaborative and testable, not as verdicts. Ask whether the hypothesis lands or needs correction before turning it into a saved belief, pattern, mode, trigger report, or note.",
@@ -2969,6 +3124,18 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
2969
3124
  "Ask about source or override reason only when that context matters."
2970
3125
  ]
2971
3126
  },
3127
+ {
3128
+ focus: "calendar_overview",
3129
+ openingQuestion: "What are you trying to understand or decide from your calendar picture?",
3130
+ coachingGoal: "Review commitments, work blocks, provider state, and existing timeboxes before creating or changing calendar records.",
3131
+ askSequence: [
3132
+ "Ask what practical calendar question the user wants the overview to answer.",
3133
+ "Ask which day, week, date range, or owner scope matters only if it changes the read.",
3134
+ "Use forge_get_calendar_overview before asking the user to reconstruct availability from memory.",
3135
+ "Reflect the scheduling or planning decision the user is trying to make.",
3136
+ "Move to calendar_event, work_block_template, task_timebox, or calendar_connection only when one concrete follow-up action is visible."
3137
+ ]
3138
+ },
2972
3139
  {
2973
3140
  focus: "calendar_connection",
2974
3141
  openingQuestion: "Which calendar provider are you trying to connect, and what do you want Forge to do with it?",
@@ -3004,6 +3171,29 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
3004
3171
  "Ask for a short audit note only if the reason would otherwise be unclear later."
3005
3172
  ]
3006
3173
  },
3174
+ {
3175
+ focus: "operator_overview",
3176
+ openingQuestion: "What are you trying to understand about Forge overall right now?",
3177
+ coachingGoal: "Read the broad Forge state before choosing a specific entity action.",
3178
+ askSequence: [
3179
+ "Ask what broad Forge question the user wants the overview to answer.",
3180
+ "Ask which owner or user scope matters only if several humans or bots are in play.",
3181
+ "Use forge_get_operator_overview before reopening a create or update intake.",
3182
+ "Reflect the attention cue, priority, or handoff decision the overview should support.",
3183
+ "Move into a specific entity flow only when the overview points to a concrete goal, project, task, habit, note, Psyche record, or action."
3184
+ ]
3185
+ },
3186
+ {
3187
+ focus: "operator_context",
3188
+ openingQuestion: "What current work, risk, or next move are you trying to check?",
3189
+ coachingGoal: "Inspect current work, active runs, risk, and next moves before changing records.",
3190
+ askSequence: [
3191
+ "Ask whether the user is checking current work, risk, blockers, active sessions, or the next move.",
3192
+ "Use forge_get_operator_context before mutating tasks, projects, runs, or notes when the current state is uncertain.",
3193
+ "Reflect whether the read is meant to decide continue, stop, reprioritize, update, or create.",
3194
+ "Move to task_run, work_adjustment, task, project, or note flow only when one concrete follow-up is visible."
3195
+ ]
3196
+ },
3007
3197
  {
3008
3198
  focus: "self_observation",
3009
3199
  openingQuestion: "What happened in the situation, and what did you feel, think, or do next?",
@@ -3014,9 +3204,23 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
3014
3204
  "Ask what emotion, body signal, thought, or meaning showed up.",
3015
3205
  "Ask what behavior showed up: what the user did, wanted to do, avoided, or repeated next.",
3016
3206
  "Ask what the immediate consequence was, including short-term relief or cost if it is visible.",
3017
- "Decide whether this should stay a lightweight self-observation or become a trigger_report, behavior_pattern, behavior, belief_entry, mode_profile, mode_guide_session, event_type, emotion_definition, or wiki page.",
3207
+ "Decide whether this should stay a lightweight self-observation or become a trigger_report, behavior_pattern, behavior, belief_entry, mode_profile, mode_guide_session, flashcard, event_type, emotion_definition, or wiki page.",
3018
3208
  "Remember that self-observation is note-backed and should be written through an observed note with frontmatter.observedAt only when a lightweight observation is the right container.",
3019
- "Do not promote self-observation over functional analysis: use behavior_pattern for recurring loops, trigger_report for one emotionally meaningful episode, mode_guide_session or mode_profile for a central part-state, belief_entry for a central sentence, and wiki_page for durable memory."
3209
+ "Do not promote self-observation over functional analysis: use behavior_pattern for recurring loops, trigger_report for one emotionally meaningful episode, mode_guide_session or mode_profile for a central part-state, belief_entry for a central sentence, flashcard for a rehearsable reminder during an urge or trigger, and wiki_page for durable memory."
3210
+ ]
3211
+ },
3212
+ {
3213
+ focus: "flashcard",
3214
+ openingQuestion: "What exact moment or urge should this card help you meet?",
3215
+ coachingGoal: "Craft a concise therapeutic reminder and retrieval cues before visual styling.",
3216
+ askSequence: [
3217
+ "Ask what moment, urge sentence, or trigger situation should bring this flashcard up.",
3218
+ "Reflect what the card is trying to help the user interrupt, remember, tolerate, or choose.",
3219
+ "Ask for the simple message that should be centered on the card, or offer a concise working sentence if the user's meaning is clearer than the wording.",
3220
+ "Ask for tags and trigger wording before asking for a title, because retrieval matters more than the title.",
3221
+ "Ask for color, typography, image, layout, and visual tone only after the message and retrieval cues are clear.",
3222
+ "Use shared batch CRUD for create, update, delete, restore, and search; flashcard is a normal stored Psyche entity, not a dedicated route family.",
3223
+ "When the user reports an urge or trigger, search flashcards by tags, triggerSentence, triggerSituation, message, title, and linked Psyche records, then show the message first with brief psychotherapy-informed support around it."
3020
3224
  ]
3021
3225
  },
3022
3226
  {
@@ -3500,6 +3704,53 @@ const AGENT_ONBOARDING_PSYCHE_PLAYBOOKS = [
3500
3704
  "Use candidate mode interpretations as testable hypotheses tied to the user's answers, not as certain labels."
3501
3705
  ]
3502
3706
  },
3707
+ {
3708
+ focus: "flashcard",
3709
+ useWhen: "Use when the user wants a small therapeutic reminder card for an urge, trigger sentence, mode activation, belief activation, value reminder, or relapse-prevention moment.",
3710
+ coachingGoal: "Help the user craft one brief, memorable, usable message and enough retrieval cues to find it at the exact moment it matters.",
3711
+ askSequence: [
3712
+ "Start from the moment when the card should be shown, especially the user's own urge sentence or trigger cue.",
3713
+ "Reflect what the card is trying to interrupt, steady, or help the user remember.",
3714
+ "Ask what the card should say in one simple sentence or short message.",
3715
+ "If the message is almost clear, offer one concise therapeutic wording and invite correction.",
3716
+ "Ask for tags and trigger wording before title because retrieval matters more than the title.",
3717
+ "Ask about color, typography, image, or layout only after the message and retrieval cues are clear.",
3718
+ "Link values, behaviors, patterns, beliefs, modes, or reports only when those links will help future retrieval or coaching."
3719
+ ],
3720
+ requiredForCreate: ["message"],
3721
+ highValueOptionalFields: [
3722
+ "triggerSentence",
3723
+ "triggerSituation",
3724
+ "tags",
3725
+ "title",
3726
+ "visualStyle",
3727
+ "backgroundColor",
3728
+ "textColor",
3729
+ "accentColor",
3730
+ "typography",
3731
+ "imageUrl",
3732
+ "linkedBehaviorIds",
3733
+ "linkedPatternIds",
3734
+ "linkedBeliefIds",
3735
+ "linkedModeIds",
3736
+ "linkedValueIds"
3737
+ ],
3738
+ exampleQuestions: [
3739
+ "What exact urge sentence or situation should make this card appear?",
3740
+ "When you are in that moment, what do you most need to remember?",
3741
+ "Should this card interrupt the urge, comfort a part, challenge a belief, or point you back to a value?",
3742
+ "If this were only one sentence on the card, what should it say?",
3743
+ "What tags would help us find it later?",
3744
+ "What visual tone should it have: calm, urgent, warm, clinical, or playful?"
3745
+ ],
3746
+ notes: [
3747
+ "The message is the main content; title is optional and compact.",
3748
+ "Use shared batch entity routes for flashcard create, update, delete, restore, and search.",
3749
+ "When the user says they feel an urge or trigger, search flashcards by triggerSentence, triggerSituation, tags, message, and linked Psyche records before creating a new card.",
3750
+ "When showing a card, display or quote the message first, then add brief grounding, urge-surfing, cognitive defusion, schema-mode, or values-based coaching around it.",
3751
+ "Do not make the user fill styling fields before the therapeutic sentence is clear."
3752
+ ]
3753
+ },
3503
3754
  {
3504
3755
  focus: "trigger_report",
3505
3756
  useWhen: "Use for one specific emotionally meaningful incident that should be mapped from situation through emotions, thoughts, behaviors, consequences, and next moves.",
@@ -3619,12 +3870,14 @@ function buildPlaybookRouteInfo(focus) {
3619
3870
  const guide = AGENT_ONBOARDING_ENTITY_CATALOG.find((entry) => entry.entityType === focus);
3620
3871
  const routePosture = guide?.classification ?? classifyOnboardingEntity(focus);
3621
3872
  const apiAccessHint = [
3873
+ `Focus: ${focus}.`,
3622
3874
  `Route posture: ${routePosture}.`,
3623
3875
  guide?.preferredMutationPath
3624
3876
  ? `Mutation: ${guide.preferredMutationPath}.`
3625
3877
  : null,
3626
3878
  guide?.preferredReadPath ? `Read: ${guide.preferredReadPath}.` : null,
3627
- guide?.preferredMutationTool ? `Tool: ${guide.preferredMutationTool}.` : null
3879
+ guide?.preferredMutationTool ? `Tool: ${guide.preferredMutationTool}.` : null,
3880
+ `Entity type: ${focus}.`
3628
3881
  ]
3629
3882
  .filter(Boolean)
3630
3883
  .join(" ");
@@ -4223,7 +4476,7 @@ function buildAgentOnboardingPayload(request) {
4223
4476
  movement: "Forge Movement is the first-class mobility surface. It is a timeline of stays and trips: stays capture time spent in the same place, and trips capture travel between places. Use it for time-in-place questions, travel-history review, specific stay or trip edits, selected-span aggregates, known places, and links to other Forge records rather than pretending stays and trips are normal batch CRUD entities.",
4224
4477
  lifeForce: "Life Force is Forge's energy-budget and fatigue model. Read it through the dedicated life-force payload and update it through focused profile, weekday-template, and fatigue-signal routes rather than generic entity CRUD.",
4225
4478
  workbench: "Workbench is Forge's graph-flow execution system. Treat flows, runs, published outputs, node results, and latest-node-output reads as a dedicated API family instead of a normal entity-batch surface.",
4226
- psyche: "Forge Psyche is the reflective domain for values, patterns, behaviors, beliefs, modes, and trigger reports. It is sensitive and should be handled deliberately."
4479
+ psyche: "Forge Psyche is the reflective domain for values, patterns, behaviors, beliefs, modes, flashcards, and trigger reports. It is sensitive and should be handled deliberately."
4227
4480
  },
4228
4481
  psycheSubmoduleModel: {
4229
4482
  value: "A value is the direction the user wants to move toward. Values orient action and can link back to goals, projects, tasks, and Psyche records.",
@@ -4233,6 +4486,7 @@ function buildAgentOnboardingPayload(request) {
4233
4486
  schemaCatalog: "The schema catalog is the reference taxonomy of maladaptive and adaptive schemas. Belief entries can optionally point to one schema by schemaId, but the schema catalog is not itself the user's belief record.",
4234
4487
  modeProfile: "A mode profile is a durable description of a recurring part-state or strategy, including family, fear, burden, protective job, and origin context.",
4235
4488
  modeGuideSession: "A mode guide session is the guided reasoning worksheet that stores answers and candidate mode interpretations before or alongside a durable mode profile.",
4489
+ flashcard: "A flashcard is a small therapeutic reminder card. It has one main message, optional title, tags, trigger cue, visual styling, optional image, and links to Psyche records so agents can retrieve and show it during urges or triggering situations.",
4236
4490
  eventType: "An event type is reusable incident taxonomy for trigger reports, such as criticism, conflict, rupture, or overload.",
4237
4491
  emotionDefinition: "An emotion definition is reusable emotion vocabulary for trigger reports. Reports can either reference one or fall back to raw labels.",
4238
4492
  triggerReport: "A trigger report is the one-episode incident chain: situation, emotions, thoughts, behaviors, consequences, extra mode labels, schema themes, and next moves."
@@ -4253,7 +4507,7 @@ function buildAgentOnboardingPayload(request) {
4253
4507
  "Task runs represent live work sessions on tasks and are separate from task status.",
4254
4508
  "Notes can link to one or many entities and are the canonical place for Markdown progress context or close-out evidence.",
4255
4509
  "Psyche values can link to goals, projects, and tasks.",
4256
- "Behavior patterns, behaviors, beliefs, modes, and trigger reports cross-link to describe one reflective model rather than isolated records.",
4510
+ "Behavior patterns, behaviors, beliefs, modes, flashcards, and trigger reports cross-link to describe one reflective model rather than isolated records.",
4257
4511
  "Insights can point at one entity, but they exist to capture interpretation or advice rather than raw work items."
4258
4512
  ],
4259
4513
  entityRouteModel: {
@@ -4275,6 +4529,7 @@ function buildAgentOnboardingPayload(request) {
4275
4529
  "belief_entry",
4276
4530
  "mode_profile",
4277
4531
  "mode_guide_session",
4532
+ "flashcard",
4278
4533
  "event_type",
4279
4534
  "emotion_definition",
4280
4535
  "trigger_report",
@@ -4592,7 +4847,8 @@ function buildAgentOnboardingPayload(request) {
4592
4847
  calendar_overview: "/api/v1/calendar/overview",
4593
4848
  operatorOverview: "/api/v1/operator/overview",
4594
4849
  operator_overview: "/api/v1/operator/overview",
4595
- operatorContext: "/api/v1/operator/context"
4850
+ operatorContext: "/api/v1/operator/context",
4851
+ operator_context: "/api/v1/operator/context"
4596
4852
  }
4597
4853
  },
4598
4854
  multiUserModel: {
@@ -6355,6 +6611,8 @@ export async function buildServer(options = {}) {
6355
6611
  app.setErrorHandler((error, request, reply) => {
6356
6612
  const validationIssues = error instanceof ZodError ? formatValidationIssues(error) : undefined;
6357
6613
  const routeUrl = request.routeOptions.url || request.url;
6614
+ const isBodyTooLarge = typeof error.code === "string" &&
6615
+ error.code === "FST_ERR_CTP_BODY_TOO_LARGE";
6358
6616
  const validationHelp = validationIssues
6359
6617
  ? buildValidationHelp(request.method, routeUrl, validationIssues)
6360
6618
  : undefined;
@@ -6364,20 +6622,24 @@ export async function buildServer(options = {}) {
6364
6622
  ? error.statusCode
6365
6623
  : error instanceof ZodError
6366
6624
  ? 400
6367
- : 500;
6625
+ : isBodyTooLarge
6626
+ ? 413
6627
+ : 500;
6368
6628
  if (!shouldSkipAutomaticDiagnosticRoute(routeUrl)) {
6369
6629
  try {
6370
6630
  recordDiagnosticLog({
6371
6631
  level: statusCode >= 500 ? "error" : "warning",
6372
6632
  source: normalizeDiagnosticSource(request.headers["x-forge-source"]),
6373
6633
  scope: "api_error",
6374
- eventKey: isHttpError(error)
6375
- ? error.code
6376
- : isManagerError(error)
6634
+ eventKey: isBodyTooLarge
6635
+ ? "payload_too_large"
6636
+ : isHttpError(error)
6377
6637
  ? error.code
6378
- : statusCode === 400
6379
- ? "invalid_request"
6380
- : "internal_error",
6638
+ : isManagerError(error)
6639
+ ? error.code
6640
+ : statusCode === 400
6641
+ ? "invalid_request"
6642
+ : "internal_error",
6381
6643
  message: getErrorMessage(error),
6382
6644
  route: routeUrl,
6383
6645
  functionName: "setErrorHandler",
@@ -6401,13 +6663,25 @@ export async function buildServer(options = {}) {
6401
6663
  ? error.code
6402
6664
  : isManagerError(error)
6403
6665
  ? error.code
6404
- : statusCode === 400
6405
- ? "invalid_request"
6406
- : "internal_error",
6666
+ : isBodyTooLarge
6667
+ ? "payload_too_large"
6668
+ : statusCode === 400
6669
+ ? "invalid_request"
6670
+ : "internal_error",
6407
6671
  error: validationIssues
6408
6672
  ? `Request validation failed for ${request.method.toUpperCase()} ${routeUrl}. ${validationHelp?.validationSummary ?? ""}`.trim()
6409
- : getErrorMessage(error),
6673
+ : isBodyTooLarge
6674
+ ? "The request body is too large. Use chunked HealthKit sync."
6675
+ : getErrorMessage(error),
6410
6676
  statusCode,
6677
+ ...(isBodyTooLarge
6678
+ ? {
6679
+ recommendedMode: "chunked",
6680
+ maxBytes: typeof request.routeOptions.bodyLimit === "number"
6681
+ ? request.routeOptions.bodyLimit
6682
+ : undefined
6683
+ }
6684
+ : {}),
6411
6685
  ...(validationIssues ? { details: validationIssues } : {}),
6412
6686
  ...(validationHelp ?? {}),
6413
6687
  ...(isHttpError(error) && error.details ? error.details : {}),
@@ -7343,7 +7617,27 @@ export async function buildServer(options = {}) {
7343
7617
  watch: buildWatchBootstrap(pairing)
7344
7618
  };
7345
7619
  });
7346
- app.post("/api/v1/mobile/healthkit/sync", async (request) => ({
7620
+ app.post("/api/v1/mobile/healthkit/sync-sessions", async (request) => ({
7621
+ upload: startMobileHealthSyncSession(mobileHealthSyncSessionStartSchema.parse(request.body ?? {}))
7622
+ }));
7623
+ app.post("/api/v1/mobile/healthkit/sync-sessions/:id/chunks", { bodyLimit: 1_250_000 }, async (request) => {
7624
+ const { id } = request.params;
7625
+ const rawPayloadJson = JSON.stringify((request.body ?? {}).payload ?? {});
7626
+ return {
7627
+ chunk: ingestMobileHealthSyncChunk(id, mobileHealthSyncChunkSchema.parse(request.body ?? {}), rawPayloadJson)
7628
+ };
7629
+ });
7630
+ app.post("/api/v1/mobile/healthkit/sync-sessions/:id/complete", async (request) => {
7631
+ const { id } = request.params;
7632
+ return {
7633
+ sync: completeMobileHealthSyncSession(id, mobileHealthSyncSessionCompleteSchema.parse(request.body ?? {}))
7634
+ };
7635
+ });
7636
+ app.delete("/api/v1/mobile/healthkit/sync-sessions/:id", async (request) => {
7637
+ const { id } = request.params;
7638
+ return { upload: abortMobileHealthSyncSession(id) };
7639
+ });
7640
+ app.post("/api/v1/mobile/healthkit/sync", { bodyLimit: 8_000_000 }, async (request) => ({
7347
7641
  sync: ingestMobileHealthSync(mobileHealthSyncSchema.parse(request.body ?? {}))
7348
7642
  }));
7349
7643
  app.patch("/api/v1/health/workouts/:id", async (request, reply) => {