forge-openclaw-plugin 0.2.68 → 0.2.70
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.
- package/dist/assets/{board-DFNV9VAZ.js → board-BfqxFNiQ.js} +1 -1
- package/dist/assets/index-BfLQnCNZ.js +91 -0
- package/dist/assets/index-DIapFz9v.css +1 -0
- package/dist/assets/{motion-CXdn34ih.js → motion-C0ALlgho.js} +1 -1
- package/dist/assets/{table-CEq3bTDv.js → table-WcMjnJll.js} +1 -1
- package/dist/assets/{ui-g7FaEglG.js → ui-B5I-3U91.js} +1 -1
- package/dist/assets/vendor-B-Lq_OG3.css +1 -0
- package/dist/assets/vendor-C56o26_3.js +2163 -0
- package/dist/index.html +8 -8
- package/dist/server/server/migrations/061_health_workout_raw_evidence.sql +95 -0
- package/dist/server/server/migrations/062_health_mobile_sync_sessions.sql +55 -0
- package/dist/server/server/src/app.js +149 -21
- package/dist/server/server/src/health-workout-analytics.js +572 -0
- package/dist/server/server/src/health.js +612 -4
- package/dist/server/server/src/openapi.js +162 -0
- package/dist/server/server/src/psyche-types.js +59 -0
- package/dist/server/server/src/services/devrage.js +191 -0
- package/dist/server/src/lib/api.js +13 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -1
- package/server/migrations/061_health_workout_raw_evidence.sql +95 -0
- package/server/migrations/062_health_mobile_sync_sessions.sql +55 -0
- package/skills/forge-openclaw/SKILL.md +35 -6
- package/skills/forge-openclaw/entity_conversation_playbooks.md +179 -18
- package/dist/assets/index-B0PIKEnz.css +0 -1
- package/dist/assets/index-BofyMuFh.js +0 -90
- package/dist/assets/vendor-BcOHGipZ.js +0 -1341
- package/dist/assets/vendor-DT3pnAKJ.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-
|
|
17
|
-
<link rel="modulepreload" crossorigin href="/forge/assets/vendor-
|
|
18
|
-
<link rel="modulepreload" crossorigin href="/forge/assets/board-
|
|
19
|
-
<link rel="modulepreload" crossorigin href="/forge/assets/ui-
|
|
20
|
-
<link rel="modulepreload" crossorigin href="/forge/assets/motion-
|
|
21
|
-
<link rel="modulepreload" crossorigin href="/forge/assets/table-
|
|
22
|
-
<link rel="stylesheet" crossorigin href="/forge/assets/vendor-
|
|
23
|
-
<link rel="stylesheet" crossorigin href="/forge/assets/index-
|
|
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">
|
|
22
|
+
<link rel="stylesheet" crossorigin href="/forge/assets/vendor-B-Lq_OG3.css">
|
|
23
|
+
<link rel="stylesheet" crossorigin href="/forge/assets/index-DIapFz9v.css">
|
|
24
24
|
</head>
|
|
25
25
|
<body class="bg-canvas text-ink antialiased">
|
|
26
26
|
<div id="root"></div>
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS health_workout_time_series (
|
|
2
|
+
id TEXT PRIMARY KEY,
|
|
3
|
+
workout_id TEXT NOT NULL REFERENCES health_workout_sessions(id) ON DELETE CASCADE,
|
|
4
|
+
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
5
|
+
source_sample_uid TEXT NOT NULL,
|
|
6
|
+
series_index INTEGER NOT NULL DEFAULT 0,
|
|
7
|
+
metric_key TEXT NOT NULL,
|
|
8
|
+
label TEXT NOT NULL DEFAULT '',
|
|
9
|
+
category TEXT NOT NULL DEFAULT '',
|
|
10
|
+
unit TEXT NOT NULL DEFAULT '',
|
|
11
|
+
value REAL NOT NULL,
|
|
12
|
+
started_at TEXT NOT NULL,
|
|
13
|
+
ended_at TEXT NOT NULL,
|
|
14
|
+
source_device TEXT NOT NULL DEFAULT '',
|
|
15
|
+
source_bundle_identifier TEXT,
|
|
16
|
+
source_product_type TEXT,
|
|
17
|
+
capture_method TEXT NOT NULL DEFAULT 'associated_workout',
|
|
18
|
+
quality_flags_json TEXT NOT NULL DEFAULT '[]',
|
|
19
|
+
metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
20
|
+
provenance_json TEXT NOT NULL DEFAULT '{}',
|
|
21
|
+
created_at TEXT NOT NULL,
|
|
22
|
+
updated_at TEXT NOT NULL,
|
|
23
|
+
UNIQUE (workout_id, metric_key, source_sample_uid, series_index)
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
CREATE INDEX IF NOT EXISTS idx_health_workout_time_series_workout_metric
|
|
27
|
+
ON health_workout_time_series(workout_id, metric_key, started_at);
|
|
28
|
+
|
|
29
|
+
CREATE INDEX IF NOT EXISTS idx_health_workout_time_series_user_metric
|
|
30
|
+
ON health_workout_time_series(user_id, metric_key, started_at DESC);
|
|
31
|
+
|
|
32
|
+
CREATE TABLE IF NOT EXISTS health_workout_routes (
|
|
33
|
+
id TEXT PRIMARY KEY,
|
|
34
|
+
workout_id TEXT NOT NULL REFERENCES health_workout_sessions(id) ON DELETE CASCADE,
|
|
35
|
+
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
36
|
+
source_route_uid TEXT NOT NULL,
|
|
37
|
+
point_index INTEGER NOT NULL,
|
|
38
|
+
recorded_at TEXT NOT NULL,
|
|
39
|
+
latitude REAL NOT NULL,
|
|
40
|
+
longitude REAL NOT NULL,
|
|
41
|
+
altitude_meters REAL,
|
|
42
|
+
horizontal_accuracy_meters REAL,
|
|
43
|
+
vertical_accuracy_meters REAL,
|
|
44
|
+
speed_mps REAL,
|
|
45
|
+
course_degrees REAL,
|
|
46
|
+
metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
47
|
+
provenance_json TEXT NOT NULL DEFAULT '{}',
|
|
48
|
+
created_at TEXT NOT NULL,
|
|
49
|
+
updated_at TEXT NOT NULL,
|
|
50
|
+
UNIQUE (workout_id, source_route_uid, point_index)
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
CREATE INDEX IF NOT EXISTS idx_health_workout_routes_workout
|
|
54
|
+
ON health_workout_routes(workout_id, point_index);
|
|
55
|
+
|
|
56
|
+
CREATE TABLE IF NOT EXISTS health_zone_profiles (
|
|
57
|
+
id TEXT PRIMARY KEY,
|
|
58
|
+
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
59
|
+
model_version TEXT NOT NULL DEFAULT 'forge-hrr-v1',
|
|
60
|
+
birth_year INTEGER,
|
|
61
|
+
sex_at_birth TEXT,
|
|
62
|
+
known_max_hr REAL,
|
|
63
|
+
threshold_hr REAL,
|
|
64
|
+
resting_hr_override REAL,
|
|
65
|
+
custom_zones_json TEXT NOT NULL DEFAULT '[]',
|
|
66
|
+
inferred_max_hr REAL,
|
|
67
|
+
inferred_resting_hr REAL,
|
|
68
|
+
confidence TEXT NOT NULL DEFAULT 'medium',
|
|
69
|
+
thresholds_json TEXT NOT NULL DEFAULT '[]',
|
|
70
|
+
metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
71
|
+
created_at TEXT NOT NULL,
|
|
72
|
+
updated_at TEXT NOT NULL,
|
|
73
|
+
UNIQUE(user_id, model_version)
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
CREATE TABLE IF NOT EXISTS health_workout_analytics (
|
|
77
|
+
id TEXT PRIMARY KEY,
|
|
78
|
+
workout_id TEXT NOT NULL REFERENCES health_workout_sessions(id) ON DELETE CASCADE,
|
|
79
|
+
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
80
|
+
zone_profile_id TEXT REFERENCES health_zone_profiles(id) ON DELETE SET NULL,
|
|
81
|
+
model_version TEXT NOT NULL DEFAULT 'forge-hrr-v1',
|
|
82
|
+
confidence TEXT NOT NULL DEFAULT 'unavailable',
|
|
83
|
+
data_quality_json TEXT NOT NULL DEFAULT '{}',
|
|
84
|
+
zone_durations_json TEXT NOT NULL DEFAULT '[]',
|
|
85
|
+
hr_summary_json TEXT NOT NULL DEFAULT '{}',
|
|
86
|
+
load_json TEXT NOT NULL DEFAULT '{}',
|
|
87
|
+
route_summary_json TEXT NOT NULL DEFAULT '{}',
|
|
88
|
+
computed_at TEXT NOT NULL,
|
|
89
|
+
created_at TEXT NOT NULL,
|
|
90
|
+
updated_at TEXT NOT NULL,
|
|
91
|
+
UNIQUE(workout_id, model_version)
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
CREATE INDEX IF NOT EXISTS idx_health_workout_analytics_user
|
|
95
|
+
ON health_workout_analytics(user_id, computed_at DESC);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS health_mobile_sync_sessions (
|
|
2
|
+
id TEXT PRIMARY KEY,
|
|
3
|
+
pairing_session_id TEXT NOT NULL REFERENCES companion_pairing_sessions(id) ON DELETE CASCADE,
|
|
4
|
+
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
5
|
+
status TEXT NOT NULL DEFAULT 'running',
|
|
6
|
+
schema_version TEXT NOT NULL DEFAULT 'healthkit-sync-v2',
|
|
7
|
+
requested_families_json TEXT NOT NULL DEFAULT '[]',
|
|
8
|
+
source_metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
9
|
+
expected_counts_json TEXT NOT NULL DEFAULT '{}',
|
|
10
|
+
received_counts_json TEXT NOT NULL DEFAULT '{}',
|
|
11
|
+
byte_totals_json TEXT NOT NULL DEFAULT '{}',
|
|
12
|
+
affected_workout_ids_json TEXT NOT NULL DEFAULT '[]',
|
|
13
|
+
error_json TEXT NOT NULL DEFAULT '{}',
|
|
14
|
+
started_at TEXT NOT NULL,
|
|
15
|
+
completed_at TEXT,
|
|
16
|
+
failed_at TEXT,
|
|
17
|
+
aborted_at TEXT,
|
|
18
|
+
expired_at TEXT,
|
|
19
|
+
created_at TEXT NOT NULL,
|
|
20
|
+
updated_at TEXT NOT NULL
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
CREATE INDEX IF NOT EXISTS idx_health_mobile_sync_sessions_pairing_status
|
|
24
|
+
ON health_mobile_sync_sessions(pairing_session_id, status, started_at DESC);
|
|
25
|
+
|
|
26
|
+
CREATE TABLE IF NOT EXISTS health_mobile_sync_chunks (
|
|
27
|
+
id TEXT PRIMARY KEY,
|
|
28
|
+
sync_session_id TEXT NOT NULL REFERENCES health_mobile_sync_sessions(id) ON DELETE CASCADE,
|
|
29
|
+
chunk_id TEXT NOT NULL,
|
|
30
|
+
sequence INTEGER NOT NULL,
|
|
31
|
+
family TEXT NOT NULL,
|
|
32
|
+
checksum_sha256 TEXT NOT NULL,
|
|
33
|
+
record_count INTEGER NOT NULL DEFAULT 0,
|
|
34
|
+
byte_count INTEGER NOT NULL DEFAULT 0,
|
|
35
|
+
payload_json TEXT NOT NULL DEFAULT '{}',
|
|
36
|
+
payload_summary_json TEXT NOT NULL DEFAULT '{}',
|
|
37
|
+
received_at TEXT NOT NULL,
|
|
38
|
+
applied_at TEXT,
|
|
39
|
+
created_at TEXT NOT NULL,
|
|
40
|
+
updated_at TEXT NOT NULL,
|
|
41
|
+
UNIQUE(sync_session_id, chunk_id)
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
CREATE INDEX IF NOT EXISTS idx_health_mobile_sync_chunks_session_sequence
|
|
45
|
+
ON health_mobile_sync_chunks(sync_session_id, sequence);
|
|
46
|
+
|
|
47
|
+
CREATE TABLE IF NOT EXISTS health_mobile_sync_family_cursors (
|
|
48
|
+
id TEXT PRIMARY KEY,
|
|
49
|
+
pairing_session_id TEXT NOT NULL REFERENCES companion_pairing_sessions(id) ON DELETE CASCADE,
|
|
50
|
+
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
51
|
+
family TEXT NOT NULL,
|
|
52
|
+
cursor_json TEXT NOT NULL DEFAULT '{}',
|
|
53
|
+
updated_at TEXT NOT NULL,
|
|
54
|
+
UNIQUE(pairing_session_id, family)
|
|
55
|
+
);
|
|
@@ -46,7 +46,7 @@ import { getInsightsPayload } from "./services/insights.js";
|
|
|
46
46
|
import { buildLifeForcePayload, createFatigueSignal, listLifeForceTemplates, resolveLifeForceUser, updateLifeForceProfile, updateLifeForceTemplate } from "./services/life-force.js";
|
|
47
47
|
import { createEntities, deleteEntities, deleteEntity, getSettingsBinPayload, restoreEntities, searchEntities, updateEntities } from "./services/entity-crud.js";
|
|
48
48
|
import { getPsycheOverview } from "./services/psyche.js";
|
|
49
|
-
import { syncDevrageMetricHistoryIfNeeded } from "./services/devrage.js";
|
|
49
|
+
import { getPsycheMetricsViewData, syncDevrageMetricHistoryIfNeeded } from "./services/devrage.js";
|
|
50
50
|
import { exportPsycheObservationCalendar, getPsycheObservationCalendar } from "./services/psyche-observation-calendar.js";
|
|
51
51
|
import { getProjectBoard, getProjectSummary, listProjectSummaries } from "./services/projects.js";
|
|
52
52
|
import { createDataBackup, exportData, getDataManagementState, maybeRunAutomaticBackup, restoreDataBackup, scanForDataRecoveryCandidates, switchDataRoot, updateDataManagementSettings } from "./services/data-management.js";
|
|
@@ -66,7 +66,7 @@ import { registerWebRoutes } from "./web.js";
|
|
|
66
66
|
import { createManagerRuntime } from "./managers/runtime.js";
|
|
67
67
|
import { isManagerError } from "./managers/type-guards.js";
|
|
68
68
|
import { buildCompanionPairingTransport, getCompanionIrohStatus, stopCompanionIroh } from "./services/companion-iroh.js";
|
|
69
|
-
import { createCompanionPairingSession, createCompanionPairingSessionSchema, createSleepSession, createSleepSessionSchema, createWorkoutSession, createWorkoutSessionSchema, deleteSleepSession, deleteWorkoutSession, getCompanionPairingSessionById, getCompanionOverview, getFitnessViewData, getSleepSessionById, getSleepSessionDetailById, getSleepTimelineOverlaysForRange, getSleepViewData, getVitalsViewData, getWorkoutSessionById, heartbeatCompanionPairing, heartbeatCompanionPairingSchema, ingestMobileHealthSync, mobileHealthSyncSchema, patchCompanionPairingSourceState, patchCompanionPairingSourceStateSchema, companionSourceKeySchema, requireValidPairing, revokeAllCompanionPairingSessions, revokeAllCompanionPairingSessionsSchema, revokeCompanionPairingSession, updateMobileCompanionSourceState, updateMobileCompanionSourceStateSchema, verifyCompanionPairing, verifyCompanionPairingSchema, updateSleepMetadata, updateSleepMetadataSchema, updateWorkoutMetadata, updateWorkoutMetadataSchema } from "./health.js";
|
|
69
|
+
import { createCompanionPairingSession, createCompanionPairingSessionSchema, createSleepSession, createSleepSessionSchema, createWorkoutSession, createWorkoutSessionSchema, deleteSleepSession, deleteWorkoutSession, getCompanionPairingSessionById, getCompanionOverview, getFitnessViewData, getSleepSessionById, getSleepSessionDetailById, getSleepTimelineOverlaysForRange, getSleepViewData, getVitalsViewData, getHealthZoneProfileForUser, getWorkoutSessionById, getWorkoutSessionDetailById, heartbeatCompanionPairing, heartbeatCompanionPairingSchema, healthZoneProfilePatchSchema, abortMobileHealthSyncSession, completeMobileHealthSyncSession, ingestMobileHealthSync, ingestMobileHealthSyncChunk, mobileHealthSyncChunkSchema, mobileHealthSyncSessionCompleteSchema, mobileHealthSyncSessionStartSchema, mobileHealthSyncSchema, patchHealthZoneProfileForUser, patchCompanionPairingSourceState, patchCompanionPairingSourceStateSchema, companionSourceKeySchema, requireValidPairing, startMobileHealthSyncSession, revokeAllCompanionPairingSessions, revokeAllCompanionPairingSessionsSchema, revokeCompanionPairingSession, updateMobileCompanionSourceState, updateMobileCompanionSourceStateSchema, verifyCompanionPairing, verifyCompanionPairingSchema, updateSleepMetadata, updateSleepMetadataSchema, updateWorkoutMetadata, updateWorkoutMetadataSchema } from "./health.js";
|
|
70
70
|
import { analyzeMovementUserBoxPreflight, createMovementUserBox, createMovementPlace, deleteMovementUserBox, getMovementAllTimeSummary, getMovementBoxDetail, getMovementDayDetail, getMovementMobileBootstrap, getMovementTimeline, getMovementSelectionAggregate, getMovementSettings, getMovementTripDetail, getMovementMonthSummary, invalidateAutomaticMovementBox, listMovementPlaces, movementAutomaticBoxInvalidateSchema, movementMobileBootstrapSchema, movementMobilePlaceMutationSchema, movementMobileStayPatchSchema, movementMobileUserBoxCreateSchema, movementMobileUserBoxPreflightSchema, movementMobileUserBoxPatchSchema, movementMobileAutomaticBoxInvalidateSchema, movementMobileTimelineSchema, movementPlaceMutationSchema, movementPlacePatchSchema, movementSelectionAggregateSchema, movementStayPatchSchema, movementTripPatchSchema, movementUserBoxCreateSchema, movementUserBoxPreflightSchema, movementUserBoxPatchSchema, movementSettingsPatchSchema, movementTimelineQuerySchema, movementTripPointPatchSchema, deleteMovementStay, deleteMovementTrip, deleteMovementTripPoint, updateMovementPlace, updateMovementSettings, updateMovementStay, updateMovementTrip, updateMovementUserBox, updateMovementTripPoint, resolveMovementTimelineSegmentForBox } from "./movement.js";
|
|
71
71
|
import { getScreenTimeAllTimeSummary, getScreenTimeDayDetail, getScreenTimeMonthSummary, getScreenTimeSettings, screenTimeSettingsPatchSchema, updateScreenTimeSettings } from "./screen-time.js";
|
|
72
72
|
import { assertWatchReady, buildWatchBootstrap, ingestWatchCaptureBatch, mobileWatchBootstrapSchema, mobileWatchCaptureBatchSchema, mobileWatchHabitCheckInSchema } from "./watch-mobile.js";
|
|
@@ -1988,7 +1988,7 @@ function buildPreferredMutationPath(entityType) {
|
|
|
1988
1988
|
case "life_force":
|
|
1989
1989
|
return "Use the dedicated Life Force route family for overview, profile edits, weekday templates, and fatigue signals.";
|
|
1990
1990
|
case "workbench":
|
|
1991
|
-
return "Use the dedicated Workbench route family for flow CRUD, execution, run history, published outputs, node results, and latest-node-output reads.";
|
|
1991
|
+
return "Use the dedicated Workbench route family for flow CRUD, execution, saved-flow chat follow-ups, run history, published outputs, node results, and latest-node-output reads.";
|
|
1992
1992
|
case "self_observation":
|
|
1993
1993
|
return "Read the calendar surface; mutate it by creating or updating note-backed observations with frontmatter.observedAt.";
|
|
1994
1994
|
case "sleep_overview":
|
|
@@ -2701,7 +2701,7 @@ const AGENT_ONBOARDING_ENTITY_CATALOG = [
|
|
|
2701
2701
|
}),
|
|
2702
2702
|
enrichOnboardingEntityGuide({
|
|
2703
2703
|
entityType: "movement",
|
|
2704
|
-
purpose: "The specialized Movement surface for day, month, all-time, timeline, trip, place, selection, and manual overlay work.",
|
|
2704
|
+
purpose: "The specialized Movement surface for day, month, all-time, timeline, trip, place, selection, settings, and manual overlay work.",
|
|
2705
2705
|
minimumCreateFields: [],
|
|
2706
2706
|
relationshipRules: [
|
|
2707
2707
|
"Movement is a specialized domain surface, not a normal batch CRUD entity family.",
|
|
@@ -2731,7 +2731,7 @@ const AGENT_ONBOARDING_ENTITY_CATALOG = [
|
|
|
2731
2731
|
}),
|
|
2732
2732
|
enrichOnboardingEntityGuide({
|
|
2733
2733
|
entityType: "workbench",
|
|
2734
|
-
purpose: "The specialized Workbench surface for flow catalog work, flow CRUD, execution, run history, published outputs, node results, and latest-node-output reads.",
|
|
2734
|
+
purpose: "The specialized Workbench surface for flow catalog work, flow CRUD, execution, saved-flow chat follow-ups, run history, published outputs, node results, and latest-node-output reads.",
|
|
2735
2735
|
minimumCreateFields: [],
|
|
2736
2736
|
relationshipRules: [
|
|
2737
2737
|
"Workbench is a specialized execution surface, not a normal batch CRUD entity family.",
|
|
@@ -2969,6 +2969,18 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
|
|
|
2969
2969
|
"Ask about source or override reason only when that context matters."
|
|
2970
2970
|
]
|
|
2971
2971
|
},
|
|
2972
|
+
{
|
|
2973
|
+
focus: "calendar_overview",
|
|
2974
|
+
openingQuestion: "What are you trying to understand or decide from your calendar picture?",
|
|
2975
|
+
coachingGoal: "Review commitments, work blocks, provider state, and existing timeboxes before creating or changing calendar records.",
|
|
2976
|
+
askSequence: [
|
|
2977
|
+
"Ask what practical calendar question the user wants the overview to answer.",
|
|
2978
|
+
"Ask which day, week, date range, or owner scope matters only if it changes the read.",
|
|
2979
|
+
"Use forge_get_calendar_overview before asking the user to reconstruct availability from memory.",
|
|
2980
|
+
"Reflect the scheduling or planning decision the user is trying to make.",
|
|
2981
|
+
"Move to calendar_event, work_block_template, task_timebox, or calendar_connection only when one concrete follow-up action is visible."
|
|
2982
|
+
]
|
|
2983
|
+
},
|
|
2972
2984
|
{
|
|
2973
2985
|
focus: "calendar_connection",
|
|
2974
2986
|
openingQuestion: "Which calendar provider are you trying to connect, and what do you want Forge to do with it?",
|
|
@@ -3004,6 +3016,29 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
|
|
|
3004
3016
|
"Ask for a short audit note only if the reason would otherwise be unclear later."
|
|
3005
3017
|
]
|
|
3006
3018
|
},
|
|
3019
|
+
{
|
|
3020
|
+
focus: "operator_overview",
|
|
3021
|
+
openingQuestion: "What are you trying to understand about Forge overall right now?",
|
|
3022
|
+
coachingGoal: "Read the broad Forge state before choosing a specific entity action.",
|
|
3023
|
+
askSequence: [
|
|
3024
|
+
"Ask what broad Forge question the user wants the overview to answer.",
|
|
3025
|
+
"Ask which owner or user scope matters only if several humans or bots are in play.",
|
|
3026
|
+
"Use forge_get_operator_overview before reopening a create or update intake.",
|
|
3027
|
+
"Reflect the attention cue, priority, or handoff decision the overview should support.",
|
|
3028
|
+
"Move into a specific entity flow only when the overview points to a concrete goal, project, task, habit, note, Psyche record, or action."
|
|
3029
|
+
]
|
|
3030
|
+
},
|
|
3031
|
+
{
|
|
3032
|
+
focus: "operator_context",
|
|
3033
|
+
openingQuestion: "What current work, risk, or next move are you trying to check?",
|
|
3034
|
+
coachingGoal: "Inspect current work, active runs, risk, and next moves before changing records.",
|
|
3035
|
+
askSequence: [
|
|
3036
|
+
"Ask whether the user is checking current work, risk, blockers, active sessions, or the next move.",
|
|
3037
|
+
"Use forge_get_operator_context before mutating tasks, projects, runs, or notes when the current state is uncertain.",
|
|
3038
|
+
"Reflect whether the read is meant to decide continue, stop, reprioritize, update, or create.",
|
|
3039
|
+
"Move to task_run, work_adjustment, task, project, or note flow only when one concrete follow-up is visible."
|
|
3040
|
+
]
|
|
3041
|
+
},
|
|
3007
3042
|
{
|
|
3008
3043
|
focus: "self_observation",
|
|
3009
3044
|
openingQuestion: "What happened in the situation, and what did you feel, think, or do next?",
|
|
@@ -3175,7 +3210,9 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
|
|
|
3175
3210
|
"Ask whether the focus is a stay, a trip, a place, a timeline window, or a selected span.",
|
|
3176
3211
|
"Ask for the time window, place, or movement item that makes the question concrete.",
|
|
3177
3212
|
"Ask what they are trying to notice, preserve, or answer through that movement context.",
|
|
3213
|
+
"If the user is changing movement operating behavior, ask whether this is about passive tracking, publish mode, retention, or companion readiness.",
|
|
3178
3214
|
"Choose the dedicated day, month, all-time, timeline, places, trip-detail, or selection route once the question shape is clear.",
|
|
3215
|
+
"Use the dedicated settings route for passive capture, publish mode, and retention behavior instead of treating those as place, stay, or trip edits.",
|
|
3179
3216
|
"Use allTime for whole-history aggregates, selection for a bounded selected-span aggregate, and tripDetail only when a concrete trip id is known.",
|
|
3180
3217
|
"If the truth of one uncertain span is still unclear, read the timeline or saved-box detail before you mutate it.",
|
|
3181
3218
|
"Skip the meta lane question when the user already named the exact correction or review target and only one ambiguity remains.",
|
|
@@ -3216,6 +3253,9 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
|
|
|
3216
3253
|
"Use listFlows for the saved flow catalog and boxCatalog for available input-box contracts; do not collapse both into a vague catalog read.",
|
|
3217
3254
|
"Ask which flow, slug, run, or node the request is about.",
|
|
3218
3255
|
"Ask whether they need the stable flow contract, one run result, one published output, one node result, or the latest node output.",
|
|
3256
|
+
"If the user is creating or editing a flow, clarify what the flow should reliably produce, what input contract it should accept, and what smallest structural change is intended before asking for node details.",
|
|
3257
|
+
"If the user wants to delete or archive a flow, confirm the saved flow and whether published outputs or run history need to be preserved elsewhere before calling the delete route.",
|
|
3258
|
+
"If the user wants to send a follow-up into a saved flow chat, confirm the saved flow and what the message should accomplish instead of treating it as a new run or note.",
|
|
3219
3259
|
"If the user already named the flow and action clearly, skip the meta lane question and ask only for the missing run, node, or output scope.",
|
|
3220
3260
|
"If the user wants a stable public input contract or published output, prefer those dedicated reads instead of detouring through run history first.",
|
|
3221
3261
|
"If the user is still shaping a payload or edit, prefer flow detail or box catalog reads before asking for structured inputs.",
|
|
@@ -4378,8 +4418,9 @@ function buildAgentOnboardingPayload(request) {
|
|
|
4378
4418
|
aliases: ["movement", "Movement"],
|
|
4379
4419
|
summary: "Dedicated movement workspace API. Use these routes for stays, trips, time-in-place questions, visited places, trip detail, selection aggregates, user-defined overlays, and repair actions on already-recorded movement data.",
|
|
4380
4420
|
routeSelectionQuestions: [
|
|
4381
|
-
"Is the user asking for a day, month, all-time, timeline, place, trip detail,
|
|
4421
|
+
"Is the user asking for a day, month, all-time, timeline, place, trip detail, selected-span, or settings answer?",
|
|
4382
4422
|
"Is this a missing-gap overlay, a saved-overlay repair, or an edit to one already-recorded stay, trip, or trip point?",
|
|
4423
|
+
"If this is about operating behavior, is the change about passive tracking, publish mode, retention, or companion readiness?",
|
|
4383
4424
|
"If the target is already known, what one time, place, or saved-object detail is still missing before acting?"
|
|
4384
4425
|
],
|
|
4385
4426
|
methodRoutes: {
|
|
@@ -4438,6 +4479,7 @@ function buildAgentOnboardingPayload(request) {
|
|
|
4438
4479
|
"Movement is not a normal batch CRUD entity family. It is a dedicated record of stays and trips: a stay means the user remained in the same place for a span of time, and a trip means they traveled between places.",
|
|
4439
4480
|
"Route-selection questions are internal. User-facing questions should ask for the useful time window, place, selected span, stay, or trip instead of reciting day/month/all-time/timeline/selection route keys.",
|
|
4440
4481
|
"Use /api/v1/movement/day, /month, /all-time, /timeline, or /selection when the user wants behavioral answers such as how long they stayed at home, when they traveled, which places dominated a period, or what happened across a selected span.",
|
|
4482
|
+
"Use GET /api/v1/movement/settings and PATCH /api/v1/movement/settings when the user wants to inspect or change passive capture, publish mode, retention mode, or companion readiness. Do not route settings changes through stays, trips, places, or batch CRUD.",
|
|
4441
4483
|
"Use the movement write routes when the user wants to add a place or manual overlay, update a specific stay or trip, repair one recorded movement span, or attach movement context to another Forge record. If the user is filling a missing-data gap, the usual write path is a user-defined overlay box rather than a raw stay or trip patch.",
|
|
4442
4484
|
"If the user is revising or removing an existing correction, first identify whether the saved object is a user-defined box, automatic box, recorded stay, recorded trip, or trip point so the repair or delete path stays truthful.",
|
|
4443
4485
|
"For an explicit statement like 'that missing block was me staying home', do not reopen broad intake. Preflight only if timing overlap is unclear, then create a user-defined `stay` box for that interval and read the updated timeline back."
|
|
@@ -4515,8 +4557,10 @@ function buildAgentOnboardingPayload(request) {
|
|
|
4515
4557
|
aliases: ["workbench", "Workbench"],
|
|
4516
4558
|
summary: "Dedicated graph-flow API. Use it for flow catalog reads, flow CRUD, execution, run history, published outputs, node results, and latest successful node outputs.",
|
|
4517
4559
|
routeSelectionQuestions: [
|
|
4518
|
-
"Is the job flow discovery, flow editing, execution, run history, published output, run detail, node result, latest node output, or flow chat follow-up?",
|
|
4560
|
+
"Is the job flow discovery, flow creation, flow editing, flow deletion, execution, run history, published output, run detail, node result, latest node output, or flow chat follow-up?",
|
|
4519
4561
|
"Does the user need a stable public contract or one execution artifact?",
|
|
4562
|
+
"For flow CRUD, what stable input contract, expected output, or lifecycle effect must stay true?",
|
|
4563
|
+
"For flow chat follow-up, which saved flow should receive the message and what should the message accomplish?",
|
|
4520
4564
|
"If the flow is already known, what one run, node, or output scope detail is still missing before acting?"
|
|
4521
4565
|
],
|
|
4522
4566
|
methodRoutes: {
|
|
@@ -4562,6 +4606,10 @@ function buildAgentOnboardingPayload(request) {
|
|
|
4562
4606
|
"Route-selection questions are internal. User-facing questions should ask whether the user needs the saved flow, its input contract, one run, one node, or the public result instead of reciting Workbench route keys.",
|
|
4563
4607
|
"Use the flow routes when the agent needs stable public input contracts, published outputs, node-level results, or reusable execution history.",
|
|
4564
4608
|
"If the user is still figuring out inputs or editable structure, read flow detail or box catalog before asking them to author a payload from memory.",
|
|
4609
|
+
"For flow creation, clarify what the flow should reliably produce, which input contract it should accept, and which first node or box anchors the flow before asking for structured payload details.",
|
|
4610
|
+
"For flow edits, ask what behavior should change while preserving the public contract unless the user explicitly wants the contract changed.",
|
|
4611
|
+
"For flow deletion, confirm the saved flow and whether published outputs or run history need preservation elsewhere before using the delete route.",
|
|
4612
|
+
"For saved flow chat follow-ups, use POST /api/v1/workbench/flows/:id/chat only when the user wants to continue a flow-specific conversation. Do not turn that into a new run, note, or generic entity update unless the user asks.",
|
|
4565
4613
|
"Prefer the dedicated output and node-result routes over reverse-engineering raw traces.",
|
|
4566
4614
|
"If the user only wants a published output, latest node output, or run detail, do not reopen a flow-edit intake before reading that artifact.",
|
|
4567
4615
|
"If the user already named the flow and wants one output or one run, skip the broad lane question and ask only for the missing run, node, or output scope."
|
|
@@ -4579,7 +4627,8 @@ function buildAgentOnboardingPayload(request) {
|
|
|
4579
4627
|
calendar_overview: "/api/v1/calendar/overview",
|
|
4580
4628
|
operatorOverview: "/api/v1/operator/overview",
|
|
4581
4629
|
operator_overview: "/api/v1/operator/overview",
|
|
4582
|
-
operatorContext: "/api/v1/operator/context"
|
|
4630
|
+
operatorContext: "/api/v1/operator/context",
|
|
4631
|
+
operator_context: "/api/v1/operator/context"
|
|
4583
4632
|
}
|
|
4584
4633
|
},
|
|
4585
4634
|
multiUserModel: {
|
|
@@ -4825,6 +4874,8 @@ function buildAgentOnboardingPayload(request) {
|
|
|
4825
4874
|
movementTimeline: '{"routeKey":"timeline","query":{"from":"2026-05-01T00:00:00.000Z","to":"2026-05-06T23:59:59.999Z","userIds":["user_operator"]}}',
|
|
4826
4875
|
movementSelection: '{"routeKey":"selection","query":{"from":"2026-05-01T00:00:00.000Z","to":"2026-05-14T23:59:59.999Z","placeIds":["place_home"],"userIds":["user_operator"]}}',
|
|
4827
4876
|
movementTripDetail: '{"routeKey":"tripDetail","pathParams":{"id":"trip_123"}}',
|
|
4877
|
+
movementSettings: '{"routeKey":"settings","query":{"userIds":["user_operator"]}}',
|
|
4878
|
+
movementSettingsUpdate: '{"routeKey":"settingsUpdate","body":{"trackingEnabled":true,"publishMode":"draft_review","retentionMode":"aggregates_only"}}',
|
|
4828
4879
|
movementMissingStayPreflight: '{"routeKey":"userBoxPreflight","body":{"kind":"stay","startedAt":"2026-05-06T13:00:00.000Z","endedAt":"2026-05-06T15:00:00.000Z","placeLabel":"Home","userId":"user_operator"}}',
|
|
4829
4880
|
movementMissingStayCreate: '{"routeKey":"userBoxCreate","body":{"kind":"stay","startedAt":"2026-05-06T13:00:00.000Z","endedAt":"2026-05-06T15:00:00.000Z","placeLabel":"Home","userId":"user_operator","note":"Manual correction after reviewing the timeline."}}',
|
|
4830
4881
|
lifeForceOverview: '{"routeKey":"overview"}',
|
|
@@ -4833,10 +4884,14 @@ function buildAgentOnboardingPayload(request) {
|
|
|
4833
4884
|
lifeForceFatigueSignal: '{"routeKey":"fatigueSignal","body":{"signal":"tired","intensity":7,"note":"Sharp post-lunch dip after clinic admin."}}',
|
|
4834
4885
|
workbenchFlowCatalog: '{"routeKey":"listFlows","query":{"includeArchived":false}}',
|
|
4835
4886
|
workbenchBoxCatalog: '{"routeKey":"boxCatalog"}',
|
|
4887
|
+
workbenchCreateFlow: '{"routeKey":"createFlow","body":{"title":"Research digest","slug":"research-digest","description":"Turn a topic into a cited digest with a stable published summary.","nodes":[],"edges":[]}}',
|
|
4888
|
+
workbenchUpdateFlow: '{"routeKey":"updateFlow","pathParams":{"id":"flow_research_digest"},"body":{"description":"Keep the same input contract but add a stronger evidence-check node."}}',
|
|
4889
|
+
workbenchDeleteFlow: '{"routeKey":"deleteFlow","pathParams":{"id":"flow_research_digest"}}',
|
|
4836
4890
|
workbenchRunDetail: '{"routeKey":"runDetail","pathParams":{"id":"flow_research_digest","runId":"run_123"}}',
|
|
4837
4891
|
workbenchPublishedOutput: '{"routeKey":"publishedOutput","pathParams":{"id":"flow_research_digest"}}',
|
|
4838
4892
|
workbenchLatestNodeOutput: '{"routeKey":"latestNodeOutput","pathParams":{"id":"flow_research_digest","nodeId":"node_summary"}}',
|
|
4839
|
-
workbenchRunFlow: '{"routeKey":"runFlow","pathParams":{"id":"flow_research_digest"},"body":{"input":{"topic":"question flow quality"}}}'
|
|
4893
|
+
workbenchRunFlow: '{"routeKey":"runFlow","pathParams":{"id":"flow_research_digest"},"body":{"input":{"topic":"question flow quality"}}}',
|
|
4894
|
+
workbenchChatFlow: '{"routeKey":"chatFlow","pathParams":{"id":"flow_research_digest"},"body":{"message":"Refine the summary around API route risks and keep the published output stable."}}'
|
|
4840
4895
|
}
|
|
4841
4896
|
}
|
|
4842
4897
|
};
|
|
@@ -5398,6 +5453,12 @@ function buildOperatorOverviewRouteGuide() {
|
|
|
5398
5453
|
summary: "Aggregate Psyche state across values, patterns, behaviors, beliefs, modes, and trigger reports.",
|
|
5399
5454
|
requiredScope: "psyche.read"
|
|
5400
5455
|
},
|
|
5456
|
+
{
|
|
5457
|
+
id: "psyche_metrics",
|
|
5458
|
+
path: "/api/v1/psyche/metrics",
|
|
5459
|
+
summary: "Daily Psyche metric read model with stored devrage history and summary statistics.",
|
|
5460
|
+
requiredScope: "psyche.read"
|
|
5461
|
+
},
|
|
5401
5462
|
{
|
|
5402
5463
|
id: "xp_metrics",
|
|
5403
5464
|
path: "/api/v1/metrics/xp",
|
|
@@ -6330,6 +6391,8 @@ export async function buildServer(options = {}) {
|
|
|
6330
6391
|
app.setErrorHandler((error, request, reply) => {
|
|
6331
6392
|
const validationIssues = error instanceof ZodError ? formatValidationIssues(error) : undefined;
|
|
6332
6393
|
const routeUrl = request.routeOptions.url || request.url;
|
|
6394
|
+
const isBodyTooLarge = typeof error.code === "string" &&
|
|
6395
|
+
error.code === "FST_ERR_CTP_BODY_TOO_LARGE";
|
|
6333
6396
|
const validationHelp = validationIssues
|
|
6334
6397
|
? buildValidationHelp(request.method, routeUrl, validationIssues)
|
|
6335
6398
|
: undefined;
|
|
@@ -6339,20 +6402,24 @@ export async function buildServer(options = {}) {
|
|
|
6339
6402
|
? error.statusCode
|
|
6340
6403
|
: error instanceof ZodError
|
|
6341
6404
|
? 400
|
|
6342
|
-
:
|
|
6405
|
+
: isBodyTooLarge
|
|
6406
|
+
? 413
|
|
6407
|
+
: 500;
|
|
6343
6408
|
if (!shouldSkipAutomaticDiagnosticRoute(routeUrl)) {
|
|
6344
6409
|
try {
|
|
6345
6410
|
recordDiagnosticLog({
|
|
6346
6411
|
level: statusCode >= 500 ? "error" : "warning",
|
|
6347
6412
|
source: normalizeDiagnosticSource(request.headers["x-forge-source"]),
|
|
6348
6413
|
scope: "api_error",
|
|
6349
|
-
eventKey:
|
|
6350
|
-
?
|
|
6351
|
-
:
|
|
6414
|
+
eventKey: isBodyTooLarge
|
|
6415
|
+
? "payload_too_large"
|
|
6416
|
+
: isHttpError(error)
|
|
6352
6417
|
? error.code
|
|
6353
|
-
:
|
|
6354
|
-
?
|
|
6355
|
-
:
|
|
6418
|
+
: isManagerError(error)
|
|
6419
|
+
? error.code
|
|
6420
|
+
: statusCode === 400
|
|
6421
|
+
? "invalid_request"
|
|
6422
|
+
: "internal_error",
|
|
6356
6423
|
message: getErrorMessage(error),
|
|
6357
6424
|
route: routeUrl,
|
|
6358
6425
|
functionName: "setErrorHandler",
|
|
@@ -6376,13 +6443,25 @@ export async function buildServer(options = {}) {
|
|
|
6376
6443
|
? error.code
|
|
6377
6444
|
: isManagerError(error)
|
|
6378
6445
|
? error.code
|
|
6379
|
-
:
|
|
6380
|
-
? "
|
|
6381
|
-
:
|
|
6446
|
+
: isBodyTooLarge
|
|
6447
|
+
? "payload_too_large"
|
|
6448
|
+
: statusCode === 400
|
|
6449
|
+
? "invalid_request"
|
|
6450
|
+
: "internal_error",
|
|
6382
6451
|
error: validationIssues
|
|
6383
6452
|
? `Request validation failed for ${request.method.toUpperCase()} ${routeUrl}. ${validationHelp?.validationSummary ?? ""}`.trim()
|
|
6384
|
-
:
|
|
6453
|
+
: isBodyTooLarge
|
|
6454
|
+
? "The request body is too large. Use chunked HealthKit sync."
|
|
6455
|
+
: getErrorMessage(error),
|
|
6385
6456
|
statusCode,
|
|
6457
|
+
...(isBodyTooLarge
|
|
6458
|
+
? {
|
|
6459
|
+
recommendedMode: "chunked",
|
|
6460
|
+
maxBytes: typeof request.routeOptions.bodyLimit === "number"
|
|
6461
|
+
? request.routeOptions.bodyLimit
|
|
6462
|
+
: undefined
|
|
6463
|
+
}
|
|
6464
|
+
: {}),
|
|
6386
6465
|
...(validationIssues ? { details: validationIssues } : {}),
|
|
6387
6466
|
...(validationHelp ?? {}),
|
|
6388
6467
|
...(isHttpError(error) && error.details ? error.details : {}),
|
|
@@ -6772,12 +6851,37 @@ export async function buildServer(options = {}) {
|
|
|
6772
6851
|
app.get("/api/v1/health/vitals", async (request) => ({
|
|
6773
6852
|
vitals: getVitalsViewData(resolveScopedUserIds(request.query))
|
|
6774
6853
|
}));
|
|
6854
|
+
app.get("/api/v1/health/zone-profile", async (request) => {
|
|
6855
|
+
const userIds = resolveScopedUserIds(request.query);
|
|
6856
|
+
return {
|
|
6857
|
+
zoneProfile: getHealthZoneProfileForUser(userIds?.[0] ?? "user_operator")
|
|
6858
|
+
};
|
|
6859
|
+
});
|
|
6860
|
+
app.patch("/api/v1/health/zone-profile", async (request) => {
|
|
6861
|
+
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/health/zone-profile" });
|
|
6862
|
+
const userIds = resolveScopedUserIds(request.query);
|
|
6863
|
+
void auth;
|
|
6864
|
+
return {
|
|
6865
|
+
zoneProfile: patchHealthZoneProfileForUser(userIds?.[0] ?? "user_operator", healthZoneProfilePatchSchema.parse(request.body ?? {}))
|
|
6866
|
+
};
|
|
6867
|
+
});
|
|
6775
6868
|
app.post("/api/v1/health/workouts", async (request, reply) => {
|
|
6776
6869
|
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/health/workouts" });
|
|
6777
6870
|
const workout = createWorkoutSession(createWorkoutSessionSchema.parse(request.body ?? {}), toActivityContext(auth));
|
|
6778
6871
|
reply.code(201);
|
|
6779
6872
|
return { workout };
|
|
6780
6873
|
});
|
|
6874
|
+
app.get("/api/v1/health/workouts/:id/detail", async (request, reply) => {
|
|
6875
|
+
const { id } = request.params;
|
|
6876
|
+
const query = request.query;
|
|
6877
|
+
const resolution = query.resolution === "raw" ? "raw" : "adaptive";
|
|
6878
|
+
const detail = getWorkoutSessionDetailById(id, resolution);
|
|
6879
|
+
if (!detail) {
|
|
6880
|
+
reply.code(404);
|
|
6881
|
+
return { error: "Workout session not found" };
|
|
6882
|
+
}
|
|
6883
|
+
return detail;
|
|
6884
|
+
});
|
|
6781
6885
|
app.get("/api/v1/health/workouts/:id", async (request, reply) => {
|
|
6782
6886
|
const { id } = request.params;
|
|
6783
6887
|
const workout = getWorkoutSessionById(id);
|
|
@@ -7293,7 +7397,27 @@ export async function buildServer(options = {}) {
|
|
|
7293
7397
|
watch: buildWatchBootstrap(pairing)
|
|
7294
7398
|
};
|
|
7295
7399
|
});
|
|
7296
|
-
app.post("/api/v1/mobile/healthkit/sync", async (request) => ({
|
|
7400
|
+
app.post("/api/v1/mobile/healthkit/sync-sessions", async (request) => ({
|
|
7401
|
+
upload: startMobileHealthSyncSession(mobileHealthSyncSessionStartSchema.parse(request.body ?? {}))
|
|
7402
|
+
}));
|
|
7403
|
+
app.post("/api/v1/mobile/healthkit/sync-sessions/:id/chunks", { bodyLimit: 1_250_000 }, async (request) => {
|
|
7404
|
+
const { id } = request.params;
|
|
7405
|
+
const rawPayloadJson = JSON.stringify((request.body ?? {}).payload ?? {});
|
|
7406
|
+
return {
|
|
7407
|
+
chunk: ingestMobileHealthSyncChunk(id, mobileHealthSyncChunkSchema.parse(request.body ?? {}), rawPayloadJson)
|
|
7408
|
+
};
|
|
7409
|
+
});
|
|
7410
|
+
app.post("/api/v1/mobile/healthkit/sync-sessions/:id/complete", async (request) => {
|
|
7411
|
+
const { id } = request.params;
|
|
7412
|
+
return {
|
|
7413
|
+
sync: completeMobileHealthSyncSession(id, mobileHealthSyncSessionCompleteSchema.parse(request.body ?? {}))
|
|
7414
|
+
};
|
|
7415
|
+
});
|
|
7416
|
+
app.delete("/api/v1/mobile/healthkit/sync-sessions/:id", async (request) => {
|
|
7417
|
+
const { id } = request.params;
|
|
7418
|
+
return { upload: abortMobileHealthSyncSession(id) };
|
|
7419
|
+
});
|
|
7420
|
+
app.post("/api/v1/mobile/healthkit/sync", { bodyLimit: 8_000_000 }, async (request) => ({
|
|
7297
7421
|
sync: ingestMobileHealthSync(mobileHealthSyncSchema.parse(request.body ?? {}))
|
|
7298
7422
|
}));
|
|
7299
7423
|
app.patch("/api/v1/health/workouts/:id", async (request, reply) => {
|
|
@@ -7365,6 +7489,10 @@ export async function buildServer(options = {}) {
|
|
|
7365
7489
|
const userIds = resolveScopedUserIds(request.query);
|
|
7366
7490
|
return { overview: getPsycheOverview(userIds) };
|
|
7367
7491
|
});
|
|
7492
|
+
app.get("/api/v1/psyche/metrics", async (request) => {
|
|
7493
|
+
requirePsycheScopedAccess(request.headers, ["psyche.read"], { route: "/api/v1/psyche/metrics" });
|
|
7494
|
+
return { metrics: getPsycheMetricsViewData() };
|
|
7495
|
+
});
|
|
7368
7496
|
app.get("/api/v1/psyche/questionnaires", async (request) => {
|
|
7369
7497
|
requirePsycheScopedAccess(request.headers, ["psyche.read"], { route: "/api/v1/psyche/questionnaires" });
|
|
7370
7498
|
const userIds = resolveScopedUserIds(request.query);
|