forge-openclaw-plugin 0.2.24 → 0.2.26

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 (208) hide show
  1. package/README.md +13 -0
  2. package/dist/assets/{board-_C6oMy5w.js → board-ta0rUHOf.js} +3 -3
  3. package/dist/assets/{board-_C6oMy5w.js.map → board-ta0rUHOf.js.map} +1 -1
  4. package/dist/assets/index-Ro0ZF_az.css +1 -0
  5. package/dist/assets/index-ytlpSj23.js +79 -0
  6. package/dist/assets/index-ytlpSj23.js.map +1 -0
  7. package/dist/assets/{motion-D4sZgCHd.js → motion-fBKPB6yw.js} +3 -3
  8. package/dist/assets/motion-fBKPB6yw.js.map +1 -0
  9. package/dist/assets/{table-BWzTaky1.js → table-C-IGTQni.js} +2 -2
  10. package/dist/assets/{table-BWzTaky1.js.map → table-C-IGTQni.js.map} +1 -1
  11. package/dist/assets/{ui-BzK4azQb.js → ui-DInOpaYF.js} +2 -2
  12. package/dist/assets/{ui-BzK4azQb.js.map → ui-DInOpaYF.js.map} +1 -1
  13. package/dist/assets/vendor-lE3tZJcC.js +876 -0
  14. package/dist/assets/vendor-lE3tZJcC.js.map +1 -0
  15. package/dist/index.html +7 -8
  16. package/dist/openclaw/local-runtime.d.ts +3 -1
  17. package/dist/openclaw/local-runtime.js +51 -15
  18. package/dist/openclaw/parity.d.ts +1 -1
  19. package/dist/openclaw/parity.js +29 -0
  20. package/dist/openclaw/plugin-entry-shared.d.ts +1 -0
  21. package/dist/openclaw/plugin-entry-shared.js +31 -6
  22. package/dist/openclaw/plugin-sdk-types.d.ts +29 -0
  23. package/dist/openclaw/routes.js +236 -0
  24. package/dist/openclaw/session-bootstrap.d.ts +78 -0
  25. package/dist/openclaw/session-bootstrap.js +240 -0
  26. package/dist/openclaw/tools.js +279 -6
  27. package/dist/server/server/migrations/001_core.sql +411 -0
  28. package/dist/server/server/migrations/002_psyche.sql +392 -0
  29. package/dist/server/server/migrations/003_habits.sql +30 -0
  30. package/dist/server/server/migrations/004_habit_links.sql +8 -0
  31. package/dist/server/server/migrations/005_habit_psyche_links.sql +24 -0
  32. package/dist/server/server/migrations/006_work_adjustments.sql +14 -0
  33. package/dist/server/server/migrations/007_weekly_review_closures.sql +17 -0
  34. package/dist/server/server/migrations/008_calendar_execution.sql +147 -0
  35. package/dist/server/server/migrations/009_true_calendar_events.sql +195 -0
  36. package/dist/server/server/migrations/010_calendar_selection_state.sql +6 -0
  37. package/dist/server/server/migrations/011_calendar_timezone_backfill.sql +11 -0
  38. package/dist/server/server/migrations/012_work_block_ranges.sql +7 -0
  39. package/dist/server/server/migrations/013_microsoft_local_auth_settings.sql +8 -0
  40. package/dist/server/server/migrations/014_note_tags_and_ephemeral.sql +8 -0
  41. package/dist/server/server/migrations/015_multi_user_and_strategies.sql +244 -0
  42. package/dist/server/server/migrations/016_health_companion.sql +158 -0
  43. package/dist/server/server/migrations/016_strategy_contracts_and_user_graph.sql +22 -0
  44. package/dist/server/server/migrations/017_preferences.sql +131 -0
  45. package/dist/server/server/migrations/018_preference_catalogs.sql +31 -0
  46. package/dist/server/server/migrations/019_wiki_memory.sql +255 -0
  47. package/dist/server/server/migrations/020_wiki_page_hierarchy.sql +11 -0
  48. package/dist/server/server/migrations/021_hide_evidence_from_wiki_index.sql +3 -0
  49. package/dist/server/server/migrations/022_wiki_ingest_background.sql +85 -0
  50. package/dist/server/server/migrations/023_diagnostic_logs.sql +28 -0
  51. package/dist/server/server/migrations/024_questionnaires.sql +96 -0
  52. package/dist/server/server/migrations/025_ai_model_connections.sql +26 -0
  53. package/dist/server/server/migrations/026_custom_theme_settings.sql +2 -0
  54. package/dist/server/server/migrations/027_ai_processors.sql +31 -0
  55. package/dist/server/server/migrations/028_movement_domain.sql +136 -0
  56. package/dist/server/server/migrations/029_watch_micro_capture.sql +23 -0
  57. package/dist/server/server/migrations/030_surface_layouts.sql +5 -0
  58. package/dist/server/server/migrations/031_ai_processor_runtime_upgrades.sql +10 -0
  59. package/dist/server/server/migrations/032_ai_connectors.sql +44 -0
  60. package/dist/server/server/migrations/033_movement_trip_point_sync.sql +36 -0
  61. package/dist/server/server/migrations/034_movement_segment_sync.sql +49 -0
  62. package/dist/server/server/migrations/035_google_local_auth_settings.sql +2 -0
  63. package/dist/server/server/migrations/036_google_local_auth_client_secret.sql +2 -0
  64. package/dist/server/{app.js → server/src/app.js} +992 -25
  65. package/dist/server/server/src/connectors/box-registry.js +188 -0
  66. package/dist/server/{db.js → server/src/db.js} +6 -0
  67. package/dist/server/server/src/debug.js +19 -0
  68. package/dist/server/server/src/discovery-advertiser.js +114 -0
  69. package/dist/server/{health.js → server/src/health.js} +39 -11
  70. package/dist/server/{index.js → server/src/index.js} +4 -0
  71. package/dist/server/{managers → server/src/managers}/platform/llm-manager.js +40 -4
  72. package/dist/server/{managers → server/src/managers}/platform/openai-responses-provider.js +129 -19
  73. package/dist/server/server/src/movement.js +2935 -0
  74. package/dist/server/{openapi.js → server/src/openapi.js} +628 -5
  75. package/dist/server/{psyche-types.js → server/src/psyche-types.js} +15 -1
  76. package/dist/server/server/src/questionnaire-flow.js +552 -0
  77. package/dist/server/server/src/questionnaire-seeds.js +853 -0
  78. package/dist/server/server/src/questionnaire-types.js +340 -0
  79. package/dist/server/server/src/repositories/ai-connectors.js +1207 -0
  80. package/dist/server/server/src/repositories/ai-processors.js +547 -0
  81. package/dist/server/{repositories → server/src/repositories}/calendar.js +1 -1
  82. package/dist/server/{repositories → server/src/repositories}/entity-ownership.js +9 -1
  83. package/dist/server/{repositories → server/src/repositories}/habits.js +69 -5
  84. package/dist/server/server/src/repositories/model-settings.js +216 -0
  85. package/dist/server/{repositories → server/src/repositories}/notes.js +57 -15
  86. package/dist/server/{repositories → server/src/repositories}/preferences.js +124 -0
  87. package/dist/server/server/src/repositories/questionnaires.js +1338 -0
  88. package/dist/server/{repositories → server/src/repositories}/settings.js +156 -12
  89. package/dist/server/server/src/repositories/surface-layouts.js +76 -0
  90. package/dist/server/{repositories → server/src/repositories}/wiki-memory.js +5 -1
  91. package/dist/server/{services → server/src/services}/calendar-runtime.js +775 -58
  92. package/dist/server/{services → server/src/services}/entity-crud.js +81 -2
  93. package/dist/server/server/src/services/google-calendar-oauth-config.js +176 -0
  94. package/dist/server/server/src/services/openai-codex-oauth.js +153 -0
  95. package/dist/server/server/src/services/psyche-observation-calendar.js +46 -0
  96. package/dist/server/{types.js → server/src/types.js} +621 -14
  97. package/dist/server/server/src/watch-mobile.js +562 -0
  98. package/dist/server/{web.js → server/src/web.js} +30 -4
  99. package/dist/server/src/components/customization/utility-widgets.js +330 -0
  100. package/dist/server/src/components/workbench-boxes/health/health-boxes.js +92 -0
  101. package/dist/server/src/components/workbench-boxes/kanban/kanban-boxes.js +128 -0
  102. package/dist/server/src/components/workbench-boxes/movement/movement-boxes.js +37 -0
  103. package/dist/server/src/components/workbench-boxes/notes/notes-boxes.js +114 -0
  104. package/dist/server/src/components/workbench-boxes/projects/projects-boxes.js +57 -0
  105. package/dist/server/src/components/workbench-boxes/shared/define-workbench-box.js +4 -0
  106. package/dist/server/src/components/workbench-boxes/shared/generic-node-view.js +13 -0
  107. package/dist/server/src/components/workbench-boxes/today/today-boxes.js +63 -0
  108. package/dist/server/src/lib/api-error.js +37 -0
  109. package/dist/server/src/lib/api.js +1859 -0
  110. package/dist/server/src/lib/calendar-name-deduper.js +144 -0
  111. package/dist/server/src/lib/diagnostics.js +67 -0
  112. package/dist/server/src/lib/psyche-types.js +1 -0
  113. package/dist/server/src/lib/questionnaire-types.js +1 -0
  114. package/dist/server/src/lib/runtime-paths.js +24 -0
  115. package/dist/server/src/lib/schemas.js +234 -0
  116. package/dist/server/src/lib/snapshot-normalizer.js +374 -0
  117. package/dist/server/src/lib/theme-system.js +319 -0
  118. package/dist/server/src/lib/types.js +1 -0
  119. package/dist/server/src/lib/utils.js +22 -0
  120. package/dist/server/src/lib/workbench/boxes.js +16 -0
  121. package/dist/server/src/lib/workbench/nodes.js +15 -0
  122. package/dist/server/src/lib/workbench/registry.js +73 -0
  123. package/dist/server/src/lib/workbench/runtime.js +181 -0
  124. package/openclaw.plugin.json +1 -1
  125. package/package.json +6 -1
  126. package/server/index.js +68 -0
  127. package/server/migrations/024_questionnaires.sql +96 -0
  128. package/server/migrations/025_ai_model_connections.sql +26 -0
  129. package/server/migrations/026_custom_theme_settings.sql +2 -0
  130. package/server/migrations/027_ai_processors.sql +31 -0
  131. package/server/migrations/028_movement_domain.sql +136 -0
  132. package/server/migrations/029_watch_micro_capture.sql +23 -0
  133. package/server/migrations/030_surface_layouts.sql +5 -0
  134. package/server/migrations/031_ai_processor_runtime_upgrades.sql +10 -0
  135. package/server/migrations/032_ai_connectors.sql +44 -0
  136. package/server/migrations/033_movement_trip_point_sync.sql +36 -0
  137. package/server/migrations/034_movement_segment_sync.sql +49 -0
  138. package/server/migrations/035_google_local_auth_settings.sql +2 -0
  139. package/server/migrations/036_google_local_auth_client_secret.sql +2 -0
  140. package/skills/forge-openclaw/SKILL.md +15 -1
  141. package/skills/forge-openclaw/entity_conversation_playbooks.md +523 -87
  142. package/skills/forge-openclaw/psyche_entity_playbooks.md +331 -221
  143. package/dist/assets/index-DTCwBWAs.js +0 -65
  144. package/dist/assets/index-DTCwBWAs.js.map +0 -1
  145. package/dist/assets/index-DttXlAgi.css +0 -1
  146. package/dist/assets/motion-D4sZgCHd.js.map +0 -1
  147. package/dist/assets/vendor-De38P6YR.js +0 -729
  148. package/dist/assets/vendor-De38P6YR.js.map +0 -1
  149. package/dist/assets/viz-C6hfyqzu.js +0 -34
  150. package/dist/assets/viz-C6hfyqzu.js.map +0 -1
  151. package/skills/forge-openclaw/cron_jobs.md +0 -395
  152. /package/dist/server/{demo-data.js → server/src/demo-data.js} +0 -0
  153. /package/dist/server/{e2e-server.js → server/src/e2e-server.js} +0 -0
  154. /package/dist/server/{errors.js → server/src/errors.js} +0 -0
  155. /package/dist/server/{managers → server/src/managers}/base.js +0 -0
  156. /package/dist/server/{managers → server/src/managers}/contracts.js +0 -0
  157. /package/dist/server/{managers → server/src/managers}/platform/api-gateway-manager.js +0 -0
  158. /package/dist/server/{managers → server/src/managers}/platform/audit-manager.js +0 -0
  159. /package/dist/server/{managers → server/src/managers}/platform/authentication-manager.js +0 -0
  160. /package/dist/server/{managers → server/src/managers}/platform/authorization-manager.js +0 -0
  161. /package/dist/server/{managers → server/src/managers}/platform/background-job-manager.js +0 -0
  162. /package/dist/server/{managers → server/src/managers}/platform/configuration-manager.js +0 -0
  163. /package/dist/server/{managers → server/src/managers}/platform/database-manager.js +0 -0
  164. /package/dist/server/{managers → server/src/managers}/platform/event-bus-manager.js +0 -0
  165. /package/dist/server/{managers → server/src/managers}/platform/external-service-manager.js +0 -0
  166. /package/dist/server/{managers → server/src/managers}/platform/health-manager.js +0 -0
  167. /package/dist/server/{managers → server/src/managers}/platform/migration-manager.js +0 -0
  168. /package/dist/server/{managers → server/src/managers}/platform/search-index-manager.js +0 -0
  169. /package/dist/server/{managers → server/src/managers}/platform/secrets-manager.js +0 -0
  170. /package/dist/server/{managers → server/src/managers}/platform/session-manager.js +0 -0
  171. /package/dist/server/{managers → server/src/managers}/platform/storage-manager.js +0 -0
  172. /package/dist/server/{managers → server/src/managers}/platform/token-manager.js +0 -0
  173. /package/dist/server/{managers → server/src/managers}/platform/transaction-manager.js +0 -0
  174. /package/dist/server/{managers → server/src/managers}/platform/trusted-network.js +0 -0
  175. /package/dist/server/{managers → server/src/managers}/runtime.js +0 -0
  176. /package/dist/server/{managers → server/src/managers}/type-guards.js +0 -0
  177. /package/dist/server/{preferences-seeds.js → server/src/preferences-seeds.js} +0 -0
  178. /package/dist/server/{preferences-types.js → server/src/preferences-types.js} +0 -0
  179. /package/dist/server/{repositories → server/src/repositories}/activity-events.js +0 -0
  180. /package/dist/server/{repositories → server/src/repositories}/collaboration.js +0 -0
  181. /package/dist/server/{repositories → server/src/repositories}/deleted-entities.js +0 -0
  182. /package/dist/server/{repositories → server/src/repositories}/diagnostic-logs.js +0 -0
  183. /package/dist/server/{repositories → server/src/repositories}/domains.js +0 -0
  184. /package/dist/server/{repositories → server/src/repositories}/event-log.js +0 -0
  185. /package/dist/server/{repositories → server/src/repositories}/goals.js +0 -0
  186. /package/dist/server/{repositories → server/src/repositories}/projects.js +0 -0
  187. /package/dist/server/{repositories → server/src/repositories}/psyche.js +0 -0
  188. /package/dist/server/{repositories → server/src/repositories}/rewards.js +0 -0
  189. /package/dist/server/{repositories → server/src/repositories}/strategies.js +0 -0
  190. /package/dist/server/{repositories → server/src/repositories}/tags.js +0 -0
  191. /package/dist/server/{repositories → server/src/repositories}/task-runs.js +0 -0
  192. /package/dist/server/{repositories → server/src/repositories}/tasks.js +0 -0
  193. /package/dist/server/{repositories → server/src/repositories}/users.js +0 -0
  194. /package/dist/server/{repositories → server/src/repositories}/weekly-reviews.js +0 -0
  195. /package/dist/server/{repositories → server/src/repositories}/work-adjustments.js +0 -0
  196. /package/dist/server/{seed-demo.js → server/src/seed-demo.js} +0 -0
  197. /package/dist/server/{services → server/src/services}/context.js +0 -0
  198. /package/dist/server/{services → server/src/services}/dashboard.js +0 -0
  199. /package/dist/server/{services → server/src/services}/gamification.js +0 -0
  200. /package/dist/server/{services → server/src/services}/insights.js +0 -0
  201. /package/dist/server/{services → server/src/services}/projects.js +0 -0
  202. /package/dist/server/{services → server/src/services}/psyche.js +0 -0
  203. /package/dist/server/{services → server/src/services}/relations.js +0 -0
  204. /package/dist/server/{services → server/src/services}/reviews.js +0 -0
  205. /package/dist/server/{services → server/src/services}/run-recovery.js +0 -0
  206. /package/dist/server/{services → server/src/services}/tagging.js +0 -0
  207. /package/dist/server/{services → server/src/services}/task-run-watchdog.js +0 -0
  208. /package/dist/server/{services → server/src/services}/work-time.js +0 -0
@@ -0,0 +1,188 @@
1
+ import { getDatabase } from "../db.js";
2
+ import { getFitnessViewData, getSleepViewData } from "../health.js";
3
+ import { listMovementPlaces } from "../movement.js";
4
+ import { createNote, listNotes } from "../repositories/notes.js";
5
+ import { updateTask } from "../repositories/tasks.js";
6
+ import { searchEntities } from "../services/entity-crud.js";
7
+ import { executeCommonWorkbenchTool, mapWorkbenchTools } from "../../../src/lib/workbench/runtime.js";
8
+ import { getWorkbenchNodeCatalog, getWorkbenchNodeDefinition } from "../../../src/lib/workbench/registry.js";
9
+ function createSnapshotForConnectorOutput(boxId) {
10
+ const outputId = boxId.replace(/^connector-output:/, "");
11
+ const rows = getDatabase()
12
+ .prepare(`SELECT id, title, published_outputs_json, last_run_json FROM ai_connectors ORDER BY updated_at DESC`)
13
+ .all();
14
+ for (const row of rows) {
15
+ const outputs = JSON.parse(row.published_outputs_json || "[]");
16
+ const output = outputs.find((entry) => entry.id === outputId);
17
+ if (!output) {
18
+ continue;
19
+ }
20
+ const lastRun = row.last_run_json
21
+ ? JSON.parse(row.last_run_json)
22
+ : null;
23
+ const published = lastRun?.result?.outputs?.[outputId] ?? null;
24
+ return {
25
+ boxId,
26
+ label: output.label,
27
+ capturedAt: new Date().toISOString(),
28
+ contentText: published?.text ?? `${row.title}\nNo published output has been generated yet.`,
29
+ contentJson: published?.json ?? null,
30
+ tools: []
31
+ };
32
+ }
33
+ return null;
34
+ }
35
+ function createRuntimeContext(input) {
36
+ const services = {
37
+ entities: {
38
+ search: searchEntities
39
+ },
40
+ notes: {
41
+ create: createNote,
42
+ list: listNotes
43
+ },
44
+ movement: {
45
+ listPlaces: listMovementPlaces
46
+ },
47
+ health: {
48
+ getSleepViewData: getSleepViewData,
49
+ getFitnessViewData: getFitnessViewData
50
+ },
51
+ tasks: {
52
+ update: ((taskId, patch) => updateTask(taskId, patch, { source: "agent", actor: "Workbench" }))
53
+ }
54
+ };
55
+ return {
56
+ actor: input?.actor ?? { userIds: null, source: "api" },
57
+ services,
58
+ routeParams: input?.routeParams,
59
+ filters: input?.filters,
60
+ now: new Date().toISOString()
61
+ };
62
+ }
63
+ function toCatalogEntry(definition) {
64
+ if (!definition) {
65
+ return null;
66
+ }
67
+ const toPortDefinition = (port) => ({
68
+ key: port.key,
69
+ label: port.label,
70
+ kind: port.kind,
71
+ required: port.required ?? false,
72
+ expandableKeys: "expandableKeys" in port ? (port.expandableKeys ?? []) : []
73
+ });
74
+ return {
75
+ id: definition.id,
76
+ boxId: definition.id,
77
+ surfaceId: definition.surfaceId,
78
+ routePath: definition.routePath,
79
+ title: definition.title,
80
+ label: definition.title,
81
+ icon: definition.icon ?? null,
82
+ description: definition.description,
83
+ category: definition.category,
84
+ tags: definition.tags,
85
+ capabilityModes: [
86
+ "content",
87
+ ...(definition.tools.length > 0 ? ["tool"] : [])
88
+ ],
89
+ inputs: definition.inputs.map(toPortDefinition),
90
+ params: definition.params.map(toPortDefinition),
91
+ output: definition.output.map(toPortDefinition),
92
+ tools: definition.tools,
93
+ outputs: definition.output.map(toPortDefinition),
94
+ toolAdapters: definition.tools,
95
+ snapshotResolverKey: undefined
96
+ };
97
+ }
98
+ export function listForgeBoxCatalog() {
99
+ return getWorkbenchNodeCatalog();
100
+ }
101
+ export function getForgeBoxCatalogEntry(boxId) {
102
+ return toCatalogEntry(getWorkbenchNodeDefinition(boxId));
103
+ }
104
+ export function buildConnectorOutputCatalogEntry(input) {
105
+ return {
106
+ id: `connector-output:${input.outputId}`,
107
+ boxId: `connector-output:${input.outputId}`,
108
+ surfaceId: null,
109
+ routePath: `/workbench/${input.connectorId}`,
110
+ title: `${input.title} output`,
111
+ label: `${input.title} output`,
112
+ icon: null,
113
+ description: "Published Workbench output.",
114
+ category: "Workbench outputs",
115
+ tags: ["workbench", "output"],
116
+ capabilityModes: ["content"],
117
+ inputs: [],
118
+ params: [],
119
+ output: [
120
+ {
121
+ key: "primary",
122
+ label: "Published output",
123
+ kind: "content",
124
+ required: false,
125
+ expandableKeys: []
126
+ }
127
+ ],
128
+ tools: [],
129
+ outputs: [
130
+ {
131
+ key: "primary",
132
+ label: "Published output",
133
+ kind: "content",
134
+ required: false,
135
+ expandableKeys: []
136
+ }
137
+ ],
138
+ toolAdapters: []
139
+ };
140
+ }
141
+ export function resolveForgeBoxSnapshot(boxId, contextInput, executionInput) {
142
+ if (boxId.startsWith("connector-output:")) {
143
+ return (createSnapshotForConnectorOutput(boxId) ?? {
144
+ boxId,
145
+ label: boxId,
146
+ capturedAt: new Date().toISOString(),
147
+ contentText: "This connector output has not been generated yet.",
148
+ contentJson: null,
149
+ tools: []
150
+ });
151
+ }
152
+ const definition = getWorkbenchNodeDefinition(boxId);
153
+ if (!definition) {
154
+ return {
155
+ boxId,
156
+ label: boxId,
157
+ capturedAt: new Date().toISOString(),
158
+ contentText: "This Workbench node is not registered.",
159
+ contentJson: null,
160
+ tools: []
161
+ };
162
+ }
163
+ const execution = definition.execute({
164
+ nodeId: boxId,
165
+ definition,
166
+ inputs: executionInput?.inputs ?? {},
167
+ params: executionInput?.params ?? {},
168
+ context: createRuntimeContext(contextInput)
169
+ });
170
+ if (execution instanceof Promise) {
171
+ throw new Error("Workbench box execution must be synchronous for snapshot resolution.");
172
+ }
173
+ return {
174
+ boxId: definition.id,
175
+ label: definition.title,
176
+ capturedAt: new Date().toISOString(),
177
+ contentText: execution.primaryText,
178
+ contentJson: execution.payload,
179
+ tools: mapWorkbenchTools(definition.tools)
180
+ };
181
+ }
182
+ export function executeForgeBoxTool(boxId, toolKey, args, contextInput) {
183
+ const definition = getWorkbenchNodeDefinition(boxId);
184
+ if (!definition) {
185
+ throw new Error(`Unknown Forge box: ${boxId}`);
186
+ }
187
+ return executeCommonWorkbenchTool(createRuntimeContext(contextInput), definition, toolKey, args);
188
+ }
@@ -3,6 +3,8 @@ import { mkdir, readdir, readFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { DatabaseSync } from "node:sqlite";
6
+ import { logForgeDebug } from "./debug.js";
7
+ import { ensureQuestionnaireSeeds } from "./repositories/questionnaires.js";
6
8
  function nowIso() {
7
9
  return new Date().toISOString();
8
10
  }
@@ -288,6 +290,7 @@ export async function initializeDatabase() {
288
290
  await mkdir(getDataDir(), { recursive: true });
289
291
  const database = getDatabase();
290
292
  const migrationFiles = await listMigrationFiles();
293
+ const pendingMigrations = [];
291
294
  database.exec(`
292
295
  CREATE TABLE IF NOT EXISTS migrations (
293
296
  id TEXT PRIMARY KEY,
@@ -302,6 +305,7 @@ export async function initializeDatabase() {
302
305
  if (applied.has(file)) {
303
306
  continue;
304
307
  }
308
+ pendingMigrations.push(file);
305
309
  const sql = await readFile(path.join(migrationsDir, file), "utf8");
306
310
  database.exec("BEGIN");
307
311
  try {
@@ -316,9 +320,11 @@ export async function initializeDatabase() {
316
320
  throw error;
317
321
  }
318
322
  }
323
+ logForgeDebug(`[forge-db] initialized database path=${getDatabasePath()} applied_count=${appliedRows.length} pending_applied=${pendingMigrations.length} pending_list=${pendingMigrations.join(",") || "none"}`);
319
324
  if (seedDemoDataEnabled) {
320
325
  seedData();
321
326
  }
327
+ ensureQuestionnaireSeeds();
322
328
  }
323
329
  export function configureDatabaseSeeding(enabled) {
324
330
  seedDemoDataEnabled = enabled;
@@ -0,0 +1,19 @@
1
+ function isTruthyFlag(value) {
2
+ if (!value) {
3
+ return false;
4
+ }
5
+ const normalized = value.trim().toLowerCase();
6
+ return (normalized === "1" ||
7
+ normalized === "true" ||
8
+ normalized === "yes" ||
9
+ normalized === "on");
10
+ }
11
+ export function isForgeDebugLoggingEnabled(env = process.env) {
12
+ return isTruthyFlag(env.FORGE_DEBUG_LOGS);
13
+ }
14
+ export function logForgeDebug(message, env = process.env) {
15
+ if (!isForgeDebugLoggingEnabled(env)) {
16
+ return;
17
+ }
18
+ console.info(message);
19
+ }
@@ -0,0 +1,114 @@
1
+ import { execFile } from "node:child_process";
2
+ import os from "node:os";
3
+ import { promisify } from "node:util";
4
+ import { Bonjour } from "bonjour-service";
5
+ const execFileAsync = promisify(execFile);
6
+ export async function startForgeDiscoveryAdvertiser(options) {
7
+ if (options.enabled === false || process.env.FORGE_DISABLE_DISCOVERY_ADVERTISEMENT === "1") {
8
+ return null;
9
+ }
10
+ const basePath = normalizeBasePath(options.basePath);
11
+ const tailscaleTargets = await resolveTailscaleTargets({
12
+ apiBaseUrl: options.tailscaleApiBaseUrl,
13
+ uiBaseUrl: options.tailscaleUiBaseUrl,
14
+ basePath
15
+ });
16
+ const bonjour = new Bonjour();
17
+ const service = bonjour.publish({
18
+ name: buildServiceName(),
19
+ type: "forge",
20
+ protocol: "tcp",
21
+ port: options.port,
22
+ txt: {
23
+ apiPath: "/api/v1",
24
+ uiPath: basePath,
25
+ tsApiBaseUrl: tailscaleTargets.apiBaseUrl ?? "",
26
+ tsUiBaseUrl: tailscaleTargets.uiBaseUrl ?? "",
27
+ tsDnsName: tailscaleTargets.dnsName ?? "",
28
+ watchReady: "1"
29
+ }
30
+ });
31
+ if (typeof service.start === "function") {
32
+ service.start();
33
+ }
34
+ return {
35
+ stop: () => {
36
+ if (typeof service.stop === "function") {
37
+ service.stop(() => {
38
+ bonjour.destroy();
39
+ });
40
+ return;
41
+ }
42
+ bonjour.destroy();
43
+ }
44
+ };
45
+ }
46
+ function buildServiceName() {
47
+ const hostname = os.hostname().trim();
48
+ return hostname ? `Forge on ${hostname}` : "Forge";
49
+ }
50
+ function normalizeBasePath(value) {
51
+ if (!value || value === "/") {
52
+ return "/";
53
+ }
54
+ const withLeadingSlash = value.startsWith("/") ? value : `/${value}`;
55
+ return withLeadingSlash.endsWith("/") ? withLeadingSlash : `${withLeadingSlash}/`;
56
+ }
57
+ async function resolveTailscaleTargets(input) {
58
+ const explicitApi = normalizeHttpsUrl(input.apiBaseUrl);
59
+ const explicitUi = normalizeHttpsUrl(input.uiBaseUrl);
60
+ if (explicitApi || explicitUi) {
61
+ return {
62
+ apiBaseUrl: explicitApi,
63
+ uiBaseUrl: explicitUi,
64
+ dnsName: readDnsNameFromUrl(explicitApi ?? explicitUi)
65
+ };
66
+ }
67
+ const dnsName = await readTailscaleDnsName();
68
+ if (!dnsName) {
69
+ return { apiBaseUrl: null, uiBaseUrl: null, dnsName: null };
70
+ }
71
+ return {
72
+ apiBaseUrl: `https://${dnsName}/api/v1`,
73
+ uiBaseUrl: `https://${dnsName}${input.basePath}`,
74
+ dnsName
75
+ };
76
+ }
77
+ function normalizeHttpsUrl(value) {
78
+ const trimmed = value?.trim();
79
+ if (!trimmed) {
80
+ return null;
81
+ }
82
+ try {
83
+ const url = new URL(trimmed);
84
+ return url.protocol === "https:" ? url.toString().replace(/\/$/, "") : null;
85
+ }
86
+ catch {
87
+ return null;
88
+ }
89
+ }
90
+ function readDnsNameFromUrl(value) {
91
+ if (!value) {
92
+ return null;
93
+ }
94
+ try {
95
+ return new URL(value).hostname;
96
+ }
97
+ catch {
98
+ return null;
99
+ }
100
+ }
101
+ async function readTailscaleDnsName() {
102
+ try {
103
+ const { stdout } = await execFileAsync("tailscale", ["status", "--json"], {
104
+ timeout: 1_500,
105
+ env: process.env
106
+ });
107
+ const parsed = JSON.parse(stdout);
108
+ const dnsName = parsed.Self?.DNSName?.trim().replace(/\.$/, "");
109
+ return dnsName || null;
110
+ }
111
+ catch {
112
+ return null;
113
+ }
114
+ }
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
2
2
  import { z } from "zod";
3
3
  import { getDatabase, runInTransaction } from "./db.js";
4
4
  import { HttpError } from "./errors.js";
5
+ import { getMovementMobileBootstrap, ingestMovementSync, movementSyncPayloadSchema } from "./movement.js";
5
6
  import { recordActivityEvent } from "./repositories/activity-events.js";
6
7
  import { recordHabitGeneratedWorkoutReward } from "./repositories/rewards.js";
7
8
  const healthLinkSchema = z.object({
@@ -119,7 +120,8 @@ export const mobileHealthSyncSchema = z.object({
119
120
  links: z.array(healthLinkSchema).default([]),
120
121
  annotations: workoutAnnotationSchema.partial().default({})
121
122
  }))
122
- .default([])
123
+ .default([]),
124
+ movement: movementSyncPayloadSchema.default({})
123
125
  });
124
126
  export const verifyCompanionPairingSchema = z.object({
125
127
  sessionId: z.string().trim().min(1),
@@ -606,7 +608,7 @@ export function verifyCompanionPairing(payload) {
606
608
  .get(pairing.id))
607
609
  };
608
610
  }
609
- function requireValidPairing(sessionId, pairingToken) {
611
+ export function requireValidPairing(sessionId, pairingToken) {
610
612
  const row = getDatabase()
611
613
  .prepare(`SELECT * FROM companion_pairing_sessions WHERE id = ?`)
612
614
  .get(sessionId);
@@ -838,6 +840,7 @@ export function ingestMobileHealthSync(payload) {
838
840
  let createdCount = 0;
839
841
  let updatedCount = 0;
840
842
  let mergedCount = 0;
843
+ const movementSync = ingestMovementSync(pairing, parsed.movement);
841
844
  for (const sleep of parsed.sleepSessions) {
842
845
  const result = insertOrUpdateSleepSession(pairing, sleep);
843
846
  if (result.mode === "created") {
@@ -879,8 +882,16 @@ export function ingestMobileHealthSync(payload) {
879
882
  .run(runId, pairing.id, pairing.user_id, parsed.device.sourceDevice, JSON.stringify({
880
883
  permissions: parsed.permissions,
881
884
  sleepSessions: parsed.sleepSessions.length,
882
- workouts: parsed.workouts.length
883
- }), parsed.sleepSessions.length + parsed.workouts.length, createdCount, updatedCount, mergedCount, now, now, now);
885
+ workouts: parsed.workouts.length,
886
+ movement: {
887
+ knownPlaces: parsed.movement.knownPlaces.length,
888
+ stays: parsed.movement.stays.length,
889
+ trips: parsed.movement.trips.length
890
+ }
891
+ }), parsed.sleepSessions.length +
892
+ parsed.workouts.length +
893
+ parsed.movement.stays.length +
894
+ parsed.movement.trips.length, createdCount + movementSync.createdCount, updatedCount + movementSync.updatedCount, mergedCount, now, now, now);
884
895
  recordActivityEvent({
885
896
  entityType: "system",
886
897
  entityId: pairing.id,
@@ -892,8 +903,10 @@ export function ingestMobileHealthSync(payload) {
892
903
  metadata: {
893
904
  sleepSessions: parsed.sleepSessions.length,
894
905
  workouts: parsed.workouts.length,
895
- createdCount,
896
- updatedCount,
906
+ movementStays: parsed.movement.stays.length,
907
+ movementTrips: parsed.movement.trips.length,
908
+ createdCount: createdCount + movementSync.createdCount,
909
+ updatedCount: updatedCount + movementSync.updatedCount,
897
910
  mergedCount
898
911
  }
899
912
  });
@@ -904,10 +917,14 @@ export function ingestMobileHealthSync(payload) {
904
917
  imported: {
905
918
  sleepSessions: parsed.sleepSessions.length,
906
919
  workouts: parsed.workouts.length,
907
- createdCount,
908
- updatedCount,
909
- mergedCount
910
- }
920
+ createdCount: createdCount + movementSync.createdCount,
921
+ updatedCount: updatedCount + movementSync.updatedCount,
922
+ mergedCount,
923
+ movementStays: parsed.movement.stays.length,
924
+ movementTrips: parsed.movement.trips.length,
925
+ movementKnownPlaces: parsed.movement.knownPlaces.length
926
+ },
927
+ movement: getMovementMobileBootstrap(pairing)
911
928
  };
912
929
  });
913
930
  }
@@ -916,6 +933,14 @@ export function getCompanionOverview(userIds) {
916
933
  const importRuns = listHealthImportRunRows(userIds).map(mapHealthImportRun);
917
934
  const sleepSessions = listSleepRows(userIds).map(mapSleepSession);
918
935
  const workouts = listWorkoutRows(userIds).map(mapWorkoutSession);
936
+ const movementSummary = importRuns.reduce((totals, run) => {
937
+ const movement = safeJsonParse(JSON.stringify(run.payloadSummary.movement ?? {}), {}) ?? {};
938
+ return {
939
+ knownPlaces: totals.knownPlaces + (movement.knownPlaces ?? 0),
940
+ stays: totals.stays + (movement.stays ?? 0),
941
+ trips: totals.trips + (movement.trips ?? 0)
942
+ };
943
+ }, { knownPlaces: 0, stays: 0, trips: 0 });
919
944
  const activePairings = pairings.filter((pairing) => pairing.status !== "revoked");
920
945
  const recentPermissionStates = importRuns
921
946
  .map((run) => safeJsonParse(JSON.stringify(run.payloadSummary), {}))
@@ -951,7 +976,10 @@ export function getCompanionOverview(userIds) {
951
976
  }).length,
952
977
  linkedWorkouts: workouts.filter((session) => session.links.length > 0).length,
953
978
  habitGeneratedWorkouts: workouts.filter((session) => session.sourceType === "habit_generated").length,
954
- reconciledWorkouts: workouts.filter((session) => session.reconciliationStatus === "merged").length
979
+ reconciledWorkouts: workouts.filter((session) => session.reconciliationStatus === "merged").length,
980
+ movementKnownPlaces: movementSummary.knownPlaces,
981
+ movementStays: movementSummary.stays,
982
+ movementTrips: movementSummary.trips
955
983
  },
956
984
  permissions: {
957
985
  healthKitAuthorized: recentPermissionStates.some((state) => state.healthKitAuthorized === true),
@@ -1,9 +1,13 @@
1
1
  import { buildServer } from "./app.js";
2
2
  import { closeDatabase } from "./db.js";
3
+ import { startForgeDiscoveryAdvertiser } from "./discovery-advertiser.js";
3
4
  const port = Number(process.env.PORT ?? 4317);
4
5
  const host = process.env.HOST ?? "0.0.0.0";
6
+ const basePath = process.env.FORGE_BASE_PATH ?? "/forge/";
5
7
  const app = await buildServer();
8
+ const discoveryAdvertiser = await startForgeDiscoveryAdvertiser({ port, basePath });
6
9
  const close = async () => {
10
+ discoveryAdvertiser?.stop();
7
11
  await app.close();
8
12
  closeDatabase();
9
13
  };
@@ -1,5 +1,6 @@
1
1
  import { AbstractManager } from "../base.js";
2
- import { readEncryptedSecret } from "../../repositories/calendar.js";
2
+ import { refreshOpenAICodexToken } from "@mariozechner/pi-ai/oauth";
3
+ import { readEncryptedSecret, storeEncryptedSecret } from "../../repositories/calendar.js";
3
4
  function emitDiagnostic(logger, input) {
4
5
  logger?.(input);
5
6
  }
@@ -32,7 +33,7 @@ export class LlmManager extends AbstractManager {
32
33
  });
33
34
  return null;
34
35
  }
35
- const apiKey = this.readApiKey(profile.secretId);
36
+ const apiKey = await this.readApiKey(profile.secretId);
36
37
  if (!apiKey) {
37
38
  emitDiagnostic(logger, {
38
39
  level: "error",
@@ -72,7 +73,7 @@ export class LlmManager extends AbstractManager {
72
73
  });
73
74
  throw new Error("Unsupported LLM provider.");
74
75
  }
75
- const apiKey = explicitApiKey?.trim() || this.readApiKey(profile.secretId);
76
+ const apiKey = explicitApiKey?.trim() || (await this.readApiKey(profile.secretId));
76
77
  if (!apiKey) {
77
78
  emitDiagnostic(logger, {
78
79
  level: "error",
@@ -107,12 +108,29 @@ export class LlmManager extends AbstractManager {
107
108
  outputPreview: result.outputPreview
108
109
  };
109
110
  }
111
+ async runTextPrompt(profile, input, logger) {
112
+ const provider = this.resolveProvider(profile.provider);
113
+ if (!provider?.runText) {
114
+ throw new Error("This LLM provider does not support text prompt execution.");
115
+ }
116
+ const apiKey = input.explicitApiKey?.trim() || (await this.readApiKey(profile.secretId));
117
+ if (!apiKey) {
118
+ throw new Error("Missing provider credential for prompt execution.");
119
+ }
120
+ return await provider.runText({
121
+ apiKey,
122
+ profile,
123
+ systemPrompt: input.systemPrompt,
124
+ prompt: input.prompt,
125
+ logger
126
+ });
127
+ }
110
128
  resolveProvider(providerName) {
111
129
  return (this.providers.get(providerName) ??
112
130
  this.providers.get("openai-responses") ??
113
131
  null);
114
132
  }
115
- readApiKey(secretId) {
133
+ async readApiKey(secretId) {
116
134
  if (!secretId) {
117
135
  return null;
118
136
  }
@@ -121,6 +139,24 @@ export class LlmManager extends AbstractManager {
121
139
  return null;
122
140
  }
123
141
  const payload = this.secretsManager.openJson(cipherText);
142
+ if (payload.kind === "oauth" &&
143
+ payload.provider === "openai-codex" &&
144
+ typeof payload.refresh === "string") {
145
+ let access = payload.access?.trim() || null;
146
+ const expires = typeof payload.expires === "number" ? payload.expires : Date.now();
147
+ if (!access || expires <= Date.now() + 60_000) {
148
+ const refreshed = await refreshOpenAICodexToken(payload.refresh);
149
+ const nextPayload = {
150
+ ...payload,
151
+ access: refreshed.access,
152
+ refresh: refreshed.refresh,
153
+ expires: refreshed.expires
154
+ };
155
+ storeEncryptedSecret(secretId, this.secretsManager.sealJson(nextPayload), "Refreshed OpenAI Codex OAuth credential");
156
+ access = refreshed.access;
157
+ }
158
+ return access;
159
+ }
124
160
  return payload.apiKey?.trim() || null;
125
161
  }
126
162
  }