forge-openclaw-plugin 0.2.70 → 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 (32) 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/063_psyche_flashcards.sql +31 -0
  10. package/dist/server/server/src/app.js +226 -6
  11. package/dist/server/server/src/health.js +56 -27
  12. package/dist/server/server/src/openapi.js +1 -0
  13. package/dist/server/server/src/psyche-types.js +54 -0
  14. package/dist/server/server/src/repositories/psyche.js +146 -1
  15. package/dist/server/server/src/services/entity-crud.js +27 -5
  16. package/dist/server/server/src/services/gamification.js +2 -0
  17. package/dist/server/server/src/services/knowledge-graph.js +87 -1
  18. package/dist/server/server/src/services/psyche-observation-calendar.js +1 -0
  19. package/dist/server/server/src/services/psyche.js +4 -1
  20. package/dist/server/server/src/types.js +3 -0
  21. package/dist/server/src/lib/api.js +43 -0
  22. package/dist/server/src/lib/entity-visuals.js +9 -0
  23. package/dist/server/src/lib/knowledge-graph-types.js +19 -0
  24. package/dist/server/src/lib/psyche-schemas.js +177 -0
  25. package/openclaw.plugin.json +1 -1
  26. package/package.json +1 -1
  27. package/server/migrations/063_psyche_flashcards.sql +31 -0
  28. package/skills/forge-openclaw/SKILL.md +20 -4
  29. package/skills/forge-openclaw/entity_conversation_playbooks.md +6 -3
  30. package/skills/forge-openclaw/psyche_entity_playbooks.md +64 -3
  31. package/dist/assets/index-BfLQnCNZ.js +0 -91
  32. 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,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);
@@ -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.",
@@ -3049,9 +3204,23 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
3049
3204
  "Ask what emotion, body signal, thought, or meaning showed up.",
3050
3205
  "Ask what behavior showed up: what the user did, wanted to do, avoided, or repeated next.",
3051
3206
  "Ask what the immediate consequence was, including short-term relief or cost if it is visible.",
3052
- "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.",
3053
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.",
3054
- "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."
3055
3224
  ]
3056
3225
  },
3057
3226
  {
@@ -3535,6 +3704,53 @@ const AGENT_ONBOARDING_PSYCHE_PLAYBOOKS = [
3535
3704
  "Use candidate mode interpretations as testable hypotheses tied to the user's answers, not as certain labels."
3536
3705
  ]
3537
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
+ },
3538
3754
  {
3539
3755
  focus: "trigger_report",
3540
3756
  useWhen: "Use for one specific emotionally meaningful incident that should be mapped from situation through emotions, thoughts, behaviors, consequences, and next moves.",
@@ -3654,12 +3870,14 @@ function buildPlaybookRouteInfo(focus) {
3654
3870
  const guide = AGENT_ONBOARDING_ENTITY_CATALOG.find((entry) => entry.entityType === focus);
3655
3871
  const routePosture = guide?.classification ?? classifyOnboardingEntity(focus);
3656
3872
  const apiAccessHint = [
3873
+ `Focus: ${focus}.`,
3657
3874
  `Route posture: ${routePosture}.`,
3658
3875
  guide?.preferredMutationPath
3659
3876
  ? `Mutation: ${guide.preferredMutationPath}.`
3660
3877
  : null,
3661
3878
  guide?.preferredReadPath ? `Read: ${guide.preferredReadPath}.` : null,
3662
- guide?.preferredMutationTool ? `Tool: ${guide.preferredMutationTool}.` : null
3879
+ guide?.preferredMutationTool ? `Tool: ${guide.preferredMutationTool}.` : null,
3880
+ `Entity type: ${focus}.`
3663
3881
  ]
3664
3882
  .filter(Boolean)
3665
3883
  .join(" ");
@@ -4258,7 +4476,7 @@ function buildAgentOnboardingPayload(request) {
4258
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.",
4259
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.",
4260
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.",
4261
- 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."
4262
4480
  },
4263
4481
  psycheSubmoduleModel: {
4264
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.",
@@ -4268,6 +4486,7 @@ function buildAgentOnboardingPayload(request) {
4268
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.",
4269
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.",
4270
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.",
4271
4490
  eventType: "An event type is reusable incident taxonomy for trigger reports, such as criticism, conflict, rupture, or overload.",
4272
4491
  emotionDefinition: "An emotion definition is reusable emotion vocabulary for trigger reports. Reports can either reference one or fall back to raw labels.",
4273
4492
  triggerReport: "A trigger report is the one-episode incident chain: situation, emotions, thoughts, behaviors, consequences, extra mode labels, schema themes, and next moves."
@@ -4288,7 +4507,7 @@ function buildAgentOnboardingPayload(request) {
4288
4507
  "Task runs represent live work sessions on tasks and are separate from task status.",
4289
4508
  "Notes can link to one or many entities and are the canonical place for Markdown progress context or close-out evidence.",
4290
4509
  "Psyche values can link to goals, projects, and tasks.",
4291
- "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.",
4292
4511
  "Insights can point at one entity, but they exist to capture interpretation or advice rather than raw work items."
4293
4512
  ],
4294
4513
  entityRouteModel: {
@@ -4310,6 +4529,7 @@ function buildAgentOnboardingPayload(request) {
4310
4529
  "belief_entry",
4311
4530
  "mode_profile",
4312
4531
  "mode_guide_session",
4532
+ "flashcard",
4313
4533
  "event_type",
4314
4534
  "emotion_definition",
4315
4535
  "trigger_report",
@@ -3000,6 +3000,39 @@ function upsertMobileSyncFamilyCursors(pairing, finalCursor) {
3000
3000
  stmt.run(`hmscur_${randomUUID().replaceAll("-", "").slice(0, 10)}`, pairing.id, pairing.user_id, family, JSON.stringify(cursor), now);
3001
3001
  }
3002
3002
  }
3003
+ function validateMobileSyncExpectedCounts(syncSessionId, expectedCounts) {
3004
+ const expectedEntries = Object.entries(expectedCounts).filter(([, expected]) => Number.isFinite(expected) && expected > 0);
3005
+ if (expectedEntries.length === 0) {
3006
+ return;
3007
+ }
3008
+ const progress = updateMobileSyncSessionProgress(syncSessionId);
3009
+ const missingFamilies = expectedEntries
3010
+ .map(([family, expected]) => ({
3011
+ family,
3012
+ expected,
3013
+ received: progress.receivedCounts[family] ?? 0
3014
+ }))
3015
+ .filter((entry) => entry.received < entry.expected);
3016
+ if (missingFamilies.length > 0) {
3017
+ throw new HttpError(409, "missing_required_chunks", "The HealthKit sync session is missing required chunks.", { families: missingFamilies });
3018
+ }
3019
+ }
3020
+ function markMobileSyncSessionFailed(session, error) {
3021
+ const now = nowIso();
3022
+ const errorMessage = error instanceof Error ? error.message : String(error);
3023
+ getDatabase()
3024
+ .prepare(`UPDATE health_mobile_sync_sessions
3025
+ SET status = 'failed', failed_at = ?, error_json = ?, updated_at = ?
3026
+ WHERE id = ?`)
3027
+ .run(now, JSON.stringify({
3028
+ message: errorMessage
3029
+ }), now, session.id);
3030
+ getDatabase()
3031
+ .prepare(`UPDATE companion_pairing_sessions
3032
+ SET last_sync_error = ?, updated_at = ?
3033
+ WHERE id = ?`)
3034
+ .run(errorMessage, now, session.pairing_session_id);
3035
+ }
3003
3036
  export function completeMobileHealthSyncSession(syncSessionId, payload) {
3004
3037
  const parsed = mobileHealthSyncSessionCompleteSchema.parse(payload);
3005
3038
  const session = ensureRunningMobileSyncSession(syncSessionId);
@@ -3015,35 +3048,31 @@ export function completeMobileHealthSyncSession(syncSessionId, payload) {
3015
3048
  .prepare(`SELECT * FROM companion_pairing_sessions WHERE id = ?`)
3016
3049
  .get(session.pairing_session_id);
3017
3050
  try {
3018
- const { assembled, tombstones } = mergeMobileHealthSyncChunks(session, chunks);
3019
- const sync = ingestMobileHealthSync(assembled);
3020
- const deletedWorkoutCount = applyWorkoutTombstones(pairing, tombstones);
3021
- upsertMobileSyncFamilyCursors(pairing, parsed.finalCursor);
3022
- const now = nowIso();
3023
- getDatabase()
3024
- .prepare(`UPDATE health_mobile_sync_sessions
3025
- SET status = 'completed', expected_counts_json = ?, completed_at = ?,
3026
- updated_at = ?
3027
- WHERE id = ?`)
3028
- .run(JSON.stringify(parsed.expectedCounts), now, now, syncSessionId);
3029
- return {
3030
- ...sync,
3031
- upload: {
3032
- syncSessionId,
3033
- chunks: chunks.length,
3034
- deletedWorkoutCount
3035
- }
3036
- };
3051
+ return runInTransaction(() => {
3052
+ validateMobileSyncExpectedCounts(syncSessionId, parsed.expectedCounts);
3053
+ const { assembled, tombstones } = mergeMobileHealthSyncChunks(session, chunks);
3054
+ const sync = ingestMobileHealthSync(assembled);
3055
+ const deletedWorkoutCount = applyWorkoutTombstones(pairing, tombstones);
3056
+ upsertMobileSyncFamilyCursors(pairing, parsed.finalCursor);
3057
+ const now = nowIso();
3058
+ getDatabase()
3059
+ .prepare(`UPDATE health_mobile_sync_sessions
3060
+ SET status = 'completed', expected_counts_json = ?, completed_at = ?,
3061
+ updated_at = ?
3062
+ WHERE id = ?`)
3063
+ .run(JSON.stringify(parsed.expectedCounts), now, now, syncSessionId);
3064
+ return {
3065
+ ...sync,
3066
+ upload: {
3067
+ syncSessionId,
3068
+ chunks: chunks.length,
3069
+ deletedWorkoutCount
3070
+ }
3071
+ };
3072
+ });
3037
3073
  }
3038
3074
  catch (error) {
3039
- const now = nowIso();
3040
- getDatabase()
3041
- .prepare(`UPDATE health_mobile_sync_sessions
3042
- SET status = 'failed', failed_at = ?, error_json = ?, updated_at = ?
3043
- WHERE id = ?`)
3044
- .run(now, JSON.stringify({
3045
- message: error instanceof Error ? error.message : String(error)
3046
- }), now, syncSessionId);
3075
+ markMobileSyncSessionFailed(session, error);
3047
3076
  throw error;
3048
3077
  }
3049
3078
  }
@@ -1314,6 +1314,7 @@ export function buildOpenApiDocument() {
1314
1314
  "belief_entry",
1315
1315
  "mode_profile",
1316
1316
  "mode_guide_session",
1317
+ "flashcard",
1317
1318
  "trigger_report",
1318
1319
  "note",
1319
1320
  "tag",
@@ -13,12 +13,16 @@ export const behaviorKindSchema = z.enum(["away", "committed", "recovery"]);
13
13
  export const beliefTypeSchema = z.enum(["absolute", "conditional"]);
14
14
  export const modeFamilySchema = z.enum(["coping", "child", "critic_parent", "healthy_adult", "happy_child"]);
15
15
  export const schemaTypeSchema = z.enum(["maladaptive", "adaptive"]);
16
+ export const flashcardTypographySchema = z.enum(["serif", "sans", "mono", "display"]);
17
+ export const flashcardLayoutSchema = z.enum(["centered", "top_left", "image_split", "poster"]);
18
+ export const flashcardVisualStyleSchema = z.enum(["calm", "urgent", "warm", "clinical", "playful"]);
16
19
  export const PSYCHE_ENTITY_TYPES = [
17
20
  "psyche_value",
18
21
  "behavior_pattern",
19
22
  "behavior",
20
23
  "belief_entry",
21
24
  "mode_profile",
25
+ "flashcard",
22
26
  "trigger_report"
23
27
  ];
24
28
  export const domainSchema = z.object({
@@ -184,6 +188,32 @@ export const modeGuideSessionSchema = z.object({
184
188
  updatedAt: z.string(),
185
189
  ...ownedEntityFieldsSchema
186
190
  });
191
+ export const flashcardSchema = z.object({
192
+ id: z.string(),
193
+ domainId: z.string(),
194
+ title: trimmedString,
195
+ message: nonEmptyTrimmedString,
196
+ triggerSentence: trimmedString,
197
+ triggerSituation: trimmedString,
198
+ tags: z.array(trimmedString).default([]),
199
+ backgroundColor: trimmedString,
200
+ textColor: trimmedString,
201
+ accentColor: trimmedString,
202
+ typography: flashcardTypographySchema,
203
+ imageUrl: trimmedString,
204
+ imageAlt: trimmedString,
205
+ layout: flashcardLayoutSchema,
206
+ visualStyle: flashcardVisualStyleSchema,
207
+ linkedValueIds: uniqueStringArraySchema.default([]),
208
+ linkedBehaviorIds: uniqueStringArraySchema.default([]),
209
+ linkedPatternIds: uniqueStringArraySchema.default([]),
210
+ linkedBeliefIds: uniqueStringArraySchema.default([]),
211
+ linkedModeIds: uniqueStringArraySchema.default([]),
212
+ linkedReportIds: uniqueStringArraySchema.default([]),
213
+ createdAt: z.string(),
214
+ updatedAt: z.string(),
215
+ ...ownedEntityFieldsSchema
216
+ });
187
217
  export const triggerEmotionSchema = z.object({
188
218
  id: z.string(),
189
219
  emotionDefinitionId: z.string().nullable().default(null),
@@ -341,6 +371,7 @@ export const psycheOverviewPayloadSchema = z.object({
341
371
  behaviors: z.array(behaviorSchema),
342
372
  beliefs: z.array(beliefEntrySchema),
343
373
  modes: z.array(modeProfileSchema),
374
+ flashcards: z.array(flashcardSchema),
344
375
  reports: z.array(triggerReportSchema),
345
376
  schemaPressure: z.array(schemaPressureEntrySchema),
346
377
  devrageMetric: devrageMetricPayloadSchema,
@@ -456,6 +487,29 @@ export const createModeGuideSessionSchema = z.object({
456
487
  userId: optionalOwnedUserIdSchema
457
488
  });
458
489
  export const updateModeGuideSessionSchema = createModeGuideSessionSchema.partial();
490
+ export const createFlashcardSchema = z.object({
491
+ title: trimmedString.default(""),
492
+ message: nonEmptyTrimmedString,
493
+ triggerSentence: trimmedString.default(""),
494
+ triggerSituation: trimmedString.default(""),
495
+ tags: z.array(trimmedString).default([]),
496
+ backgroundColor: trimmedString.default("#f8fafc"),
497
+ textColor: trimmedString.default("#111827"),
498
+ accentColor: trimmedString.default("#6ee7b7"),
499
+ typography: flashcardTypographySchema.default("serif"),
500
+ imageUrl: trimmedString.default(""),
501
+ imageAlt: trimmedString.default(""),
502
+ layout: flashcardLayoutSchema.default("centered"),
503
+ visualStyle: flashcardVisualStyleSchema.default("calm"),
504
+ linkedValueIds: uniqueStringArraySchema.default([]),
505
+ linkedBehaviorIds: uniqueStringArraySchema.default([]),
506
+ linkedPatternIds: uniqueStringArraySchema.default([]),
507
+ linkedBeliefIds: uniqueStringArraySchema.default([]),
508
+ linkedModeIds: uniqueStringArraySchema.default([]),
509
+ linkedReportIds: uniqueStringArraySchema.default([]),
510
+ userId: optionalOwnedUserIdSchema
511
+ });
512
+ export const updateFlashcardSchema = createFlashcardSchema.partial();
459
513
  export const createEventTypeSchema = z.object({
460
514
  label: nonEmptyTrimmedString,
461
515
  description: trimmedString.default(""),