forge-openclaw-plugin 0.2.42 → 0.2.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/README.md +6 -0
  2. package/dist/assets/{board-C3BJqvZu.js → board-CAszQU7Y.js} +2 -2
  3. package/dist/assets/{board-C3BJqvZu.js.map → board-CAszQU7Y.js.map} +1 -1
  4. package/dist/assets/index-DtEvFzXp.css +1 -0
  5. package/dist/assets/index-s24CefIb.js +91 -0
  6. package/dist/assets/index-s24CefIb.js.map +1 -0
  7. package/dist/assets/{motion-Cv9HdOn4.js → motion-CU5aNClV.js} +2 -2
  8. package/dist/assets/{motion-Cv9HdOn4.js.map → motion-CU5aNClV.js.map} +1 -1
  9. package/dist/assets/{table-B1MDOEFp.js → table-CK0KcPYW.js} +2 -2
  10. package/dist/assets/{table-B1MDOEFp.js.map → table-CK0KcPYW.js.map} +1 -1
  11. package/dist/assets/{ui-CQzq3TE5.js → ui-B5MjRjKe.js} +2 -2
  12. package/dist/assets/{ui-CQzq3TE5.js.map → ui-B5MjRjKe.js.map} +1 -1
  13. package/dist/assets/{vendor-DK-mJFy6.js → vendor-D_NZFJze.js} +2 -2
  14. package/dist/assets/{vendor-DK-mJFy6.js.map → vendor-D_NZFJze.js.map} +1 -1
  15. package/dist/index.html +7 -7
  16. package/dist/openclaw/tools.js +42 -10
  17. package/dist/server/server/src/app.js +151 -34
  18. package/dist/server/server/src/health.js +49 -0
  19. package/dist/server/server/src/movement.js +2 -3
  20. package/dist/server/server/src/openapi.js +19 -1
  21. package/dist/server/src/components/ui/info-tooltip.js +1 -1
  22. package/dist/server/src/lib/knowledge-graph.js +2 -2
  23. package/dist/server/src/lib/schemas.js +1 -1
  24. package/openclaw.plugin.json +1 -1
  25. package/package.json +43 -2
  26. package/skills/forge-openclaw/entity_conversation_playbooks.md +23 -4
  27. package/skills/forge-openclaw/psyche_entity_playbooks.md +8 -0
  28. package/dist/assets/index-Dj8vB0vh.css +0 -1
  29. package/dist/assets/index-Fe5y4VWU.js +0 -91
  30. package/dist/assets/index-Fe5y4VWU.js.map +0 -1
package/dist/index.html CHANGED
@@ -13,14 +13,14 @@
13
13
  />
14
14
  <link rel="icon" type="image/png" href="/forge/assets/favicon-BCHm9dUV.ico" />
15
15
  <link rel="alternate icon" href="/forge/assets/favicon-BCHm9dUV.ico" />
16
- <script type="module" crossorigin src="/forge/assets/index-Fe5y4VWU.js"></script>
17
- <link rel="modulepreload" crossorigin href="/forge/assets/vendor-DK-mJFy6.js">
18
- <link rel="modulepreload" crossorigin href="/forge/assets/board-C3BJqvZu.js">
19
- <link rel="modulepreload" crossorigin href="/forge/assets/ui-CQzq3TE5.js">
20
- <link rel="modulepreload" crossorigin href="/forge/assets/motion-Cv9HdOn4.js">
21
- <link rel="modulepreload" crossorigin href="/forge/assets/table-B1MDOEFp.js">
16
+ <script type="module" crossorigin src="/forge/assets/index-s24CefIb.js"></script>
17
+ <link rel="modulepreload" crossorigin href="/forge/assets/vendor-D_NZFJze.js">
18
+ <link rel="modulepreload" crossorigin href="/forge/assets/board-CAszQU7Y.js">
19
+ <link rel="modulepreload" crossorigin href="/forge/assets/ui-B5MjRjKe.js">
20
+ <link rel="modulepreload" crossorigin href="/forge/assets/motion-CU5aNClV.js">
21
+ <link rel="modulepreload" crossorigin href="/forge/assets/table-CK0KcPYW.js">
22
22
  <link rel="stylesheet" crossorigin href="/forge/assets/vendor-DT3pnAKJ.css">
23
- <link rel="stylesheet" crossorigin href="/forge/assets/index-Dj8vB0vh.css">
23
+ <link rel="stylesheet" crossorigin href="/forge/assets/index-DtEvFzXp.css">
24
24
  </head>
25
25
  <body class="bg-canvas text-ink antialiased">
26
26
  <div id="root"></div>
@@ -11,6 +11,46 @@ function jsonResult(payload) {
11
11
  details: payload
12
12
  };
13
13
  }
14
+ function normalizeText(value) {
15
+ return typeof value === "string" ? value.trim() : "";
16
+ }
17
+ function normalizeOptionalText(value) {
18
+ const text = normalizeText(value);
19
+ return text.length > 0 ? text : undefined;
20
+ }
21
+ function normalizeTaskRunStartRequest(params) {
22
+ const taskId = normalizeText(params.taskId);
23
+ if (!taskId) {
24
+ throw new Error("forge_start_task_run requires a non-empty taskId.");
25
+ }
26
+ const actor = normalizeText(params.actor);
27
+ if (!actor) {
28
+ throw new Error("forge_start_task_run requires a non-empty actor.");
29
+ }
30
+ const timerMode = params.timerMode === "planned" ? "planned" : "unlimited";
31
+ const plannedDurationSeconds = typeof params.plannedDurationSeconds === "number" &&
32
+ Number.isInteger(params.plannedDurationSeconds)
33
+ ? params.plannedDurationSeconds
34
+ : null;
35
+ if (timerMode === "planned" && plannedDurationSeconds === null) {
36
+ throw new Error("forge_start_task_run requires plannedDurationSeconds when timerMode is planned.");
37
+ }
38
+ return {
39
+ taskId,
40
+ body: {
41
+ actor,
42
+ timerMode,
43
+ plannedDurationSeconds: timerMode === "planned" ? plannedDurationSeconds : null,
44
+ overrideReason: normalizeOptionalText(params.overrideReason),
45
+ isCurrent: typeof params.isCurrent === "boolean" ? params.isCurrent : undefined,
46
+ leaseTtlSeconds: typeof params.leaseTtlSeconds === "number" &&
47
+ Number.isInteger(params.leaseTtlSeconds)
48
+ ? params.leaseTtlSeconds
49
+ : undefined,
50
+ note: normalizeOptionalText(params.note)
51
+ }
52
+ };
53
+ }
14
54
  async function runRead(config, path) {
15
55
  const result = await callConfiguredForgeApi(config, {
16
56
  method: "GET",
@@ -954,19 +994,11 @@ export function registerForgePluginTools(api, config) {
954
994
  note: Type.Optional(Type.String())
955
995
  }),
956
996
  async execute(_toolCallId, params) {
957
- const typed = params;
997
+ const typed = normalizeTaskRunStartRequest(params);
958
998
  return jsonResult(await runWrite(config, {
959
999
  method: "POST",
960
1000
  path: `/api/v1/tasks/${typed.taskId}/runs`,
961
- body: {
962
- actor: typed.actor,
963
- timerMode: typed.timerMode,
964
- plannedDurationSeconds: typed.plannedDurationSeconds,
965
- overrideReason: typed.overrideReason,
966
- isCurrent: typed.isCurrent,
967
- leaseTtlSeconds: typed.leaseTtlSeconds,
968
- note: typed.note
969
- }
1001
+ body: typed.body
970
1002
  }));
971
1003
  }
972
1004
  });
@@ -61,7 +61,7 @@ import { buildOpenApiDocument } from "./openapi.js";
61
61
  import { registerWebRoutes } from "./web.js";
62
62
  import { createManagerRuntime } from "./managers/runtime.js";
63
63
  import { isManagerError } from "./managers/type-guards.js";
64
- import { createCompanionPairingSession, createCompanionPairingSessionSchema, createSleepSession, createSleepSessionSchema, createWorkoutSession, createWorkoutSessionSchema, deleteSleepSession, deleteWorkoutSession, getCompanionPairingSessionById, getCompanionOverview, getFitnessViewData, getSleepSessionById, getSleepSessionDetailById, getSleepViewData, getVitalsViewData, getWorkoutSessionById, ingestMobileHealthSync, mobileHealthSyncSchema, patchCompanionPairingSourceState, patchCompanionPairingSourceStateSchema, companionSourceKeySchema, requireValidPairing, revokeAllCompanionPairingSessions, revokeAllCompanionPairingSessionsSchema, revokeCompanionPairingSession, updateMobileCompanionSourceState, updateMobileCompanionSourceStateSchema, verifyCompanionPairing, verifyCompanionPairingSchema, updateSleepMetadata, updateSleepMetadataSchema, updateWorkoutMetadata, updateWorkoutMetadataSchema } from "./health.js";
64
+ import { createCompanionPairingSession, createCompanionPairingSessionSchema, createSleepSession, createSleepSessionSchema, createWorkoutSession, createWorkoutSessionSchema, deleteSleepSession, deleteWorkoutSession, getCompanionPairingSessionById, getCompanionOverview, getFitnessViewData, getSleepSessionById, getSleepSessionDetailById, getSleepTimelineOverlaysForRange, getSleepViewData, getVitalsViewData, getWorkoutSessionById, ingestMobileHealthSync, mobileHealthSyncSchema, patchCompanionPairingSourceState, patchCompanionPairingSourceStateSchema, companionSourceKeySchema, requireValidPairing, revokeAllCompanionPairingSessions, revokeAllCompanionPairingSessionsSchema, revokeCompanionPairingSession, updateMobileCompanionSourceState, updateMobileCompanionSourceStateSchema, verifyCompanionPairing, verifyCompanionPairingSchema, updateSleepMetadata, updateSleepMetadataSchema, updateWorkoutMetadata, updateWorkoutMetadataSchema } from "./health.js";
65
65
  import { analyzeMovementUserBoxPreflight, createMovementUserBox, createMovementPlace, deleteMovementUserBox, getMovementAllTimeSummary, getMovementBoxDetail, getMovementDayDetail, getMovementMobileBootstrap, getMovementTimeline, getMovementSelectionAggregate, getMovementSettings, getMovementTripDetail, getMovementMonthSummary, invalidateAutomaticMovementBox, listMovementPlaces, movementAutomaticBoxInvalidateSchema, movementMobileBootstrapSchema, movementMobilePlaceMutationSchema, movementMobileStayPatchSchema, movementMobileUserBoxCreateSchema, movementMobileUserBoxPreflightSchema, movementMobileUserBoxPatchSchema, movementMobileAutomaticBoxInvalidateSchema, movementMobileTimelineSchema, movementPlaceMutationSchema, movementPlacePatchSchema, movementSelectionAggregateSchema, movementStayPatchSchema, movementTripPatchSchema, movementUserBoxCreateSchema, movementUserBoxPreflightSchema, movementUserBoxPatchSchema, movementSettingsPatchSchema, movementTimelineQuerySchema, movementTripPointPatchSchema, deleteMovementStay, deleteMovementTrip, deleteMovementTripPoint, updateMovementPlace, updateMovementSettings, updateMovementStay, updateMovementTrip, updateMovementUserBox, updateMovementTripPoint, resolveMovementTimelineSegmentForBox } from "./movement.js";
66
66
  import { getScreenTimeAllTimeSummary, getScreenTimeDayDetail, getScreenTimeMonthSummary, getScreenTimeSettings, screenTimeSettingsPatchSchema, updateScreenTimeSettings } from "./screen-time.js";
67
67
  import { assertWatchReady, buildWatchBootstrap, ingestWatchCaptureBatch, mobileWatchBootstrapSchema, mobileWatchCaptureBatchSchema, mobileWatchHabitCheckInSchema } from "./watch-mobile.js";
@@ -1980,6 +1980,29 @@ function buildPreferredMutationPath(entityType) {
1980
1980
  return "Read-only surface.";
1981
1981
  }
1982
1982
  }
1983
+ function buildPreferredMutationTool(entityType) {
1984
+ if (entityType in AGENT_ONBOARDING_BATCH_ROUTE_BASES) {
1985
+ return "forge_create_entities | forge_update_entities | forge_delete_entities | forge_search_entities";
1986
+ }
1987
+ switch (entityType) {
1988
+ case "wiki_page":
1989
+ return "forge_upsert_wiki_page";
1990
+ case "calendar_connection":
1991
+ return "forge_connect_calendar_provider | forge_sync_calendar_connection";
1992
+ case "task_run":
1993
+ return "forge_start_task_run | forge_heartbeat_task_run | forge_focus_task_run | forge_complete_task_run | forge_release_task_run";
1994
+ case "questionnaire_run":
1995
+ return "forge_start_questionnaire_run | forge_update_questionnaire_run | forge_complete_questionnaire_run";
1996
+ case "preference_judgment":
1997
+ return "forge_submit_preferences_judgment";
1998
+ case "preference_signal":
1999
+ return "forge_submit_preferences_signal";
2000
+ case "work_adjustment":
2001
+ return "forge_adjust_work_minutes";
2002
+ default:
2003
+ return null;
2004
+ }
2005
+ }
1983
2006
  function buildPreferredReadPath(entityType) {
1984
2007
  if (entityType in AGENT_ONBOARDING_BATCH_ROUTE_BASES) {
1985
2008
  return AGENT_ONBOARDING_BATCH_ROUTE_BASES[entityType];
@@ -2024,11 +2047,10 @@ function enrichOnboardingEntityGuide(entry) {
2024
2047
  : null,
2025
2048
  preferredMutationPath: buildPreferredMutationPath(entry.entityType),
2026
2049
  preferredReadPath: buildPreferredReadPath(entry.entityType),
2027
- preferredMutationTool: classification === "batch_crud_entity"
2028
- ? "forge_create_entities | forge_update_entities | forge_delete_entities | forge_search_entities"
2029
- : classification === "specialized_domain_surface"
2050
+ preferredMutationTool: buildPreferredMutationTool(entry.entityType) ??
2051
+ (classification === "specialized_domain_surface"
2030
2052
  ? "Follow forge_get_agent_onboarding.entityRouteModel.specializedDomainSurfaces for the dedicated route family."
2031
- : null
2053
+ : null)
2032
2054
  };
2033
2055
  }
2034
2056
  const AGENT_ONBOARDING_ENTITY_CATALOG = [
@@ -2758,7 +2780,8 @@ const AGENT_ONBOARDING_CONVERSATION_RULES = [
2758
2780
  "For specialized surfaces, ask what would make the answer or change useful before you ask route-shaped details such as provider, weekday, flow id, run id, or trip id.",
2759
2781
  "When the user already named a precise correction or review target, do not widen back out into a meta lane question. Confirm only the missing route-selecting detail and then act.",
2760
2782
  "Once the route family is clear, say it plainly enough that another agent could follow the same path without guessing.",
2761
- "For Movement specifically, treat missing-data corrections as user-defined overlay boxes unless the user is editing an already-recorded stay or trip. When the user already gave a clear instruction like 'that missing block was home', act after only the last ambiguity is resolved."
2783
+ "For Movement specifically, treat missing-data corrections as user-defined overlay boxes unless the user is editing an already-recorded stay or trip. When the user already gave a clear instruction like 'that missing block was home', act after only the last ambiguity is resolved.",
2784
+ "For meaning-bearing updates, especially in Psyche, briefly say what feels newly true before you ask for the one structural detail that still changes the save."
2762
2785
  ];
2763
2786
  const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
2764
2787
  {
@@ -2917,7 +2940,8 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
2917
2940
  "Confirm the task.",
2918
2941
  "Confirm the actor only if it is not already obvious.",
2919
2942
  "Ask whether the run should be planned or unlimited only if that changes the action.",
2920
- "Start the run instead of turning it into a longer intake."
2943
+ "Start the run instead of turning it into a longer intake.",
2944
+ "Use the dedicated task-run tool for start, heartbeat, focus, complete, and release work instead of bouncing to the UI or a generic web route."
2921
2945
  ]
2922
2946
  },
2923
2947
  {
@@ -3071,6 +3095,7 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
3071
3095
  "Ask whether the focus is a stay, a trip, a place, a timeline window, or a selected span.",
3072
3096
  "Ask for the time window, place, or movement item that makes the question concrete.",
3073
3097
  "Ask what they are trying to notice, preserve, or answer through that movement context.",
3098
+ "Choose the dedicated day, month, all-time, timeline, places, trip-detail, or selection route once the question shape is clear.",
3074
3099
  "Skip the meta lane question when the user already named the exact correction or review target and only one ambiguity remains.",
3075
3100
  "If the request is filling a missing-data gap, use a user-defined movement box rather than a raw stay or trip patch.",
3076
3101
  "If the request is repairing already-saved movement data, use the repair route that matches the saved object instead of treating it like a missing span.",
@@ -3104,6 +3129,7 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
3104
3129
  "Ask whether they need the flow contract, a run result, a published output, or a node result.",
3105
3130
  "Ask what the user is trying to learn, repair, or publish through that flow.",
3106
3131
  "If the user already named the flow and action clearly, skip the meta lane question and ask only for the missing run, node, or output scope.",
3132
+ "If the user wants a stable public input contract or published output, prefer those dedicated reads instead of detouring through run history first.",
3107
3133
  "If the user is still shaping a payload or edit, prefer flow detail or box catalog reads before asking for structured inputs.",
3108
3134
  "Prefer flow detail or published-output reads for stable contracts, and use run or node-result routes only when the user is asking about execution history or debugging.",
3109
3135
  "Route to the dedicated workbench route family once the execution lane is clear."
@@ -3853,6 +3879,61 @@ const AGENT_ONBOARDING_TOOL_INPUT_CATALOG = [
3853
3879
  example: '{"taskRunId":"run_123","actor":"aurel","note":"Stopping for now; blocked on feedback","closeoutNote":{"contentMarkdown":"Blocked on feedback from design before I can continue."}}'
3854
3880
  }
3855
3881
  ];
3882
+ const VALIDATION_ROUTE_TOOL_MAP = {
3883
+ "POST /api/v1/entities/search": "forge_search_entities",
3884
+ "POST /api/v1/entities/create": "forge_create_entities",
3885
+ "POST /api/v1/entities/update": "forge_update_entities",
3886
+ "POST /api/v1/entities/delete": "forge_delete_entities",
3887
+ "POST /api/v1/entities/restore": "forge_restore_entities",
3888
+ "POST /api/v1/tasks/:id/runs": "forge_start_task_run",
3889
+ "POST /api/v1/task-runs/:id/heartbeat": "forge_heartbeat_task_run",
3890
+ "POST /api/v1/task-runs/:id/focus": "forge_focus_task_run",
3891
+ "POST /api/v1/task-runs/:id/complete": "forge_complete_task_run",
3892
+ "POST /api/v1/task-runs/:id/release": "forge_release_task_run",
3893
+ "POST /api/tasks/:id/runs": "forge_start_task_run",
3894
+ "POST /api/task-runs/:id/heartbeat": "forge_heartbeat_task_run",
3895
+ "POST /api/task-runs/:id/focus": "forge_focus_task_run",
3896
+ "POST /api/task-runs/:id/complete": "forge_complete_task_run",
3897
+ "POST /api/task-runs/:id/release": "forge_release_task_run"
3898
+ };
3899
+ function formatValidationSummary(issues) {
3900
+ return issues
3901
+ .slice(0, 3)
3902
+ .map((issue) => `${issue.path}: ${issue.message}`)
3903
+ .join("; ");
3904
+ }
3905
+ function buildValidationHelp(method, routeUrl, issues) {
3906
+ const route = routeUrl || "unknown_route";
3907
+ const toolName = VALIDATION_ROUTE_TOOL_MAP[`${method.toUpperCase()} ${route}`];
3908
+ const toolInput = toolName
3909
+ ? AGENT_ONBOARDING_TOOL_INPUT_CATALOG.find((entry) => entry.toolName === toolName)
3910
+ : undefined;
3911
+ return {
3912
+ route,
3913
+ validationSummary: formatValidationSummary(issues),
3914
+ ...(toolInput
3915
+ ? {
3916
+ expectedShape: {
3917
+ toolName: toolInput.toolName,
3918
+ inputShape: toolInput.inputShape,
3919
+ requiredFields: [...toolInput.requiredFields],
3920
+ example: toolInput.example,
3921
+ notes: [...toolInput.notes]
3922
+ }
3923
+ }
3924
+ : {
3925
+ expectedShape: {
3926
+ inputShape: "See details[] for the rejected fields on this route.",
3927
+ requiredFields: [],
3928
+ example: null,
3929
+ notes: [
3930
+ "Retry with only the documented top-level keys for this route.",
3931
+ "Omit optional fields instead of sending null unless the route contract explicitly allows null."
3932
+ ]
3933
+ }
3934
+ })
3935
+ };
3936
+ }
3856
3937
  function buildAgentOnboardingPayload(request) {
3857
3938
  const origin = getRequestOrigin(request);
3858
3939
  return {
@@ -4198,10 +4279,12 @@ function buildAgentOnboardingPayload(request) {
4198
4279
  verifyCommands: [
4199
4280
  `curl -s ${origin}/api/v1/health`,
4200
4281
  "openclaw plugins install ./projects/forge/openclaw-plugin",
4282
+ "openclaw plugins info forge-openclaw-plugin",
4201
4283
  "openclaw gateway restart"
4202
4284
  ],
4203
4285
  configNotes: [
4204
4286
  "Localhost and Tailscale targets can usually use the operator-session path without a long-lived token.",
4287
+ "If your current OpenClaw build blocks the repo-local install because of the package scanner, keep the repo folder on plugins.load.paths and verify that plugins info still points at the local Forge source path before continuing.",
4205
4288
  "Use a distinct actor label such as Albert (claw) so OpenClaw-originated work stays obvious in Forge provenance.",
4206
4289
  "Create each agent as a Forge bot user, then use userId or userIds in tool inputs whenever the agent should focus on one human, one bot, or a specific collaboration slice."
4207
4290
  ]
@@ -4558,6 +4641,30 @@ function resolveScopedUserIds(query) {
4558
4641
  const unique = Array.from(new Set(values));
4559
4642
  return unique.length > 0 ? unique : undefined;
4560
4643
  }
4644
+ function attachMovementTimelineSleepOverlays(movement, userIds) {
4645
+ const rangeStart = movement.segments.reduce((earliest, segment) => {
4646
+ if (!earliest || Date.parse(segment.startedAt) < Date.parse(earliest)) {
4647
+ return segment.startedAt;
4648
+ }
4649
+ return earliest;
4650
+ }, null);
4651
+ const rangeEnd = movement.segments.reduce((latest, segment) => {
4652
+ if (!latest || Date.parse(segment.endedAt) > Date.parse(latest)) {
4653
+ return segment.endedAt;
4654
+ }
4655
+ return latest;
4656
+ }, null);
4657
+ return {
4658
+ ...movement,
4659
+ sleepOverlays: rangeStart && rangeEnd
4660
+ ? getSleepTimelineOverlaysForRange({
4661
+ startedAt: rangeStart,
4662
+ endedAt: rangeEnd,
4663
+ userIds
4664
+ })
4665
+ : []
4666
+ };
4667
+ }
4561
4668
  function readRequestedUserIdFromBody(body) {
4562
4669
  if (!body || typeof body !== "object" || Array.isArray(body)) {
4563
4670
  return undefined;
@@ -4581,13 +4688,17 @@ function syncEntityOwnerFromBody(options) {
4581
4688
  setEntityOwner(options.entityType, options.entityId, owner.id);
4582
4689
  }
4583
4690
  function buildV1Context(userIds) {
4584
- const goals = filterOwnedEntities("goal", listGoals(), userIds);
4585
- const tasks = filterOwnedEntities("task", listTasks(), userIds);
4586
- const habits = filterOwnedEntities("habit", listHabits(), userIds);
4587
4691
  const users = listUsers();
4588
- const dashboard = getDashboard({ userIds });
4589
- const selectedUsers = userIds && userIds.length > 0
4590
- ? users.filter((user) => userIds.includes(user.id))
4692
+ const validUserIdSet = new Set(users.map((user) => user.id));
4693
+ const scopedUserIds = userIds && userIds.length > 0
4694
+ ? userIds.filter((userId) => validUserIdSet.has(userId))
4695
+ : undefined;
4696
+ const goals = filterOwnedEntities("goal", listGoals(), scopedUserIds);
4697
+ const tasks = filterOwnedEntities("task", listTasks(), scopedUserIds);
4698
+ const habits = filterOwnedEntities("habit", listHabits(), scopedUserIds);
4699
+ const dashboard = getDashboard({ userIds: scopedUserIds });
4700
+ const selectedUsers = scopedUserIds && scopedUserIds.length > 0
4701
+ ? users.filter((user) => scopedUserIds.includes(user.id))
4591
4702
  : users;
4592
4703
  return {
4593
4704
  meta: {
@@ -4599,23 +4710,23 @@ function buildV1Context(userIds) {
4599
4710
  },
4600
4711
  metrics: buildGamificationProfile(goals, tasks, habits),
4601
4712
  dashboard,
4602
- overview: getOverviewContext(new Date(), { userIds }),
4603
- today: getTodayContext(new Date(), { userIds }),
4604
- risk: getRiskContext(new Date(), { userIds }),
4713
+ overview: getOverviewContext(new Date(), { userIds: scopedUserIds }),
4714
+ today: getTodayContext(new Date(), { userIds: scopedUserIds }),
4715
+ risk: getRiskContext(new Date(), { userIds: scopedUserIds }),
4605
4716
  goals,
4606
- projects: listProjectSummaries({ userIds }),
4717
+ projects: listProjectSummaries({ userIds: scopedUserIds }),
4607
4718
  tags: listTags(),
4608
4719
  tasks,
4609
4720
  habits,
4610
4721
  users,
4611
- strategies: listStrategies({ userIds }),
4722
+ strategies: listStrategies({ userIds: scopedUserIds }),
4612
4723
  userScope: {
4613
- selectedUserIds: userIds ?? [],
4724
+ selectedUserIds: scopedUserIds ?? [],
4614
4725
  selectedUsers
4615
4726
  },
4616
4727
  activeTaskRuns: listTaskRuns({ active: true, limit: 25 }),
4617
4728
  activity: dashboard.recentActivity,
4618
- lifeForce: buildLifeForcePayload(new Date(), userIds)
4729
+ lifeForce: buildLifeForcePayload(new Date(), scopedUserIds)
4619
4730
  };
4620
4731
  }
4621
4732
  function buildXpMetricsPayload() {
@@ -5045,6 +5156,10 @@ export async function buildServer(options = {}) {
5045
5156
  });
5046
5157
  app.setErrorHandler((error, request, reply) => {
5047
5158
  const validationIssues = error instanceof ZodError ? formatValidationIssues(error) : undefined;
5159
+ const routeUrl = request.routeOptions.url || request.url;
5160
+ const validationHelp = validationIssues
5161
+ ? buildValidationHelp(request.method, routeUrl, validationIssues)
5162
+ : undefined;
5048
5163
  const statusCode = isHttpError(error)
5049
5164
  ? error.statusCode
5050
5165
  : isManagerError(error)
@@ -5052,7 +5167,6 @@ export async function buildServer(options = {}) {
5052
5167
  : error instanceof ZodError
5053
5168
  ? 400
5054
5169
  : 500;
5055
- const routeUrl = request.routeOptions.url || request.url;
5056
5170
  if (!shouldSkipAutomaticDiagnosticRoute(routeUrl)) {
5057
5171
  try {
5058
5172
  recordDiagnosticLog({
@@ -5093,10 +5207,11 @@ export async function buildServer(options = {}) {
5093
5207
  ? "invalid_request"
5094
5208
  : "internal_error",
5095
5209
  error: validationIssues
5096
- ? "Request validation failed"
5210
+ ? `Request validation failed for ${request.method.toUpperCase()} ${routeUrl}. ${validationHelp?.validationSummary ?? ""}`.trim()
5097
5211
  : getErrorMessage(error),
5098
5212
  statusCode,
5099
5213
  ...(validationIssues ? { details: validationIssues } : {}),
5214
+ ...(validationHelp ?? {}),
5100
5215
  ...(isHttpError(error) && error.details ? error.details : {}),
5101
5216
  ...(isManagerError(error) && error.details ? error.details : {})
5102
5217
  });
@@ -5514,14 +5629,15 @@ export async function buildServer(options = {}) {
5514
5629
  });
5515
5630
  app.get("/api/v1/movement/timeline", async (request) => {
5516
5631
  const parsed = movementTimelineQuerySchema.parse(request.query ?? {});
5632
+ const userIds = parsed.userIds.length > 0
5633
+ ? parsed.userIds
5634
+ : (resolveScopedUserIds(request.query) ?? []);
5635
+ const movement = getMovementTimeline({
5636
+ ...parsed,
5637
+ userIds
5638
+ });
5517
5639
  return {
5518
- movement: getMovementTimeline({
5519
- ...parsed,
5520
- userIds: parsed.userIds.length > 0
5521
- ? parsed.userIds
5522
- : (resolveScopedUserIds(request.query) ??
5523
- [])
5524
- })
5640
+ movement: attachMovementTimelineSleepOverlays(movement, userIds)
5525
5641
  };
5526
5642
  });
5527
5643
  app.get("/api/v1/movement/settings", async (request) => ({
@@ -5790,12 +5906,13 @@ export async function buildServer(options = {}) {
5790
5906
  app.post("/api/v1/mobile/movement/timeline", async (request) => {
5791
5907
  const parsed = movementMobileTimelineSchema.parse(request.body ?? {});
5792
5908
  const pairing = requireValidPairing(parsed.sessionId, parsed.pairingToken);
5909
+ const movement = getMovementTimeline({
5910
+ before: parsed.before,
5911
+ limit: parsed.limit,
5912
+ userIds: [pairing.user_id]
5913
+ });
5793
5914
  return {
5794
- movement: getMovementTimeline({
5795
- before: parsed.before,
5796
- limit: parsed.limit,
5797
- userIds: [pairing.user_id]
5798
- })
5915
+ movement: attachMovementTimelineSleepOverlays(movement, [pairing.user_id])
5799
5916
  };
5800
5917
  });
5801
5918
  app.post("/api/v1/mobile/movement/boxes/:id/detail", async (request, reply) => {
@@ -838,6 +838,55 @@ function listSleepRows(userIds) {
838
838
  ORDER BY started_at DESC`)
839
839
  .all(...params);
840
840
  }
841
+ export function getSleepTimelineOverlaysForRange(input) {
842
+ const rangeStartMs = Date.parse(input.startedAt);
843
+ const rangeEndMs = Date.parse(input.endedAt);
844
+ if (Number.isNaN(rangeStartMs) ||
845
+ Number.isNaN(rangeEndMs) ||
846
+ rangeEndMs <= rangeStartMs) {
847
+ return [];
848
+ }
849
+ return listSleepRows(input.userIds)
850
+ .map((row) => mapSleepSession(row))
851
+ .filter((session) => {
852
+ const sessionStartMs = Date.parse(session.startedAt);
853
+ const sessionEndMs = Date.parse(session.endedAt);
854
+ if (Number.isNaN(sessionStartMs) || Number.isNaN(sessionEndMs)) {
855
+ return false;
856
+ }
857
+ return sessionStartMs < rangeEndMs && sessionEndMs > rangeStartMs;
858
+ })
859
+ .sort((left, right) => {
860
+ const startedDelta = Date.parse(left.startedAt) - Date.parse(right.startedAt);
861
+ if (startedDelta !== 0) {
862
+ return startedDelta;
863
+ }
864
+ const endedDelta = Date.parse(left.endedAt) - Date.parse(right.endedAt);
865
+ if (endedDelta !== 0) {
866
+ return endedDelta;
867
+ }
868
+ return left.id.localeCompare(right.id);
869
+ })
870
+ .map((session) => {
871
+ const derived = session.derived;
872
+ return {
873
+ id: session.id,
874
+ externalUid: session.externalUid,
875
+ startedAt: session.startedAt,
876
+ endedAt: session.endedAt,
877
+ localDateKey: session.localDateKey,
878
+ sourceTimezone: session.sourceTimezone,
879
+ asleepSeconds: session.asleepSeconds,
880
+ timeInBedSeconds: session.timeInBedSeconds,
881
+ sleepScore: session.sleepScore,
882
+ regularityScore: session.regularityScore,
883
+ efficiency: typeof derived?.efficiency === "number" ? derived.efficiency : null,
884
+ recoveryState: typeof derived?.recoveryState === "string"
885
+ ? derived.recoveryState
886
+ : null
887
+ };
888
+ });
889
+ }
841
890
  function listWorkoutRows(userIds) {
842
891
  const params = [];
843
892
  const where = userIds && userIds.length > 0
@@ -2780,10 +2780,9 @@ function updateMovementBoxOverrideState(id, input) {
2780
2780
  overridden_user_box_ids_json = ?,
2781
2781
  override_ranges_json = ?,
2782
2782
  is_overridden = ?,
2783
- is_fully_hidden = ?,
2784
- updated_at = ?
2783
+ is_fully_hidden = ?
2785
2784
  WHERE id = ?`)
2786
- .run(input.overrideCount, JSON.stringify(input.overriddenAutomaticBoxIds), input.trueStartedAt, input.trueEndedAt, input.overriddenStartedAt, input.overriddenEndedAt, input.overriddenByBoxId, JSON.stringify(input.overriddenUserBoxIds), JSON.stringify(input.overrideRanges), input.isOverridden ? 1 : 0, input.isFullyHidden ? 1 : 0, nowIso(), id);
2785
+ .run(input.overrideCount, JSON.stringify(input.overriddenAutomaticBoxIds), input.trueStartedAt, input.trueEndedAt, input.overriddenStartedAt, input.overriddenEndedAt, input.overriddenByBoxId, JSON.stringify(input.overriddenUserBoxIds), JSON.stringify(input.overrideRanges), input.isOverridden ? 1 : 0, input.isFullyHidden ? 1 : 0, id);
2787
2786
  }
2788
2787
  function recomputeMovementBoxOverrideState(userId) {
2789
2788
  const rows = listMovementBoxRows({ userIds: [userId] });
@@ -333,6 +333,18 @@ export function buildOpenApiDocument() {
333
333
  message: { type: "string" }
334
334
  }
335
335
  };
336
+ const validationExpectedShape = {
337
+ type: "object",
338
+ additionalProperties: true,
339
+ required: ["inputShape", "requiredFields", "notes"],
340
+ properties: {
341
+ toolName: { type: "string" },
342
+ inputShape: { type: "string" },
343
+ requiredFields: arrayOf({ type: "string" }),
344
+ example: { type: ["string", "null"] },
345
+ notes: arrayOf({ type: "string" })
346
+ }
347
+ };
336
348
  const errorResponse = {
337
349
  type: "object",
338
350
  additionalProperties: true,
@@ -341,7 +353,12 @@ export function buildOpenApiDocument() {
341
353
  code: { type: "string" },
342
354
  error: { type: "string" },
343
355
  statusCode: { type: "integer" },
344
- details: arrayOf({ $ref: "#/components/schemas/ValidationIssue" })
356
+ route: { type: "string" },
357
+ validationSummary: { type: "string" },
358
+ details: arrayOf({ $ref: "#/components/schemas/ValidationIssue" }),
359
+ expectedShape: {
360
+ $ref: "#/components/schemas/ValidationExpectedShape"
361
+ }
345
362
  }
346
363
  };
347
364
  const tag = {
@@ -4098,6 +4115,7 @@ export function buildOpenApiDocument() {
4098
4115
  components: {
4099
4116
  schemas: {
4100
4117
  ValidationIssue: validationIssue,
4118
+ ValidationExpectedShape: validationExpectedShape,
4101
4119
  ErrorResponse: errorResponse,
4102
4120
  Tag: tag,
4103
4121
  Goal: goal,
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useId, useRef, useState } from "react";
3
3
  import { CircleHelp } from "lucide-react";
4
- import { cn } from "@/lib/utils";
4
+ import { cn } from "../../lib/utils.js";
5
5
  export function FieldHint({ children, className }) {
6
6
  return _jsx("div", { className: cn("text-sm leading-6 text-white/50", className), children: children });
7
7
  }
@@ -1,5 +1,5 @@
1
- import { getEntityVisual, isEntityKind } from "@/lib/entity-visuals";
2
- import { KNOWLEDGE_GRAPH_HIERARCHY_LANES, KNOWLEDGE_GRAPH_HIERARCHY_ORDER, KNOWLEDGE_GRAPH_RELATION_FAMILY_LABELS, KNOWLEDGE_GRAPH_RELATION_LABELS, buildKnowledgeGraphNodeId } from "@/lib/knowledge-graph-types";
1
+ import { getEntityVisual, isEntityKind } from "./entity-visuals.js";
2
+ import { KNOWLEDGE_GRAPH_HIERARCHY_LANES, KNOWLEDGE_GRAPH_HIERARCHY_ORDER, KNOWLEDGE_GRAPH_RELATION_FAMILY_LABELS, KNOWLEDGE_GRAPH_RELATION_LABELS, buildKnowledgeGraphNodeId } from "./knowledge-graph-types.js";
3
3
  export function getKnowledgeGraphNodeVisual(node) {
4
4
  const kind = isEntityKind(node.entityKind) ? node.entityKind : "note";
5
5
  return getEntityVisual(kind);
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { forgeCustomThemeSchema, forgeThemePreferenceSchema } from "@/lib/theme-system";
2
+ import { forgeCustomThemeSchema, forgeThemePreferenceSchema } from "./theme-system.js";
3
3
  export const appLocaleSchema = z.enum(["en", "fr"]);
4
4
  export const inlineCreateNoteSchema = z.object({
5
5
  contentMarkdown: z.string().trim().min(1, "Note content is required"),
@@ -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.42",
5
+ "version": "0.2.44",
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.42",
3
+ "version": "0.2.44",
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",
@@ -44,17 +44,58 @@
44
44
  "openclaw": "2026.4.15"
45
45
  },
46
46
  "dependencies": {
47
+ "@dnd-kit/core": "^6.3.1",
48
+ "@dnd-kit/sortable": "^10.0.0",
49
+ "@dnd-kit/utilities": "^3.2.2",
47
50
  "@azure/msal-node": "^5.1.2",
48
51
  "@fastify/cors": "^10.0.1",
49
52
  "@fastify/multipart": "^9.4.0",
53
+ "@fontsource-variable/plus-jakarta-sans": "^5.2.8",
54
+ "@fontsource-variable/sora": "^5.2.8",
55
+ "@fontsource/space-grotesk": "^5.2.10",
56
+ "@hookform/resolvers": "^5.1.1",
57
+ "@mariozechner/pi-ai": "^0.66.1",
58
+ "@radix-ui/react-dialog": "^1.1.14",
59
+ "@reduxjs/toolkit": "^2.11.2",
50
60
  "@sinclair/typebox": "^0.34.48",
61
+ "@tanstack/react-query": "^5.80.10",
62
+ "@tanstack/react-table": "^8.21.3",
63
+ "@tanstack/react-virtual": "^3.13.12",
64
+ "@tauri-apps/api": "^2.8.0",
65
+ "@tiptap/react": "^2.15.0",
66
+ "@tiptap/starter-kit": "^2.15.0",
67
+ "@types/react-grid-layout": "^1.3.6",
68
+ "@xyflow/react": "^12.10.2",
51
69
  "adm-zip": "^0.5.17",
70
+ "bonjour-service": "^1.3.0",
71
+ "class-variance-authority": "^0.7.1",
72
+ "clsx": "^2.1.1",
73
+ "cron-parser": "^5.5.0",
52
74
  "fastify": "^5.8.5",
75
+ "framer-motion": "^12.16.0",
76
+ "graphology": "^0.26.0",
77
+ "graphology-layout": "^0.6.1",
78
+ "graphology-layout-forceatlas2": "^0.10.1",
79
+ "lucide-react": "^0.525.0",
53
80
  "node-ical": "^0.20.1",
81
+ "qrcode": "^1.5.4",
82
+ "react": "^19.1.0",
83
+ "react-arborist": "^3.4.3",
84
+ "react-dom": "^19.1.0",
85
+ "react-grid-layout": "^2.2.3",
86
+ "react-hook-form": "^7.57.0",
87
+ "react-is": "^19.2.5",
88
+ "react-redux": "^9.2.0",
89
+ "react-router-dom": "^7.13.1",
90
+ "recharts": "^3.1.0",
91
+ "sigma": "^3.0.2",
92
+ "tailwind-merge": "^3.3.1",
54
93
  "tsdav": "^2.1.8",
55
- "zod": "^3.25.67"
94
+ "zod": "^3.25.67",
95
+ "zustand": "^5.0.5"
56
96
  },
57
97
  "overrides": {
98
+ "basic-ftp": "^5.3.0",
58
99
  "axios": "^1.15.0",
59
100
  "follow-redirects": "^1.16.0",
60
101
  "hono": "4.12.14"