forge-openclaw-plugin 0.2.99 → 0.2.100
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/activity-copy-Bj4h9OcF.js +1 -0
- package/dist/assets/activity-page-5oyCFOns.js +1 -0
- package/dist/assets/ai-surface-workspace-qgk_B57-.js +1 -0
- package/dist/assets/atlas-panel-rfH2qOez.js +1 -0
- package/dist/assets/{board-Ju0h0SeG.js → board-BkDRaMp6.js} +1 -1
- package/dist/assets/calendar-display-preferences-Cid-2RnL.js +1 -0
- package/dist/assets/calendar-page-Bo2iua-a.js +1 -0
- package/dist/assets/calendar-rules-DA1g3QUk.js +1 -0
- package/dist/assets/calendar-ui-Cy1XRwzV.js +1 -0
- package/dist/assets/calendar-week-toolbar-DU1Q4RYj.js +1 -0
- package/dist/assets/charts-P7EVhIog.js +36 -0
- package/dist/assets/companion-sync-lab-page-CosNknOK.js +1 -0
- package/dist/assets/daily-metrics-dashboard-LjuGAB3f.js +1 -0
- package/dist/assets/date-keys-Cj1G3TOn.js +1 -0
- package/dist/assets/entity-links-DwpxhW2H.js +1 -0
- package/dist/assets/entity-note-count-link-BmGDB572.js +1 -0
- package/dist/assets/entity-notes-surface-DgEgicaE.js +1 -0
- package/dist/assets/execution-board-CDRXQB85.js +1 -0
- package/dist/assets/faceted-token-search-CE1YauRd.js +1 -0
- package/dist/assets/flagship-signal-deck-DDds90Gl.js +1 -0
- package/dist/assets/floating-action-menu-CJkI2iFy.js +1 -0
- package/dist/assets/forms-BFlTgZ3W.js +1 -0
- package/dist/assets/goal-detail-page-cJvHaLMQ.js +1 -0
- package/dist/assets/goals-page-f_39hvUV.js +1 -0
- package/dist/assets/graph-BZV40eAE.css +1 -0
- package/dist/assets/graph-D6JLqDbD.js +318 -0
- package/dist/assets/habits-page-DKb96_mj.js +1 -0
- package/dist/assets/health-link-options-Cpx8w7uM.js +1 -0
- package/dist/assets/index-BHTUu_4M.js +19 -0
- package/dist/assets/index-CZbuZQjw.css +1 -0
- package/dist/assets/insight-flow-dialog-pzAzyayN.js +1 -0
- package/dist/assets/insights-page-Dc9oFltJ.js +8 -0
- package/dist/assets/kanban-page-JAxerYh6.js +1 -0
- package/dist/assets/knowledge-graph-page-UQ3skqEi.js +1 -0
- package/dist/assets/life-force-page-BGDbQuVh.js +1 -0
- package/dist/assets/life-force-workspace-B1fYSXRC.js +1 -0
- package/dist/assets/maps-B-YMMjus.css +1 -0
- package/dist/assets/maps-ClgJoCjz.js +803 -0
- package/dist/assets/metric-tile-DX6TclqM.js +1 -0
- package/dist/assets/{motion-DRPJkN3a.js → motion-BeD44FeG.js} +1 -1
- package/dist/assets/movement-page-6HP6nGJx.js +1 -0
- package/dist/assets/note-markdown-DiW2-5d3.js +3 -0
- package/dist/assets/note-tags-input-DDLXf54U.js +1 -0
- package/dist/assets/notes-page-BuguDjhz.js +1 -0
- package/dist/assets/open-in-graph-button-Cg5VrKsC.js +1 -0
- package/dist/assets/orbit-map-GD05-0oS.js +1 -0
- package/dist/assets/overview-page-DuOs2OCB.js +1 -0
- package/dist/assets/page-hero-CQWo1Mm_.js +1 -0
- package/dist/assets/pill-cluster-BJogDRDJ.js +1 -0
- package/dist/assets/preference-entity-handoff-button-D4WAs9pC.js +1 -0
- package/dist/assets/preferences-page-BaJTMU1I.js +1 -0
- package/dist/assets/project-collections-DvaX20q_.js +1 -0
- package/dist/assets/project-detail-page-drPIFZGb.js +1 -0
- package/dist/assets/project-management-hierarchy-page-BUbRXvny.js +1 -0
- package/dist/assets/project-management-section-nav-C2Ud8Zdd.js +1 -0
- package/dist/assets/projects-page-BGzEZUtg.js +1 -0
- package/dist/assets/psyche-behaviors-page-Dmm_Io9D.js +5 -0
- package/dist/assets/psyche-flashcards-page-BgNKJ6QJ.js +1 -0
- package/dist/assets/psyche-goal-map-page-DXJs98Vr.js +1 -0
- package/dist/assets/psyche-graph-CFgs_Bqc.js +1 -0
- package/dist/assets/psyche-metrics-page-zYTJDbyZ.js +1 -0
- package/dist/assets/psyche-mode-guide-page-XPgRfCOf.js +1 -0
- package/dist/assets/psyche-modes-page-B-GA8oRF.js +1 -0
- package/dist/assets/psyche-page--r6a3e1t.js +1 -0
- package/dist/assets/psyche-patterns-page-BM5-3bMm.js +5 -0
- package/dist/assets/psyche-questionnaire-builder-page-CJshQ-mg.js +1 -0
- package/dist/assets/psyche-questionnaire-detail-page-USmR5G5A.js +1 -0
- package/dist/assets/psyche-questionnaire-run-detail-page-D7iBCmTi.js +1 -0
- package/dist/assets/psyche-questionnaire-run-page-Cpil-kDh.js +1 -0
- package/dist/assets/psyche-questionnaires-page-C-_y3VwS.js +1 -0
- package/dist/assets/psyche-report-detail-page--dkSPRaj.js +3 -0
- package/dist/assets/psyche-reports-page-CUaOXmIN.js +1 -0
- package/dist/assets/psyche-schemas-HFmg37Wj.js +1 -0
- package/dist/assets/psyche-schemas-beliefs-page-BX6xaap3.js +9 -0
- package/dist/assets/psyche-screen-time-page-CAAI4mD7.js +1 -0
- package/dist/assets/psyche-self-observation-page-BZ6FLuwa.js +1 -0
- package/dist/assets/psyche-values-page-yEV6MGt8.js +5 -0
- package/dist/assets/query-cache-IQ8W-LNC.js +1 -0
- package/dist/assets/report-chain-fields-fZ8Xd4H6.js +1 -0
- package/dist/assets/rewards-page-C2HQjIAf.js +1 -0
- package/dist/assets/scheduling-rules-editor-BHOpHOrV.js +1 -0
- package/dist/assets/schema-badge-DyKbxb51.js +1 -0
- package/dist/assets/schema-visuals-D6nxjbYC.js +1 -0
- package/dist/assets/select-menu-BX-pZNqL.js +1 -0
- package/dist/assets/settings-agents-page-VuYXTiyc.js +6 -0
- package/dist/assets/settings-bin-page-BNzvYaOk.js +1 -0
- package/dist/assets/settings-calendar-page-CjSFB53S.js +5 -0
- package/dist/assets/settings-data-page-CGSlryuI.js +1 -0
- package/dist/assets/settings-logs-page-BTK5fine.js +1 -0
- package/dist/assets/settings-mobile-page-CRaObOGo.js +1 -0
- package/dist/assets/settings-models-page-DFshpYF8.js +1 -0
- package/dist/assets/settings-page-BP81Mb5R.js +1 -0
- package/dist/assets/settings-rewards-page-CDJ1PH2G.js +1 -0
- package/dist/assets/settings-section-nav-CCFm27r2.js +1 -0
- package/dist/assets/settings-users-page-TdUocFPa.js +1 -0
- package/dist/assets/settings-wiki-page-B2zX0QQG.js +1 -0
- package/dist/assets/sleep-page-cI1GMVzk.js +1 -0
- package/dist/assets/sports-page-06LTqp0V.js +1 -0
- package/dist/assets/state-B-4sS1xO.js +1 -0
- package/dist/assets/strategies-page-DXP9Kx8s.js +1 -0
- package/dist/assets/strategy-detail-page-D6mx_Mik.js +1 -0
- package/dist/assets/strategy-dialog-BvzomTaF.js +1 -0
- package/dist/assets/{table-DewbFlTh.js → table-WfAPUppN.js} +1 -1
- package/dist/assets/task-detail-page-BIWIggdp.js +1 -0
- package/dist/assets/timebox-planning-dialog-CaCnoslG.js +1 -0
- package/dist/assets/today-page-DO2mRPT2.js +1 -0
- package/dist/assets/training-load-page-CyZ0mlEr.js +1 -0
- package/dist/assets/{ui-C2IvSrAz.js → ui-C13Nbgas.js} +4 -4
- package/dist/assets/use-psyche-focus-target-C1C_XjYG.js +1 -0
- package/dist/assets/vendor-CRS-psbw.css +1 -0
- package/dist/assets/vendor-DHkYh85p.js +1052 -0
- package/dist/assets/vitals-page-BQvEjTc6.js +1 -0
- package/dist/assets/weekly-review-page-Tp6Q9CRj.js +1 -0
- package/dist/assets/weight-loss-page-BBzlhLVV.js +1 -0
- package/dist/assets/wiki-article-markdown-DQYohmW2.js +4 -0
- package/dist/assets/wiki-editor-page-Dem_3eZv.js +26 -0
- package/dist/assets/wiki-ingest-history-page-BxoOcCoJ.js +1 -0
- package/dist/assets/wiki-ingest-modal-DhguKk3J.js +1 -0
- package/dist/assets/wiki-page-BLRxVXkl.js +1 -0
- package/dist/assets/workbench-flow-page-DqMkCCTy.js +5 -0
- package/dist/assets/workbench-page-BWd02wPw.js +1 -0
- package/dist/assets/workout-detail-page-BD8u7GyL.js +2 -0
- package/dist/index.html +148 -9
- package/dist/openclaw/tools.js +340 -0
- package/dist/server/server/migrations/065_weight_loss_nutrition_insights.sql +236 -0
- package/dist/server/server/migrations/066_watch_action_receipts.sql +20 -0
- package/dist/server/server/src/app.js +266 -13
- package/dist/server/server/src/health-weight-loss.js +1378 -0
- package/dist/server/server/src/health.js +188 -35
- package/dist/server/server/src/openapi.js +449 -0
- package/dist/server/server/src/services/context.js +6 -7
- package/dist/server/server/src/services/doctor.js +39 -4
- package/dist/server/server/src/services/gamification.js +146 -34
- package/dist/server/server/src/watch-mobile.js +564 -4
- package/dist/server/server/src/web.js +18 -5
- package/dist/server/src/components/ui/info-tooltip.js +48 -3
- package/dist/server/src/lib/api.js +131 -0
- package/dist/server/src/lib/weight-loss-types.js +1 -0
- package/openclaw.plugin.json +14 -1
- package/package.json +1 -1
- package/server/migrations/065_weight_loss_nutrition_insights.sql +236 -0
- package/server/migrations/066_watch_action_receipts.sql +20 -0
- package/skills/forge-openclaw/SKILL.md +26 -5
- package/skills/forge-openclaw/entity_conversation_playbooks.md +134 -5
- package/skills/forge-openclaw/psyche_entity_playbooks.md +45 -0
- package/dist/assets/index-Cn5Wpwau.css +0 -1
- package/dist/assets/index-CwvGs8n4.js +0 -91
- package/dist/assets/vendor-B-Lq_OG3.css +0 -1
- package/dist/assets/vendor-DL2K5ayT.js +0 -2186
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { z } from "zod";
|
|
2
|
+
import { z, ZodError } from "zod";
|
|
3
3
|
import { getDatabase, runInTransaction } from "./db.js";
|
|
4
4
|
import { HttpError } from "./errors.js";
|
|
5
5
|
import { updateWorkoutMetadata } from "./health.js";
|
|
6
6
|
import { canonicalizeMovementCategoryTags, listMovementPlaces, normalizeMovementCategoryTag, updateMovementPlace } from "./movement.js";
|
|
7
|
-
import { listHabits } from "./repositories/habits.js";
|
|
7
|
+
import { createHabitCheckIn, listHabits } from "./repositories/habits.js";
|
|
8
|
+
import { listGoals } from "./repositories/goals.js";
|
|
9
|
+
import { listProjectSummaries } from "./services/projects.js";
|
|
10
|
+
import { listTasks, updateTask } from "./repositories/tasks.js";
|
|
11
|
+
import { claimTaskRun, completeTaskRun, focusTaskRun, heartbeatTaskRun, listTaskRuns, releaseTaskRun } from "./repositories/task-runs.js";
|
|
8
12
|
import { formatLocalDateKey } from "../../src/lib/date-keys.js";
|
|
9
13
|
const watchCapability = "watch-ready";
|
|
10
14
|
const watchHistoryStateSchema = z.enum(["aligned", "unaligned", "unknown"]);
|
|
@@ -29,6 +33,16 @@ const watchCaptureEventTypeSchema = z.enum([
|
|
|
29
33
|
"dictated_note",
|
|
30
34
|
"retrospective_label"
|
|
31
35
|
]);
|
|
36
|
+
const watchCommandKindSchema = z.enum([
|
|
37
|
+
"habit_check_in",
|
|
38
|
+
"capture_event",
|
|
39
|
+
"task_run_start",
|
|
40
|
+
"task_run_heartbeat",
|
|
41
|
+
"task_run_focus",
|
|
42
|
+
"task_run_complete",
|
|
43
|
+
"task_run_release",
|
|
44
|
+
"task_status_update"
|
|
45
|
+
]);
|
|
32
46
|
const watchDeviceSchema = z.object({
|
|
33
47
|
name: z.string().trim().default("Apple Watch"),
|
|
34
48
|
platform: z.string().trim().default("watchos"),
|
|
@@ -72,6 +86,20 @@ export const mobileWatchCaptureBatchSchema = z.object({
|
|
|
72
86
|
device: watchDeviceSchema.default({}),
|
|
73
87
|
events: z.array(watchCaptureEventSchema).max(100).default([])
|
|
74
88
|
});
|
|
89
|
+
export const mobileWatchCommandBatchSchema = z.object({
|
|
90
|
+
sessionId: z.string().trim().min(1),
|
|
91
|
+
pairingToken: z.string().trim().min(1),
|
|
92
|
+
device: watchDeviceSchema.default({}),
|
|
93
|
+
commands: z
|
|
94
|
+
.array(z.object({
|
|
95
|
+
id: z.string().trim().min(1),
|
|
96
|
+
kind: watchCommandKindSchema,
|
|
97
|
+
createdAt: z.string().datetime(),
|
|
98
|
+
payload: z.record(z.string(), z.unknown()).default({})
|
|
99
|
+
}))
|
|
100
|
+
.max(100)
|
|
101
|
+
.default([])
|
|
102
|
+
});
|
|
75
103
|
function safeJsonParse(raw, fallback) {
|
|
76
104
|
if (!raw || raw.trim().length === 0) {
|
|
77
105
|
return fallback;
|
|
@@ -86,9 +114,21 @@ function safeJsonParse(raw, fallback) {
|
|
|
86
114
|
function nowIso() {
|
|
87
115
|
return new Date().toISOString();
|
|
88
116
|
}
|
|
117
|
+
function watchActorLabel(pairing) {
|
|
118
|
+
const settings = getDatabase()
|
|
119
|
+
.prepare(`SELECT operator_name FROM app_settings WHERE id = 1`)
|
|
120
|
+
.get();
|
|
121
|
+
const operatorName = settings?.operator_name?.trim();
|
|
122
|
+
return operatorName || pairing.user_id || "Albert";
|
|
123
|
+
}
|
|
89
124
|
function formatDateKey(date) {
|
|
90
125
|
return formatLocalDateKey(date);
|
|
91
126
|
}
|
|
127
|
+
function userScopeFilter(pairing) {
|
|
128
|
+
return pairing.user_id === "user_operator"
|
|
129
|
+
? {}
|
|
130
|
+
: { userIds: [pairing.user_id] };
|
|
131
|
+
}
|
|
92
132
|
function parseDateKey(dateKey) {
|
|
93
133
|
const [year, month, day] = dateKey.split("-").map(Number);
|
|
94
134
|
return new Date(year, month - 1, day);
|
|
@@ -477,6 +517,238 @@ export function assertWatchReady(pairing) {
|
|
|
477
517
|
throw new HttpError(403, "watch_pairing_not_enabled", "This companion pairing is not allowed to serve watch data.");
|
|
478
518
|
}
|
|
479
519
|
}
|
|
520
|
+
function compactTask(task) {
|
|
521
|
+
return {
|
|
522
|
+
id: String(task.id ?? ""),
|
|
523
|
+
title: String(task.title ?? "Untitled work"),
|
|
524
|
+
status: String(task.status ?? "backlog"),
|
|
525
|
+
level: String(task.level ?? "task"),
|
|
526
|
+
priority: String(task.priority ?? "medium"),
|
|
527
|
+
dueDate: typeof task.dueDate === "string" ? task.dueDate : null,
|
|
528
|
+
projectId: typeof task.projectId === "string" ? task.projectId : null,
|
|
529
|
+
goalId: typeof task.goalId === "string" ? task.goalId : null,
|
|
530
|
+
parentWorkItemId: typeof task.parentWorkItemId === "string" ? task.parentWorkItemId : null,
|
|
531
|
+
points: typeof task.points === "number" ? task.points : 0,
|
|
532
|
+
effort: String(task.effort ?? ""),
|
|
533
|
+
energy: String(task.energy ?? ""),
|
|
534
|
+
updatedAt: String(task.updatedAt ?? "")
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
function compactTaskRun(run) {
|
|
538
|
+
return {
|
|
539
|
+
id: String(run.id ?? ""),
|
|
540
|
+
taskId: String(run.taskId ?? ""),
|
|
541
|
+
taskTitle: String(run.taskTitle ?? "Active work"),
|
|
542
|
+
actor: String(run.actor ?? ""),
|
|
543
|
+
status: String(run.status ?? ""),
|
|
544
|
+
isCurrent: Boolean(run.isCurrent),
|
|
545
|
+
timerMode: String(run.timerMode ?? "unlimited"),
|
|
546
|
+
plannedDurationSeconds: typeof run.plannedDurationSeconds === "number"
|
|
547
|
+
? run.plannedDurationSeconds
|
|
548
|
+
: null,
|
|
549
|
+
creditedSeconds: typeof run.creditedSeconds === "number" ? run.creditedSeconds : 0,
|
|
550
|
+
claimedAt: String(run.claimedAt ?? ""),
|
|
551
|
+
heartbeatAt: String(run.heartbeatAt ?? ""),
|
|
552
|
+
leaseExpiresAt: String(run.leaseExpiresAt ?? "")
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
function buildWorkSnapshot(pairing) {
|
|
556
|
+
const scope = userScopeFilter(pairing);
|
|
557
|
+
const allTasks = listTasks({ ...scope, limit: 100 }).map(compactTask);
|
|
558
|
+
const activeRuns = listTaskRuns({
|
|
559
|
+
...scope,
|
|
560
|
+
active: true,
|
|
561
|
+
limit: 12
|
|
562
|
+
}).map(compactTaskRun);
|
|
563
|
+
const visibleTasks = allTasks.filter((task) => task.status !== "done");
|
|
564
|
+
const statuses = ["focus", "in_progress", "blocked", "backlog", "done"];
|
|
565
|
+
const lanes = statuses.map((status) => {
|
|
566
|
+
const laneTasks = allTasks
|
|
567
|
+
.filter((task) => task.status === status)
|
|
568
|
+
.slice(0, status === "done" ? 5 : 12);
|
|
569
|
+
return {
|
|
570
|
+
id: status,
|
|
571
|
+
title: status === "in_progress"
|
|
572
|
+
? "In progress"
|
|
573
|
+
: status.charAt(0).toUpperCase() + status.slice(1),
|
|
574
|
+
count: allTasks.filter((task) => task.status === status).length,
|
|
575
|
+
tasks: laneTasks
|
|
576
|
+
};
|
|
577
|
+
});
|
|
578
|
+
return {
|
|
579
|
+
actor: watchActorLabel(pairing),
|
|
580
|
+
activeRuns,
|
|
581
|
+
currentRun: activeRuns.find((run) => run.isCurrent) ?? activeRuns[0] ?? null,
|
|
582
|
+
nextTask: visibleTasks.find((task) => task.status === "focus") ??
|
|
583
|
+
visibleTasks.find((task) => task.status === "in_progress") ??
|
|
584
|
+
visibleTasks[0] ??
|
|
585
|
+
null,
|
|
586
|
+
lanes,
|
|
587
|
+
visibleCount: visibleTasks.length,
|
|
588
|
+
doneCount: allTasks.filter((task) => task.status === "done").length
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
function buildDirectionSnapshot(pairing) {
|
|
592
|
+
const scope = userScopeFilter(pairing);
|
|
593
|
+
const goals = listGoals()
|
|
594
|
+
.filter((goal) => pairing.user_id === "user_operator" ||
|
|
595
|
+
goal.userId === pairing.user_id)
|
|
596
|
+
.filter((goal) => goal.status === "active")
|
|
597
|
+
.slice(0, 8)
|
|
598
|
+
.map((goal) => ({
|
|
599
|
+
id: goal.id,
|
|
600
|
+
title: goal.title,
|
|
601
|
+
horizon: goal.horizon,
|
|
602
|
+
status: goal.status,
|
|
603
|
+
targetPoints: goal.targetPoints
|
|
604
|
+
}));
|
|
605
|
+
const projects = listProjectSummaries(scope)
|
|
606
|
+
.filter((project) => project.status === "active")
|
|
607
|
+
.slice(0, 8)
|
|
608
|
+
.map((project) => ({
|
|
609
|
+
id: project.id,
|
|
610
|
+
title: project.title,
|
|
611
|
+
status: project.status,
|
|
612
|
+
workflowStatus: project.workflowStatus,
|
|
613
|
+
goalId: project.goalId,
|
|
614
|
+
goalTitle: project.goalTitle,
|
|
615
|
+
activeRunCount: project.time.activeRunCount,
|
|
616
|
+
openTaskCount: project.activeTaskCount
|
|
617
|
+
}));
|
|
618
|
+
return { goals, projects };
|
|
619
|
+
}
|
|
620
|
+
function buildTodaySnapshot(pairing) {
|
|
621
|
+
const todayKey = formatLocalDateKey();
|
|
622
|
+
const tasks = listTasks({ ...userScopeFilter(pairing), limit: 100 }).map(compactTask);
|
|
623
|
+
const dueToday = tasks
|
|
624
|
+
.filter((task) => task.status !== "done" && task.dueDate === todayKey)
|
|
625
|
+
.slice(0, 8);
|
|
626
|
+
return {
|
|
627
|
+
dateKey: todayKey,
|
|
628
|
+
dueTasks: dueToday,
|
|
629
|
+
dueCount: dueToday.length,
|
|
630
|
+
recentDone: tasks.filter((task) => task.status === "done").slice(0, 5)
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
function buildHealthSnapshot(userId) {
|
|
634
|
+
const workout = getDatabase()
|
|
635
|
+
.prepare(`SELECT id, workout_type, started_at, ended_at, duration_seconds,
|
|
636
|
+
average_heart_rate, max_heart_rate, derived_json
|
|
637
|
+
FROM health_workout_sessions
|
|
638
|
+
WHERE user_id = ?
|
|
639
|
+
ORDER BY started_at DESC
|
|
640
|
+
LIMIT 1`)
|
|
641
|
+
.get(userId);
|
|
642
|
+
const latestVitals = getDatabase()
|
|
643
|
+
.prepare(`SELECT date_key, metrics_json
|
|
644
|
+
FROM health_daily_summaries
|
|
645
|
+
WHERE user_id = ?
|
|
646
|
+
AND summary_type = 'vitals'
|
|
647
|
+
ORDER BY date_key DESC
|
|
648
|
+
LIMIT 1`)
|
|
649
|
+
.get(userId);
|
|
650
|
+
const latestVitalMetrics = safeJsonParse(latestVitals?.metrics_json, {});
|
|
651
|
+
const workoutDerived = safeJsonParse(workout?.derived_json, {});
|
|
652
|
+
const trainingLoad = typeof workoutDerived.trainingLoad === "number"
|
|
653
|
+
? workoutDerived.trainingLoad
|
|
654
|
+
: typeof workoutDerived.trimp === "number"
|
|
655
|
+
? workoutDerived.trimp
|
|
656
|
+
: null;
|
|
657
|
+
const heartRateSampleCount = typeof workoutDerived.heartRateSampleCount === "number"
|
|
658
|
+
? workoutDerived.heartRateSampleCount
|
|
659
|
+
: typeof workoutDerived.hrSampleCount === "number"
|
|
660
|
+
? workoutDerived.hrSampleCount
|
|
661
|
+
: 0;
|
|
662
|
+
return {
|
|
663
|
+
lastWorkout: workout
|
|
664
|
+
? {
|
|
665
|
+
id: workout.id,
|
|
666
|
+
workoutType: workout.workout_type,
|
|
667
|
+
startedAt: workout.started_at,
|
|
668
|
+
endedAt: workout.ended_at,
|
|
669
|
+
durationSeconds: workout.duration_seconds ?? 0,
|
|
670
|
+
averageHeartRate: workout.average_heart_rate,
|
|
671
|
+
maxHeartRate: workout.max_heart_rate,
|
|
672
|
+
trainingLoad,
|
|
673
|
+
heartRateSampleCount
|
|
674
|
+
}
|
|
675
|
+
: null,
|
|
676
|
+
latestVitals: latestVitals
|
|
677
|
+
? {
|
|
678
|
+
dayKey: latestVitals.date_key,
|
|
679
|
+
metricCount: Object.keys(latestVitalMetrics).length
|
|
680
|
+
}
|
|
681
|
+
: null
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
function buildMovementSnapshot(userId) {
|
|
685
|
+
const latestStay = getDatabase()
|
|
686
|
+
.prepare(`SELECT id, label, started_at, ended_at
|
|
687
|
+
FROM movement_stays
|
|
688
|
+
WHERE user_id = ?
|
|
689
|
+
ORDER BY started_at DESC
|
|
690
|
+
LIMIT 1`)
|
|
691
|
+
.get(userId);
|
|
692
|
+
const latestTrip = getDatabase()
|
|
693
|
+
.prepare(`SELECT id, label, started_at, ended_at
|
|
694
|
+
FROM movement_trips
|
|
695
|
+
WHERE user_id = ?
|
|
696
|
+
ORDER BY started_at DESC
|
|
697
|
+
LIMIT 1`)
|
|
698
|
+
.get(userId);
|
|
699
|
+
const unlabeledPlaceCount = listMovementPlaces([userId]).filter((place) => place.categoryTags.length === 0).length;
|
|
700
|
+
return {
|
|
701
|
+
latestStay: latestStay
|
|
702
|
+
? {
|
|
703
|
+
id: latestStay.id,
|
|
704
|
+
label: latestStay.label,
|
|
705
|
+
startedAt: latestStay.started_at,
|
|
706
|
+
endedAt: latestStay.ended_at
|
|
707
|
+
}
|
|
708
|
+
: null,
|
|
709
|
+
latestTrip: latestTrip
|
|
710
|
+
? {
|
|
711
|
+
id: latestTrip.id,
|
|
712
|
+
label: latestTrip.label,
|
|
713
|
+
startedAt: latestTrip.started_at,
|
|
714
|
+
endedAt: latestTrip.ended_at
|
|
715
|
+
}
|
|
716
|
+
: null,
|
|
717
|
+
unlabeledPlaceCount
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
function buildSyncSnapshot(pairing) {
|
|
721
|
+
const queuedCaptureCount = getDatabase()
|
|
722
|
+
.prepare(`SELECT COUNT(*) AS count
|
|
723
|
+
FROM watch_capture_events
|
|
724
|
+
WHERE user_id = ?`)
|
|
725
|
+
.get(pairing.user_id);
|
|
726
|
+
const actionReceiptCount = getDatabase()
|
|
727
|
+
.prepare(`SELECT COUNT(*) AS count
|
|
728
|
+
FROM watch_action_receipts
|
|
729
|
+
WHERE user_id = ?`)
|
|
730
|
+
.get(pairing.user_id);
|
|
731
|
+
return {
|
|
732
|
+
pairingSessionId: pairing.id,
|
|
733
|
+
generatedAt: nowIso(),
|
|
734
|
+
storedCaptureCount: queuedCaptureCount.count,
|
|
735
|
+
actionReceiptCount: actionReceiptCount.count
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
function buildWatchSurfaces() {
|
|
739
|
+
return [
|
|
740
|
+
{ id: "now", title: "Now", icon: "sparkle" },
|
|
741
|
+
{ id: "work", title: "Work", icon: "kanban" },
|
|
742
|
+
{ id: "habits", title: "Habits", icon: "habit" },
|
|
743
|
+
{ id: "goals", title: "Goals", icon: "scope" },
|
|
744
|
+
{ id: "today", title: "Today", icon: "calendar" },
|
|
745
|
+
{ id: "health", title: "Health", icon: "heart" },
|
|
746
|
+
{ id: "movement", title: "Move", icon: "location" },
|
|
747
|
+
{ id: "psyche", title: "Psyche", icon: "mind" },
|
|
748
|
+
{ id: "inbox", title: "Inbox", icon: "tray" },
|
|
749
|
+
{ id: "sync", title: "Sync", icon: "antenna" }
|
|
750
|
+
];
|
|
751
|
+
}
|
|
480
752
|
export function buildWatchBootstrap(pairing, options) {
|
|
481
753
|
assertWatchReady(pairing);
|
|
482
754
|
const habits = listHabits({ status: "active", limit: 64 })
|
|
@@ -511,8 +783,37 @@ export function buildWatchBootstrap(pairing, options) {
|
|
|
511
783
|
last7History: history
|
|
512
784
|
};
|
|
513
785
|
});
|
|
786
|
+
const pendingPrompts = buildPendingPrompts(pairing.user_id);
|
|
787
|
+
const work = buildWorkSnapshot(pairing);
|
|
788
|
+
const direction = buildDirectionSnapshot(pairing);
|
|
789
|
+
const today = buildTodaySnapshot(pairing);
|
|
790
|
+
const generatedAt = nowIso();
|
|
514
791
|
return {
|
|
515
|
-
|
|
792
|
+
schemaVersion: 2,
|
|
793
|
+
generatedAt,
|
|
794
|
+
surfaces: buildWatchSurfaces(),
|
|
795
|
+
now: {
|
|
796
|
+
currentRun: work.currentRun,
|
|
797
|
+
nextTask: work.nextTask,
|
|
798
|
+
dueHabitCount: habits.filter((habit) => habit.dueToday).length,
|
|
799
|
+
pendingPromptCount: pendingPrompts.length,
|
|
800
|
+
generatedAt
|
|
801
|
+
},
|
|
802
|
+
work,
|
|
803
|
+
goals: direction.goals,
|
|
804
|
+
projects: direction.projects,
|
|
805
|
+
today,
|
|
806
|
+
health: buildHealthSnapshot(pairing.user_id),
|
|
807
|
+
movement: buildMovementSnapshot(pairing.user_id),
|
|
808
|
+
psyche: {
|
|
809
|
+
emotionOptions,
|
|
810
|
+
triggerOptions,
|
|
811
|
+
routinePromptOptions
|
|
812
|
+
},
|
|
813
|
+
inbox: {
|
|
814
|
+
prompts: pendingPrompts
|
|
815
|
+
},
|
|
816
|
+
sync: buildSyncSnapshot(pairing),
|
|
516
817
|
habits,
|
|
517
818
|
checkInOptions: {
|
|
518
819
|
activities: activityOptions,
|
|
@@ -522,7 +823,7 @@ export function buildWatchBootstrap(pairing, options) {
|
|
|
522
823
|
routinePrompts: routinePromptOptions,
|
|
523
824
|
recentPeople: recentPeopleLabels(pairing.user_id)
|
|
524
825
|
},
|
|
525
|
-
pendingPrompts
|
|
826
|
+
pendingPrompts
|
|
526
827
|
};
|
|
527
828
|
}
|
|
528
829
|
export function ingestWatchCaptureBatch(pairing, input) {
|
|
@@ -573,3 +874,262 @@ export function ingestWatchCaptureBatch(pairing, input) {
|
|
|
573
874
|
};
|
|
574
875
|
});
|
|
575
876
|
}
|
|
877
|
+
function readActionReceipt(userId, actionId) {
|
|
878
|
+
return getDatabase()
|
|
879
|
+
.prepare(`SELECT action_id, kind, processed_at, status, result_json, error_json
|
|
880
|
+
FROM watch_action_receipts
|
|
881
|
+
WHERE user_id = ? AND action_id = ?`)
|
|
882
|
+
.get(userId, actionId);
|
|
883
|
+
}
|
|
884
|
+
function writeActionReceipt(pairing, command, receipt) {
|
|
885
|
+
const processedAt = nowIso();
|
|
886
|
+
getDatabase()
|
|
887
|
+
.prepare(`INSERT INTO watch_action_receipts (
|
|
888
|
+
id, pairing_session_id, user_id, action_id, kind, received_at,
|
|
889
|
+
processed_at, status, result_json, error_json, created_at
|
|
890
|
+
)
|
|
891
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
892
|
+
ON CONFLICT(user_id, action_id) DO NOTHING`)
|
|
893
|
+
.run(`watchact_${randomUUID().replaceAll("-", "").slice(0, 12)}`, pairing.id, pairing.user_id, command.id, command.kind, command.createdAt, processedAt, receipt.status === "failed" ? "failed" : "processed", JSON.stringify(receipt.result), JSON.stringify(receipt.error ?? {}), processedAt);
|
|
894
|
+
return {
|
|
895
|
+
actionId: command.id,
|
|
896
|
+
kind: command.kind,
|
|
897
|
+
processedAt,
|
|
898
|
+
...receipt
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
const habitCommandPayloadSchema = z.object({
|
|
902
|
+
habitId: z.string().trim().min(1),
|
|
903
|
+
dateKey: z.string().trim().min(1).default(() => formatLocalDateKey()),
|
|
904
|
+
status: z.enum(["done", "missed"]),
|
|
905
|
+
note: z.string().trim().default("")
|
|
906
|
+
});
|
|
907
|
+
const captureCommandPayloadSchema = z.object({
|
|
908
|
+
eventType: watchCaptureEventTypeSchema,
|
|
909
|
+
recordedAt: z.string().datetime().default(() => nowIso()),
|
|
910
|
+
promptId: z.string().trim().min(1).nullable().optional().default(null),
|
|
911
|
+
linkedContext: watchLinkedContextSchema.default({}),
|
|
912
|
+
payload: z.record(z.string(), z.unknown()).default({})
|
|
913
|
+
});
|
|
914
|
+
const taskRunStartCommandPayloadSchema = z.object({
|
|
915
|
+
taskId: z.string().trim().min(1),
|
|
916
|
+
actor: z.string().trim().min(1).optional(),
|
|
917
|
+
timerMode: z.enum(["planned", "unlimited"]).default("unlimited"),
|
|
918
|
+
plannedDurationSeconds: z
|
|
919
|
+
.number()
|
|
920
|
+
.int()
|
|
921
|
+
.min(60)
|
|
922
|
+
.max(86_400)
|
|
923
|
+
.nullable()
|
|
924
|
+
.default(null),
|
|
925
|
+
isCurrent: z.boolean().default(true),
|
|
926
|
+
leaseTtlSeconds: z.number().int().min(1).max(14_400).default(900),
|
|
927
|
+
note: z.string().trim().default(""),
|
|
928
|
+
overrideReason: z.string().trim().optional()
|
|
929
|
+
});
|
|
930
|
+
const taskRunIdCommandPayloadSchema = z.object({
|
|
931
|
+
runId: z.string().trim().min(1),
|
|
932
|
+
actor: z.string().trim().min(1).optional(),
|
|
933
|
+
leaseTtlSeconds: z.number().int().min(1).max(14_400).default(900),
|
|
934
|
+
note: z.string().trim().default(""),
|
|
935
|
+
overrideReason: z.string().trim().optional()
|
|
936
|
+
});
|
|
937
|
+
const taskStatusCommandPayloadSchema = z.object({
|
|
938
|
+
taskId: z.string().trim().min(1),
|
|
939
|
+
status: z.enum(["backlog", "focus", "in_progress", "blocked", "done"]),
|
|
940
|
+
note: z.string().trim().default("")
|
|
941
|
+
});
|
|
942
|
+
function processWatchCommand(pairing, command) {
|
|
943
|
+
const actor = watchActorLabel(pairing);
|
|
944
|
+
switch (command.kind) {
|
|
945
|
+
case "habit_check_in": {
|
|
946
|
+
const payload = habitCommandPayloadSchema.parse(command.payload);
|
|
947
|
+
const habit = createHabitCheckIn(payload.habitId, {
|
|
948
|
+
dateKey: payload.dateKey,
|
|
949
|
+
status: payload.status,
|
|
950
|
+
note: payload.note
|
|
951
|
+
}, { source: "system", actor: `watch:${command.id}` });
|
|
952
|
+
if (!habit) {
|
|
953
|
+
throw new HttpError(404, "watch_habit_not_found", "Habit not found");
|
|
954
|
+
}
|
|
955
|
+
return { habitId: habit.id, status: payload.status };
|
|
956
|
+
}
|
|
957
|
+
case "capture_event": {
|
|
958
|
+
const rawPayload = command.payload;
|
|
959
|
+
const payload = captureCommandPayloadSchema.parse({
|
|
960
|
+
...rawPayload,
|
|
961
|
+
linkedContext: typeof rawPayload.linkedContext === "object" &&
|
|
962
|
+
rawPayload.linkedContext != null &&
|
|
963
|
+
Array.isArray(rawPayload.linkedContext) === false
|
|
964
|
+
? rawPayload.linkedContext
|
|
965
|
+
: {
|
|
966
|
+
placeId: rawPayload.placeId,
|
|
967
|
+
stayId: rawPayload.stayId,
|
|
968
|
+
tripId: rawPayload.tripId,
|
|
969
|
+
workoutId: rawPayload.workoutId
|
|
970
|
+
}
|
|
971
|
+
});
|
|
972
|
+
return {
|
|
973
|
+
receipt: ingestWatchCaptureBatch(pairing, {
|
|
974
|
+
sessionId: pairing.id,
|
|
975
|
+
pairingToken: "watch-command",
|
|
976
|
+
device: {
|
|
977
|
+
name: "Apple Watch",
|
|
978
|
+
platform: "watchos",
|
|
979
|
+
appVersion: "",
|
|
980
|
+
sourceDevice: "Apple Watch"
|
|
981
|
+
},
|
|
982
|
+
events: [
|
|
983
|
+
{
|
|
984
|
+
dedupeKey: command.id,
|
|
985
|
+
eventType: payload.eventType,
|
|
986
|
+
recordedAt: payload.recordedAt,
|
|
987
|
+
promptId: payload.promptId,
|
|
988
|
+
linkedContext: payload.linkedContext,
|
|
989
|
+
payload: payload.payload
|
|
990
|
+
}
|
|
991
|
+
]
|
|
992
|
+
})
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
case "task_run_start": {
|
|
996
|
+
const payload = taskRunStartCommandPayloadSchema.parse(command.payload);
|
|
997
|
+
const result = claimTaskRun(payload.taskId, {
|
|
998
|
+
actor: payload.actor ?? actor,
|
|
999
|
+
timerMode: payload.timerMode,
|
|
1000
|
+
plannedDurationSeconds: payload.plannedDurationSeconds,
|
|
1001
|
+
isCurrent: payload.isCurrent,
|
|
1002
|
+
leaseTtlSeconds: payload.leaseTtlSeconds,
|
|
1003
|
+
note: payload.note,
|
|
1004
|
+
overrideReason: payload.overrideReason
|
|
1005
|
+
}, new Date(), { source: "system" });
|
|
1006
|
+
return { taskRun: result.run, replayed: result.replayed };
|
|
1007
|
+
}
|
|
1008
|
+
case "task_run_heartbeat": {
|
|
1009
|
+
const payload = taskRunIdCommandPayloadSchema.parse(command.payload);
|
|
1010
|
+
return {
|
|
1011
|
+
taskRun: heartbeatTaskRun(payload.runId, {
|
|
1012
|
+
actor: payload.actor ?? actor,
|
|
1013
|
+
leaseTtlSeconds: payload.leaseTtlSeconds,
|
|
1014
|
+
note: payload.note,
|
|
1015
|
+
overrideReason: payload.overrideReason
|
|
1016
|
+
}, new Date(), { source: "system" })
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
case "task_run_focus": {
|
|
1020
|
+
const payload = taskRunIdCommandPayloadSchema.parse(command.payload);
|
|
1021
|
+
return {
|
|
1022
|
+
taskRun: focusTaskRun(payload.runId, { actor: payload.actor ?? actor }, new Date(), { source: "system" })
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
case "task_run_complete": {
|
|
1026
|
+
const payload = taskRunIdCommandPayloadSchema.parse(command.payload);
|
|
1027
|
+
return {
|
|
1028
|
+
taskRun: completeTaskRun(payload.runId, { actor: payload.actor ?? actor, note: payload.note }, new Date(), { source: "system" })
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
case "task_run_release": {
|
|
1032
|
+
const payload = taskRunIdCommandPayloadSchema.parse(command.payload);
|
|
1033
|
+
return {
|
|
1034
|
+
taskRun: releaseTaskRun(payload.runId, { actor: payload.actor ?? actor, note: payload.note }, new Date(), { source: "system" })
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
case "task_status_update": {
|
|
1038
|
+
const payload = taskStatusCommandPayloadSchema.parse(command.payload);
|
|
1039
|
+
const task = updateTask(payload.taskId, { status: payload.status }, { source: "system", actor: actor });
|
|
1040
|
+
if (!task) {
|
|
1041
|
+
throw new HttpError(404, "watch_task_not_found", "Task not found");
|
|
1042
|
+
}
|
|
1043
|
+
if (payload.note.length > 0) {
|
|
1044
|
+
ingestWatchCaptureBatch(pairing, {
|
|
1045
|
+
sessionId: pairing.id,
|
|
1046
|
+
pairingToken: "watch-command",
|
|
1047
|
+
device: {
|
|
1048
|
+
name: "Apple Watch",
|
|
1049
|
+
platform: "watchos",
|
|
1050
|
+
appVersion: "",
|
|
1051
|
+
sourceDevice: "Apple Watch"
|
|
1052
|
+
},
|
|
1053
|
+
events: [
|
|
1054
|
+
{
|
|
1055
|
+
dedupeKey: `${command.id}:note`,
|
|
1056
|
+
eventType: "dictated_note",
|
|
1057
|
+
promptId: null,
|
|
1058
|
+
recordedAt: command.createdAt,
|
|
1059
|
+
linkedContext: {},
|
|
1060
|
+
payload: { note: payload.note, taskId: payload.taskId }
|
|
1061
|
+
}
|
|
1062
|
+
]
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
return { taskId: task.id, status: task.status };
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
function commandErrorPayload(error) {
|
|
1070
|
+
if (error instanceof HttpError) {
|
|
1071
|
+
return {
|
|
1072
|
+
statusCode: error.statusCode,
|
|
1073
|
+
code: error.code,
|
|
1074
|
+
message: error.message,
|
|
1075
|
+
details: error.details ?? null
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
if (error instanceof ZodError) {
|
|
1079
|
+
return {
|
|
1080
|
+
statusCode: 400,
|
|
1081
|
+
code: "watch_command_validation_failed",
|
|
1082
|
+
message: "Watch command validation failed",
|
|
1083
|
+
issues: error.issues
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
return {
|
|
1087
|
+
statusCode: 500,
|
|
1088
|
+
code: "watch_command_failed",
|
|
1089
|
+
message: error instanceof Error ? error.message : "Unknown watch command error"
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
export function ingestWatchCommandBatch(pairing, input) {
|
|
1093
|
+
assertWatchReady(pairing);
|
|
1094
|
+
const parsed = mobileWatchCommandBatchSchema.parse(input);
|
|
1095
|
+
const receipts = [];
|
|
1096
|
+
for (const command of parsed.commands) {
|
|
1097
|
+
const existing = readActionReceipt(pairing.user_id, command.id);
|
|
1098
|
+
if (existing) {
|
|
1099
|
+
receipts.push({
|
|
1100
|
+
actionId: existing.action_id,
|
|
1101
|
+
kind: existing.kind,
|
|
1102
|
+
status: "replayed",
|
|
1103
|
+
processedAt: existing.processed_at,
|
|
1104
|
+
result: safeJsonParse(existing.result_json, {}),
|
|
1105
|
+
error: safeJsonParse(existing.error_json, {})
|
|
1106
|
+
});
|
|
1107
|
+
continue;
|
|
1108
|
+
}
|
|
1109
|
+
try {
|
|
1110
|
+
const result = processWatchCommand(pairing, command);
|
|
1111
|
+
receipts.push(writeActionReceipt(pairing, command, {
|
|
1112
|
+
status: "processed",
|
|
1113
|
+
result
|
|
1114
|
+
}));
|
|
1115
|
+
}
|
|
1116
|
+
catch (error) {
|
|
1117
|
+
const errorPayload = commandErrorPayload(error);
|
|
1118
|
+
receipts.push(writeActionReceipt(pairing, command, {
|
|
1119
|
+
status: "failed",
|
|
1120
|
+
result: {},
|
|
1121
|
+
error: errorPayload
|
|
1122
|
+
}));
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
return {
|
|
1126
|
+
receivedCount: parsed.commands.length,
|
|
1127
|
+
processedCount: receipts.filter((receipt) => receipt.status === "processed")
|
|
1128
|
+
.length,
|
|
1129
|
+
replayedCount: receipts.filter((receipt) => receipt.status === "replayed")
|
|
1130
|
+
.length,
|
|
1131
|
+
failedCount: receipts.filter((receipt) => receipt.status === "failed")
|
|
1132
|
+
.length,
|
|
1133
|
+
receipts
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
@@ -143,6 +143,14 @@ function copyProxyHeaders(response, reply) {
|
|
|
143
143
|
reply.header(name, value);
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
|
+
function isHtmlResponse(contentType) {
|
|
147
|
+
const values = Array.isArray(contentType) ? contentType : [contentType];
|
|
148
|
+
return values.some((value) => typeof value === "string" && value.toLowerCase().includes("text/html"));
|
|
149
|
+
}
|
|
150
|
+
function forceUncachedHtml(reply) {
|
|
151
|
+
reply.header("Cache-Control", "no-store, max-age=0, must-revalidate");
|
|
152
|
+
reply.header("Pragma", "no-cache");
|
|
153
|
+
}
|
|
146
154
|
const hopByHopHeaders = new Set([
|
|
147
155
|
"connection",
|
|
148
156
|
"content-length",
|
|
@@ -164,7 +172,10 @@ async function proxyDevAsset(input) {
|
|
|
164
172
|
const response = await input.fetchImpl(target, { redirect: "manual" });
|
|
165
173
|
input.reply.code(response.status);
|
|
166
174
|
copyProxyHeaders(response, input.reply);
|
|
167
|
-
if (
|
|
175
|
+
if (isHtmlResponse(response.headers.get("content-type") ?? undefined)) {
|
|
176
|
+
forceUncachedHtml(input.reply);
|
|
177
|
+
}
|
|
178
|
+
else if (!response.headers.has("cache-control")) {
|
|
168
179
|
input.reply.header("Cache-Control", "no-store, max-age=0, must-revalidate");
|
|
169
180
|
}
|
|
170
181
|
if (!response.body) {
|
|
@@ -205,7 +216,10 @@ export function createKeepAliveDevAssetProxy() {
|
|
|
205
216
|
}
|
|
206
217
|
input.reply.header(name, value);
|
|
207
218
|
}
|
|
208
|
-
if (
|
|
219
|
+
if (isHtmlResponse(response.headers["content-type"])) {
|
|
220
|
+
forceUncachedHtml(input.reply);
|
|
221
|
+
}
|
|
222
|
+
else if (!response.headers["cache-control"]) {
|
|
209
223
|
input.reply.header("Cache-Control", "no-store, max-age=0, must-revalidate");
|
|
210
224
|
}
|
|
211
225
|
const chunks = [];
|
|
@@ -444,7 +458,7 @@ async function serveAsset(requestPath, reply, options) {
|
|
|
444
458
|
reply.type(contentTypes[ext] ?? "application/octet-stream");
|
|
445
459
|
reply.header("Cache-Control", "no-store, max-age=0, must-revalidate");
|
|
446
460
|
if (ext === ".html") {
|
|
447
|
-
reply
|
|
461
|
+
forceUncachedHtml(reply);
|
|
448
462
|
}
|
|
449
463
|
return payload;
|
|
450
464
|
}
|
|
@@ -453,8 +467,7 @@ async function serveAsset(requestPath, reply, options) {
|
|
|
453
467
|
try {
|
|
454
468
|
const payload = await readFile(path.join(clientDir, "index.html"));
|
|
455
469
|
reply.type(contentTypes[".html"]);
|
|
456
|
-
reply
|
|
457
|
-
reply.header("Pragma", "no-cache");
|
|
470
|
+
forceUncachedHtml(reply);
|
|
458
471
|
return payload;
|
|
459
472
|
}
|
|
460
473
|
catch {
|