forge-openclaw-plugin 0.2.47 → 0.2.48

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.
@@ -5,6 +5,7 @@ import { HttpError } from "./errors.js";
5
5
  import { updateWorkoutMetadata } from "./health.js";
6
6
  import { canonicalizeMovementCategoryTags, listMovementPlaces, normalizeMovementCategoryTag, updateMovementPlace } from "./movement.js";
7
7
  import { listHabits } from "./repositories/habits.js";
8
+ import { formatLocalDateKey } from "../../src/lib/date-keys.js";
8
9
  const watchCapability = "watch-ready";
9
10
  const watchHistoryStateSchema = z.enum(["aligned", "unaligned", "unknown"]);
10
11
  const watchPromptKindSchema = z.enum([
@@ -56,7 +57,11 @@ export const mobileWatchHabitCheckInSchema = z.object({
56
57
  sessionId: z.string().trim().min(1),
57
58
  pairingToken: z.string().trim().min(1),
58
59
  dedupeKey: z.string().trim().min(1),
59
- dateKey: z.string().trim().min(1).default(new Date().toISOString().slice(0, 10)),
60
+ dateKey: z
61
+ .string()
62
+ .trim()
63
+ .min(1)
64
+ .default(() => formatLocalDateKey()),
60
65
  status: z.enum(["done", "missed"]),
61
66
  note: z.string().trim().default(""),
62
67
  description: z.string().trim().optional()
@@ -82,24 +87,24 @@ function nowIso() {
82
87
  return new Date().toISOString();
83
88
  }
84
89
  function formatDateKey(date) {
85
- return date.toISOString().slice(0, 10);
90
+ return formatLocalDateKey(date);
86
91
  }
87
92
  function parseDateKey(dateKey) {
88
93
  const [year, month, day] = dateKey.split("-").map(Number);
89
- return new Date(Date.UTC(year, month - 1, day));
94
+ return new Date(year, month - 1, day);
90
95
  }
91
- function startOfUtcDay(date) {
92
- return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
96
+ function startOfLocalDay(date) {
97
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate());
93
98
  }
94
- function addUtcDays(date, days) {
99
+ function addLocalDays(date, days) {
95
100
  const next = new Date(date);
96
- next.setUTCDate(next.getUTCDate() + days);
101
+ next.setDate(next.getDate() + days);
97
102
  return next;
98
103
  }
99
- function startOfUtcWeek(date) {
100
- const start = startOfUtcDay(date);
101
- const offset = (start.getUTCDay() + 6) % 7;
102
- start.setUTCDate(start.getUTCDate() - offset);
104
+ function startOfLocalWeek(date) {
105
+ const start = startOfLocalDay(date);
106
+ const offset = (start.getDay() + 6) % 7;
107
+ start.setDate(start.getDate() - offset);
103
108
  return start;
104
109
  }
105
110
  function isAlignedCheckIn(habit, status) {
@@ -125,15 +130,15 @@ function buildHabitHistory(habit, options) {
125
130
  ? parseDateKey(options.anchorDateKey)
126
131
  : new Date();
127
132
  if (habit.frequency === "daily") {
128
- const today = startOfUtcDay(now);
133
+ const today = startOfLocalDay(now);
129
134
  return Array.from({ length: 7 }, (_, index) => {
130
135
  const offset = index - 6;
131
- const date = addUtcDays(today, offset);
136
+ const date = addLocalDays(today, offset);
132
137
  const dateKey = formatDateKey(date);
133
138
  const checkIn = habit.checkIns.find((entry) => entry.dateKey === dateKey) ?? null;
134
139
  return {
135
140
  id: dateKey,
136
- label: ["S", "M", "T", "W", "T", "F", "S"][date.getUTCDay()],
141
+ label: ["S", "M", "T", "W", "T", "F", "S"][date.getDay()],
137
142
  periodKey: dateKey,
138
143
  current: offset === 0,
139
144
  state: checkIn
@@ -144,13 +149,13 @@ function buildHabitHistory(habit, options) {
144
149
  };
145
150
  });
146
151
  }
147
- const thisWeek = startOfUtcWeek(now);
152
+ const thisWeek = startOfLocalWeek(now);
148
153
  return Array.from({ length: 7 }, (_, index) => {
149
154
  const offset = index - 6;
150
- const weekStart = addUtcDays(thisWeek, offset * 7);
155
+ const weekStart = addLocalDays(thisWeek, offset * 7);
151
156
  const weekKey = formatDateKey(weekStart);
152
157
  const weekEntries = habit.checkIns.filter((entry) => {
153
- const entryWeek = formatDateKey(startOfUtcWeek(parseDateKey(entry.dateKey)));
158
+ const entryWeek = formatDateKey(startOfLocalWeek(parseDateKey(entry.dateKey)));
154
159
  return entryWeek === weekKey;
155
160
  });
156
161
  const alignedCount = weekEntries.filter((entry) => isAlignedCheckIn(habit, entry.status)).length;
@@ -375,7 +380,9 @@ function projectionForStoredEvent(event) {
375
380
  const categoryCandidate = Array.isArray(event.payload.categoryTags)
376
381
  ? event.payload.categoryTags
377
382
  : typeof event.payload.category === "string"
378
- ? watchCategoryMap.get(event.payload.category) ?? [event.payload.category]
383
+ ? (watchCategoryMap.get(event.payload.category) ?? [
384
+ event.payload.category
385
+ ])
379
386
  : [];
380
387
  const categoryTags = canonicalizeMovementCategoryTags(categoryCandidate.flatMap((value) => typeof value === "string" ? [normalizeMovementCategoryTag(value)] : []));
381
388
  try {
@@ -403,12 +410,15 @@ function projectionForStoredEvent(event) {
403
410
  status: "projection_failed",
404
411
  details: {
405
412
  reason: "place_update_failed",
406
- message: error instanceof Error ? error.message : "Unknown place update error"
413
+ message: error instanceof Error
414
+ ? error.message
415
+ : "Unknown place update error"
407
416
  }
408
417
  };
409
418
  }
410
419
  }
411
- if (event.eventType === "workout_annotation" && event.linkedContext.workoutId) {
420
+ if (event.eventType === "workout_annotation" &&
421
+ event.linkedContext.workoutId) {
412
422
  try {
413
423
  const workout = updateWorkoutMetadata(event.linkedContext.workoutId, {
414
424
  subjectiveEffort: typeof event.payload.subjectiveEffort === "number"
@@ -449,7 +459,9 @@ function projectionForStoredEvent(event) {
449
459
  status: "projection_failed",
450
460
  details: {
451
461
  reason: "workout_update_failed",
452
- message: error instanceof Error ? error.message : "Unknown workout update error"
462
+ message: error instanceof Error
463
+ ? error.message
464
+ : "Unknown workout update error"
453
465
  }
454
466
  };
455
467
  }
@@ -0,0 +1,21 @@
1
+ function readPart(parts, type) {
2
+ return parts.find((part) => part.type === type)?.value ?? "";
3
+ }
4
+ export function getRuntimeTimeZone() {
5
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
6
+ }
7
+ export function formatDateKeyInTimeZone(date, timeZone) {
8
+ const parts = new Intl.DateTimeFormat("en-CA", {
9
+ timeZone,
10
+ year: "numeric",
11
+ month: "2-digit",
12
+ day: "2-digit"
13
+ }).formatToParts(date);
14
+ const year = readPart(parts, "year");
15
+ const month = readPart(parts, "month");
16
+ const day = readPart(parts, "day");
17
+ return `${year}-${month}-${day}`;
18
+ }
19
+ export function formatLocalDateKey(date = new Date()) {
20
+ return formatDateKeyInTimeZone(date, getRuntimeTimeZone());
21
+ }
@@ -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.47",
5
+ "version": "0.2.48",
6
6
  "skills": [
7
7
  "./skills"
8
8
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forge-openclaw-plugin",
3
- "version": "0.2.47",
3
+ "version": "0.2.48",
4
4
  "description": "Curated OpenClaw adapter for the Forge collaboration API, UI entrypoint, and localhost auto-start runtime.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -239,7 +239,8 @@ CRITICAL NEGATIVE-HABIT CHECK-IN RULE:
239
239
  - For a `negative` habit, the correct check-in outcome is `missed`.
240
240
  - On a `negative` habit, `missed` means the habit was resisted, the user stayed aligned, and the habit earns its XP bonus.
241
241
  - Do not treat `missed` on a `negative` habit as failure. In this case, `missed` is the successful outcome.
242
- - Habit check-ins written through `/api/v1/habits/:id/check-ins` can also include an optional `description`; when present, it replaces the habit's current `description` in the same write.
242
+ - In OpenClaw, official habit outcome logging should go through `forge_update_entities` on `entityType: "habit"` with `patch: { checkIn: { status, dateKey?, note?, description? } }`.
243
+ - Do not bypass the shared tool model with raw habit routes when the batch entity update already covers the write cleanly.
243
244
  Ask:
244
245
 
245
246
  1. What is the recurring behavior in one concrete sentence?
@@ -387,6 +388,7 @@ Use the dedicated domain routes for specialized surfaces that are not simple bat
387
388
  `/api/v1/workbench/flows/:id/output` for published outputs, and the run/node routes
388
389
  under `/api/v1/workbench/flows/:id` for run history and node-level inspection.
389
390
  - If you are unsure which specialized route family applies, check `forge_get_agent_onboarding` and use its `entityRouteModel.specializedDomainSurfaces` section before guessing.
391
+ - If the truth of the current Movement, Life Force, or Workbench state is still unclear, prefer the dedicated read before the mutation so the correction stays truthful.
390
392
  - After a concrete Movement, Life Force, or Workbench correction, read the relevant specialized view back when the user is trying to understand the result rather than only store it.
391
393
 
392
394
  Use live work tools for `task_run`:
@@ -56,6 +56,8 @@ Forge correctly, and gather only the structure that still matters.
56
56
  - For specialized surfaces, start from the user's real job in plain language, then
57
57
  narrow to the route family. Do not open with a route menu unless the user already
58
58
  named the exact object and action.
59
+ - For specialized surfaces, if the truth of the current state is still uncertain, read
60
+ the relevant dedicated view before you mutate it.
59
61
  - When the user has already named a precise correction or review target, do not widen
60
62
  back out into a meta lane question. Confirm only the missing route-selecting detail
61
63
  and then act.
@@ -411,7 +413,7 @@ about linking only after the main record already feels named and steady.
411
413
 
412
414
  Use this when the user is updating an existing record rather than creating a new one.
413
415
 
414
- 1. Ask what feels newly true, newly urgent, or newly clear.
416
+ 1. Ask what feels newly true, newly inaccurate, or newly clear.
415
417
  2. Ask what should stay true so the record keeps its core meaning.
416
418
  3. Ask what prompted the update now if that changes the shape of the record.
417
419
  4. Then ask only for the missing structural detail required by the change.
@@ -1017,9 +1019,11 @@ Arc:
1017
1019
  5. Ask what they are trying to notice, preserve, or answer through that movement context.
1018
1020
  6. Choose the dedicated day, month, all-time, timeline, places, trip-detail, or
1019
1021
  selection route once the question shape is clear.
1020
- 7. Skip the meta lane question when the user already named the exact correction or
1022
+ 7. If the truth of one uncertain span is still unclear, read the timeline or saved-box
1023
+ detail before you mutate it.
1024
+ 8. Skip the meta lane question when the user already named the exact correction or
1021
1025
  review target and only one ambiguity remains.
1022
- 8. Route to the dedicated movement read or write path once the surface is clear.
1026
+ 9. Route to the dedicated movement read or write path once the surface is clear.
1023
1027
 
1024
1028
  Direct action rules:
1025
1029
 
@@ -1036,6 +1040,8 @@ Direct action rules:
1036
1040
  - If the user wants to inspect one already-saved movement correction before editing
1037
1041
  it, read the box detail first so the follow-up write stays grounded in the saved
1038
1042
  object.
1043
+ - If the user is asking where they were during one uncertain window, prefer a timeline
1044
+ read before you create a correction. Mutate only after the lived truth is clear.
1039
1045
  - When the user has already given the real answer, for example "I stayed home during
1040
1046
  that missing block", do not ask a broad review question again. Confirm only the
1041
1047
  interval or place if that is still ambiguous, then act.
@@ -1105,11 +1111,14 @@ Arc:
1105
1111
  4. Ask what should stay true if they are changing profile or template assumptions.
1106
1112
  5. Ask whether the user is describing a stable weekly shape or just how today feels
1107
1113
  when the lane is still blurred.
1108
- 6. If the user already named the life-force lane clearly, skip the meta lane question
1114
+ 6. If the user describes a repeatable day-shape such as "Mondays crash after lunch",
1115
+ treat that as a weekday-template question before you reach for profile or
1116
+ fatigue-signal routes.
1117
+ 7. If the user already named the life-force lane clearly, skip the meta lane question
1109
1118
  and ask only for the specific weekday, profile field, or signal that still matters.
1110
- 7. If the user wants to see what changed after a write, read the overview back instead
1119
+ 8. If the user wants to see what changed after a write, read the overview back instead
1111
1120
  of leaving the result implicit.
1112
- 8. Route to the dedicated life-force path once the lane is clear.
1121
+ 9. Route to the dedicated life-force path once the lane is clear.
1113
1122
 
1114
1123
  Helpful follow-up lanes:
1115
1124
 
@@ -1133,6 +1142,8 @@ Direct action rules:
1133
1142
 
1134
1143
  - If the user is describing a durable baseline such as work capacity, recovery style,
1135
1144
  or action-point assumptions, patch the profile instead of logging a fatigue signal.
1145
+ - If the user is describing a repeatable weekday rhythm, update that weekday template
1146
+ instead of treating it as a one-off right-now feeling.
1136
1147
  - If the user is describing how one weekday should usually feel, update that weekday
1137
1148
  template instead of editing the profile.
1138
1149
  - If the user is describing right-now depletion or recovery, post a fatigue signal and
@@ -1161,12 +1172,16 @@ Arc:
1161
1172
  before you narrow to flow discovery, editing, execution, or results.
1162
1173
  2. Ask whether the job is flow discovery, one flow edit, execution, run history, published output, node-level inspection, or latest-node-output lookup.
1163
1174
  3. Ask which flow, slug, run, or node the request is about.
1164
- 4. Ask whether they need the flow contract, a run result, a published output, or a node result.
1175
+ 4. Ask whether they need the stable flow contract, one run result, one published
1176
+ output, one node result, or the latest node output.
1165
1177
  5. If the user already named the flow and action clearly, skip the meta lane
1166
1178
  question and ask only for the missing run, node, or output scope.
1167
1179
  6. If the user wants a stable public input contract or published output, prefer those
1168
1180
  dedicated reads instead of detouring through run history first.
1169
- 7. Route to the dedicated workbench route family once the execution lane is clear.
1181
+ 7. If the user is debugging one failed run, ask whether the useful artifact is the run
1182
+ summary, one node result, the latest node output, or the published output before
1183
+ you start asking for edits.
1184
+ 8. Route to the dedicated workbench route family once the execution lane is clear.
1170
1185
 
1171
1186
  Helpful follow-up lanes:
1172
1187
 
@@ -1205,6 +1220,9 @@ Direct action rules:
1205
1220
  - If the user wants to execute a known saved flow, use `/api/v1/workbench/flows/:id/run`.
1206
1221
  - If the user wants payload-first execution without depending on a saved flow id, use
1207
1222
  `/api/v1/workbench/run`.
1223
+ - If the user wants to debug one failed execution, narrow whether they need the run
1224
+ detail, one node result, the latest node output, or the published output before you
1225
+ ask for flow changes.
1208
1226
  - If the user wants one node's latest successful output, do not browse old runs first
1209
1227
  unless they explicitly want historical debugging.
1210
1228
  - If the user wants to understand what inputs a flow can accept before editing or
@@ -383,6 +383,9 @@ If the entity is not yet clear:
383
383
  hold and what the new episode or evidence changes.
384
384
  - If a recent charged episode is what made the update visible, anchor in that episode
385
385
  before you rename the durable belief, pattern, mode, or value.
386
+ - If the user already gives the new sentence in usable language, revise the wording
387
+ once and save instead of reopening evidence, origin, or repair just because those
388
+ lanes are available.
386
389
  - If another belief, value, pattern, mode, or note becomes visible, name it gently but
387
390
  do not switch containers unless the user wants to.
388
391