forge-openclaw-plugin 0.2.91 → 0.2.93

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.
@@ -0,0 +1,161 @@
1
+ CREATE TEMP TABLE health_workout_time_series_dedupe AS
2
+ SELECT
3
+ workout_id,
4
+ metric_key,
5
+ source_sample_uid,
6
+ MIN(rowid) AS survivor_rowid,
7
+ (
8
+ SELECT latest.rowid
9
+ FROM health_workout_time_series AS latest
10
+ WHERE latest.workout_id = grouped.workout_id
11
+ AND latest.metric_key = grouped.metric_key
12
+ AND latest.source_sample_uid = grouped.source_sample_uid
13
+ ORDER BY latest.updated_at DESC, latest.created_at DESC, latest.rowid DESC
14
+ LIMIT 1
15
+ ) AS latest_rowid,
16
+ (
17
+ SELECT latest.series_index
18
+ FROM health_workout_time_series AS latest
19
+ WHERE latest.workout_id = grouped.workout_id
20
+ AND latest.metric_key = grouped.metric_key
21
+ AND latest.source_sample_uid = grouped.source_sample_uid
22
+ ORDER BY latest.updated_at DESC, latest.created_at DESC, latest.rowid DESC
23
+ LIMIT 1
24
+ ) AS latest_series_index
25
+ FROM health_workout_time_series AS grouped
26
+ WHERE source_sample_uid IS NOT NULL
27
+ AND source_sample_uid != ''
28
+ GROUP BY workout_id, metric_key, source_sample_uid
29
+ HAVING COUNT(*) > 1;
30
+
31
+ UPDATE health_workout_time_series
32
+ SET
33
+ label = (
34
+ SELECT latest.label
35
+ FROM health_workout_time_series AS latest
36
+ JOIN health_workout_time_series_dedupe AS dedupe
37
+ ON dedupe.latest_rowid = latest.rowid
38
+ WHERE dedupe.survivor_rowid = health_workout_time_series.rowid
39
+ ),
40
+ category = (
41
+ SELECT latest.category
42
+ FROM health_workout_time_series AS latest
43
+ JOIN health_workout_time_series_dedupe AS dedupe
44
+ ON dedupe.latest_rowid = latest.rowid
45
+ WHERE dedupe.survivor_rowid = health_workout_time_series.rowid
46
+ ),
47
+ unit = (
48
+ SELECT latest.unit
49
+ FROM health_workout_time_series AS latest
50
+ JOIN health_workout_time_series_dedupe AS dedupe
51
+ ON dedupe.latest_rowid = latest.rowid
52
+ WHERE dedupe.survivor_rowid = health_workout_time_series.rowid
53
+ ),
54
+ value = (
55
+ SELECT latest.value
56
+ FROM health_workout_time_series AS latest
57
+ JOIN health_workout_time_series_dedupe AS dedupe
58
+ ON dedupe.latest_rowid = latest.rowid
59
+ WHERE dedupe.survivor_rowid = health_workout_time_series.rowid
60
+ ),
61
+ started_at = (
62
+ SELECT latest.started_at
63
+ FROM health_workout_time_series AS latest
64
+ JOIN health_workout_time_series_dedupe AS dedupe
65
+ ON dedupe.latest_rowid = latest.rowid
66
+ WHERE dedupe.survivor_rowid = health_workout_time_series.rowid
67
+ ),
68
+ ended_at = (
69
+ SELECT latest.ended_at
70
+ FROM health_workout_time_series AS latest
71
+ JOIN health_workout_time_series_dedupe AS dedupe
72
+ ON dedupe.latest_rowid = latest.rowid
73
+ WHERE dedupe.survivor_rowid = health_workout_time_series.rowid
74
+ ),
75
+ source_device = (
76
+ SELECT latest.source_device
77
+ FROM health_workout_time_series AS latest
78
+ JOIN health_workout_time_series_dedupe AS dedupe
79
+ ON dedupe.latest_rowid = latest.rowid
80
+ WHERE dedupe.survivor_rowid = health_workout_time_series.rowid
81
+ ),
82
+ source_bundle_identifier = (
83
+ SELECT latest.source_bundle_identifier
84
+ FROM health_workout_time_series AS latest
85
+ JOIN health_workout_time_series_dedupe AS dedupe
86
+ ON dedupe.latest_rowid = latest.rowid
87
+ WHERE dedupe.survivor_rowid = health_workout_time_series.rowid
88
+ ),
89
+ source_product_type = (
90
+ SELECT latest.source_product_type
91
+ FROM health_workout_time_series AS latest
92
+ JOIN health_workout_time_series_dedupe AS dedupe
93
+ ON dedupe.latest_rowid = latest.rowid
94
+ WHERE dedupe.survivor_rowid = health_workout_time_series.rowid
95
+ ),
96
+ capture_method = (
97
+ SELECT latest.capture_method
98
+ FROM health_workout_time_series AS latest
99
+ JOIN health_workout_time_series_dedupe AS dedupe
100
+ ON dedupe.latest_rowid = latest.rowid
101
+ WHERE dedupe.survivor_rowid = health_workout_time_series.rowid
102
+ ),
103
+ quality_flags_json = (
104
+ SELECT latest.quality_flags_json
105
+ FROM health_workout_time_series AS latest
106
+ JOIN health_workout_time_series_dedupe AS dedupe
107
+ ON dedupe.latest_rowid = latest.rowid
108
+ WHERE dedupe.survivor_rowid = health_workout_time_series.rowid
109
+ ),
110
+ metadata_json = (
111
+ SELECT latest.metadata_json
112
+ FROM health_workout_time_series AS latest
113
+ JOIN health_workout_time_series_dedupe AS dedupe
114
+ ON dedupe.latest_rowid = latest.rowid
115
+ WHERE dedupe.survivor_rowid = health_workout_time_series.rowid
116
+ ),
117
+ provenance_json = (
118
+ SELECT latest.provenance_json
119
+ FROM health_workout_time_series AS latest
120
+ JOIN health_workout_time_series_dedupe AS dedupe
121
+ ON dedupe.latest_rowid = latest.rowid
122
+ WHERE dedupe.survivor_rowid = health_workout_time_series.rowid
123
+ ),
124
+ updated_at = (
125
+ SELECT latest.updated_at
126
+ FROM health_workout_time_series AS latest
127
+ JOIN health_workout_time_series_dedupe AS dedupe
128
+ ON dedupe.latest_rowid = latest.rowid
129
+ WHERE dedupe.survivor_rowid = health_workout_time_series.rowid
130
+ )
131
+ WHERE rowid IN (
132
+ SELECT survivor_rowid
133
+ FROM health_workout_time_series_dedupe
134
+ );
135
+
136
+ DELETE FROM health_workout_time_series
137
+ WHERE rowid IN (
138
+ SELECT duplicate.rowid
139
+ FROM health_workout_time_series AS duplicate
140
+ JOIN health_workout_time_series_dedupe AS dedupe
141
+ ON dedupe.workout_id = duplicate.workout_id
142
+ AND dedupe.metric_key = duplicate.metric_key
143
+ AND dedupe.source_sample_uid = duplicate.source_sample_uid
144
+ WHERE duplicate.rowid != dedupe.survivor_rowid
145
+ );
146
+
147
+ UPDATE health_workout_time_series
148
+ SET series_index = (
149
+ SELECT dedupe.latest_series_index
150
+ FROM health_workout_time_series_dedupe AS dedupe
151
+ WHERE dedupe.survivor_rowid = health_workout_time_series.rowid
152
+ )
153
+ WHERE rowid IN (
154
+ SELECT survivor_rowid
155
+ FROM health_workout_time_series_dedupe
156
+ );
157
+
158
+ DROP TABLE health_workout_time_series_dedupe;
159
+
160
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_health_workout_time_series_sample_identity
161
+ ON health_workout_time_series(workout_id, metric_key, source_sample_uid);
@@ -2960,6 +2960,7 @@ const AGENT_ONBOARDING_ENTITY_CATALOG = [
2960
2960
  ],
2961
2961
  searchHints: [
2962
2962
  "Clarify whether the user wants a behavioral query, one trip or place, a missing-gap overlay, a manual add or update, or a link before choosing the route.",
2963
+ "For known-place creation or cleanup, ask what label, boundary, and future use should make the place recognizable before calling the dedicated place route.",
2963
2964
  "If the user already named a concrete missing span, confirm only the remaining time or place ambiguity, then use the movement overlay route and read the timeline back.",
2964
2965
  "If the user wants to revise or remove an already-saved correction, identify whether it is a user-defined box, automatic box, recorded stay, recorded trip, or trip point before choosing the repair or delete route."
2965
2966
  ],
@@ -2975,6 +2976,7 @@ const AGENT_ONBOARDING_ENTITY_CATALOG = [
2975
2976
  ],
2976
2977
  searchHints: [
2977
2978
  "Clarify whether the user wants explanation, durable model changes, or a real-time tired or recovered signal before choosing the route.",
2979
+ "For profile or weekday-template edits, ask what future planning behavior should change, such as workload, recovery time, timeboxes, meeting load, or task choice.",
2978
2980
  "Separate durable profile assumptions, weekday-template edits, and right-now fatigue signals before choosing the mutation path.",
2979
2981
  "When the user is trying to understand the practical result of a change, read the overview again after the write instead of stopping at the mutation response."
2980
2982
  ],
@@ -2990,6 +2992,7 @@ const AGENT_ONBOARDING_ENTITY_CATALOG = [
2990
2992
  ],
2991
2993
  searchHints: [
2992
2994
  "Clarify whether the user wants flow discovery, editing, execution, published output, run inspection, or node-level output before choosing the route.",
2995
+ "For one-off execution, ask whether the input contract should stay temporary or become a reusable saved flow before creating anything durable.",
2993
2996
  "Distinguish flow contract, published output, run history, latest-node-output, and chat follow-up questions before reaching for a route.",
2994
2997
  "If the user is still deciding how to run or edit a flow, read flow detail or the box catalog before asking them for structured input details."
2995
2998
  ],
@@ -3057,6 +3060,8 @@ const AGENT_ONBOARDING_CONVERSATION_RULES = [
3057
3060
  "For direct update or review requests, the next question should usually narrow the saved object, timeframe, or route family instead of reopening the whole meaning-making arc.",
3058
3061
  "For updates, start with the smallest thing that now feels wrong, newly true, or newly visible rather than restarting the whole story.",
3059
3062
  "For review requests, ask what practical question the user wants the read to answer before you ask for more scope.",
3063
+ "Treat userId, owner, and human/bot assignees as accountability and scope, not as opening form fields. Ask whose record or owner scope matters only when it changes visibility, review results, collaboration, automation behavior, or later filtering.",
3064
+ "For read and overview requests, ask for human or bot user scope only when the answer would meaningfully differ across owners; otherwise keep the next question focused on the user's practical question.",
3060
3065
  "The opening question should help the user understand what they are actually trying to save, decide, review, or change, not make them perform the schema out loud.",
3061
3066
  "If the user already named the exact correction in usable language, confirm only the missing scope, timing, or route-selecting detail that still matters, then act.",
3062
3067
  "Keep API and architecture nouns out of user-facing questions unless the user asks about implementation. Do not ask the user about surfaces, route families, CRUD, payloads, mutation paths, or read paths; ask about the human object such as a wiki page, note, trigger report, behavior pattern, belief, mode, movement timeline, energy model, weekday pattern, flow, run, or node result.",
@@ -4776,6 +4781,7 @@ function buildAgentOnboardingPayload(request) {
4776
4781
  ],
4777
4782
  routeSelectionQuestions: [
4778
4783
  "Is the user asking for a day, month, all-time, timeline, place, trip detail, selected-span, or settings answer?",
4784
+ "If this is known-place creation or cleanup, what label, boundary, or future-use distinction is still missing?",
4779
4785
  "Is this a missing-gap overlay, a saved-overlay repair, or an edit to one already-recorded stay, trip, or trip point?",
4780
4786
  "If this is about operating behavior, is the change about passive tracking, publish mode, retention, or companion readiness?",
4781
4787
  "If the target is already known, what one time, place, or saved-object detail is still missing before acting?"
@@ -4837,6 +4843,7 @@ function buildAgentOnboardingPayload(request) {
4837
4843
  "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.",
4838
4844
  "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.",
4839
4845
  "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.",
4846
+ "For known-place creation or cleanup, ask for the place label, boundary, and future use, then use POST /api/v1/movement/places or PATCH /api/v1/movement/places/:id instead of tags or generic entity writes.",
4840
4847
  "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.",
4841
4848
  "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.",
4842
4849
  "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."
@@ -4849,6 +4856,7 @@ function buildAgentOnboardingPayload(request) {
4849
4856
  routeKeys: ["overview", "profile", "weekdayTemplate", "fatigueSignal"],
4850
4857
  routeSelectionQuestions: [
4851
4858
  "Is the user trying to understand the overview, change durable profile assumptions, change a weekday curve, or log a right-now fatigue signal?",
4859
+ "What planning decision should the overview or correction change: workload, recovery, timeboxes, meetings, or task choice?",
4852
4860
  "Are they describing a repeatable weekly shape or a one-off current state?",
4853
4861
  "If the lane is already clear, what one weekday, profile field, or signal detail is still missing?"
4854
4862
  ],
@@ -4871,6 +4879,8 @@ function buildAgentOnboardingPayload(request) {
4871
4879
  "Route-selection questions are internal. User-facing questions should ask whether this is a current read, durable assumption, repeated weekday rhythm, or right-now state instead of reciting overview/profile/template/signal route keys.",
4872
4880
  "Use GET /api/v1/life-force for the current overview payload with stats, drains, recommendations, and current-curve state.",
4873
4881
  "Patch the profile only for durable personal settings, update weekday templates only for the curve itself, and post fatigue signals for real-time tired or recovered observations.",
4882
+ "If the user only needs an explanation or planning read, use the overview first and do not turn the conversation into a profile or template mutation.",
4883
+ "For profile or weekday-template edits, ask what future planning behavior should change, such as workload, recovery time, timeboxes, meeting load, or task choice, so the write is not just a nicer description.",
4874
4884
  "If the user says something like 'I always dip on Tuesdays after lunch', treat that as a weekday-template change rather than a one-off fatigue signal.",
4875
4885
  "If the user is asking what changed after a profile, template, or fatigue write, read the overview back so the effect stays visible.",
4876
4886
  "If the user already knows they want a profile change, weekday-template edit, or right-now fatigue signal, skip the broad lane question and ask only for the missing weekday, profile field, or signal detail."
@@ -4883,6 +4893,7 @@ function buildAgentOnboardingPayload(request) {
4883
4893
  routeKeys: ["overview", "profile", "weekdayTemplate", "fatigueSignal"],
4884
4894
  routeSelectionQuestions: [
4885
4895
  "Is the user trying to understand the overview, change durable profile assumptions, change a weekday curve, or log a right-now fatigue signal?",
4896
+ "What planning decision should the overview or correction change: workload, recovery, timeboxes, meetings, or task choice?",
4886
4897
  "Are they describing a repeatable weekly shape or a one-off current state?",
4887
4898
  "If the lane is already clear, what one weekday, profile field, or signal detail is still missing?"
4888
4899
  ],
@@ -4906,6 +4917,8 @@ function buildAgentOnboardingPayload(request) {
4906
4917
  "Route-selection questions are internal. User-facing questions should ask whether this is a current read, durable assumption, repeated weekday rhythm, or right-now state instead of reciting overview/profile/template/signal route keys.",
4907
4918
  "Use GET /api/v1/life-force for the current overview payload with stats, drains, recommendations, and current-curve state.",
4908
4919
  "Patch the profile only for durable personal settings, update weekday templates only for the curve itself, and post fatigue signals for real-time tired or recovered observations.",
4920
+ "If the user only needs an explanation or planning read, use the overview first and do not turn the conversation into a profile or template mutation.",
4921
+ "For profile or weekday-template edits, ask what future planning behavior should change, such as workload, recovery time, timeboxes, meeting load, or task choice, so the write is not just a nicer description.",
4909
4922
  "If the user says something like 'I always dip on Tuesdays after lunch', treat that as a weekday-template change rather than a one-off fatigue signal.",
4910
4923
  "If the user is asking what changed after a profile, template, or fatigue write, read the overview back so the effect stays visible.",
4911
4924
  "If the user already knows they want a profile change, weekday-template edit, or right-now fatigue signal, skip the broad lane question and ask only for the missing weekday, profile field, or signal detail."
@@ -4935,6 +4948,7 @@ function buildAgentOnboardingPayload(request) {
4935
4948
  ],
4936
4949
  routeSelectionQuestions: [
4937
4950
  "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?",
4951
+ "If this is execution, is it a known saved flow, a one-off input run, or a flow that should become reusable?",
4938
4952
  "Does the user need a stable public contract or one execution artifact?",
4939
4953
  "For flow CRUD, what stable input contract, expected output, or lifecycle effect must stay true?",
4940
4954
  "For flow chat follow-up, which saved flow should receive the message and what should the message accomplish?",
@@ -4984,6 +4998,7 @@ function buildAgentOnboardingPayload(request) {
4984
4998
  "Use the flow routes when the agent needs stable public input contracts, published outputs, node-level results, or reusable execution history.",
4985
4999
  "If the user is still figuring out inputs or editable structure, read flow detail or box catalog before asking them to reconstruct structured inputs from memory.",
4986
5000
  "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 input details.",
5001
+ "For one-off execution, do not create a saved flow unless the user wants reuse. Ask whether the input contract should stay temporary or become durable, then use POST /api/v1/workbench/run for the temporary case.",
4987
5002
  "For flow edits, ask what behavior should change while preserving the public contract unless the user explicitly wants the contract changed.",
4988
5003
  "For flow deletion, confirm the saved flow and whether published outputs or run history need preservation elsewhere before using the delete route.",
4989
5004
  "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.",
@@ -5255,8 +5270,12 @@ function buildAgentOnboardingPayload(request) {
5255
5270
  movementTripDetail: '{"routeKey":"tripDetail","pathParams":{"id":"trip_123"}}',
5256
5271
  movementSettings: '{"routeKey":"settings","query":{"userIds":["user_operator"]}}',
5257
5272
  movementSettingsUpdate: '{"routeKey":"settingsUpdate","body":{"trackingEnabled":true,"publishMode":"draft_review","retentionMode":"aggregates_only"}}',
5273
+ movementPlaceCreate: '{"routeKey":"placeCreate","body":{"label":"Home","centerLat":46.2044,"centerLon":6.1432,"radiusMeters":120,"userId":"user_operator","note":"Primary home boundary for future time-in-place reads."}}',
5274
+ movementPlaceUpdate: '{"routeKey":"placeUpdate","pathParams":{"id":"place_home"},"body":{"label":"Home office","radiusMeters":90,"note":"Tighten the boundary so clinic visits do not count as home."}}',
5258
5275
  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"}}',
5259
5276
  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."}}',
5277
+ movementUserBoxUpdate: '{"routeKey":"userBoxUpdate","pathParams":{"id":"box_manual_123"},"body":{"endedAt":"2026-05-06T15:30:00.000Z","note":"Extended after checking the timeline detail."}}',
5278
+ movementUserBoxDelete: '{"routeKey":"userBoxDelete","pathParams":{"id":"box_manual_123"}}',
5260
5279
  lifeForceOverview: '{"routeKey":"overview"}',
5261
5280
  lifeForceProfile: '{"routeKey":"profile","body":{"baselineDailyAp":24,"recoveryNotes":"Clinic-admin days need a lower expected afternoon load."}}',
5262
5281
  lifeForceWeekdayTemplate: '{"routeKey":"weekdayTemplate","pathParams":{"weekday":"monday"},"body":{"points":[{"hour":13,"freeAp":-4}]}}',
@@ -5267,6 +5286,8 @@ function buildAgentOnboardingPayload(request) {
5267
5286
  workbenchUpdateFlow: '{"routeKey":"updateFlow","pathParams":{"id":"flow_research_digest"},"body":{"description":"Keep the same input contract but add a stronger evidence-check node."}}',
5268
5287
  workbenchDeleteFlow: '{"routeKey":"deleteFlow","pathParams":{"id":"flow_research_digest"}}',
5269
5288
  workbenchRunDetail: '{"routeKey":"runDetail","pathParams":{"id":"flow_research_digest","runId":"run_123"}}',
5289
+ workbenchRunNodes: '{"routeKey":"runNodes","pathParams":{"id":"flow_research_digest","runId":"run_123"}}',
5290
+ workbenchNodeResult: '{"routeKey":"nodeResult","pathParams":{"id":"flow_research_digest","runId":"run_123","nodeId":"node_summary"}}',
5270
5291
  workbenchPublishedOutput: '{"routeKey":"publishedOutput","pathParams":{"id":"flow_research_digest"}}',
5271
5292
  workbenchLatestNodeOutput: '{"routeKey":"latestNodeOutput","pathParams":{"id":"flow_research_digest","nodeId":"node_summary"}}',
5272
5293
  workbenchRunFlow: '{"routeKey":"runFlow","pathParams":{"id":"flow_research_digest"},"body":{"input":{"topic":"question flow quality"}}}',
@@ -365,9 +365,15 @@ export function upsertWorkoutTimeSeries(input) {
365
365
  quality_flags_json, metadata_json, provenance_json, created_at, updated_at
366
366
  )
367
367
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
368
- ON CONFLICT(workout_id, metric_key, source_sample_uid, series_index)
369
- DO UPDATE SET value = excluded.value, started_at = excluded.started_at,
368
+ ON CONFLICT(workout_id, metric_key, source_sample_uid)
369
+ DO UPDATE SET series_index = excluded.series_index,
370
+ value = excluded.value, started_at = excluded.started_at,
370
371
  ended_at = excluded.ended_at, source_device = excluded.source_device,
372
+ label = excluded.label,
373
+ category = excluded.category,
374
+ unit = excluded.unit,
375
+ source_bundle_identifier = excluded.source_bundle_identifier,
376
+ source_product_type = excluded.source_product_type,
371
377
  capture_method = excluded.capture_method,
372
378
  quality_flags_json = excluded.quality_flags_json,
373
379
  metadata_json = excluded.metadata_json,
@@ -2735,6 +2735,97 @@ function mobileSyncSessionProgress(syncSessionId) {
2735
2735
  receivedBytes: chunks.reduce((sum, chunk) => sum + chunk.byte_count, 0)
2736
2736
  };
2737
2737
  }
2738
+ function finiteNumberFromUnknown(value) {
2739
+ if (typeof value === "number" && Number.isFinite(value)) {
2740
+ return value;
2741
+ }
2742
+ if (typeof value === "string" && value.trim().length > 0) {
2743
+ const parsed = Number(value);
2744
+ return Number.isFinite(parsed) ? parsed : null;
2745
+ }
2746
+ return null;
2747
+ }
2748
+ function nestedRecord(value) {
2749
+ return value && typeof value === "object" && !Array.isArray(value)
2750
+ ? value
2751
+ : {};
2752
+ }
2753
+ function expectedWorkoutEvidenceCounts(derived) {
2754
+ const syncCursor = nestedRecord(derived.syncCursor);
2755
+ const captureQuality = nestedRecord(derived.captureQuality);
2756
+ const syncTimeSeriesCount = finiteNumberFromUnknown(syncCursor.timeSeriesSampleCount);
2757
+ const captureHeartRateCount = finiteNumberFromUnknown(captureQuality.heartRateSamples);
2758
+ const syncRoutePointCount = finiteNumberFromUnknown(syncCursor.routePointCount);
2759
+ const captureRoutePointCount = finiteNumberFromUnknown(captureQuality.routePoints);
2760
+ const expectedTimeSeriesSamples = Math.max(0, Math.ceil(Math.max(syncTimeSeriesCount ?? 0, captureHeartRateCount ?? 0)));
2761
+ const expectedRoutePoints = Math.max(0, Math.ceil(Math.max(syncRoutePointCount ?? 0, captureRoutePointCount ?? 0)));
2762
+ return {
2763
+ expectedTimeSeriesSamples,
2764
+ expectedRoutePoints,
2765
+ hasEvidenceMetadata: syncTimeSeriesCount !== null ||
2766
+ captureHeartRateCount !== null ||
2767
+ syncRoutePointCount !== null ||
2768
+ captureRoutePointCount !== null
2769
+ };
2770
+ }
2771
+ function mobileHealthWorkoutImportState(userId) {
2772
+ const rows = getDatabase()
2773
+ .prepare(`WITH time_series_counts AS (
2774
+ SELECT workout_id, COUNT(*) AS time_series_count
2775
+ FROM health_workout_time_series
2776
+ GROUP BY workout_id
2777
+ ),
2778
+ route_counts AS (
2779
+ SELECT workout_id, COUNT(*) AS route_point_count
2780
+ FROM health_workout_routes
2781
+ GROUP BY workout_id
2782
+ )
2783
+ SELECT
2784
+ w.external_uid,
2785
+ w.derived_json,
2786
+ COALESCE(time_series_counts.time_series_count, 0) AS time_series_count,
2787
+ COALESCE(route_counts.route_point_count, 0) AS route_point_count
2788
+ FROM health_workout_sessions w
2789
+ LEFT JOIN time_series_counts ON time_series_counts.workout_id = w.id
2790
+ LEFT JOIN route_counts ON route_counts.workout_id = w.id
2791
+ WHERE w.user_id = ?
2792
+ AND w.source = 'apple_health'
2793
+ AND w.external_uid IS NOT NULL
2794
+ AND w.external_uid <> ''
2795
+ ORDER BY w.started_at DESC`)
2796
+ .all(userId);
2797
+ const alreadyUploadedWorkoutExternalUids = [];
2798
+ let incompleteWorkoutCount = 0;
2799
+ let timeSeriesSampleCount = 0;
2800
+ let routePointCount = 0;
2801
+ for (const row of rows) {
2802
+ const derived = safeJsonParse(row.derived_json, {});
2803
+ const evidenceCounts = expectedWorkoutEvidenceCounts(derived);
2804
+ const actualTimeSeriesCount = Math.max(0, row.time_series_count ?? 0);
2805
+ const actualRoutePointCount = Math.max(0, row.route_point_count ?? 0);
2806
+ const evidenceComplete = evidenceCounts.hasEvidenceMetadata
2807
+ ? actualTimeSeriesCount >= evidenceCounts.expectedTimeSeriesSamples &&
2808
+ actualRoutePointCount >= evidenceCounts.expectedRoutePoints
2809
+ : actualTimeSeriesCount + actualRoutePointCount > 0;
2810
+ if (evidenceComplete) {
2811
+ alreadyUploadedWorkoutExternalUids.push(row.external_uid.toLowerCase());
2812
+ timeSeriesSampleCount += actualTimeSeriesCount;
2813
+ routePointCount += actualRoutePointCount;
2814
+ }
2815
+ else {
2816
+ incompleteWorkoutCount += 1;
2817
+ }
2818
+ }
2819
+ return {
2820
+ alreadyUploadedWorkoutExternalUids,
2821
+ alreadyUploadedWorkoutCount: alreadyUploadedWorkoutExternalUids.length,
2822
+ existingWorkoutCount: rows.length,
2823
+ incompleteWorkoutCount,
2824
+ timeSeriesSampleCount,
2825
+ routePointCount,
2826
+ capturedAt: nowIso()
2827
+ };
2828
+ }
2738
2829
  function mobileSyncSessionUploadPayload(session, receivedChunkIds) {
2739
2830
  return {
2740
2831
  syncSessionId: session.id,
@@ -2747,6 +2838,7 @@ function mobileSyncSessionUploadPayload(session, receivedChunkIds) {
2747
2838
  supportsCompression: true,
2748
2839
  acceptedFamilies: safeJsonParse(session.requested_families_json, []),
2749
2840
  receivedChunkIds,
2841
+ workoutImportState: mobileHealthWorkoutImportState(session.user_id),
2750
2842
  progress: mobileSyncSessionProgress(session.id)
2751
2843
  };
2752
2844
  }
@@ -2,7 +2,7 @@
2
2
  "id": "forge-openclaw-plugin",
3
3
  "name": "Forge",
4
4
  "description": "Curated OpenClaw adapter for the Forge collaboration API, UI entrypoint, and localhost auto-start runtime.",
5
- "version": "0.2.91",
5
+ "version": "0.2.93",
6
6
  "activation": {
7
7
  "onStartup": true,
8
8
  "onCapabilities": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forge-openclaw-plugin",
3
- "version": "0.2.91",
3
+ "version": "0.2.93",
4
4
  "description": "Curated OpenClaw adapter for the Forge collaboration API, UI entrypoint, and localhost auto-start runtime.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -0,0 +1,161 @@
1
+ CREATE TEMP TABLE health_workout_time_series_dedupe AS
2
+ SELECT
3
+ workout_id,
4
+ metric_key,
5
+ source_sample_uid,
6
+ MIN(rowid) AS survivor_rowid,
7
+ (
8
+ SELECT latest.rowid
9
+ FROM health_workout_time_series AS latest
10
+ WHERE latest.workout_id = grouped.workout_id
11
+ AND latest.metric_key = grouped.metric_key
12
+ AND latest.source_sample_uid = grouped.source_sample_uid
13
+ ORDER BY latest.updated_at DESC, latest.created_at DESC, latest.rowid DESC
14
+ LIMIT 1
15
+ ) AS latest_rowid,
16
+ (
17
+ SELECT latest.series_index
18
+ FROM health_workout_time_series AS latest
19
+ WHERE latest.workout_id = grouped.workout_id
20
+ AND latest.metric_key = grouped.metric_key
21
+ AND latest.source_sample_uid = grouped.source_sample_uid
22
+ ORDER BY latest.updated_at DESC, latest.created_at DESC, latest.rowid DESC
23
+ LIMIT 1
24
+ ) AS latest_series_index
25
+ FROM health_workout_time_series AS grouped
26
+ WHERE source_sample_uid IS NOT NULL
27
+ AND source_sample_uid != ''
28
+ GROUP BY workout_id, metric_key, source_sample_uid
29
+ HAVING COUNT(*) > 1;
30
+
31
+ UPDATE health_workout_time_series
32
+ SET
33
+ label = (
34
+ SELECT latest.label
35
+ FROM health_workout_time_series AS latest
36
+ JOIN health_workout_time_series_dedupe AS dedupe
37
+ ON dedupe.latest_rowid = latest.rowid
38
+ WHERE dedupe.survivor_rowid = health_workout_time_series.rowid
39
+ ),
40
+ category = (
41
+ SELECT latest.category
42
+ FROM health_workout_time_series AS latest
43
+ JOIN health_workout_time_series_dedupe AS dedupe
44
+ ON dedupe.latest_rowid = latest.rowid
45
+ WHERE dedupe.survivor_rowid = health_workout_time_series.rowid
46
+ ),
47
+ unit = (
48
+ SELECT latest.unit
49
+ FROM health_workout_time_series AS latest
50
+ JOIN health_workout_time_series_dedupe AS dedupe
51
+ ON dedupe.latest_rowid = latest.rowid
52
+ WHERE dedupe.survivor_rowid = health_workout_time_series.rowid
53
+ ),
54
+ value = (
55
+ SELECT latest.value
56
+ FROM health_workout_time_series AS latest
57
+ JOIN health_workout_time_series_dedupe AS dedupe
58
+ ON dedupe.latest_rowid = latest.rowid
59
+ WHERE dedupe.survivor_rowid = health_workout_time_series.rowid
60
+ ),
61
+ started_at = (
62
+ SELECT latest.started_at
63
+ FROM health_workout_time_series AS latest
64
+ JOIN health_workout_time_series_dedupe AS dedupe
65
+ ON dedupe.latest_rowid = latest.rowid
66
+ WHERE dedupe.survivor_rowid = health_workout_time_series.rowid
67
+ ),
68
+ ended_at = (
69
+ SELECT latest.ended_at
70
+ FROM health_workout_time_series AS latest
71
+ JOIN health_workout_time_series_dedupe AS dedupe
72
+ ON dedupe.latest_rowid = latest.rowid
73
+ WHERE dedupe.survivor_rowid = health_workout_time_series.rowid
74
+ ),
75
+ source_device = (
76
+ SELECT latest.source_device
77
+ FROM health_workout_time_series AS latest
78
+ JOIN health_workout_time_series_dedupe AS dedupe
79
+ ON dedupe.latest_rowid = latest.rowid
80
+ WHERE dedupe.survivor_rowid = health_workout_time_series.rowid
81
+ ),
82
+ source_bundle_identifier = (
83
+ SELECT latest.source_bundle_identifier
84
+ FROM health_workout_time_series AS latest
85
+ JOIN health_workout_time_series_dedupe AS dedupe
86
+ ON dedupe.latest_rowid = latest.rowid
87
+ WHERE dedupe.survivor_rowid = health_workout_time_series.rowid
88
+ ),
89
+ source_product_type = (
90
+ SELECT latest.source_product_type
91
+ FROM health_workout_time_series AS latest
92
+ JOIN health_workout_time_series_dedupe AS dedupe
93
+ ON dedupe.latest_rowid = latest.rowid
94
+ WHERE dedupe.survivor_rowid = health_workout_time_series.rowid
95
+ ),
96
+ capture_method = (
97
+ SELECT latest.capture_method
98
+ FROM health_workout_time_series AS latest
99
+ JOIN health_workout_time_series_dedupe AS dedupe
100
+ ON dedupe.latest_rowid = latest.rowid
101
+ WHERE dedupe.survivor_rowid = health_workout_time_series.rowid
102
+ ),
103
+ quality_flags_json = (
104
+ SELECT latest.quality_flags_json
105
+ FROM health_workout_time_series AS latest
106
+ JOIN health_workout_time_series_dedupe AS dedupe
107
+ ON dedupe.latest_rowid = latest.rowid
108
+ WHERE dedupe.survivor_rowid = health_workout_time_series.rowid
109
+ ),
110
+ metadata_json = (
111
+ SELECT latest.metadata_json
112
+ FROM health_workout_time_series AS latest
113
+ JOIN health_workout_time_series_dedupe AS dedupe
114
+ ON dedupe.latest_rowid = latest.rowid
115
+ WHERE dedupe.survivor_rowid = health_workout_time_series.rowid
116
+ ),
117
+ provenance_json = (
118
+ SELECT latest.provenance_json
119
+ FROM health_workout_time_series AS latest
120
+ JOIN health_workout_time_series_dedupe AS dedupe
121
+ ON dedupe.latest_rowid = latest.rowid
122
+ WHERE dedupe.survivor_rowid = health_workout_time_series.rowid
123
+ ),
124
+ updated_at = (
125
+ SELECT latest.updated_at
126
+ FROM health_workout_time_series AS latest
127
+ JOIN health_workout_time_series_dedupe AS dedupe
128
+ ON dedupe.latest_rowid = latest.rowid
129
+ WHERE dedupe.survivor_rowid = health_workout_time_series.rowid
130
+ )
131
+ WHERE rowid IN (
132
+ SELECT survivor_rowid
133
+ FROM health_workout_time_series_dedupe
134
+ );
135
+
136
+ DELETE FROM health_workout_time_series
137
+ WHERE rowid IN (
138
+ SELECT duplicate.rowid
139
+ FROM health_workout_time_series AS duplicate
140
+ JOIN health_workout_time_series_dedupe AS dedupe
141
+ ON dedupe.workout_id = duplicate.workout_id
142
+ AND dedupe.metric_key = duplicate.metric_key
143
+ AND dedupe.source_sample_uid = duplicate.source_sample_uid
144
+ WHERE duplicate.rowid != dedupe.survivor_rowid
145
+ );
146
+
147
+ UPDATE health_workout_time_series
148
+ SET series_index = (
149
+ SELECT dedupe.latest_series_index
150
+ FROM health_workout_time_series_dedupe AS dedupe
151
+ WHERE dedupe.survivor_rowid = health_workout_time_series.rowid
152
+ )
153
+ WHERE rowid IN (
154
+ SELECT survivor_rowid
155
+ FROM health_workout_time_series_dedupe
156
+ );
157
+
158
+ DROP TABLE health_workout_time_series_dedupe;
159
+
160
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_health_workout_time_series_sample_identity
161
+ ON health_workout_time_series(workout_id, metric_key, source_sample_uid);
@@ -116,9 +116,17 @@ Concrete route-key examples for internal use:
116
116
  `{"routeKey":"settings","query":{"userIds":["user_operator"]}}`
117
117
  - Movement settings update:
118
118
  `{"routeKey":"settingsUpdate","body":{"trackingEnabled":true,"publishMode":"draft_review","retentionMode":"aggregates_only"}}`
119
+ - Movement known-place creation:
120
+ `{"routeKey":"placeCreate","body":{"label":"Home","centerLat":46.2044,"centerLon":6.1432,"radiusMeters":120,"userId":"user_operator","note":"Primary home boundary for future time-in-place reads."}}`
121
+ - Movement known-place update:
122
+ `{"routeKey":"placeUpdate","pathParams":{"id":"place_home"},"body":{"label":"Home office","radiusMeters":90,"note":"Tighten the boundary so clinic visits do not count as home."}}`
119
123
  - Movement missing-stay correction:
120
124
  first `{"routeKey":"userBoxPreflight","body":{"kind":"stay","startedAt":"2026-05-06T13:00:00.000Z","endedAt":"2026-05-06T15:00:00.000Z","placeLabel":"Home","userId":"user_operator"}}`,
121
125
  then `{"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."}}`
126
+ - Movement saved-overlay update:
127
+ `{"routeKey":"userBoxUpdate","pathParams":{"id":"box_manual_123"},"body":{"endedAt":"2026-05-06T15:30:00.000Z","note":"Extended after checking the timeline detail."}}`
128
+ - Movement saved-overlay delete:
129
+ `{"routeKey":"userBoxDelete","pathParams":{"id":"box_manual_123"}}`
122
130
  - Life Force overview:
123
131
  `{"routeKey":"overview"}`
124
132
  - Life Force profile edit:
@@ -139,6 +147,10 @@ Concrete route-key examples for internal use:
139
147
  `{"routeKey":"deleteFlow","pathParams":{"id":"flow_research_digest"}}`
140
148
  - Workbench run detail:
141
149
  `{"routeKey":"runDetail","pathParams":{"id":"flow_research_digest","runId":"run_123"}}`
150
+ - Workbench run nodes:
151
+ `{"routeKey":"runNodes","pathParams":{"id":"flow_research_digest","runId":"run_123"}}`
152
+ - Workbench node result:
153
+ `{"routeKey":"nodeResult","pathParams":{"id":"flow_research_digest","runId":"run_123","nodeId":"node_summary"}}`
142
154
  - Workbench published output:
143
155
  `{"routeKey":"publishedOutput","pathParams":{"id":"flow_research_digest"}}`
144
156
  - Workbench latest node output:
@@ -224,6 +236,7 @@ Entity conversation rule:
224
236
  before you reopen create or update intake.
225
237
  - When updating an entity, start with what is changing, what should stay true, and what prompted the update now.
226
238
  - When enough is clear, briefly summarize what you heard in the user's own language before asking for the last missing structural detail.
239
+ - Treat `userId` and human/bot assignees as accountability and scope, not as opening form fields. Ask whose human or bot record it is only when ownership changes visibility, review scope, collaboration, automation behavior, or later filtering; for read requests, ask user scope only when the answer would differ across owners.
227
240
  - The quick intake prompts later in this file are fallback checkpoints, not a script to read aloud.
228
241
 
229
242
  Forge data location rule:
@@ -249,6 +262,7 @@ Psyche interview rule:
249
262
  - After the first real answer, choose one follow-up lane at a time: situation, sequence, meaning, protection, cost, longing/value, or tentative name.
250
263
  - Do not minimize functional analysis, trigger chains, behavior patterns, modes, beliefs, or schema themes. Once at least one concrete example is clear, offer one careful interpretive hypothesis when it would help the user understand the function, protection, cost, belief, mode, or schema theme.
251
264
  - Phrase interpretive hypotheses as collaborative and testable, not as verdicts. A good hypothesis says what the reaction may be protecting, predicting, relieving, or costing, then asks whether that lands or needs correction.
265
+ - If several Psyche containers are plausible, do not ask the user to choose from a taxonomy menu first. Reflect the lived difference, offer one careful hypothesis when a concrete example is visible, then distinguish the options in plain language: one episode as a `trigger_report`, a recurring loop as a `behavior_pattern`, one repeated move as `behavior`, one sentence as `belief_entry`, a part-state as `mode_profile` or `mode_guide_session`, or reusable future-labeling as `event_type` or `emotion_definition`.
252
266
  - For Psyche updates, start with what feels newly true, newly visible, or newly inaccurate, then ask what should stay true before changing the formulation.
253
267
  - If a fresh episode is what made a Psyche update visible, anchor in that episode before renaming the durable belief, pattern, mode, or value.
254
268
  - If the user says they want help understanding a Psyche issue before saving it, ask one orienting question first instead of jumping straight into a full interpretation, diagnosis-like label, save suggestion, replacement belief, or suggested title.
@@ -204,6 +204,25 @@ When placement matters, prefer one hierarchy-aware linking question that can
204
204
  select or create the right goal, project, issue, or parent work item from the
205
205
  same search-first flow.
206
206
 
207
+ ## Owner And User-Scope Checkpoint
208
+
209
+ Most normal stored Forge entities can carry `userId`, and many planning records can
210
+ also carry human or bot assignees. Treat ownership as accountability and useful
211
+ visibility, not as the first field in the form.
212
+
213
+ - Do not open with "who owns this?" unless the user is explicitly delegating,
214
+ comparing human and bot work, or creating a record for someone else.
215
+ - Ask whose human or bot record it is only when ownership changes accountability,
216
+ visibility, review scope, automation behavior, or later filtering.
217
+ - For collaborative planning records, ask about assignees only after the outcome,
218
+ hierarchy placement, and owner are clear enough.
219
+ - For reviews and overviews, ask which user or owner scope matters only when the
220
+ answer would change across humans or bots.
221
+ - If the user's wording already names the owner or bot actor, use that as the
222
+ `userId` direction internally and ask only for any ambiguity that remains.
223
+ - When owner scope is irrelevant, stay with the entity's meaning, timing, route, or
224
+ links instead of adding an administrative question.
225
+
207
226
  ## Operation lane checkpoint
208
227
 
209
228
  Use this before you choose an API path or ask for more structure.
@@ -1537,9 +1556,9 @@ Preferred opening question:
1537
1556
  ## Movement
1538
1557
 
1539
1558
  Aim: clarify whether the user wants to understand time in place, review travel
1540
- behavior, add or update a stay or trip, inspect one place, change movement operating
1541
- settings, or link movement context to another Forge record before choosing the
1542
- dedicated route family.
1559
+ behavior, add or update a stay or trip, create or clean up a known place, change
1560
+ movement operating settings, or link movement context to another Forge record before
1561
+ choosing the dedicated route family.
1543
1562
 
1544
1563
  Arc:
1545
1564
 
@@ -1548,17 +1567,19 @@ Arc:
1548
1567
  2. Ask whether the user is trying to query behavior, add something manually, update
1549
1568
  an existing movement item, or link movement to another Forge entity.
1550
1569
  3. Ask whether the focus is a stay, a trip, a place, a timeline window, or a selected span.
1551
- 4. Ask for the time window, place, or movement item that makes the question concrete.
1552
- 5. Ask what they are trying to notice, preserve, or answer through that movement context.
1553
- 6. If the user is changing movement operating behavior, ask whether the change is
1570
+ 4. If this is place creation or cleanup, ask what label, boundary, and future use
1571
+ should make the place recognizable later.
1572
+ 5. Ask for the time window, place, or movement item that makes the question concrete.
1573
+ 6. Ask what they are trying to notice, preserve, or answer through that movement context.
1574
+ 7. If the user is changing movement operating behavior, ask whether the change is
1554
1575
  about passive tracking, publish mode, retention, or companion readiness.
1555
- 7. Choose the dedicated day, month, all-time, timeline, places, trip-detail,
1576
+ 8. Choose the dedicated day, month, all-time, timeline, places, trip-detail,
1556
1577
  selection, or settings route once the question shape is clear.
1557
- 8. If the truth of one uncertain span is still unclear, read the timeline or saved-box
1578
+ 9. If the truth of one uncertain span is still unclear, read the timeline or saved-box
1558
1579
  detail before you mutate it.
1559
- 9. Skip the meta lane question when the user already named the exact correction or
1580
+ 10. Skip the meta lane question when the user already named the exact correction or
1560
1581
  review target and only one ambiguity remains.
1561
- 10. Use the dedicated movement route once you know whether the user needs timeline
1582
+ 11. Use the dedicated movement route once you know whether the user needs timeline
1562
1583
  review, overlay, place or trip detail, selection summary, settings, or repair.
1563
1584
 
1564
1585
  Direct action rules:
@@ -1591,12 +1612,17 @@ Direct action rules:
1591
1612
  interval or place if that is still ambiguous, then act.
1592
1613
  - When you do act on a concrete missing-gap correction, create the overlay and read
1593
1614
  the relevant timeline back instead of leaving the correction ungrounded.
1615
+ - For known-place creation or cleanup, ask what the place should be called, what
1616
+ counts inside its boundary, and how future movement reads should use it. Use the
1617
+ dedicated place routes, not a tag or batch entity write.
1594
1618
 
1595
1619
  Helpful follow-up lanes:
1596
1620
 
1597
1621
  - whether the user wants time-in-place, travel history, one specific stay or trip, a
1598
1622
  place summary, or a link
1599
1623
  - what time window, place, stay, trip, or selection is in scope
1624
+ - what label, boundary, or future-use distinction makes a known place worth saving or
1625
+ renaming
1600
1626
  - whether the question is behavioral, such as time at home, travel frequency, or place
1601
1627
  distribution, versus an edit
1602
1628
  - whether the edit is a missing-gap overlay versus a true recorded stay/trip patch
@@ -1649,7 +1675,8 @@ Preferred opening question:
1649
1675
  ## Life Force
1650
1676
 
1651
1677
  Aim: clarify whether the user wants to review current energy state, change durable
1652
- profile assumptions, edit weekday curves, or log a real-time fatigue signal.
1678
+ profile assumptions, edit weekday curves, log a real-time fatigue signal, or make a
1679
+ planning decision based on the energy model.
1653
1680
 
1654
1681
  Arc:
1655
1682
 
@@ -1657,22 +1684,25 @@ Arc:
1657
1684
  you reduce it to one life-force lane.
1658
1685
  2. Ask whether the job is overview, profile change, weekday-template change, or fatigue signaling.
1659
1686
  3. Ask what part of the current energy picture feels most important or inaccurate.
1660
- 4. Ask what should stay true if they are changing profile or template assumptions.
1661
- 5. Ask whether the user is describing a stable weekly shape or just how today feels
1687
+ 4. Ask what planning decision should change if the model is corrected: workload,
1688
+ recovery, timeboxing, meeting load, or task choice.
1689
+ 5. Ask what should stay true if they are changing profile or template assumptions.
1690
+ 6. Ask whether the user is describing a stable weekly shape or just how today feels
1662
1691
  when the lane is still blurred.
1663
- 6. If the user describes a repeatable day-shape such as "Mondays crash after lunch",
1692
+ 7. If the user describes a repeatable day-shape such as "Mondays crash after lunch",
1664
1693
  treat that as a weekday-template question before you reach for profile or
1665
1694
  fatigue-signal routes.
1666
- 7. If the user already named the life-force lane clearly, skip the meta lane question
1695
+ 8. If the user already named the life-force lane clearly, skip the meta lane question
1667
1696
  and ask only for the specific weekday, profile field, or signal that still matters.
1668
- 8. If the user wants to see what changed after a write, read the overview back instead
1697
+ 9. If the user wants to see what changed after a write, read the overview back instead
1669
1698
  of leaving the result implicit.
1670
- 9. Route to the dedicated life-force path once the lane is clear.
1699
+ 10. Route to the dedicated life-force path once the lane is clear.
1671
1700
 
1672
1701
  Helpful follow-up lanes:
1673
1702
 
1674
1703
  - whether the user wants explanation, editing, or signaling
1675
1704
  - what part of the energy model feels off or useful
1705
+ - what planning decision the overview or correction should change
1676
1706
  - what durable assumption versus real-time state is being changed
1677
1707
  - whether the user is describing a stable weekly shape or just how today feels
1678
1708
 
@@ -1704,6 +1734,11 @@ Direct action rules:
1704
1734
  instead of treating it as a one-off right-now feeling.
1705
1735
  - If the user is describing how one weekday should usually feel, update that weekday
1706
1736
  template instead of editing the profile.
1737
+ - If the user only needs an explanation or planning read, use the overview first and
1738
+ do not turn the conversation into a profile or template mutation.
1739
+ - For profile or weekday-template edits, ask what future planning behavior should
1740
+ change, such as workload, recovery time, timeboxes, meeting load, or task choice,
1741
+ so the write is not just a more polished description.
1707
1742
  - If the user says something like "I always dip on Tuesdays after lunch", treat that
1708
1743
  as a weekday-template edit, not as a one-off fatigue signal.
1709
1744
  - If the user is describing right-now depletion or recovery, post a fatigue signal and
@@ -1737,18 +1772,20 @@ Arc:
1737
1772
  5. If the user is creating or editing a flow, clarify the flow's job, stable inputs,
1738
1773
  expected public output, and the smallest structural change before asking for node
1739
1774
  details.
1740
- 6. If the user wants to delete or archive a flow, ask which saved flow is affected
1775
+ 6. If the user wants one-off execution, clarify whether this should stay a one-time
1776
+ input run or become a reusable saved flow before creating anything durable.
1777
+ 7. If the user wants to delete or archive a flow, ask which saved flow is affected
1741
1778
  and what future run, published output, or public contract should no longer exist.
1742
- 7. If the user wants to continue a saved flow chat, ask which flow should receive the
1779
+ 8. If the user wants to continue a saved flow chat, ask which flow should receive the
1743
1780
  follow-up and what the message should accomplish.
1744
- 8. If the user already named the flow and action clearly, skip the meta lane
1781
+ 9. If the user already named the flow and action clearly, skip the meta lane
1745
1782
  question and ask only for the missing run, node, or output scope.
1746
- 9. If the user wants a stable public input contract or published output, prefer those
1783
+ 10. If the user wants a stable public input contract or published output, prefer those
1747
1784
  dedicated reads instead of detouring through run history first.
1748
- 10. If the user is debugging one failed run, ask whether the useful artifact is the run
1785
+ 11. If the user is debugging one failed run, ask whether the useful artifact is the run
1749
1786
  summary, one node result, the latest node output, or the published output before
1750
1787
  you start asking for edits.
1751
- 11. Route to the dedicated workbench route family once the execution lane is clear.
1788
+ 12. Route to the dedicated workbench route family once the execution lane is clear.
1752
1789
 
1753
1790
  Helpful follow-up lanes:
1754
1791
 
@@ -1756,6 +1793,7 @@ Helpful follow-up lanes:
1756
1793
  - what exact flow or run is in scope
1757
1794
  - whether they need whole-flow output or node-level detail
1758
1795
  - whether they need a public input contract or a published output instead of a debug trace
1796
+ - whether a requested execution should remain one-off or become a reusable saved flow
1759
1797
 
1760
1798
  Lane-to-route map:
1761
1799
 
@@ -1796,6 +1834,9 @@ Direct action rules:
1796
1834
  - If the user wants one-off input execution without depending on a saved flow id, use
1797
1835
  `POST /api/v1/workbench/run` through the dedicated one-off execution lane and keep
1798
1836
  the user-facing question about the one-off input contract.
1837
+ - For one-off execution, do not create a saved flow unless the user wants reuse. Ask
1838
+ whether the input contract should be temporary or durable, then route to
1839
+ `POST /api/v1/workbench/run` for the temporary case.
1799
1840
  - If the user wants to debug one failed execution, narrow whether they need the run
1800
1841
  detail, one node result, the latest node output, or the published output before you
1801
1842
  ask for flow changes.
@@ -251,6 +251,41 @@ short, tentative, and correctable.
251
251
  episode, or durable wiki explanation. Do not flatten schema work into a loose
252
252
  self-observation.
253
253
 
254
+ ## Entity Contrast Check
255
+
256
+ Use this when the user's material could fit several Psyche records. Do not ask the
257
+ user to choose from a taxonomy menu before you have reflected what the material is
258
+ doing.
259
+
260
+ - Choose `trigger_report` when the important thing is one charged episode and the
261
+ useful record is the sequence of situation, feeling, meaning, action, and
262
+ consequence.
263
+ - Choose `behavior_pattern` when the same cue -> body/emotion -> meaning ->
264
+ behavior/urge -> payoff -> cost loop repeats across situations and needs a
265
+ functional analysis.
266
+ - Choose `behavior` when one recurring move is the main object, even if the wider
267
+ loop is not mapped yet.
268
+ - Choose `belief_entry` when the live center is a sentence, rule, prediction, or
269
+ self/other/world assumption.
270
+ - Choose `mode_profile` when a recurring part-state has a stable voice, posture,
271
+ job, fear, or burden.
272
+ - Choose `mode_guide_session` when the user is inside the reaction and needs guided
273
+ present-moment exploration before a durable mode, belief, or pattern is clear.
274
+ - Choose `event_type` or `emotion_definition` when the reusable category or feeling
275
+ label will make future trigger reports more precise.
276
+
277
+ If two containers are genuinely plausible, say the distinction in plain language
278
+ after a reflection. For example: "We could save this as the episode if the meeting
279
+ itself is what matters, or as the recurring loop if this same freeze-and-overwork
280
+ sequence keeps happening. Which would help you more right now?" Ask that only after
281
+ the lived example is clear enough that the choice is meaningful.
282
+
283
+ After one concrete example is visible, it is often helpful to offer one careful
284
+ hypothesis before the contrast question: what the reaction may be protecting, what it
285
+ predicts, what short-term relief it creates, and what it costs. If the user corrects
286
+ the hypothesis, revise it once and then choose the record shape from the corrected
287
+ meaning.
288
+
254
289
  ## Therapeutic Direction Check
255
290
 
256
291
  Before each follow-up, quickly decide what therapeutic job the next question should do.