forge-openclaw-plugin 0.2.28 → 0.2.29

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 (34) hide show
  1. package/README.md +1 -1
  2. package/dist/assets/{board-DPFvZf-D.js → board-q8cfwaAW.js} +2 -2
  3. package/dist/assets/{board-DPFvZf-D.js.map → board-q8cfwaAW.js.map} +1 -1
  4. package/dist/assets/index-C6PCeHD_.css +1 -0
  5. package/dist/assets/index-bfHIqj0-.js +85 -0
  6. package/dist/assets/index-bfHIqj0-.js.map +1 -0
  7. package/dist/assets/{motion-Bvwc85ch.js → motion-DHfqFntt.js} +2 -2
  8. package/dist/assets/{motion-Bvwc85ch.js.map → motion-DHfqFntt.js.map} +1 -1
  9. package/dist/assets/{table-FJQTJvUR.js → table-DLweENXt.js} +2 -2
  10. package/dist/assets/{table-FJQTJvUR.js.map → table-DLweENXt.js.map} +1 -1
  11. package/dist/assets/{ui-GXFcgvSw.js → ui-BV0OYxkH.js} +2 -2
  12. package/dist/assets/{ui-GXFcgvSw.js.map → ui-BV0OYxkH.js.map} +1 -1
  13. package/dist/assets/{vendor-Cwf49UMz.js → vendor-OwcH20PM.js} +2 -2
  14. package/dist/assets/{vendor-Cwf49UMz.js.map → vendor-OwcH20PM.js.map} +1 -1
  15. package/dist/index.html +7 -7
  16. package/dist/server/server/migrations/044_macos_local_calendar_provider.sql +21 -0
  17. package/dist/server/server/src/app.js +87 -12
  18. package/dist/server/server/src/openapi.js +29 -1
  19. package/dist/server/server/src/repositories/calendar.js +144 -12
  20. package/dist/server/server/src/repositories/tasks.js +36 -17
  21. package/dist/server/server/src/services/calendar-runtime.js +613 -32
  22. package/dist/server/server/src/services/macos-calendar-helper.js +748 -0
  23. package/dist/server/server/src/types.js +46 -2
  24. package/dist/server/src/lib/api-error.js +2 -0
  25. package/dist/server/src/lib/api.js +39 -2
  26. package/dist/server/src/lib/calendar-name-deduper.js +2 -0
  27. package/openclaw.plugin.json +1 -1
  28. package/package.json +1 -1
  29. package/server/migrations/044_macos_local_calendar_provider.sql +21 -0
  30. package/skills/forge-openclaw/SKILL.md +21 -5
  31. package/skills/forge-openclaw/entity_conversation_playbooks.md +88 -5
  32. package/dist/assets/index-Auw3JrdE.css +0 -1
  33. package/dist/assets/index-D1H7myQH.js +0 -85
  34. package/dist/assets/index-D1H7myQH.js.map +0 -1
@@ -45,7 +45,8 @@ export const calendarProviderSchema = z.enum([
45
45
  "google",
46
46
  "apple",
47
47
  "caldav",
48
- "microsoft"
48
+ "microsoft",
49
+ "macos_local"
49
50
  ]);
50
51
  export const calendarConnectionStatusSchema = z.enum([
51
52
  "connected",
@@ -59,8 +60,16 @@ export const calendarEventOriginSchema = z.enum([
59
60
  "apple",
60
61
  "caldav",
61
62
  "microsoft",
63
+ "macos_local",
62
64
  "derived"
63
65
  ]);
66
+ export const macosCalendarAccessStatusSchema = z.enum([
67
+ "not_determined",
68
+ "denied",
69
+ "restricted",
70
+ "full_access",
71
+ "unavailable"
72
+ ]);
64
73
  export const calendarAvailabilitySchema = z.enum(["busy", "free"]);
65
74
  export const calendarEventStatusSchema = z.enum([
66
75
  "confirmed",
@@ -792,7 +801,13 @@ export const calendarDiscoveryCalendarSchema = z.object({
792
801
  isPrimary: z.boolean(),
793
802
  canWrite: z.boolean(),
794
803
  selectedByDefault: z.boolean(),
795
- isForgeCandidate: z.boolean()
804
+ isForgeCandidate: z.boolean(),
805
+ sourceId: trimmedString.nullable().default(null),
806
+ sourceTitle: trimmedString.nullable().default(null),
807
+ sourceType: trimmedString.nullable().default(null),
808
+ calendarType: trimmedString.nullable().default(null),
809
+ hostCalendarId: trimmedString.nullable().default(null),
810
+ canonicalKey: trimmedString.nullable().default(null)
796
811
  });
797
812
  export const calendarDiscoveryPayloadSchema = z.object({
798
813
  provider: calendarProviderSchema,
@@ -802,6 +817,19 @@ export const calendarDiscoveryPayloadSchema = z.object({
802
817
  homeUrl: z.string().nullable(),
803
818
  calendars: z.array(calendarDiscoveryCalendarSchema)
804
819
  });
820
+ export const macosLocalCalendarSourceSchema = z.object({
821
+ sourceId: nonEmptyTrimmedString,
822
+ sourceTitle: trimmedString,
823
+ sourceType: trimmedString,
824
+ accountLabel: trimmedString,
825
+ accountIdentityKey: trimmedString,
826
+ calendars: z.array(calendarDiscoveryCalendarSchema)
827
+ });
828
+ export const macosLocalCalendarDiscoveryPayloadSchema = z.object({
829
+ status: macosCalendarAccessStatusSchema,
830
+ requestedAt: z.string(),
831
+ sources: z.array(macosLocalCalendarSourceSchema)
832
+ });
805
833
  export const calendarSchema = z.object({
806
834
  id: z.string(),
807
835
  connectionId: z.string(),
@@ -814,6 +842,12 @@ export const calendarSchema = z.object({
814
842
  canWrite: z.boolean(),
815
843
  selectedForSync: z.boolean(),
816
844
  forgeManaged: z.boolean(),
845
+ sourceId: trimmedString.nullable().default(null),
846
+ sourceTitle: trimmedString.nullable().default(null),
847
+ sourceType: trimmedString.nullable().default(null),
848
+ calendarType: trimmedString.nullable().default(null),
849
+ hostCalendarId: trimmedString.nullable().default(null),
850
+ canonicalKey: trimmedString.nullable().default(null),
817
851
  lastSyncedAt: z.string().nullable(),
818
852
  createdAt: z.string(),
819
853
  updatedAt: z.string()
@@ -2358,6 +2392,15 @@ export const createCalendarConnectionSchema = z.discriminatedUnion("provider", [
2358
2392
  label: nonEmptyTrimmedString,
2359
2393
  authSessionId: nonEmptyTrimmedString,
2360
2394
  selectedCalendarUrls: z.array(nonEmptyTrimmedString.url()).min(1)
2395
+ }),
2396
+ z.object({
2397
+ provider: z.literal("macos_local"),
2398
+ label: nonEmptyTrimmedString,
2399
+ sourceId: nonEmptyTrimmedString,
2400
+ selectedCalendarUrls: z.array(nonEmptyTrimmedString.url()).min(1),
2401
+ forgeCalendarUrl: nonEmptyTrimmedString.url().nullable().optional(),
2402
+ createForgeCalendar: z.boolean().optional().default(false),
2403
+ replaceConnectionIds: z.array(nonEmptyTrimmedString).optional().default([])
2361
2404
  })
2362
2405
  ]);
2363
2406
  export const discoverCalendarConnectionSchema = z.discriminatedUnion("provider", [
@@ -2834,6 +2877,7 @@ export const updateTaskSchema = z.object({
2834
2877
  title: nonEmptyTrimmedString.optional(),
2835
2878
  description: trimmedString.optional(),
2836
2879
  status: taskStatusSchema.optional(),
2880
+ completedAt: dateTimeSchema.optional(),
2837
2881
  priority: taskPrioritySchema.optional(),
2838
2882
  owner: nonEmptyTrimmedString.optional(),
2839
2883
  userId: nonEmptyTrimmedString.nullable().optional(),
@@ -3,6 +3,7 @@ export class ForgeApiError extends Error {
3
3
  code;
4
4
  details;
5
5
  requestPath;
6
+ response;
6
7
  constructor(input) {
7
8
  super(input.message);
8
9
  this.name = "ForgeApiError";
@@ -10,6 +11,7 @@ export class ForgeApiError extends Error {
10
11
  this.code = input.code;
11
12
  this.details = input.details ?? [];
12
13
  this.requestPath = input.requestPath;
14
+ this.response = input.response ?? null;
13
15
  }
14
16
  }
15
17
  export function describeApiError(error) {
@@ -130,7 +130,12 @@ async function request(path, init) {
130
130
  ? body
131
131
  : `Request failed: ${response.status}`,
132
132
  requestPath: path,
133
- details
133
+ details,
134
+ response: typeof body === "string"
135
+ ? body
136
+ : body && typeof body === "object"
137
+ ? body
138
+ : null
134
139
  });
135
140
  }
136
141
  return body;
@@ -161,7 +166,12 @@ async function requestBlob(path, init) {
161
166
  ? maybeBody.message
162
167
  : `Request failed: ${response.status}`,
163
168
  requestPath: path,
164
- details: []
169
+ details: [],
170
+ response: typeof body === "string"
171
+ ? body
172
+ : body && typeof body === "object"
173
+ ? body
174
+ : null
165
175
  });
166
176
  }
167
177
  const disposition = response.headers.get("content-disposition");
@@ -1109,6 +1119,33 @@ export function discoverCalendarConnection(input) {
1109
1119
  discovery: dedupeCalendarDiscoveryPayload(response.discovery)
1110
1120
  }));
1111
1121
  }
1122
+ export function getMacOSLocalCalendarStatus() {
1123
+ return request("/api/v1/calendar/macos-local/status");
1124
+ }
1125
+ export function requestMacOSLocalCalendarAccess() {
1126
+ return request("/api/v1/calendar/macos-local/request-access", {
1127
+ method: "POST"
1128
+ });
1129
+ }
1130
+ export function discoverMacOSLocalCalendarSources() {
1131
+ return request("/api/v1/calendar/macos-local/discovery").then((response) => ({
1132
+ ...response,
1133
+ discovery: {
1134
+ ...response.discovery,
1135
+ sources: response.discovery.sources.map((source) => ({
1136
+ ...source,
1137
+ calendars: dedupeCalendarDiscoveryPayload({
1138
+ provider: "macos_local",
1139
+ accountLabel: source.accountLabel,
1140
+ serverUrl: "forge-macos-local://eventkit/",
1141
+ principalUrl: null,
1142
+ homeUrl: null,
1143
+ calendars: source.calendars
1144
+ }).calendars
1145
+ }))
1146
+ }
1147
+ }));
1148
+ }
1112
1149
  export function startGoogleCalendarOauth(input) {
1113
1150
  return request("/api/v1/calendar/oauth/google/start", {
1114
1151
  method: "POST",
@@ -16,6 +16,8 @@ function shortCalendarProviderLabel(provider) {
16
16
  return "Apple";
17
17
  case "microsoft":
18
18
  return "Microsoft";
19
+ case "macos_local":
20
+ return "Mac";
19
21
  case "caldav":
20
22
  default:
21
23
  return "CalDAV";
@@ -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.28",
5
+ "version": "0.2.29",
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.28",
3
+ "version": "0.2.29",
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",
@@ -0,0 +1,21 @@
1
+ ALTER TABLE calendar_calendars
2
+ ADD COLUMN source_id TEXT;
3
+
4
+ ALTER TABLE calendar_calendars
5
+ ADD COLUMN source_title TEXT;
6
+
7
+ ALTER TABLE calendar_calendars
8
+ ADD COLUMN source_type TEXT;
9
+
10
+ ALTER TABLE calendar_calendars
11
+ ADD COLUMN calendar_type TEXT;
12
+
13
+ ALTER TABLE calendar_calendars
14
+ ADD COLUMN host_calendar_id TEXT;
15
+
16
+ ALTER TABLE calendar_calendars
17
+ ADD COLUMN canonical_key TEXT;
18
+
19
+ UPDATE calendar_calendars
20
+ SET canonical_key = remote_id
21
+ WHERE canonical_key IS NULL OR TRIM(canonical_key) = '';
@@ -57,9 +57,15 @@ Entity conversation rule:
57
57
  - Prefer a progression of:
58
58
  concrete example or intent -> working name -> purpose or meaning -> placement in Forge -> operational details -> linked context.
59
59
  - Use those same playbooks for action-heavy non-Psyche flows such as `work_adjustment`, `preference_judgment`, `preference_signal`, and specialized `movement`, `life_force`, or `workbench` requests so the conversation starts from what the user is trying to understand, change, add, update, link, or run before you choose the route.
60
+ - When the operation is not already explicit, identify the job first:
61
+ add, update, review, compare, navigate, link, or run. Skip that meta question
62
+ when the action is already obvious from the user's wording.
60
63
  - For emotionally meaningful non-Psyche records such as goals, habits, and notes, reflect the meaning before you ask for structure.
61
64
  - When the user is vague, ask for one small concrete example, stake, or desired outcome before you ask them to name the record.
62
65
  - When the user is clear, say what the record seems to be becoming and ask only for the last missing detail.
66
+ - When the user wants to review, compare, inspect, or navigate an existing Forge
67
+ record, ask what they are trying to understand first and prefer the read path before
68
+ you reopen create or update intake.
63
69
  - When updating an entity, start with what is changing, what should stay true, and what prompted the update now.
64
70
  - When enough is clear, briefly summarize what you heard in the user's own language before asking for the last missing structural detail.
65
71
  - The quick intake prompts later in this file are fallback checkpoints, not a script to read aloud.
@@ -261,8 +267,9 @@ Minimum field: `label`
261
267
  Usually useful: `description`
262
268
  Ask:
263
269
 
264
- 1. What should this event type be called?
265
- 2. What kind of incident does it represent?
270
+ 1. What kind of repeated moment or incident do you want future reports to name the same way?
271
+ 2. What would count as inside this category, and what should stay outside it?
272
+ 3. If the meaning is clear but the wording is not, would you like me to suggest a concise label?
266
273
 
267
274
  `emotion_definition`
268
275
  Use for a reusable emotion vocabulary entry.
@@ -270,9 +277,9 @@ Minimum field: `label`
270
277
  Usually useful: `description`, `category`
271
278
  Ask:
272
279
 
273
- 1. What emotion label do you want to reuse?
274
- 2. How would you describe it?
275
- 3. Does it belong to a broader category?
280
+ 1. When this feeling is present, what tells you it is this feeling and not a nearby one?
281
+ 2. What would you want a later trigger report to mean when it uses this label?
282
+ 3. If the felt meaning is clear but the wording is not, would you like me to suggest a concise label or broader category?
276
283
 
277
284
  Use these rules when choosing tools.
278
285
 
@@ -296,11 +303,20 @@ Use the dedicated domain routes for specialized surfaces that are not simple bat
296
303
 
297
304
  - Movement lives under `/api/v1/movement/*`. Treat it as a dedicated timeline of `stays` and `trips`, not as generic batch CRUD. A `stay` means the user remained in the same place for a span of time. A `trip` means the user traveled between places. Use the movement routes when the user wants to understand time in place, travel behavior, specific stays or trips, known places, or selected-span aggregates such as "how long was I at home in the past 2 weeks?" or "when did I travel last month?".
298
305
  - Movement user actions are: query movement behavior, add a place or manual stay/trip overlay, update an existing stay/trip/place, or link a specific movement item to another Forge entity. Keep the explanation user-facing: where they stayed, when they traveled, what changed, and what this movement should be linked to.
306
+ - Movement read lanes map cleanly to the dedicated routes:
307
+ `/api/v1/movement/day`, `/api/v1/movement/month`, `/api/v1/movement/all-time`,
308
+ `/api/v1/movement/timeline`, `/api/v1/movement/places`,
309
+ `/api/v1/movement/selection`, and `/api/v1/movement/trips/:id`.
299
310
  - When the user is filling a missing-data gap, the default write path is a user-defined overlay box, not a raw stay or trip patch. Use `POST /api/v1/movement/user-boxes/preflight` if you need to confirm overlap or snap to the nearest missing interval, then `POST /api/v1/movement/user-boxes` with `kind: "stay"` or `kind: "trip"`.
300
311
  - Use `PATCH /api/v1/movement/stays/:id` or `PATCH /api/v1/movement/trips/:id` only when the user is editing an existing recorded stay or recorded trip. Do not use those routes to fill a missing span.
301
312
  - If the user says something as explicit as "that missing block was me staying home", do not reopen broad intake. Confirm the interval or place only if it is still ambiguous, then create the overlay and read the timeline back.
302
313
  - Life Force lives under `/api/v1/life-force*`. Use `GET /api/v1/life-force` for the current energy overview, `PATCH /api/v1/life-force/profile` for durable profile changes, `PUT /api/v1/life-force/templates/:weekday` for weekday curve edits, and `POST /api/v1/life-force/fatigue-signals` for real-time tired or recovered signals.
303
314
  - Workbench lives under `/api/v1/workbench/*`. Use those dedicated routes for flow catalog reads, flow CRUD, runs, published outputs, node results, and latest-node-output reads instead of trying to force Workbench through the batch entity routes.
315
+ - Workbench lane hints:
316
+ use `/api/v1/workbench/flows` for flow catalog and CRUD,
317
+ `/api/v1/workbench/flows/:id/run` or `/api/v1/workbench/run` for execution,
318
+ `/api/v1/workbench/flows/:id/output` for published outputs, and the run/node routes
319
+ under `/api/v1/workbench/flows/:id` for run history and node-level inspection.
304
320
  - If you are unsure which specialized route family applies, check `forge_get_agent_onboarding` and use its `entityRouteModel.specializedDomainSurfaces` section before guessing.
305
321
 
306
322
  Use live work tools for `task_run`:
@@ -15,6 +15,8 @@ Forge correctly, and gather only the structure that still matters.
15
15
  naming what you think the user is trying to preserve, clarify, decide, schedule, or
16
16
  make easier.
17
17
  - Ask only for what is missing or still unclear.
18
+ - First identify the user's job when the lane is not already explicit:
19
+ are they trying to add, update, review, compare, navigate, link, or run something?
18
20
  - Before every question, decide the one missing thing you are trying to clarify.
19
21
  - Ask first for the missing thing that would change the record shape, title, or next
20
22
  action most, not just the easiest field to fill.
@@ -78,6 +80,21 @@ Most good Forge intake flows follow this sequence:
78
80
 
79
81
  That sequence is not a script. Skip steps the user already answered.
80
82
 
83
+ ## Operation lane checkpoint
84
+
85
+ Use this before you choose an API path or ask for more structure.
86
+
87
+ - If the user has not made the operation explicit yet, clarify the job first:
88
+ add, update, review, compare, navigate, link, or run.
89
+ - Ask the lane question only when it changes the route family or the next question.
90
+ - Skip the meta lane question when the user already gave both the entity and the
91
+ action clearly, such as "pause this project", "add a home stay for that missing
92
+ block", or "run this flow again".
93
+ - For simple stored entities, once the lane is clear, fall back to the shared batch
94
+ CRUD flow.
95
+ - For specialized surfaces such as Movement, Life Force, and Workbench, use the lane
96
+ to choose the dedicated route family before you ask for lower-level details.
97
+
81
98
  ## Active-listening patterns
82
99
 
83
100
  Use one of these shapes when the user is not yet precise.
@@ -158,6 +175,21 @@ When an adjacent record becomes visible:
158
175
  - name it gently and ask whether it should be linked now, saved separately later, or
159
176
  left alone for now
160
177
 
178
+ ## Review And Navigation Moves
179
+
180
+ Use this when the user wants to inspect, compare, review, or navigate existing Forge
181
+ records rather than create something new.
182
+
183
+ - Start by asking what they are trying to understand, decide, compare, or check.
184
+ - Ask only for the scoping detail that changes the read path most:
185
+ entity, owner, timeframe, context, or comparison target.
186
+ - If the record already exists and the user wants review, do not reopen a creation
187
+ intake. Route to search, list, overview, or detail first.
188
+ - For review-heavy questions, the useful progression is:
189
+ user goal -> scope -> read path -> interpretation -> optional follow-up write.
190
+ - Only drift back into create or update intake if the user actually wants the record
191
+ changed after the review.
192
+
161
193
  ## Question Calibration Loop
162
194
 
163
195
  Use this quick internal check before every follow-up question.
@@ -913,6 +945,21 @@ Helpful follow-up lanes:
913
945
  distribution, versus an edit
914
946
  - whether the edit is a missing-gap overlay versus a true recorded stay/trip patch
915
947
 
948
+ Lane-to-route map:
949
+
950
+ - review one day or month:
951
+ `/api/v1/movement/day` or `/api/v1/movement/month`
952
+ - review long-range behavior or dominant places:
953
+ `/api/v1/movement/all-time`, `/api/v1/movement/places`, or `/api/v1/movement/selection`
954
+ - inspect the full life timeline:
955
+ `/api/v1/movement/timeline`
956
+ - inspect one trip:
957
+ `/api/v1/movement/trips/:id`
958
+ - fill a missing span:
959
+ `/api/v1/movement/user-boxes/preflight` then `/api/v1/movement/user-boxes`
960
+ - edit an already-recorded stay, trip, or trip point:
961
+ `/api/v1/movement/stays/:id`, `/api/v1/movement/trips/:id`, or `/api/v1/movement/trips/:id/points/:pointId`
962
+
916
963
  Ready to act when:
917
964
 
918
965
  - the movement surface is clear
@@ -941,6 +988,17 @@ Helpful follow-up lanes:
941
988
  - what part of the energy model feels off or useful
942
989
  - what durable assumption versus real-time state is being changed
943
990
 
991
+ Lane-to-route map:
992
+
993
+ - understand the current energy picture:
994
+ `GET /api/v1/life-force`
995
+ - change durable profile assumptions:
996
+ `PATCH /api/v1/life-force/profile`
997
+ - change one weekday curve or template:
998
+ `PUT /api/v1/life-force/templates/:weekday`
999
+ - log a real-time tired or recovered signal:
1000
+ `POST /api/v1/life-force/fatigue-signals`
1001
+
944
1002
  Ready to act when:
945
1003
 
946
1004
  - the life-force lane is clear
@@ -969,6 +1027,27 @@ Helpful follow-up lanes:
969
1027
  - what exact flow or run is in scope
970
1028
  - whether they need whole-flow output or node-level detail
971
1029
 
1030
+ Lane-to-route map:
1031
+
1032
+ - discover or inspect flows:
1033
+ `/api/v1/workbench/flows`, `/api/v1/workbench/flows/:id`, or `/api/v1/workbench/flows/by-slug/:slug`
1034
+ - create, update, or delete a flow:
1035
+ `POST/PATCH/DELETE /api/v1/workbench/flows`
1036
+ - run a known flow:
1037
+ `/api/v1/workbench/flows/:id/run`
1038
+ - run from a payload-first contract:
1039
+ `/api/v1/workbench/run`
1040
+ - inspect published output or run history:
1041
+ `/api/v1/workbench/flows/:id/output` or `/api/v1/workbench/flows/:id/runs`
1042
+ - inspect one run or node result:
1043
+ `/api/v1/workbench/flows/:id/runs/:runId`,
1044
+ `/api/v1/workbench/flows/:id/runs/:runId/nodes`,
1045
+ `/api/v1/workbench/flows/:id/runs/:runId/nodes/:nodeId`
1046
+ - inspect the latest successful node output:
1047
+ `/api/v1/workbench/flows/:id/nodes/:nodeId/output`
1048
+ - inspect available box inputs:
1049
+ `/api/v1/workbench/catalog/boxes`
1050
+
972
1051
  Ready to act when:
973
1052
 
974
1053
  - the workbench lane is clear
@@ -1145,10 +1224,12 @@ consistent.
1145
1224
  Arc:
1146
1225
 
1147
1226
  1. Ask what kind of moment or incident this label should capture in lived terms.
1148
- 2. Ask how narrow or broad it should be.
1149
- 3. Ask what would count as inside versus outside the category if that boundary is
1227
+ 2. Reflect the repeated moment back in plain language before narrowing the wording.
1228
+ 3. Ask how narrow or broad it should be.
1229
+ 4. Ask what would count as inside versus outside the category if that boundary is
1150
1230
  still fuzzy.
1151
- 4. Ask for a short description only if the label could be ambiguous later.
1231
+ 5. Offer a concise label if the lived meaning is clearer than the wording.
1232
+ 6. Ask for a short description only if the label could be ambiguous later.
1152
1233
 
1153
1234
  If the user already offered a candidate label, keep the wording provisional and ask
1154
1235
  what kinds of moments belong inside it before you ask whether the label is right.
@@ -1169,8 +1250,10 @@ Aim: define one reusable emotion entry clearly enough that future reports stay p
1169
1250
  Arc:
1170
1251
 
1171
1252
  1. Ask what this feeling is like in lived terms when the user says it.
1172
- 2. Ask what distinguishes it from nearby emotions if that matters.
1173
- 3. Ask for a short description only if later reports would benefit from it.
1253
+ 2. Reflect the felt signature back in plain language before you settle the label.
1254
+ 3. Ask what distinguishes it from nearby emotions if that matters.
1255
+ 4. Offer a concise label if the felt meaning is clearer than the wording.
1256
+ 5. Ask for a short description only if later reports would benefit from it.
1174
1257
 
1175
1258
  Helpful follow-up lanes:
1176
1259