forge-openclaw-plugin 0.2.19 → 0.2.21

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 (82) hide show
  1. package/README.md +133 -2
  2. package/dist/assets/board-_C6oMy5w.js +6 -0
  3. package/dist/assets/{board-8L3uX7_O.js.map → board-_C6oMy5w.js.map} +1 -1
  4. package/dist/assets/index-B4A6TooJ.js +63 -0
  5. package/dist/assets/index-B4A6TooJ.js.map +1 -0
  6. package/dist/assets/index-D6Xs_2mo.css +1 -0
  7. package/dist/assets/{motion-1GAqqi8M.js → motion-D4sZgCHd.js} +2 -2
  8. package/dist/assets/{motion-1GAqqi8M.js.map → motion-D4sZgCHd.js.map} +1 -1
  9. package/dist/assets/{table-DBGlgRjk.js → table-BWzTaky1.js} +2 -2
  10. package/dist/assets/{table-DBGlgRjk.js.map → table-BWzTaky1.js.map} +1 -1
  11. package/dist/assets/{ui-iTluWjC4.js → ui-BzK4azQb.js} +7 -7
  12. package/dist/assets/{ui-iTluWjC4.js.map → ui-BzK4azQb.js.map} +1 -1
  13. package/dist/assets/vendor-DT3pnAKJ.css +1 -0
  14. package/dist/assets/vendor-De38P6YR.js +729 -0
  15. package/dist/assets/vendor-De38P6YR.js.map +1 -0
  16. package/dist/assets/viz-C6hfyqzu.js +34 -0
  17. package/dist/assets/viz-C6hfyqzu.js.map +1 -0
  18. package/dist/index.html +9 -9
  19. package/dist/openclaw/parity.d.ts +1 -1
  20. package/dist/openclaw/parity.js +29 -2
  21. package/dist/openclaw/routes.js +207 -24
  22. package/dist/openclaw/tools.js +324 -35
  23. package/dist/server/app.js +2080 -92
  24. package/dist/server/db.js +3 -0
  25. package/dist/server/health.js +1284 -0
  26. package/dist/server/managers/platform/background-job-manager.js +138 -2
  27. package/dist/server/managers/platform/llm-manager.js +126 -0
  28. package/dist/server/managers/platform/openai-responses-provider.js +773 -0
  29. package/dist/server/managers/runtime.js +6 -1
  30. package/dist/server/openapi.js +718 -0
  31. package/dist/server/preferences-seeds.js +409 -0
  32. package/dist/server/preferences-types.js +368 -0
  33. package/dist/server/psyche-types.js +42 -18
  34. package/dist/server/repositories/activity-events.js +53 -4
  35. package/dist/server/repositories/calendar.js +89 -15
  36. package/dist/server/repositories/collaboration.js +8 -3
  37. package/dist/server/repositories/diagnostic-logs.js +243 -0
  38. package/dist/server/repositories/entity-ownership.js +92 -0
  39. package/dist/server/repositories/goals.js +7 -2
  40. package/dist/server/repositories/habits.js +122 -16
  41. package/dist/server/repositories/notes.js +119 -41
  42. package/dist/server/repositories/preferences.js +1765 -0
  43. package/dist/server/repositories/projects.js +18 -7
  44. package/dist/server/repositories/psyche.js +84 -27
  45. package/dist/server/repositories/rewards.js +112 -4
  46. package/dist/server/repositories/strategies.js +450 -0
  47. package/dist/server/repositories/tags.js +11 -6
  48. package/dist/server/repositories/task-runs.js +10 -2
  49. package/dist/server/repositories/tasks.js +99 -17
  50. package/dist/server/repositories/users.js +417 -0
  51. package/dist/server/repositories/wiki-memory.js +3366 -0
  52. package/dist/server/services/context.js +20 -18
  53. package/dist/server/services/dashboard.js +29 -6
  54. package/dist/server/services/entity-crud.js +21 -3
  55. package/dist/server/services/insights.js +9 -7
  56. package/dist/server/services/projects.js +2 -1
  57. package/dist/server/services/psyche.js +10 -9
  58. package/dist/server/types.js +594 -30
  59. package/openclaw.plugin.json +1 -1
  60. package/package.json +1 -1
  61. package/server/migrations/015_multi_user_and_strategies.sql +244 -0
  62. package/server/migrations/016_health_companion.sql +158 -0
  63. package/server/migrations/016_strategy_contracts_and_user_graph.sql +22 -0
  64. package/server/migrations/017_preferences.sql +131 -0
  65. package/server/migrations/018_preference_catalogs.sql +31 -0
  66. package/server/migrations/019_wiki_memory.sql +255 -0
  67. package/server/migrations/020_wiki_page_hierarchy.sql +11 -0
  68. package/server/migrations/021_hide_evidence_from_wiki_index.sql +3 -0
  69. package/server/migrations/022_wiki_ingest_background.sql +85 -0
  70. package/server/migrations/023_diagnostic_logs.sql +28 -0
  71. package/skills/forge-openclaw/SKILL.md +126 -34
  72. package/skills/forge-openclaw/entity_conversation_playbooks.md +337 -0
  73. package/skills/forge-openclaw/psyche_entity_playbooks.md +404 -0
  74. package/dist/assets/board-8L3uX7_O.js +0 -6
  75. package/dist/assets/index-Cj1IBH_w.js +0 -36
  76. package/dist/assets/index-Cj1IBH_w.js.map +0 -1
  77. package/dist/assets/index-DQT6EbuS.css +0 -1
  78. package/dist/assets/vendor-BvM2F9Dp.js +0 -503
  79. package/dist/assets/vendor-BvM2F9Dp.js.map +0 -1
  80. package/dist/assets/vendor-CRS-psbw.css +0 -1
  81. package/dist/assets/viz-CNeunkfu.js +0 -34
  82. package/dist/assets/viz-CNeunkfu.js.map +0 -1
@@ -2,7 +2,7 @@
2
2
  "id": "forge-openclaw-plugin",
3
3
  "name": "Forge",
4
4
  "description": "Curated OpenClaw adapter for the Forge collaboration API, UI entrypoint, and localhost auto-start runtime.",
5
- "version": "0.2.19",
5
+ "version": "0.2.21",
6
6
  "skills": [
7
7
  "./skills"
8
8
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forge-openclaw-plugin",
3
- "version": "0.2.19",
3
+ "version": "0.2.21",
4
4
  "description": "Curated OpenClaw adapter for the Forge collaboration API, UI entrypoint, and localhost auto-start runtime.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -0,0 +1,244 @@
1
+ CREATE TABLE IF NOT EXISTS users (
2
+ id TEXT PRIMARY KEY,
3
+ kind TEXT NOT NULL CHECK (kind IN ('human', 'bot')),
4
+ handle TEXT NOT NULL UNIQUE,
5
+ display_name TEXT NOT NULL,
6
+ description TEXT NOT NULL DEFAULT '',
7
+ accent_color TEXT NOT NULL DEFAULT '#c0c1ff',
8
+ created_at TEXT NOT NULL,
9
+ updated_at TEXT NOT NULL
10
+ );
11
+
12
+ CREATE TABLE IF NOT EXISTS user_access_grants (
13
+ id TEXT PRIMARY KEY,
14
+ subject_user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
15
+ target_user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
16
+ access_level TEXT NOT NULL DEFAULT 'view',
17
+ config_json TEXT NOT NULL DEFAULT '{}',
18
+ created_at TEXT NOT NULL,
19
+ updated_at TEXT NOT NULL,
20
+ UNIQUE (subject_user_id, target_user_id, access_level)
21
+ );
22
+
23
+ CREATE TABLE IF NOT EXISTS entity_owners (
24
+ entity_type TEXT NOT NULL,
25
+ entity_id TEXT NOT NULL,
26
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
27
+ role TEXT NOT NULL DEFAULT 'owner',
28
+ created_at TEXT NOT NULL,
29
+ updated_at TEXT NOT NULL,
30
+ PRIMARY KEY (entity_type, entity_id)
31
+ );
32
+
33
+ CREATE TABLE IF NOT EXISTS strategies (
34
+ id TEXT PRIMARY KEY,
35
+ title TEXT NOT NULL,
36
+ overview TEXT NOT NULL DEFAULT '',
37
+ end_state_description TEXT NOT NULL DEFAULT '',
38
+ status TEXT NOT NULL DEFAULT 'active',
39
+ target_goal_ids_json TEXT NOT NULL DEFAULT '[]',
40
+ target_project_ids_json TEXT NOT NULL DEFAULT '[]',
41
+ linked_entities_json TEXT NOT NULL DEFAULT '[]',
42
+ graph_json TEXT NOT NULL DEFAULT '{"nodes":[],"edges":[]}',
43
+ created_at TEXT NOT NULL,
44
+ updated_at TEXT NOT NULL
45
+ );
46
+
47
+ CREATE INDEX IF NOT EXISTS idx_users_kind ON users(kind, display_name);
48
+ CREATE INDEX IF NOT EXISTS idx_entity_owners_user ON entity_owners(user_id, entity_type, entity_id);
49
+ CREATE INDEX IF NOT EXISTS idx_strategies_status ON strategies(status, updated_at DESC);
50
+
51
+ INSERT OR IGNORE INTO users (
52
+ id,
53
+ kind,
54
+ handle,
55
+ display_name,
56
+ description,
57
+ accent_color,
58
+ created_at,
59
+ updated_at
60
+ )
61
+ VALUES (
62
+ 'user_operator',
63
+ 'human',
64
+ 'operator',
65
+ 'Operator',
66
+ 'Primary human Forge operator.',
67
+ '#f4b97a',
68
+ CURRENT_TIMESTAMP,
69
+ CURRENT_TIMESTAMP
70
+ );
71
+
72
+ UPDATE users
73
+ SET handle = COALESCE(
74
+ (
75
+ SELECT CASE
76
+ WHEN trim(lower(replace(replace(replace(operator_name, ' ', '_'), '-', '_'), '.', ''))) = '' THEN 'operator'
77
+ ELSE trim(lower(replace(replace(replace(operator_name, ' ', '_'), '-', '_'), '.', '')))
78
+ END
79
+ FROM app_settings
80
+ WHERE id = 1
81
+ ),
82
+ handle
83
+ ),
84
+ display_name = COALESCE(
85
+ (
86
+ SELECT CASE
87
+ WHEN trim(operator_name) = '' THEN 'Operator'
88
+ ELSE trim(operator_name)
89
+ END
90
+ FROM app_settings
91
+ WHERE id = 1
92
+ ),
93
+ display_name
94
+ ),
95
+ updated_at = CURRENT_TIMESTAMP
96
+ WHERE id = 'user_operator';
97
+
98
+ INSERT OR IGNORE INTO users (
99
+ id,
100
+ kind,
101
+ handle,
102
+ display_name,
103
+ description,
104
+ accent_color,
105
+ created_at,
106
+ updated_at
107
+ )
108
+ VALUES (
109
+ 'user_forge_bot',
110
+ 'bot',
111
+ 'forge_bot',
112
+ 'Forge Bot',
113
+ 'Autonomous or semi-autonomous execution partner inside Forge.',
114
+ '#7dd3fc',
115
+ CURRENT_TIMESTAMP,
116
+ CURRENT_TIMESTAMP
117
+ );
118
+
119
+ INSERT OR IGNORE INTO users (
120
+ id,
121
+ kind,
122
+ handle,
123
+ display_name,
124
+ description,
125
+ accent_color,
126
+ created_at,
127
+ updated_at
128
+ )
129
+ SELECT
130
+ 'user_human_' || lower(hex(substr(owner, 1, 16))),
131
+ 'human',
132
+ trim(lower(replace(replace(replace(owner, ' ', '_'), '-', '_'), '.', ''))),
133
+ trim(owner),
134
+ 'Backfilled from existing task ownership labels.',
135
+ '#f4b97a',
136
+ CURRENT_TIMESTAMP,
137
+ CURRENT_TIMESTAMP
138
+ FROM tasks
139
+ WHERE trim(owner) != ''
140
+ AND lower(trim(owner)) NOT IN (SELECT lower(display_name) FROM users)
141
+ GROUP BY trim(owner);
142
+
143
+ INSERT OR IGNORE INTO entity_owners (entity_type, entity_id, user_id, role, created_at, updated_at)
144
+ SELECT 'goal', id, 'user_operator', 'owner', created_at, updated_at FROM goals;
145
+
146
+ INSERT OR IGNORE INTO entity_owners (entity_type, entity_id, user_id, role, created_at, updated_at)
147
+ SELECT 'project', id, 'user_operator', 'owner', created_at, updated_at FROM projects;
148
+
149
+ INSERT OR IGNORE INTO entity_owners (entity_type, entity_id, user_id, role, created_at, updated_at)
150
+ SELECT
151
+ 'task',
152
+ tasks.id,
153
+ COALESCE(
154
+ (
155
+ SELECT users.id
156
+ FROM users
157
+ WHERE lower(users.display_name) = lower(trim(tasks.owner))
158
+ OR lower(users.handle) = lower(trim(replace(replace(replace(tasks.owner, ' ', '_'), '-', '_'), '.', '')))
159
+ ORDER BY CASE WHEN users.kind = 'human' THEN 0 ELSE 1 END, users.created_at
160
+ LIMIT 1
161
+ ),
162
+ 'user_operator'
163
+ ),
164
+ 'owner',
165
+ tasks.created_at,
166
+ tasks.updated_at
167
+ FROM tasks;
168
+
169
+ INSERT OR IGNORE INTO entity_owners (entity_type, entity_id, user_id, role, created_at, updated_at)
170
+ SELECT 'habit', id, 'user_operator', 'owner', created_at, updated_at FROM habits;
171
+
172
+ INSERT OR IGNORE INTO entity_owners (entity_type, entity_id, user_id, role, created_at, updated_at)
173
+ SELECT 'tag', id, 'user_operator', 'owner', created_at, created_at FROM tags;
174
+
175
+ INSERT OR IGNORE INTO entity_owners (entity_type, entity_id, user_id, role, created_at, updated_at)
176
+ SELECT 'note', id, 'user_operator', 'owner', created_at, updated_at FROM notes;
177
+
178
+ INSERT OR IGNORE INTO entity_owners (entity_type, entity_id, user_id, role, created_at, updated_at)
179
+ SELECT 'insight', id, 'user_operator', 'owner', created_at, updated_at FROM insights;
180
+
181
+ INSERT OR IGNORE INTO entity_owners (entity_type, entity_id, user_id, role, created_at, updated_at)
182
+ SELECT 'calendar_event', id, 'user_operator', 'owner', created_at, updated_at FROM calendar_events;
183
+
184
+ INSERT OR IGNORE INTO entity_owners (entity_type, entity_id, user_id, role, created_at, updated_at)
185
+ SELECT 'work_block_template', id, 'user_operator', 'owner', created_at, updated_at FROM work_block_templates;
186
+
187
+ INSERT OR IGNORE INTO entity_owners (entity_type, entity_id, user_id, role, created_at, updated_at)
188
+ SELECT 'task_timebox', id, 'user_operator', 'owner', created_at, updated_at FROM task_timeboxes;
189
+
190
+ INSERT OR IGNORE INTO entity_owners (entity_type, entity_id, user_id, role, created_at, updated_at)
191
+ SELECT 'psyche_value', id, 'user_operator', 'owner', created_at, updated_at FROM psyche_values;
192
+
193
+ INSERT OR IGNORE INTO entity_owners (entity_type, entity_id, user_id, role, created_at, updated_at)
194
+ SELECT 'behavior_pattern', id, 'user_operator', 'owner', created_at, updated_at FROM behavior_patterns;
195
+
196
+ INSERT OR IGNORE INTO entity_owners (entity_type, entity_id, user_id, role, created_at, updated_at)
197
+ SELECT 'behavior', id, 'user_operator', 'owner', created_at, updated_at FROM psyche_behaviors;
198
+
199
+ INSERT OR IGNORE INTO entity_owners (entity_type, entity_id, user_id, role, created_at, updated_at)
200
+ SELECT 'belief_entry', id, 'user_operator', 'owner', created_at, updated_at FROM belief_entries;
201
+
202
+ INSERT OR IGNORE INTO entity_owners (entity_type, entity_id, user_id, role, created_at, updated_at)
203
+ SELECT 'mode_profile', id, 'user_operator', 'owner', created_at, updated_at FROM mode_profiles;
204
+
205
+ INSERT OR IGNORE INTO entity_owners (entity_type, entity_id, user_id, role, created_at, updated_at)
206
+ SELECT 'mode_guide_session', id, 'user_operator', 'owner', created_at, updated_at FROM mode_guide_sessions;
207
+
208
+ INSERT OR IGNORE INTO entity_owners (entity_type, entity_id, user_id, role, created_at, updated_at)
209
+ SELECT 'event_type', id, 'user_operator', 'owner', created_at, updated_at FROM event_types;
210
+
211
+ INSERT OR IGNORE INTO entity_owners (entity_type, entity_id, user_id, role, created_at, updated_at)
212
+ SELECT 'emotion_definition', id, 'user_operator', 'owner', created_at, updated_at FROM emotion_definitions;
213
+
214
+ INSERT OR IGNORE INTO entity_owners (entity_type, entity_id, user_id, role, created_at, updated_at)
215
+ SELECT 'trigger_report', id, 'user_operator', 'owner', created_at, updated_at FROM trigger_reports;
216
+
217
+ INSERT OR IGNORE INTO entity_owners (entity_type, entity_id, user_id, role, created_at, updated_at)
218
+ SELECT 'strategy', id, 'user_operator', 'owner', created_at, updated_at FROM strategies;
219
+
220
+ INSERT OR IGNORE INTO user_access_grants (
221
+ id,
222
+ subject_user_id,
223
+ target_user_id,
224
+ access_level,
225
+ config_json,
226
+ created_at,
227
+ updated_at
228
+ )
229
+ SELECT
230
+ 'grant_' || lower(hex(randomblob(8))),
231
+ subject_users.id,
232
+ target_users.id,
233
+ CASE
234
+ WHEN subject_users.id = target_users.id THEN 'manage'
235
+ ELSE 'view'
236
+ END,
237
+ CASE
238
+ WHEN subject_users.id = target_users.id THEN '{"self":true,"mutable":true}'
239
+ ELSE '{"discoverable":true,"linkedEntities":true}'
240
+ END,
241
+ CURRENT_TIMESTAMP,
242
+ CURRENT_TIMESTAMP
243
+ FROM users AS subject_users
244
+ CROSS JOIN users AS target_users;
@@ -0,0 +1,158 @@
1
+ CREATE TABLE IF NOT EXISTS companion_pairing_sessions (
2
+ id TEXT PRIMARY KEY,
3
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
4
+ label TEXT NOT NULL DEFAULT '',
5
+ pairing_token TEXT NOT NULL,
6
+ status TEXT NOT NULL DEFAULT 'pending',
7
+ capability_flags_json TEXT NOT NULL DEFAULT '[]',
8
+ device_name TEXT,
9
+ platform TEXT,
10
+ app_version TEXT,
11
+ api_base_url TEXT NOT NULL DEFAULT '',
12
+ last_seen_at TEXT,
13
+ last_sync_at TEXT,
14
+ last_sync_error TEXT,
15
+ paired_at TEXT,
16
+ expires_at TEXT NOT NULL,
17
+ created_at TEXT NOT NULL,
18
+ updated_at TEXT NOT NULL
19
+ );
20
+
21
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_companion_pairing_token
22
+ ON companion_pairing_sessions(pairing_token);
23
+
24
+ CREATE INDEX IF NOT EXISTS idx_companion_pairing_user
25
+ ON companion_pairing_sessions(user_id, status, updated_at DESC);
26
+
27
+ CREATE TABLE IF NOT EXISTS health_import_runs (
28
+ id TEXT PRIMARY KEY,
29
+ pairing_session_id TEXT REFERENCES companion_pairing_sessions(id) ON DELETE SET NULL,
30
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
31
+ source TEXT NOT NULL,
32
+ source_device TEXT NOT NULL DEFAULT '',
33
+ status TEXT NOT NULL DEFAULT 'completed',
34
+ payload_summary_json TEXT NOT NULL DEFAULT '{}',
35
+ imported_count INTEGER NOT NULL DEFAULT 0,
36
+ created_count INTEGER NOT NULL DEFAULT 0,
37
+ updated_count INTEGER NOT NULL DEFAULT 0,
38
+ merged_count INTEGER NOT NULL DEFAULT 0,
39
+ error_message TEXT,
40
+ imported_at TEXT NOT NULL,
41
+ created_at TEXT NOT NULL,
42
+ updated_at TEXT NOT NULL
43
+ );
44
+
45
+ CREATE INDEX IF NOT EXISTS idx_health_import_runs_user
46
+ ON health_import_runs(user_id, imported_at DESC);
47
+
48
+ CREATE TABLE IF NOT EXISTS health_sleep_sessions (
49
+ id TEXT PRIMARY KEY,
50
+ external_uid TEXT NOT NULL,
51
+ pairing_session_id TEXT REFERENCES companion_pairing_sessions(id) ON DELETE SET NULL,
52
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
53
+ source TEXT NOT NULL,
54
+ source_type TEXT NOT NULL DEFAULT 'healthkit',
55
+ source_device TEXT NOT NULL DEFAULT '',
56
+ started_at TEXT NOT NULL,
57
+ ended_at TEXT NOT NULL,
58
+ time_in_bed_seconds INTEGER NOT NULL DEFAULT 0,
59
+ asleep_seconds INTEGER NOT NULL DEFAULT 0,
60
+ awake_seconds INTEGER NOT NULL DEFAULT 0,
61
+ sleep_score REAL,
62
+ regularity_score REAL,
63
+ bedtime_consistency_minutes INTEGER,
64
+ wake_consistency_minutes INTEGER,
65
+ stage_breakdown_json TEXT NOT NULL DEFAULT '[]',
66
+ recovery_metrics_json TEXT NOT NULL DEFAULT '{}',
67
+ links_json TEXT NOT NULL DEFAULT '[]',
68
+ annotations_json TEXT NOT NULL DEFAULT '{}',
69
+ provenance_json TEXT NOT NULL DEFAULT '{}',
70
+ derived_json TEXT NOT NULL DEFAULT '{}',
71
+ created_at TEXT NOT NULL,
72
+ updated_at TEXT NOT NULL,
73
+ UNIQUE (user_id, source, external_uid)
74
+ );
75
+
76
+ CREATE INDEX IF NOT EXISTS idx_health_sleep_user
77
+ ON health_sleep_sessions(user_id, started_at DESC);
78
+
79
+ CREATE TABLE IF NOT EXISTS health_workout_sessions (
80
+ id TEXT PRIMARY KEY,
81
+ external_uid TEXT NOT NULL,
82
+ pairing_session_id TEXT REFERENCES companion_pairing_sessions(id) ON DELETE SET NULL,
83
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
84
+ source TEXT NOT NULL,
85
+ source_type TEXT NOT NULL DEFAULT 'healthkit',
86
+ workout_type TEXT NOT NULL,
87
+ source_device TEXT NOT NULL DEFAULT '',
88
+ started_at TEXT NOT NULL,
89
+ ended_at TEXT NOT NULL,
90
+ duration_seconds INTEGER NOT NULL DEFAULT 0,
91
+ active_energy_kcal REAL,
92
+ total_energy_kcal REAL,
93
+ distance_meters REAL,
94
+ step_count INTEGER,
95
+ exercise_minutes REAL,
96
+ average_heart_rate REAL,
97
+ max_heart_rate REAL,
98
+ subjective_effort INTEGER,
99
+ mood_before TEXT NOT NULL DEFAULT '',
100
+ mood_after TEXT NOT NULL DEFAULT '',
101
+ meaning_text TEXT NOT NULL DEFAULT '',
102
+ planned_context TEXT NOT NULL DEFAULT '',
103
+ social_context TEXT NOT NULL DEFAULT '',
104
+ links_json TEXT NOT NULL DEFAULT '[]',
105
+ tags_json TEXT NOT NULL DEFAULT '[]',
106
+ annotations_json TEXT NOT NULL DEFAULT '{}',
107
+ provenance_json TEXT NOT NULL DEFAULT '{}',
108
+ derived_json TEXT NOT NULL DEFAULT '{}',
109
+ generated_from_habit_id TEXT REFERENCES habits(id) ON DELETE SET NULL,
110
+ generated_from_check_in_id TEXT REFERENCES habit_check_ins(id) ON DELETE SET NULL,
111
+ reconciliation_status TEXT NOT NULL DEFAULT 'standalone',
112
+ created_at TEXT NOT NULL,
113
+ updated_at TEXT NOT NULL,
114
+ UNIQUE (user_id, source, external_uid)
115
+ );
116
+
117
+ CREATE INDEX IF NOT EXISTS idx_health_workouts_user
118
+ ON health_workout_sessions(user_id, started_at DESC);
119
+
120
+ CREATE TABLE IF NOT EXISTS health_daily_summaries (
121
+ id TEXT PRIMARY KEY,
122
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
123
+ date_key TEXT NOT NULL,
124
+ summary_type TEXT NOT NULL,
125
+ metrics_json TEXT NOT NULL DEFAULT '{}',
126
+ derived_json TEXT NOT NULL DEFAULT '{}',
127
+ source TEXT NOT NULL DEFAULT 'derived',
128
+ created_at TEXT NOT NULL,
129
+ updated_at TEXT NOT NULL,
130
+ UNIQUE (user_id, date_key, summary_type)
131
+ );
132
+
133
+ CREATE INDEX IF NOT EXISTS idx_health_daily_summaries_user
134
+ ON health_daily_summaries(user_id, date_key DESC, summary_type);
135
+
136
+ ALTER TABLE habits
137
+ ADD COLUMN generated_health_event_template_json TEXT NOT NULL DEFAULT '{}';
138
+
139
+ ALTER TABLE forge_events
140
+ ADD COLUMN place_label TEXT NOT NULL DEFAULT '';
141
+
142
+ ALTER TABLE forge_events
143
+ ADD COLUMN place_address TEXT NOT NULL DEFAULT '';
144
+
145
+ ALTER TABLE forge_events
146
+ ADD COLUMN place_timezone TEXT NOT NULL DEFAULT '';
147
+
148
+ ALTER TABLE forge_events
149
+ ADD COLUMN place_latitude REAL;
150
+
151
+ ALTER TABLE forge_events
152
+ ADD COLUMN place_longitude REAL;
153
+
154
+ ALTER TABLE forge_events
155
+ ADD COLUMN place_source TEXT NOT NULL DEFAULT '';
156
+
157
+ ALTER TABLE forge_events
158
+ ADD COLUMN place_external_id TEXT NOT NULL DEFAULT '';
@@ -0,0 +1,22 @@
1
+ ALTER TABLE strategies ADD COLUMN is_locked INTEGER NOT NULL DEFAULT 0;
2
+ ALTER TABLE strategies ADD COLUMN locked_at TEXT;
3
+ ALTER TABLE strategies ADD COLUMN locked_by_user_id TEXT REFERENCES users(id) ON DELETE SET NULL;
4
+
5
+ CREATE INDEX IF NOT EXISTS idx_strategies_locked_by_user ON strategies(locked_by_user_id, updated_at DESC);
6
+
7
+ DELETE FROM user_access_grants
8
+ WHERE rowid NOT IN (
9
+ SELECT MIN(rowid)
10
+ FROM user_access_grants
11
+ GROUP BY subject_user_id, target_user_id
12
+ );
13
+
14
+ UPDATE user_access_grants
15
+ SET access_level = 'manage',
16
+ config_json = CASE
17
+ WHEN subject_user_id = target_user_id THEN
18
+ '{"self":true,"mutable":true,"linkedEntities":true,"rights":{"discoverable":true,"canListUsers":true,"canReadProfile":true,"canReadEntities":true,"canSearchEntities":true,"canLinkEntities":true,"canAffectEntities":true,"canManageStrategies":true,"canCreateOnBehalf":true,"canViewMetrics":true,"canViewActivity":true}}'
19
+ ELSE
20
+ '{"self":false,"mutable":false,"linkedEntities":true,"rights":{"discoverable":true,"canListUsers":true,"canReadProfile":true,"canReadEntities":true,"canSearchEntities":true,"canLinkEntities":true,"canAffectEntities":true,"canManageStrategies":true,"canCreateOnBehalf":true,"canViewMetrics":true,"canViewActivity":true}}'
21
+ END,
22
+ updated_at = CURRENT_TIMESTAMP;
@@ -0,0 +1,131 @@
1
+ CREATE TABLE IF NOT EXISTS preference_profiles (
2
+ id TEXT PRIMARY KEY,
3
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
4
+ domain TEXT NOT NULL,
5
+ default_context_id TEXT,
6
+ model_version TEXT NOT NULL,
7
+ created_at TEXT NOT NULL,
8
+ updated_at TEXT NOT NULL,
9
+ UNIQUE (user_id, domain)
10
+ );
11
+
12
+ CREATE TABLE IF NOT EXISTS preference_contexts (
13
+ id TEXT PRIMARY KEY,
14
+ profile_id TEXT NOT NULL REFERENCES preference_profiles(id) ON DELETE CASCADE,
15
+ name TEXT NOT NULL,
16
+ description TEXT NOT NULL DEFAULT '',
17
+ share_mode TEXT NOT NULL DEFAULT 'blended' CHECK (share_mode IN ('shared', 'isolated', 'blended')),
18
+ active INTEGER NOT NULL DEFAULT 1,
19
+ is_default INTEGER NOT NULL DEFAULT 0,
20
+ decay_days INTEGER NOT NULL DEFAULT 90,
21
+ created_at TEXT NOT NULL,
22
+ updated_at TEXT NOT NULL
23
+ );
24
+
25
+ CREATE TABLE IF NOT EXISTS preference_items (
26
+ id TEXT PRIMARY KEY,
27
+ profile_id TEXT NOT NULL REFERENCES preference_profiles(id) ON DELETE CASCADE,
28
+ label TEXT NOT NULL,
29
+ description TEXT NOT NULL DEFAULT '',
30
+ tags_json TEXT NOT NULL DEFAULT '[]',
31
+ feature_weights_json TEXT NOT NULL DEFAULT '{}',
32
+ source_entity_type TEXT,
33
+ source_entity_id TEXT,
34
+ metadata_json TEXT NOT NULL DEFAULT '{}',
35
+ created_at TEXT NOT NULL,
36
+ updated_at TEXT NOT NULL
37
+ );
38
+
39
+ CREATE TABLE IF NOT EXISTS pairwise_judgments (
40
+ id TEXT PRIMARY KEY,
41
+ profile_id TEXT NOT NULL REFERENCES preference_profiles(id) ON DELETE CASCADE,
42
+ context_id TEXT NOT NULL REFERENCES preference_contexts(id) ON DELETE CASCADE,
43
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
44
+ left_item_id TEXT NOT NULL REFERENCES preference_items(id) ON DELETE CASCADE,
45
+ right_item_id TEXT NOT NULL REFERENCES preference_items(id) ON DELETE CASCADE,
46
+ outcome TEXT NOT NULL CHECK (outcome IN ('left', 'right', 'tie', 'skip')),
47
+ strength REAL NOT NULL DEFAULT 1,
48
+ response_time_ms INTEGER,
49
+ source TEXT NOT NULL DEFAULT 'ui',
50
+ reason_tags_json TEXT NOT NULL DEFAULT '[]',
51
+ created_at TEXT NOT NULL
52
+ );
53
+
54
+ CREATE TABLE IF NOT EXISTS absolute_signals (
55
+ id TEXT PRIMARY KEY,
56
+ profile_id TEXT NOT NULL REFERENCES preference_profiles(id) ON DELETE CASCADE,
57
+ context_id TEXT NOT NULL REFERENCES preference_contexts(id) ON DELETE CASCADE,
58
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
59
+ item_id TEXT NOT NULL REFERENCES preference_items(id) ON DELETE CASCADE,
60
+ signal_type TEXT NOT NULL CHECK (signal_type IN ('favorite', 'veto', 'must_have', 'bookmark', 'neutral', 'compare_later')),
61
+ strength REAL NOT NULL DEFAULT 1,
62
+ source TEXT NOT NULL DEFAULT 'ui',
63
+ created_at TEXT NOT NULL
64
+ );
65
+
66
+ CREATE TABLE IF NOT EXISTS preference_item_scores (
67
+ id TEXT PRIMARY KEY,
68
+ profile_id TEXT NOT NULL REFERENCES preference_profiles(id) ON DELETE CASCADE,
69
+ context_id TEXT NOT NULL REFERENCES preference_contexts(id) ON DELETE CASCADE,
70
+ item_id TEXT NOT NULL REFERENCES preference_items(id) ON DELETE CASCADE,
71
+ latent_score REAL NOT NULL DEFAULT 0,
72
+ confidence REAL NOT NULL DEFAULT 0,
73
+ uncertainty REAL NOT NULL DEFAULT 1,
74
+ evidence_count INTEGER NOT NULL DEFAULT 0,
75
+ pairwise_wins INTEGER NOT NULL DEFAULT 0,
76
+ pairwise_losses INTEGER NOT NULL DEFAULT 0,
77
+ pairwise_ties INTEGER NOT NULL DEFAULT 0,
78
+ signal_count INTEGER NOT NULL DEFAULT 0,
79
+ conflict_count INTEGER NOT NULL DEFAULT 0,
80
+ status TEXT NOT NULL DEFAULT 'uncertain',
81
+ dominant_dimensions_json TEXT NOT NULL DEFAULT '[]',
82
+ explanation_json TEXT NOT NULL DEFAULT '[]',
83
+ manual_status TEXT,
84
+ manual_score REAL,
85
+ confidence_lock REAL,
86
+ bookmarked INTEGER NOT NULL DEFAULT 0,
87
+ compare_later INTEGER NOT NULL DEFAULT 0,
88
+ frozen INTEGER NOT NULL DEFAULT 0,
89
+ last_inferred_at TEXT NOT NULL,
90
+ last_judgment_at TEXT,
91
+ updated_at TEXT NOT NULL,
92
+ UNIQUE (context_id, item_id)
93
+ );
94
+
95
+ CREATE TABLE IF NOT EXISTS preference_dimension_summaries (
96
+ id TEXT PRIMARY KEY,
97
+ profile_id TEXT NOT NULL REFERENCES preference_profiles(id) ON DELETE CASCADE,
98
+ context_id TEXT NOT NULL REFERENCES preference_contexts(id) ON DELETE CASCADE,
99
+ dimension_id TEXT NOT NULL,
100
+ leaning REAL NOT NULL DEFAULT 0,
101
+ confidence REAL NOT NULL DEFAULT 0,
102
+ movement REAL NOT NULL DEFAULT 0,
103
+ context_sensitivity REAL NOT NULL DEFAULT 0,
104
+ evidence_count INTEGER NOT NULL DEFAULT 0,
105
+ updated_at TEXT NOT NULL,
106
+ UNIQUE (context_id, dimension_id)
107
+ );
108
+
109
+ CREATE TABLE IF NOT EXISTS preference_snapshots (
110
+ id TEXT PRIMARY KEY,
111
+ profile_id TEXT NOT NULL REFERENCES preference_profiles(id) ON DELETE CASCADE,
112
+ context_id TEXT NOT NULL REFERENCES preference_contexts(id) ON DELETE CASCADE,
113
+ summary_metrics_json TEXT NOT NULL DEFAULT '{}',
114
+ serialized_model_state_json TEXT NOT NULL DEFAULT '{}',
115
+ created_at TEXT NOT NULL
116
+ );
117
+
118
+ CREATE INDEX IF NOT EXISTS idx_preference_profiles_user_domain
119
+ ON preference_profiles(user_id, domain);
120
+ CREATE INDEX IF NOT EXISTS idx_preference_contexts_profile_active
121
+ ON preference_contexts(profile_id, active, is_default);
122
+ CREATE INDEX IF NOT EXISTS idx_preference_items_profile
123
+ ON preference_items(profile_id, updated_at DESC);
124
+ CREATE INDEX IF NOT EXISTS idx_pairwise_judgments_context_created
125
+ ON pairwise_judgments(context_id, created_at DESC);
126
+ CREATE INDEX IF NOT EXISTS idx_absolute_signals_context_created
127
+ ON absolute_signals(context_id, created_at DESC);
128
+ CREATE INDEX IF NOT EXISTS idx_preference_scores_context
129
+ ON preference_item_scores(context_id, status, confidence DESC, latent_score DESC);
130
+ CREATE INDEX IF NOT EXISTS idx_preference_snapshots_context
131
+ ON preference_snapshots(context_id, created_at DESC);
@@ -0,0 +1,31 @@
1
+ CREATE TABLE IF NOT EXISTS preference_catalogs (
2
+ id TEXT PRIMARY KEY,
3
+ profile_id TEXT NOT NULL REFERENCES preference_profiles(id) ON DELETE CASCADE,
4
+ domain TEXT NOT NULL,
5
+ slug TEXT NOT NULL,
6
+ title TEXT NOT NULL,
7
+ description TEXT NOT NULL DEFAULT '',
8
+ source TEXT NOT NULL DEFAULT 'custom' CHECK (source IN ('seeded', 'custom')),
9
+ archived INTEGER NOT NULL DEFAULT 0,
10
+ created_at TEXT NOT NULL,
11
+ updated_at TEXT NOT NULL,
12
+ UNIQUE (profile_id, slug)
13
+ );
14
+
15
+ CREATE TABLE IF NOT EXISTS preference_catalog_items (
16
+ id TEXT PRIMARY KEY,
17
+ catalog_id TEXT NOT NULL REFERENCES preference_catalogs(id) ON DELETE CASCADE,
18
+ label TEXT NOT NULL,
19
+ description TEXT NOT NULL DEFAULT '',
20
+ tags_json TEXT NOT NULL DEFAULT '[]',
21
+ feature_weights_json TEXT NOT NULL DEFAULT '{}',
22
+ position INTEGER NOT NULL DEFAULT 0,
23
+ archived INTEGER NOT NULL DEFAULT 0,
24
+ created_at TEXT NOT NULL,
25
+ updated_at TEXT NOT NULL
26
+ );
27
+
28
+ CREATE INDEX IF NOT EXISTS idx_preference_catalogs_profile
29
+ ON preference_catalogs(profile_id, archived, source, title);
30
+ CREATE INDEX IF NOT EXISTS idx_preference_catalog_items_catalog
31
+ ON preference_catalog_items(catalog_id, archived, position, label);