forge-openclaw-plugin 0.2.40 → 0.2.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.html CHANGED
@@ -13,7 +13,7 @@
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-46NL_IaJ.js"></script>
16
+ <script type="module" crossorigin src="/forge/assets/index-Fe5y4VWU.js"></script>
17
17
  <link rel="modulepreload" crossorigin href="/forge/assets/vendor-DK-mJFy6.js">
18
18
  <link rel="modulepreload" crossorigin href="/forge/assets/board-C3BJqvZu.js">
19
19
  <link rel="modulepreload" crossorigin href="/forge/assets/ui-CQzq3TE5.js">
@@ -42,6 +42,11 @@ export type ForgeCliRegistrarContext = {
42
42
  debug?(message: string): void;
43
43
  };
44
44
  };
45
+ export type ForgeCliCommandDescriptor = {
46
+ name: string;
47
+ description: string;
48
+ hasSubcommands?: boolean;
49
+ };
45
50
  export type ForgePluginServiceContext = {
46
51
  config?: unknown;
47
52
  workspaceDir?: string;
@@ -77,6 +82,7 @@ export type ForgePluginRegistrationApi = {
77
82
  }): void;
78
83
  registerCli?(registrar: (context: ForgeCliRegistrarContext) => void, options?: {
79
84
  commands?: string[];
85
+ descriptors?: ForgeCliCommandDescriptor[];
80
86
  }): void;
81
87
  registerService?(service: ForgeRegisteredService): void;
82
88
  };
@@ -1158,5 +1158,14 @@ export function registerForgePluginCli(api, config) {
1158
1158
  .action(async () => {
1159
1159
  console.log(JSON.stringify(await runRouteCheck(config), null, 2));
1160
1160
  });
1161
- }, { commands: ["forge"] });
1161
+ }, {
1162
+ commands: ["forge"],
1163
+ descriptors: [
1164
+ {
1165
+ name: "forge",
1166
+ description: "Inspect and operate Forge through the OpenClaw plugin",
1167
+ hasSubcommands: true
1168
+ }
1169
+ ]
1170
+ });
1162
1171
  }
@@ -62,7 +62,7 @@ import { registerWebRoutes } from "./web.js";
62
62
  import { createManagerRuntime } from "./managers/runtime.js";
63
63
  import { isManagerError } from "./managers/type-guards.js";
64
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";
65
- import { analyzeMovementUserBoxPreflight, createMovementUserBox, createMovementPlace, deleteMovementUserBox, getMovementAllTimeSummary, getMovementBoxDetail, getMovementDayDetail, getMovementMobileBootstrap, getMovementTimeline, getMovementSelectionAggregate, getMovementSettings, getMovementTripDetail, getMovementMonthSummary, invalidateAutomaticMovementBox, listMovementPlaces, movementAutomaticBoxInvalidateSchema, movementMobileBootstrapSchema, movementMobilePlaceMutationSchema, 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";
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";
68
68
  const COMPATIBILITY_SUNSET = "transitional-node";
@@ -2654,7 +2654,8 @@ const AGENT_ONBOARDING_ENTITY_CATALOG = [
2654
2654
  ],
2655
2655
  searchHints: [
2656
2656
  "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.",
2657
- "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."
2657
+ "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.",
2658
+ "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."
2658
2659
  ],
2659
2660
  fieldGuide: []
2660
2661
  }),
@@ -2668,7 +2669,8 @@ const AGENT_ONBOARDING_ENTITY_CATALOG = [
2668
2669
  ],
2669
2670
  searchHints: [
2670
2671
  "Clarify whether the user wants explanation, durable model changes, or a real-time tired or recovered signal before choosing the route.",
2671
- "Separate durable profile assumptions, weekday-template edits, and right-now fatigue signals before choosing the mutation path."
2672
+ "Separate durable profile assumptions, weekday-template edits, and right-now fatigue signals before choosing the mutation path.",
2673
+ "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."
2672
2674
  ],
2673
2675
  fieldGuide: []
2674
2676
  }),
@@ -2682,7 +2684,8 @@ const AGENT_ONBOARDING_ENTITY_CATALOG = [
2682
2684
  ],
2683
2685
  searchHints: [
2684
2686
  "Clarify whether the user wants flow discovery, editing, execution, published output, run inspection, or node-level output before choosing the route.",
2685
- "Distinguish flow contract, published output, run history, and latest-node-output questions before reaching for a route."
2687
+ "Distinguish flow contract, published output, run history, latest-node-output, and chat follow-up questions before reaching for a route.",
2688
+ "If the user is still deciding how to run or edit a flow, read flow detail or the box catalog before asking them for payload structure."
2686
2689
  ],
2687
2690
  fieldGuide: []
2688
2691
  }),
@@ -3061,7 +3064,7 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
3061
3064
  },
3062
3065
  {
3063
3066
  focus: "movement",
3064
- openingQuestion: "Are you trying to understand where you stayed and traveled, change one stay or trip, or answer a question about your movement behavior?",
3067
+ openingQuestion: "What are you trying to understand, correct, or preserve about where you stayed and traveled?",
3065
3068
  coachingGoal: "Clarify whether the user wants a time-in-place query, travel-history review, a missing-gap overlay, one stay or trip change, one place summary, or a link before choosing the dedicated movement route.",
3066
3069
  askSequence: [
3067
3070
  "Ask whether the user is trying to query behavior, add something manually, update an existing movement item, or link movement to another Forge entity.",
@@ -3071,6 +3074,7 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
3071
3074
  "Skip the meta lane question when the user already named the exact correction or review target and only one ambiguity remains.",
3072
3075
  "If the request is filling a missing-data gap, use a user-defined movement box rather than a raw stay or trip patch.",
3073
3076
  "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.",
3077
+ "If the request is removing an already-saved correction, identify whether it is a user-defined box, recorded stay, recorded trip, or trip point before choosing the delete route.",
3074
3078
  "When the user wants place creation or place cleanup, use the dedicated movement place routes rather than a generic entity mutation.",
3075
3079
  "When the user already gave a concrete correction like 'I stayed home during that missing block', confirm only the interval or place if needed, then create the overlay and read the timeline back."
3076
3080
  ]
@@ -3086,12 +3090,13 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
3086
3090
  "Ask whether the user is describing a stable weekly shape or just how today feels when the lane is still blurred.",
3087
3091
  "If the user already named the life-force lane clearly, skip the meta lane question and ask only for the specific weekday, profile field, or signal that still matters.",
3088
3092
  "If the request is about a durable baseline, use profile or weekday-template mutation rather than a fatigue signal; if it is about right now, prefer the fatigue-signal route.",
3093
+ "If the user wants to see what changed after a write, read the overview back instead of leaving the result implicit.",
3089
3094
  "Route to the dedicated life-force path once the lane is clear."
3090
3095
  ]
3091
3096
  },
3092
3097
  {
3093
3098
  focus: "workbench",
3094
- openingQuestion: "Are you trying to inspect a flow, change it, run it, or inspect one run's outputs?",
3099
+ openingQuestion: "What are you trying to inspect, change, run, or publish through Workbench?",
3095
3100
  coachingGoal: "Clarify whether the user wants flow discovery, editing, execution, run history, published outputs, or node-level inspection before using the dedicated workbench route family.",
3096
3101
  askSequence: [
3097
3102
  "Ask whether the job is flow discovery, one flow edit, execution, run history, published output, node-level inspection, or latest-node-output lookup.",
@@ -3099,6 +3104,7 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
3099
3104
  "Ask whether they need the flow contract, a run result, a published output, or a node result.",
3100
3105
  "Ask what the user is trying to learn, repair, or publish through that flow.",
3101
3106
  "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.",
3107
+ "If the user is still shaping a payload or edit, prefer flow detail or box catalog reads before asking for structured inputs.",
3102
3108
  "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.",
3103
3109
  "Route to the dedicated workbench route family once the execution lane is clear."
3104
3110
  ]
@@ -4065,25 +4071,32 @@ function buildAgentOnboardingPayload(request) {
4065
4071
  allTime: "/api/v1/movement/all-time",
4066
4072
  timeline: "/api/v1/movement/timeline",
4067
4073
  places: "/api/v1/movement/places",
4074
+ boxDetail: "/api/v1/movement/boxes/:id",
4068
4075
  tripDetail: "/api/v1/movement/trips/:id",
4069
4076
  selection: "/api/v1/movement/selection",
4070
4077
  settings: "/api/v1/movement/settings"
4071
4078
  },
4072
4079
  writeRoutes: {
4080
+ settingsUpdate: "/api/v1/movement/settings",
4073
4081
  placeCreate: "/api/v1/movement/places",
4074
4082
  placeUpdate: "/api/v1/movement/places/:id",
4075
4083
  userBoxCreate: "/api/v1/movement/user-boxes",
4076
4084
  userBoxPreflight: "/api/v1/movement/user-boxes/preflight",
4077
4085
  userBoxUpdate: "/api/v1/movement/user-boxes/:id",
4086
+ userBoxDelete: "/api/v1/movement/user-boxes/:id",
4078
4087
  automaticBoxInvalidate: "/api/v1/movement/automatic-boxes/:id/invalidate",
4079
4088
  stayUpdate: "/api/v1/movement/stays/:id",
4089
+ stayDelete: "/api/v1/movement/stays/:id",
4080
4090
  tripUpdate: "/api/v1/movement/trips/:id",
4081
- tripPointUpdate: "/api/v1/movement/trips/:id/points/:pointId"
4091
+ tripDelete: "/api/v1/movement/trips/:id",
4092
+ tripPointUpdate: "/api/v1/movement/trips/:id/points/:pointId",
4093
+ tripPointDelete: "/api/v1/movement/trips/:id/points/:pointId"
4082
4094
  },
4083
4095
  notes: [
4084
4096
  "Movement is not a normal batch CRUD entity family. It is a dedicated record of stays and trips: a stay means the user remained in the same place for a span of time, and a trip means they traveled between places.",
4085
4097
  "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.",
4086
4098
  "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.",
4099
+ "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.",
4087
4100
  "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."
4088
4101
  ]
4089
4102
  },
@@ -4101,6 +4114,7 @@ function buildAgentOnboardingPayload(request) {
4101
4114
  "Life Force is a focused domain surface, not a batch CRUD entity type.",
4102
4115
  "Use GET /api/v1/life-force for the current overview payload with stats, drains, recommendations, and current-curve state.",
4103
4116
  "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.",
4117
+ "If the user is asking what changed after a profile, template, or fatigue write, read the overview back so the effect stays visible.",
4104
4118
  "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."
4105
4119
  ]
4106
4120
  },
@@ -4129,6 +4143,7 @@ function buildAgentOnboardingPayload(request) {
4129
4143
  notes: [
4130
4144
  "Workbench is a dedicated execution surface, not a batch CRUD entity family.",
4131
4145
  "Use the flow routes when the agent needs stable public input contracts, published outputs, node-level results, or reusable execution history.",
4146
+ "If the user is still figuring out inputs or editable structure, read flow detail or box catalog before asking them to author a payload from memory.",
4132
4147
  "Prefer the dedicated output and node-result routes over reverse-engineering raw traces.",
4133
4148
  "If the user already named the flow and wants one output or one run, skip the broad lane question and ask only for the missing run, node, or output scope."
4134
4149
  ]
@@ -4330,8 +4345,8 @@ function buildAgentOnboardingPayload(request) {
4330
4345
  saveSuggestionPlacement: "end_of_message",
4331
4346
  saveSuggestionTone: "gentle_optional",
4332
4347
  maxQuestionsPerTurn: 1,
4333
- psycheExplorationRule: "When a Psyche entity needs understanding first, begin with one exploratory question before any working formulation, replacement belief, suggested title, or save pitch. Keep the opening reflection to one or two short sentences, stay in plain prose instead of bullets or numbered lists, keep that first reply short, do not mention Forge search or save structure yet, avoid colons or list-shaped phrasing, prefer what/when/how over why until the experience is grounded, wait for the user's answer before offering a fuller formulation, ask permission before moving from charged exploration into naming or challenge when needed, do not widen into adjacent entities until the current one has a working sentence the user recognizes, and once the lived experience is coherent stop deepening and help the user name it cleanly. If the user accepts the wording, move toward the save instead of reopening deeper exploration.",
4334
- specializedSurfaceRule: "For Movement, Life Force, and Workbench, clarify the lane first, then name the dedicated route family in plain language and do not guess at a generic CRUD path. When the user already named a precise correction or review target, confirm only the route-selecting detail that is still missing. The canonical runtime routes stay under /api/v1/*, and the OpenClaw HTTP mirror exposes the same families under /forge/v1/movement, /forge/v1/life-force, and /forge/v1/workbench.",
4348
+ psycheExplorationRule: "When a Psyche entity needs understanding first, begin with one exploratory question before any working formulation, replacement belief, suggested title, or save pitch. Keep the opening reflection to one or two short sentences, stay in plain prose instead of bullets or numbered lists, keep that first reply short, do not mention Forge search or save structure yet, avoid colons or list-shaped phrasing, prefer what/when/how over why until the experience is grounded, wait for the user's answer before offering a fuller formulation, ask permission before moving from charged exploration into naming or challenge when needed, do not widen into adjacent entities until the current one has a working sentence the user recognizes, and once the lived experience is coherent stop deepening and help the user name it cleanly. When the user is updating a Psyche record because of one fresh episode, anchor in that episode before renaming the durable formulation. If the user accepts the wording, move toward the save instead of reopening deeper exploration.",
4349
+ specializedSurfaceRule: "For Movement, Life Force, and Workbench, clarify the lane first, then name the dedicated route family in plain language and do not guess at a generic CRUD path. When the user already named a precise correction or review target, confirm only the route-selecting detail that is still missing. After a concrete specialized-surface correction, read the relevant specialized view back when the user is trying to understand the result rather than just store it. The canonical runtime routes stay under /api/v1/*, and the OpenClaw HTTP mirror exposes the same families under /forge/v1/movement, /forge/v1/life-force, and /forge/v1/workbench.",
4335
4350
  reviewShortcutRule: "When the user is reviewing or correcting an existing record, narrow the saved object, timeframe, or route family first. Do not reopen the whole intake unless the user is actually redefining the record.",
4336
4351
  readModelWriteRule: "Self-observation is note-backed and should be written through observed notes with frontmatter.observedAt. Sleep and workout sessions stay on batch CRUD by default; use the reflective review helpers only when enriching one already-known record after review.",
4337
4352
  psycheOpeningQuestionRule: "Prefer a concrete opening question tied to the entity: ask when the value mattered, what happened the last time the pattern appeared, what cue or body signal came first before the behavior, what the belief starts saying about self or outcome, what feels most at risk inside the mode, what the part is trying to get the user to do or stop doing, or where the shift began in the incident. Reflect briefly before the question, choose one follow-up lane at a time, say what is becoming clearer before the next deeper question, and if several Psyche entities are visible hold the adjacent ones lightly until the main container is clear.",
@@ -5868,9 +5883,20 @@ export async function buildServer(options = {}) {
5868
5883
  };
5869
5884
  });
5870
5885
  app.patch("/api/v1/mobile/movement/stays/:id", async (request, reply) => {
5871
- reply.code(409);
5886
+ const parsed = movementMobileStayPatchSchema.parse(request.body ?? {});
5887
+ const pairing = requireValidPairing(parsed.sessionId, parsed.pairingToken);
5888
+ const { id } = request.params;
5889
+ const stay = updateMovementStay(id, parsed.patch, {
5890
+ actor: "Forge Companion",
5891
+ source: "system"
5892
+ }, { userId: pairing.user_id });
5893
+ if (!stay) {
5894
+ reply.code(404);
5895
+ return { error: "Movement stay not found" };
5896
+ }
5872
5897
  return {
5873
- error: "Recorded stays are immutable in product UI. Create or edit a user-defined movement box instead."
5898
+ stay,
5899
+ place: stay.place
5874
5900
  };
5875
5901
  });
5876
5902
  app.patch("/api/v1/mobile/movement/trips/:id", async (request, reply) => {
@@ -130,7 +130,7 @@ const linkedPersonSchema = z.object({
130
130
  label: z.string().trim().min(1)
131
131
  });
132
132
  const movementPlaceInputSchema = z.object({
133
- externalUid: z.string().trim().min(1).default(""),
133
+ externalUid: z.string().trim().default(""),
134
134
  label: z.string().trim().min(1),
135
135
  aliases: z.array(z.string().trim()).default([]),
136
136
  latitude: z.number().finite(),
@@ -1011,6 +1011,61 @@ function mapMovementPlace(row) {
1011
1011
  : null
1012
1012
  };
1013
1013
  }
1014
+ function readMovementPlaceLearningState(row) {
1015
+ const metadata = safeJsonParse(row.metadata_json, {});
1016
+ const sampleCount = Number(metadata.distributionSampleCount);
1017
+ const averageLatitude = Number(metadata.distributionAverageLatitude);
1018
+ const averageLongitude = Number(metadata.distributionAverageLongitude);
1019
+ const maxDistanceMeters = Number(metadata.distributionMaxDistanceMeters);
1020
+ return {
1021
+ sampleCount: Number.isFinite(sampleCount) && sampleCount >= 1 ? Math.floor(sampleCount) : 1,
1022
+ averageLatitude: Number.isFinite(averageLatitude) ? averageLatitude : row.latitude,
1023
+ averageLongitude: Number.isFinite(averageLongitude) ? averageLongitude : row.longitude,
1024
+ maxDistanceMeters: Number.isFinite(maxDistanceMeters) && maxDistanceMeters >= 0
1025
+ ? maxDistanceMeters
1026
+ : 0
1027
+ };
1028
+ }
1029
+ function learnMovementPlaceObservation(input) {
1030
+ const existing = getDatabase()
1031
+ .prepare(`SELECT *
1032
+ FROM movement_places
1033
+ WHERE id = ?`)
1034
+ .get(input.placeId);
1035
+ if (!existing) {
1036
+ return null;
1037
+ }
1038
+ const metadata = safeJsonParse(existing.metadata_json, {});
1039
+ const learning = readMovementPlaceLearningState(existing);
1040
+ const nextSampleCount = learning.sampleCount + 1;
1041
+ const nextAverageLatitude = (learning.averageLatitude * learning.sampleCount + input.observation.latitude) /
1042
+ nextSampleCount;
1043
+ const nextAverageLongitude = (learning.averageLongitude * learning.sampleCount + input.observation.longitude) /
1044
+ nextSampleCount;
1045
+ const observationDistance = haversineDistanceMeters({ latitude: nextAverageLatitude, longitude: nextAverageLongitude }, input.observation);
1046
+ const nextMaxDistanceMeters = Math.max(learning.maxDistanceMeters, observationDistance);
1047
+ const nextRadiusMeters = Math.min(2000, Math.max(existing.radius_meters, Math.ceil(nextMaxDistanceMeters + 25)));
1048
+ const nextMetadata = {
1049
+ ...metadata,
1050
+ distributionSampleCount: String(nextSampleCount),
1051
+ distributionAverageLatitude: nextAverageLatitude.toFixed(6),
1052
+ distributionAverageLongitude: nextAverageLongitude.toFixed(6),
1053
+ distributionMaxDistanceMeters: nextMaxDistanceMeters.toFixed(1),
1054
+ distributionLastObservedAt: nowIso()
1055
+ };
1056
+ getDatabase()
1057
+ .prepare(`UPDATE movement_places
1058
+ SET latitude = ?,
1059
+ longitude = ?,
1060
+ radius_meters = ?,
1061
+ metadata_json = ?,
1062
+ updated_at = ?
1063
+ WHERE id = ?`)
1064
+ .run(nextAverageLatitude, nextAverageLongitude, nextRadiusMeters, JSON.stringify(nextMetadata), nowIso(), input.placeId);
1065
+ return mapMovementPlace(getDatabase()
1066
+ .prepare(`SELECT * FROM movement_places WHERE id = ?`)
1067
+ .get(input.placeId));
1068
+ }
1014
1069
  function mapMovementStay(row, placesById) {
1015
1070
  const note = row.published_note_id ? getNoteById(row.published_note_id) : null;
1016
1071
  const metrics = safeJsonParse(row.metrics_json, {});
@@ -1637,6 +1692,9 @@ function upsertMovementSettings(userId, input) {
1637
1692
  }
1638
1693
  function upsertMovementPlaceInternal(input) {
1639
1694
  const parsed = movementPlaceInputSchema.parse(input.place);
1695
+ const externalUid = parsed.externalUid.trim().length > 0
1696
+ ? parsed.externalUid.trim()
1697
+ : `movement-place-${randomUUID().replaceAll("-", "").slice(0, 20)}`;
1640
1698
  const now = nowIso();
1641
1699
  const existing = input.id && input.id.trim().length > 0
1642
1700
  ? getDatabase()
@@ -1644,14 +1702,14 @@ function upsertMovementPlaceInternal(input) {
1644
1702
  FROM movement_places
1645
1703
  WHERE id = ?`)
1646
1704
  .get(input.id)
1647
- : parsed.externalUid.trim().length > 0
1705
+ : externalUid.length > 0
1648
1706
  ? getDatabase()
1649
1707
  .prepare(`SELECT *
1650
1708
  FROM movement_places
1651
1709
  WHERE user_id = ?
1652
1710
  AND source = ?
1653
1711
  AND external_uid = ?`)
1654
- .get(input.userId, input.source, parsed.externalUid)
1712
+ .get(input.userId, input.source, externalUid)
1655
1713
  : undefined;
1656
1714
  const id = existing?.id ?? input.id ?? `mpl_${randomUUID().replaceAll("-", "").slice(0, 10)}`;
1657
1715
  getDatabase()
@@ -1677,7 +1735,7 @@ function upsertMovementPlaceInternal(input) {
1677
1735
  metadata_json = excluded.metadata_json,
1678
1736
  source = excluded.source,
1679
1737
  updated_at = excluded.updated_at`)
1680
- .run(id, parsed.externalUid, input.userId, parsed.label, JSON.stringify(uniqStrings(parsed.aliases)), parsed.latitude, parsed.longitude, parsed.radiusMeters, JSON.stringify(canonicalizeMovementCategoryTags(parsed.categoryTags)), parsed.visibility, parsed.wikiNoteId, JSON.stringify(parsed.linkedEntities), JSON.stringify(parsed.linkedPeople), JSON.stringify(parsed.metadata), input.source, existing?.created_at ?? now, now);
1738
+ .run(id, externalUid, input.userId, parsed.label, JSON.stringify(uniqStrings(parsed.aliases)), parsed.latitude, parsed.longitude, parsed.radiusMeters, JSON.stringify(canonicalizeMovementCategoryTags(parsed.categoryTags)), parsed.visibility, parsed.wikiNoteId, JSON.stringify(parsed.linkedEntities), JSON.stringify(parsed.linkedPeople), JSON.stringify(parsed.metadata), input.source, existing?.created_at ?? now, now);
1681
1739
  syncPlaceWikiMetadata(id);
1682
1740
  return mapMovementPlace(getDatabase()
1683
1741
  .prepare(`SELECT * FROM movement_places WHERE id = ?`)
@@ -3868,6 +3926,8 @@ export function updateMovementStay(stayId, patch, context, options = {}) {
3868
3926
  ...safeJsonParse(existing.metadata_json, {}),
3869
3927
  ...(parsed.metadata ?? {})
3870
3928
  };
3929
+ const shouldLearnPlaceObservation = resolvedPlace !== null &&
3930
+ (hasOwn(parsed, "placeId") || hasOwn(parsed, "placeExternalUid"));
3871
3931
  getDatabase()
3872
3932
  .prepare(`UPDATE movement_stays
3873
3933
  SET place_id = ?,
@@ -3886,6 +3946,17 @@ export function updateMovementStay(stayId, patch, context, options = {}) {
3886
3946
  WHERE id = ?`)
3887
3947
  .run(resolvedPlace?.id ?? null, parsed.label ?? parsed.placeLabel ?? existing.label, parsed.status ?? existing.status, parsed.classification ?? existing.classification, startedAt, endedAt, parsed.centerLatitude ?? existing.center_latitude, parsed.centerLongitude ?? existing.center_longitude, parsed.radiusMeters ?? existing.radius_meters, parsed.sampleCount ?? existing.sample_count, JSON.stringify(metrics), JSON.stringify(metadata), nowIso(), stayId);
3888
3948
  reconcileMovementOverlapValidation(existing.user_id);
3949
+ let learnedPlace = resolvedPlace ? mapMovementPlace(resolvedPlace) : null;
3950
+ if (resolvedPlace && shouldLearnPlaceObservation) {
3951
+ learnedPlace =
3952
+ learnMovementPlaceObservation({
3953
+ placeId: resolvedPlace.id,
3954
+ observation: {
3955
+ latitude: parsed.centerLatitude ?? existing.center_latitude,
3956
+ longitude: parsed.centerLongitude ?? existing.center_longitude
3957
+ }
3958
+ }) ?? learnedPlace;
3959
+ }
3889
3960
  const places = listMovementPlaceRows([existing.user_id]).map(mapMovementPlace);
3890
3961
  const placesById = new Map(places.map((place) => [place.id, place]));
3891
3962
  const updated = mapMovementStay(getDatabase()
@@ -3906,7 +3977,10 @@ export function updateMovementStay(stayId, patch, context, options = {}) {
3906
3977
  durationSeconds: updated.durationSeconds
3907
3978
  }
3908
3979
  });
3909
- return updated;
3980
+ return {
3981
+ ...updated,
3982
+ place: learnedPlace && updated.placeId === learnedPlace.id ? learnedPlace : updated.place
3983
+ };
3910
3984
  }
3911
3985
  export function updateMovementTrip(tripId, patch, context, options = {}) {
3912
3986
  const existing = getDatabase()
@@ -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.40",
5
+ "version": "0.2.42",
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.40",
3
+ "version": "0.2.42",
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",
@@ -141,6 +141,7 @@ Psyche interview rule:
141
141
  - Sound professionally warm and therapist-like: grounded, accurate, reflective, and intentional, not clinical, vague, or lecture-like.
142
142
  - After the first real answer, choose one follow-up lane at a time: situation, sequence, meaning, protection, cost, longing/value, or tentative name.
143
143
  - For Psyche updates, start with what feels newly true, newly visible, or newly inaccurate, then ask what should stay true before changing the formulation.
144
+ - If a fresh episode is what made a Psyche update visible, anchor in that episode before renaming the durable belief, pattern, mode, or value.
144
145
  - 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.
145
146
  - In that first exploratory turn, keep the reflection to one or two short sentences, avoid numbered lists or schema dumps, and wait for the user's answer before offering a fuller formulation.
146
147
  - In that first exploratory turn, stay in plain prose, end with one question, and do not mention Forge fields or save formatting yet unless the user interrupts to save immediately.
@@ -386,6 +387,7 @@ Use the dedicated domain routes for specialized surfaces that are not simple bat
386
387
  `/api/v1/workbench/flows/:id/output` for published outputs, and the run/node routes
387
388
  under `/api/v1/workbench/flows/:id` for run history and node-level inspection.
388
389
  - If you are unsure which specialized route family applies, check `forge_get_agent_onboarding` and use its `entityRouteModel.specializedDomainSurfaces` section before guessing.
390
+ - 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.
389
391
 
390
392
  Use live work tools for `task_run`:
391
393
  `forge_log_work`, `forge_start_task_run`, `forge_heartbeat_task_run`, `forge_focus_task_run`, `forge_complete_task_run`, `forge_release_task_run`
@@ -1016,6 +1016,11 @@ Direct action rules:
1016
1016
  traveled.
1017
1017
  - Use raw `PATCH /api/v1/movement/stays/:id` or `/api/v1/movement/trips/:id` only for
1018
1018
  editing an already-recorded stay or trip, not for filling a missing span.
1019
+ - If the user wants to undo or remove one manual overlay, delete the saved
1020
+ user-defined box instead of patching a recorded stay or trip.
1021
+ - If the user wants to inspect one already-saved movement correction before editing
1022
+ it, read the box detail first so the follow-up write stays grounded in the saved
1023
+ object.
1019
1024
  - When the user has already given the real answer, for example "I stayed home during
1020
1025
  that missing block", do not ask a broad review question again. Confirm only the
1021
1026
  interval or place if that is still ambiguous, then act.
@@ -1045,14 +1050,20 @@ Lane-to-route map:
1045
1050
  `/api/v1/movement/places` or `/api/v1/movement/places/:id`
1046
1051
  - inspect one trip:
1047
1052
  `/api/v1/movement/trips/:id`
1053
+ - inspect one saved movement box before repairing it:
1054
+ `/api/v1/movement/boxes/:id`
1048
1055
  - fill a missing span:
1049
1056
  `/api/v1/movement/user-boxes/preflight` then `/api/v1/movement/user-boxes`
1050
1057
  - repair or revise one saved overlay:
1051
1058
  `/api/v1/movement/user-boxes/:id`
1059
+ - delete one saved overlay:
1060
+ `DELETE /api/v1/movement/user-boxes/:id`
1052
1061
  - repair one recorded automatic box:
1053
1062
  `/api/v1/movement/automatic-boxes/:id/invalidate`
1054
1063
  - edit an already-recorded stay, trip, or trip point:
1055
1064
  `/api/v1/movement/stays/:id`, `/api/v1/movement/trips/:id`, or `/api/v1/movement/trips/:id/points/:pointId`
1065
+ - delete an already-recorded stay, trip, or trip point:
1066
+ `DELETE /api/v1/movement/stays/:id`, `DELETE /api/v1/movement/trips/:id`, or `DELETE /api/v1/movement/trips/:id/points/:pointId`
1056
1067
 
1057
1068
  Ready to act when:
1058
1069
 
@@ -1063,7 +1074,7 @@ Ready to act when:
1063
1074
 
1064
1075
  Preferred opening question:
1065
1076
 
1066
- - "What are you trying to make clearer or correct about where you stayed and traveled?"
1077
+ - "What are you trying to understand, correct, or preserve about where you stayed and traveled?"
1067
1078
 
1068
1079
  ## Life Force
1069
1080
 
@@ -1109,6 +1120,8 @@ Direct action rules:
1109
1120
  template instead of editing the profile.
1110
1121
  - If the user is describing right-now depletion or recovery, post a fatigue signal and
1111
1122
  then read the overview back if they want to see the updated picture.
1123
+ - After a profile or weekday-template change, read the overview back when the user is
1124
+ trying to understand the practical impact of the change, not just store it silently.
1112
1125
 
1113
1126
  Ready to act when:
1114
1127
 
@@ -1118,7 +1131,7 @@ Ready to act when:
1118
1131
 
1119
1132
  Preferred opening question:
1120
1133
 
1121
- - "What feels most off, important, or worth tracking in your energy picture right now?"
1134
+ - "What feels most off, important, or worth understanding in your energy picture right now?"
1122
1135
 
1123
1136
  ## Workbench
1124
1137
 
@@ -1153,6 +1166,8 @@ Lane-to-route map:
1153
1166
  `/api/v1/workbench/flows/:id/run`
1154
1167
  - run from a payload-first contract:
1155
1168
  `/api/v1/workbench/run`
1169
+ - send one follow-up message into a saved flow chat:
1170
+ `/api/v1/workbench/flows/:id/chat`
1156
1171
  - inspect published output or run history:
1157
1172
  `/api/v1/workbench/flows/:id/output` or `/api/v1/workbench/flows/:id/runs`
1158
1173
  - inspect one run or node result:
@@ -1173,6 +1188,8 @@ Direct action rules:
1173
1188
  `/api/v1/workbench/run`.
1174
1189
  - If the user wants one node's latest successful output, do not browse old runs first
1175
1190
  unless they explicitly want historical debugging.
1191
+ - If the user wants to understand what inputs a flow can accept before editing or
1192
+ running it, read the box catalog or flow detail before asking for a payload.
1176
1193
 
1177
1194
  Ready to act when:
1178
1195
 
@@ -1182,7 +1199,7 @@ Ready to act when:
1182
1199
 
1183
1200
  Preferred opening question:
1184
1201
 
1185
- - "What are you trying to inspect a flow, change it, run it, or publish from Workbench?"
1202
+ - "What are you trying to inspect, change, run, or publish through Workbench?"
1186
1203
 
1187
1204
  ## Preference Catalog
1188
1205
 
@@ -350,6 +350,9 @@ If the entity is already clear:
350
350
 
351
351
  - reflect briefly
352
352
  - name the core meaning in the user's language
353
+ - if the user already gave a serviceable belief sentence, mode name, value phrase, or
354
+ pattern title, stay inside that wording long enough to reflect the live stake before
355
+ you ask for the one missing structural detail
353
356
  - keep the user's own wording when it is already serviceable and only refine one
354
357
  phrase if needed
355
358
  - ask only for the one missing structural detail
@@ -376,6 +379,8 @@ If the entity is not yet clear:
376
379
  replacing it with a more polished label unless they ask for help wording it.
377
380
  - When the user says the current wording misses, say what seems newly true before you
378
381
  ask whether the old name still fits.
382
+ - If a recent charged episode is what made the update visible, anchor in that episode
383
+ before you rename the durable belief, pattern, mode, or value.
379
384
  - If another belief, value, pattern, mode, or note becomes visible, name it gently but
380
385
  do not switch containers unless the user wants to.
381
386
 
@@ -386,9 +391,11 @@ from scratch.
386
391
 
387
392
  1. Ask what feels newly true, newly visible, or newly inaccurate.
388
393
  2. Ask what still feels essentially right and should not be lost.
389
- 3. Re-check the name only if the old wording no longer fits the lived experience.
390
- 4. Ask for one concrete recent example if the update is abstract or sweeping.
391
- 5. Then change only the parts the new understanding actually affects.
394
+ 3. If the update was triggered by one recent charged episode, anchor in that episode
395
+ before you re-check the durable formulation.
396
+ 4. Re-check the name only if the old wording no longer fits the lived experience.
397
+ 5. Ask for one concrete recent example if the update is abstract or sweeping.
398
+ 6. Then change only the parts the new understanding actually affects.
392
399
 
393
400
  If the update is mostly about wording, offer one revised sentence yourself and ask
394
401
  whether it lands more accurately before you reopen the whole exploration.