forge-openclaw-plugin 0.2.42 → 0.2.43

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 (27) 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-Bqqi_RSB.js +91 -0
  5. package/dist/assets/index-Bqqi_RSB.js.map +1 -0
  6. package/dist/assets/index-IrxlatFN.css +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/server/server/src/app.js +58 -26
  17. package/dist/server/server/src/health.js +49 -0
  18. package/dist/server/src/components/ui/info-tooltip.js +1 -1
  19. package/dist/server/src/lib/knowledge-graph.js +2 -2
  20. package/dist/server/src/lib/schemas.js +1 -1
  21. package/openclaw.plugin.json +1 -1
  22. package/package.json +43 -2
  23. package/skills/forge-openclaw/entity_conversation_playbooks.md +9 -0
  24. package/skills/forge-openclaw/psyche_entity_playbooks.md +8 -0
  25. package/dist/assets/index-Dj8vB0vh.css +0 -1
  26. package/dist/assets/index-Fe5y4VWU.js +0 -91
  27. 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-Bqqi_RSB.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-IrxlatFN.css">
24
24
  </head>
25
25
  <body class="bg-canvas text-ink antialiased">
26
26
  <div id="root"></div>
@@ -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";
@@ -4198,10 +4198,12 @@ function buildAgentOnboardingPayload(request) {
4198
4198
  verifyCommands: [
4199
4199
  `curl -s ${origin}/api/v1/health`,
4200
4200
  "openclaw plugins install ./projects/forge/openclaw-plugin",
4201
+ "openclaw plugins info forge-openclaw-plugin",
4201
4202
  "openclaw gateway restart"
4202
4203
  ],
4203
4204
  configNotes: [
4204
4205
  "Localhost and Tailscale targets can usually use the operator-session path without a long-lived token.",
4206
+ "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
4207
  "Use a distinct actor label such as Albert (claw) so OpenClaw-originated work stays obvious in Forge provenance.",
4206
4208
  "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
4209
  ]
@@ -4558,6 +4560,30 @@ function resolveScopedUserIds(query) {
4558
4560
  const unique = Array.from(new Set(values));
4559
4561
  return unique.length > 0 ? unique : undefined;
4560
4562
  }
4563
+ function attachMovementTimelineSleepOverlays(movement, userIds) {
4564
+ const rangeStart = movement.segments.reduce((earliest, segment) => {
4565
+ if (!earliest || Date.parse(segment.startedAt) < Date.parse(earliest)) {
4566
+ return segment.startedAt;
4567
+ }
4568
+ return earliest;
4569
+ }, null);
4570
+ const rangeEnd = movement.segments.reduce((latest, segment) => {
4571
+ if (!latest || Date.parse(segment.endedAt) > Date.parse(latest)) {
4572
+ return segment.endedAt;
4573
+ }
4574
+ return latest;
4575
+ }, null);
4576
+ return {
4577
+ ...movement,
4578
+ sleepOverlays: rangeStart && rangeEnd
4579
+ ? getSleepTimelineOverlaysForRange({
4580
+ startedAt: rangeStart,
4581
+ endedAt: rangeEnd,
4582
+ userIds
4583
+ })
4584
+ : []
4585
+ };
4586
+ }
4561
4587
  function readRequestedUserIdFromBody(body) {
4562
4588
  if (!body || typeof body !== "object" || Array.isArray(body)) {
4563
4589
  return undefined;
@@ -4581,13 +4607,17 @@ function syncEntityOwnerFromBody(options) {
4581
4607
  setEntityOwner(options.entityType, options.entityId, owner.id);
4582
4608
  }
4583
4609
  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
4610
  const users = listUsers();
4588
- const dashboard = getDashboard({ userIds });
4589
- const selectedUsers = userIds && userIds.length > 0
4590
- ? users.filter((user) => userIds.includes(user.id))
4611
+ const validUserIdSet = new Set(users.map((user) => user.id));
4612
+ const scopedUserIds = userIds && userIds.length > 0
4613
+ ? userIds.filter((userId) => validUserIdSet.has(userId))
4614
+ : undefined;
4615
+ const goals = filterOwnedEntities("goal", listGoals(), scopedUserIds);
4616
+ const tasks = filterOwnedEntities("task", listTasks(), scopedUserIds);
4617
+ const habits = filterOwnedEntities("habit", listHabits(), scopedUserIds);
4618
+ const dashboard = getDashboard({ userIds: scopedUserIds });
4619
+ const selectedUsers = scopedUserIds && scopedUserIds.length > 0
4620
+ ? users.filter((user) => scopedUserIds.includes(user.id))
4591
4621
  : users;
4592
4622
  return {
4593
4623
  meta: {
@@ -4599,23 +4629,23 @@ function buildV1Context(userIds) {
4599
4629
  },
4600
4630
  metrics: buildGamificationProfile(goals, tasks, habits),
4601
4631
  dashboard,
4602
- overview: getOverviewContext(new Date(), { userIds }),
4603
- today: getTodayContext(new Date(), { userIds }),
4604
- risk: getRiskContext(new Date(), { userIds }),
4632
+ overview: getOverviewContext(new Date(), { userIds: scopedUserIds }),
4633
+ today: getTodayContext(new Date(), { userIds: scopedUserIds }),
4634
+ risk: getRiskContext(new Date(), { userIds: scopedUserIds }),
4605
4635
  goals,
4606
- projects: listProjectSummaries({ userIds }),
4636
+ projects: listProjectSummaries({ userIds: scopedUserIds }),
4607
4637
  tags: listTags(),
4608
4638
  tasks,
4609
4639
  habits,
4610
4640
  users,
4611
- strategies: listStrategies({ userIds }),
4641
+ strategies: listStrategies({ userIds: scopedUserIds }),
4612
4642
  userScope: {
4613
- selectedUserIds: userIds ?? [],
4643
+ selectedUserIds: scopedUserIds ?? [],
4614
4644
  selectedUsers
4615
4645
  },
4616
4646
  activeTaskRuns: listTaskRuns({ active: true, limit: 25 }),
4617
4647
  activity: dashboard.recentActivity,
4618
- lifeForce: buildLifeForcePayload(new Date(), userIds)
4648
+ lifeForce: buildLifeForcePayload(new Date(), scopedUserIds)
4619
4649
  };
4620
4650
  }
4621
4651
  function buildXpMetricsPayload() {
@@ -5514,14 +5544,15 @@ export async function buildServer(options = {}) {
5514
5544
  });
5515
5545
  app.get("/api/v1/movement/timeline", async (request) => {
5516
5546
  const parsed = movementTimelineQuerySchema.parse(request.query ?? {});
5547
+ const userIds = parsed.userIds.length > 0
5548
+ ? parsed.userIds
5549
+ : (resolveScopedUserIds(request.query) ?? []);
5550
+ const movement = getMovementTimeline({
5551
+ ...parsed,
5552
+ userIds
5553
+ });
5517
5554
  return {
5518
- movement: getMovementTimeline({
5519
- ...parsed,
5520
- userIds: parsed.userIds.length > 0
5521
- ? parsed.userIds
5522
- : (resolveScopedUserIds(request.query) ??
5523
- [])
5524
- })
5555
+ movement: attachMovementTimelineSleepOverlays(movement, userIds)
5525
5556
  };
5526
5557
  });
5527
5558
  app.get("/api/v1/movement/settings", async (request) => ({
@@ -5790,12 +5821,13 @@ export async function buildServer(options = {}) {
5790
5821
  app.post("/api/v1/mobile/movement/timeline", async (request) => {
5791
5822
  const parsed = movementMobileTimelineSchema.parse(request.body ?? {});
5792
5823
  const pairing = requireValidPairing(parsed.sessionId, parsed.pairingToken);
5824
+ const movement = getMovementTimeline({
5825
+ before: parsed.before,
5826
+ limit: parsed.limit,
5827
+ userIds: [pairing.user_id]
5828
+ });
5793
5829
  return {
5794
- movement: getMovementTimeline({
5795
- before: parsed.before,
5796
- limit: parsed.limit,
5797
- userIds: [pairing.user_id]
5798
- })
5830
+ movement: attachMovementTimelineSleepOverlays(movement, [pairing.user_id])
5799
5831
  };
5800
5832
  });
5801
5833
  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
@@ -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.43",
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.43",
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"
@@ -416,6 +416,15 @@ Use this when the user is updating an existing record rather than creating a new
416
416
  If the current title or shape may no longer fit, offer one revised formulation yourself
417
417
  before asking the user to rewrite it from scratch.
418
418
 
419
+ ## Update-first openers
420
+
421
+ Use these when the user is correcting or revising something that already exists.
422
+
423
+ - "What feels different enough now that this record needs to change?"
424
+ - "What still feels right and should stay intact while we update it?"
425
+ - "If this is really one correction rather than a full rethink, what is the exact part you want changed?"
426
+ - "I can stay narrow here. What is the one thing that no longer fits?"
427
+
419
428
  ## Goal
420
429
 
421
430
  Aim: clarify the direction and why it matters, not just produce a title.
@@ -379,6 +379,8 @@ If the entity is not yet clear:
379
379
  replacing it with a more polished label unless they ask for help wording it.
380
380
  - When the user says the current wording misses, say what seems newly true before you
381
381
  ask whether the old name still fits.
382
+ - Before you revise a durable formulation, say what the old wording was trying to
383
+ hold and what the new episode or evidence changes.
382
384
  - If a recent charged episode is what made the update visible, anchor in that episode
383
385
  before you rename the durable belief, pattern, mode, or value.
384
386
  - If another belief, value, pattern, mode, or note becomes visible, name it gently but
@@ -404,6 +406,12 @@ Good update opener:
404
406
 
405
407
  - "Before we change it, what feels newly true about this now, and what still feels basically right?"
406
408
 
409
+ Other strong update openers:
410
+
411
+ - "What about the old wording no longer lands, and what still feels true inside it?"
412
+ - "If this update comes from one fresh moment, what happened there that changed how this record feels?"
413
+ - "Do you want to revise the whole formulation, or only the part that now feels inaccurate?"
414
+
407
415
  ## Safety rule
408
416
 
409
417
  If the user shows imminent risk of self-harm, suicide, violence, inability to stay