forge-openclaw-plugin 0.2.26 → 0.2.27

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 (108) hide show
  1. package/README.md +59 -3
  2. package/dist/assets/{board-ta0rUHOf.js → board-C6jCchjI.js} +2 -2
  3. package/dist/assets/{board-ta0rUHOf.js.map → board-C6jCchjI.js.map} +1 -1
  4. package/dist/assets/index-DVvS8iiU.css +1 -0
  5. package/dist/assets/index-zYB-9Dfo.js +85 -0
  6. package/dist/assets/index-zYB-9Dfo.js.map +1 -0
  7. package/dist/assets/knowledge-graph-layout.worker-DRvzPxhP.js +2 -0
  8. package/dist/assets/knowledge-graph-layout.worker-DRvzPxhP.js.map +1 -0
  9. package/dist/assets/{motion-fBKPB6yw.js → motion-DFHrH2rd.js} +2 -2
  10. package/dist/assets/{motion-fBKPB6yw.js.map → motion-DFHrH2rd.js.map} +1 -1
  11. package/dist/assets/{table-C-IGTQni.js → table-ZL7Di_u3.js} +2 -2
  12. package/dist/assets/{table-C-IGTQni.js.map → table-ZL7Di_u3.js.map} +1 -1
  13. package/dist/assets/{ui-DInOpaYF.js → ui-CKNPpz7q.js} +2 -2
  14. package/dist/assets/{ui-DInOpaYF.js.map → ui-CKNPpz7q.js.map} +1 -1
  15. package/dist/assets/vendor-DoNZuFhn.js +1247 -0
  16. package/dist/assets/vendor-DoNZuFhn.js.map +1 -0
  17. package/dist/index.html +7 -7
  18. package/dist/openclaw/local-runtime.js +16 -0
  19. package/dist/openclaw/routes.d.ts +27 -0
  20. package/dist/openclaw/routes.js +16 -12
  21. package/dist/server/server/migrations/037_workbench_public_inputs_and_run_inputs.sql +5 -0
  22. package/dist/server/server/migrations/038_data_management_settings.sql +11 -0
  23. package/dist/server/server/migrations/039_life_force_and_action_points.sql +114 -0
  24. package/dist/server/server/migrations/040_screen_time_domain.sql +89 -0
  25. package/dist/server/server/migrations/041_companion_source_states.sql +21 -0
  26. package/dist/server/server/migrations/042_movement_boxes.sql +47 -0
  27. package/dist/server/server/migrations/043_movement_box_overlap_overrides.sql +26 -0
  28. package/dist/server/server/src/app.js +1684 -117
  29. package/dist/server/server/src/connectors/box-registry.js +44 -9
  30. package/dist/server/server/src/data-management-types.js +107 -0
  31. package/dist/server/server/src/db.js +68 -4
  32. package/dist/server/server/src/demo-data.js +2 -2
  33. package/dist/server/server/src/health.js +702 -18
  34. package/dist/server/server/src/managers/platform/llm-manager.js +7 -4
  35. package/dist/server/server/src/managers/platform/mock-workbench-provider.js +149 -0
  36. package/dist/server/server/src/managers/platform/secrets-manager.js +18 -1
  37. package/dist/server/server/src/managers/runtime.js +9 -0
  38. package/dist/server/server/src/movement.js +1971 -112
  39. package/dist/server/server/src/openapi.js +489 -1
  40. package/dist/server/server/src/psyche-types.js +9 -1
  41. package/dist/server/server/src/repositories/activity-events.js +8 -0
  42. package/dist/server/server/src/repositories/ai-connectors.js +522 -74
  43. package/dist/server/server/src/repositories/habits.js +37 -1
  44. package/dist/server/server/src/repositories/model-settings.js +13 -3
  45. package/dist/server/server/src/repositories/notes.js +3 -0
  46. package/dist/server/server/src/repositories/settings.js +380 -18
  47. package/dist/server/server/src/repositories/tasks.js +170 -10
  48. package/dist/server/server/src/runtime-data-root.js +82 -0
  49. package/dist/server/server/src/screen-time.js +802 -0
  50. package/dist/server/server/src/services/data-management.js +788 -0
  51. package/dist/server/server/src/services/entity-crud.js +205 -2
  52. package/dist/server/server/src/services/knowledge-graph.js +1455 -0
  53. package/dist/server/server/src/services/life-force-model.js +197 -0
  54. package/dist/server/server/src/services/life-force.js +1270 -0
  55. package/dist/server/server/src/services/psyche-observation-calendar.js +383 -16
  56. package/dist/server/server/src/types.js +286 -13
  57. package/dist/server/server/src/web.js +228 -13
  58. package/dist/server/src/components/customization/utility-widgets.js +136 -27
  59. package/dist/server/src/components/ui/info-tooltip.js +25 -0
  60. package/dist/server/src/components/workbench-boxes/calendar/calendar-boxes.js +78 -0
  61. package/dist/server/src/components/workbench-boxes/goals/goals-boxes.js +62 -0
  62. package/dist/server/src/components/workbench-boxes/habits/habits-boxes.js +62 -0
  63. package/dist/server/src/components/workbench-boxes/health/health-boxes.js +63 -8
  64. package/dist/server/src/components/workbench-boxes/insights/insights-boxes.js +50 -0
  65. package/dist/server/src/components/workbench-boxes/kanban/kanban-boxes.js +62 -54
  66. package/dist/server/src/components/workbench-boxes/movement/movement-boxes.js +18 -8
  67. package/dist/server/src/components/workbench-boxes/notes/notes-boxes.js +56 -38
  68. package/dist/server/src/components/workbench-boxes/overview/overview-boxes.js +65 -0
  69. package/dist/server/src/components/workbench-boxes/preferences/preferences-boxes.js +78 -0
  70. package/dist/server/src/components/workbench-boxes/projects/projects-boxes.js +35 -30
  71. package/dist/server/src/components/workbench-boxes/psyche/psyche-boxes.js +88 -0
  72. package/dist/server/src/components/workbench-boxes/questionnaires/questionnaires-boxes.js +61 -0
  73. package/dist/server/src/components/workbench-boxes/review/review-boxes.js +53 -0
  74. package/dist/server/src/components/workbench-boxes/shared/define-workbench-box.js +3 -1
  75. package/dist/server/src/components/workbench-boxes/shared/generic-node-view.js +39 -3
  76. package/dist/server/src/components/workbench-boxes/strategies/strategies-boxes.js +62 -0
  77. package/dist/server/src/components/workbench-boxes/tasks/tasks-boxes.js +76 -0
  78. package/dist/server/src/components/workbench-boxes/today/today-boxes.js +47 -32
  79. package/dist/server/src/components/workbench-boxes/wiki/wiki-boxes.js +60 -0
  80. package/dist/server/src/lib/api.js +280 -21
  81. package/dist/server/src/lib/data-management-types.js +1 -0
  82. package/dist/server/src/lib/entity-visuals.js +279 -0
  83. package/dist/server/src/lib/knowledge-graph-types.js +276 -0
  84. package/dist/server/src/lib/knowledge-graph.js +470 -0
  85. package/dist/server/src/lib/schemas.js +4 -0
  86. package/dist/server/src/lib/snapshot-normalizer.js +43 -1
  87. package/dist/server/src/lib/workbench/contracts.js +229 -0
  88. package/dist/server/src/lib/workbench/nodes.js +200 -0
  89. package/dist/server/src/lib/workbench/registry.js +52 -5
  90. package/dist/server/src/lib/workbench/runtime.js +254 -38
  91. package/dist/server/src/lib/workbench/tool-catalog.js +68 -0
  92. package/openclaw.plugin.json +1 -1
  93. package/package.json +1 -1
  94. package/server/migrations/037_workbench_public_inputs_and_run_inputs.sql +5 -0
  95. package/server/migrations/038_data_management_settings.sql +11 -0
  96. package/server/migrations/039_life_force_and_action_points.sql +114 -0
  97. package/server/migrations/040_screen_time_domain.sql +89 -0
  98. package/server/migrations/041_companion_source_states.sql +21 -0
  99. package/server/migrations/042_movement_boxes.sql +47 -0
  100. package/server/migrations/043_movement_box_overlap_overrides.sql +26 -0
  101. package/skills/forge-openclaw/SKILL.md +24 -11
  102. package/skills/forge-openclaw/entity_conversation_playbooks.md +210 -34
  103. package/skills/forge-openclaw/psyche_entity_playbooks.md +113 -17
  104. package/dist/assets/index-Ro0ZF_az.css +0 -1
  105. package/dist/assets/index-ytlpSj23.js +0 -79
  106. package/dist/assets/index-ytlpSj23.js.map +0 -1
  107. package/dist/assets/vendor-lE3tZJcC.js +0 -876
  108. package/dist/assets/vendor-lE3tZJcC.js.map +0 -1
@@ -1,31 +1,147 @@
1
1
  import { psycheObservationCalendarPayloadSchema } from "../psyche-types.js";
2
- import { listNotesByObservedAtRange, resolveNoteObservedAt } from "../repositories/notes.js";
2
+ import { listActivityEvents } from "../repositories/activity-events.js";
3
3
  import { filterOwnedEntities } from "../repositories/entity-ownership.js";
4
+ import { listNotesByObservedAtRange, resolveNoteObservedAt } from "../repositories/notes.js";
4
5
  import { listBehaviorPatterns, listTriggerReports } from "../repositories/psyche.js";
5
- function collectAvailableTags(observations) {
6
+ const SELF_OBSERVATION_TAG = "Self-observation";
7
+ const FORGE_ACTIVITY_TAG = "Forge activity";
8
+ const ENTITY_TAG_PREFIX = "Entity · ";
9
+ const SOURCE_TAG_PREFIX = "Source · ";
10
+ const ACTIVITY_ENTITY_LABELS = {
11
+ system: "System",
12
+ goal: "Goal",
13
+ project: "Project",
14
+ task: "Task",
15
+ strategy: "Strategy",
16
+ domain: "Domain",
17
+ task_run: "Task run",
18
+ habit: "Habit",
19
+ tag: "Tag",
20
+ note: "Note",
21
+ insight: "Insight",
22
+ psyche_value: "Psyche value",
23
+ behavior_pattern: "Pattern",
24
+ behavior: "Behavior",
25
+ belief_entry: "Belief",
26
+ mode_profile: "Mode",
27
+ mode_guide_session: "Mode guide session",
28
+ trigger_report: "Trigger report",
29
+ questionnaire_run: "Questionnaire run",
30
+ event_type: "Event type",
31
+ emotion_definition: "Emotion",
32
+ approval_request: "Approval request",
33
+ agent_action: "Agent action",
34
+ reward: "Reward",
35
+ session: "Session",
36
+ calendar_connection: "Calendar connection",
37
+ calendar: "Calendar",
38
+ calendar_event: "Calendar event",
39
+ work_block: "Work block",
40
+ work_block_template: "Work block template",
41
+ task_timebox: "Task timebox",
42
+ preference_catalog: "Preference catalog",
43
+ preference_catalog_item: "Preference concept",
44
+ preference_context: "Preference context",
45
+ preference_item: "Preference item",
46
+ questionnaire_instrument: "Questionnaire",
47
+ sleep_session: "Sleep",
48
+ workout_session: "Workout"
49
+ };
50
+ const ACTIVITY_SOURCE_LABELS = {
51
+ ui: "UI",
52
+ openclaw: "OpenClaw",
53
+ agent: "Agent",
54
+ system: "System"
55
+ };
56
+ function normalizeTags(tags) {
6
57
  const seen = new Set();
7
- const tags = [];
8
- for (const observation of observations) {
9
- for (const tag of observation.note.tags ?? []) {
10
- const normalized = tag.trim().toLowerCase();
11
- if (!normalized || seen.has(normalized)) {
12
- continue;
13
- }
14
- seen.add(normalized);
15
- tags.push(tag);
58
+ const normalized = [];
59
+ for (const rawTag of tags) {
60
+ const tag = rawTag.trim();
61
+ const key = tag.toLowerCase();
62
+ if (!tag || seen.has(key)) {
63
+ continue;
16
64
  }
65
+ seen.add(key);
66
+ normalized.push(tag);
17
67
  }
68
+ return normalized;
69
+ }
70
+ function compareIso(left, right) {
71
+ return left.observedAt.localeCompare(right.observedAt);
72
+ }
73
+ function buildObservationTags(noteTags = []) {
74
+ return normalizeTags([SELF_OBSERVATION_TAG, ...noteTags]);
75
+ }
76
+ function buildActivityTags(event) {
77
+ return normalizeTags([
78
+ FORGE_ACTIVITY_TAG,
79
+ `${ENTITY_TAG_PREFIX}${ACTIVITY_ENTITY_LABELS[event.entityType] ?? event.entityType}`,
80
+ `${SOURCE_TAG_PREFIX}${ACTIVITY_SOURCE_LABELS[event.source] ?? event.source}`
81
+ ]);
82
+ }
83
+ function collectAvailableTags(observations, activity) {
84
+ const tags = normalizeTags([
85
+ ...observations.flatMap((observation) => observation.tags),
86
+ ...activity.flatMap((entry) => entry.tags)
87
+ ]);
18
88
  return tags.sort((left, right) => left.localeCompare(right));
19
89
  }
20
- export function getPsycheObservationCalendar({ from, to, userIds }) {
90
+ function buildObservationSearchText(observation) {
91
+ return [
92
+ observation.note.contentPlain,
93
+ observation.note.contentMarkdown,
94
+ observation.note.author ?? "",
95
+ observation.tags.join(" "),
96
+ observation.linkedPatterns.map((pattern) => pattern.title).join(" "),
97
+ observation.linkedReports.map((report) => report.title).join(" "),
98
+ observation.note.links
99
+ .map((link) => `${link.entityType} ${link.entityId}`)
100
+ .join(" ")
101
+ ]
102
+ .join(" ")
103
+ .toLowerCase();
104
+ }
105
+ function buildActivitySearchText(entry) {
106
+ return [
107
+ entry.event.title,
108
+ entry.event.description,
109
+ entry.event.actor ?? "",
110
+ entry.event.eventType,
111
+ entry.event.entityType,
112
+ ACTIVITY_ENTITY_LABELS[entry.event.entityType] ?? entry.event.entityType,
113
+ ACTIVITY_SOURCE_LABELS[entry.event.source] ?? entry.event.source,
114
+ entry.tags.join(" ")
115
+ ]
116
+ .join(" ")
117
+ .toLowerCase();
118
+ }
119
+ function matchesSelectedTags(tags, selectedTags) {
120
+ if (selectedTags.length === 0) {
121
+ return true;
122
+ }
123
+ const normalizedEntryTags = new Set(tags.map((tag) => tag.toLowerCase()));
124
+ return selectedTags.some((tag) => normalizedEntryTags.has(tag.toLowerCase()));
125
+ }
126
+ function summarizeText(value, limit = 96) {
127
+ const text = value.replace(/\s+/g, " ").trim();
128
+ if (text.length <= limit) {
129
+ return text;
130
+ }
131
+ return `${text.slice(0, Math.max(0, limit - 3)).trimEnd()}...`;
132
+ }
133
+ function buildCalendarPayload({ from, to, userIds }) {
21
134
  const patterns = filterOwnedEntities("behavior_pattern", listBehaviorPatterns(), userIds);
22
135
  const reports = filterOwnedEntities("trigger_report", listTriggerReports(200), userIds);
23
136
  const notes = listNotesByObservedAtRange({ from, to, userIds, limit: 600 });
137
+ const activityEvents = listActivityEvents({ from, to, userIds, limit: 1200 });
24
138
  const patternsById = new Map(patterns.map((pattern) => [pattern.id, pattern]));
25
139
  const reportsById = new Map(reports.map((report) => [report.id, report]));
26
- const observations = notes.map((note) => ({
140
+ const observations = notes
141
+ .map((note) => ({
27
142
  id: note.id,
28
143
  observedAt: resolveNoteObservedAt(note),
144
+ tags: buildObservationTags(note.tags ?? []),
29
145
  note,
30
146
  linkedPatterns: note.links
31
147
  .filter((link) => link.entityType === "behavior_pattern")
@@ -35,12 +151,263 @@ export function getPsycheObservationCalendar({ from, to, userIds }) {
35
151
  .filter((link) => link.entityType === "trigger_report")
36
152
  .map((link) => reportsById.get(link.entityId) ?? null)
37
153
  .filter((report) => report !== null)
38
- }));
39
- return psycheObservationCalendarPayloadSchema.parse({
154
+ }))
155
+ .sort(compareIso);
156
+ const activity = activityEvents
157
+ .map((event) => ({
158
+ id: event.id,
159
+ observedAt: event.createdAt,
160
+ tags: buildActivityTags(event),
161
+ event
162
+ }))
163
+ .sort(compareIso);
164
+ return {
40
165
  generatedAt: new Date().toISOString(),
41
166
  from,
42
167
  to,
43
168
  observations,
44
- availableTags: collectAvailableTags(observations)
169
+ activity,
170
+ availableTags: collectAvailableTags(observations, activity)
171
+ };
172
+ }
173
+ export function getPsycheObservationCalendar({ from, to, userIds }) {
174
+ return psycheObservationCalendarPayloadSchema.parse(buildCalendarPayload({ from, to, userIds }));
175
+ }
176
+ export function filterPsycheObservationCalendar(payload, filters) {
177
+ const selectedTags = normalizeTags(filters.tags ?? []);
178
+ const includeObservations = filters.includeObservations ?? true;
179
+ const includeActivity = filters.includeActivity ?? true;
180
+ const search = filters.search?.trim().toLowerCase() ?? "";
181
+ const observations = (includeObservations ? payload.observations : []).filter((observation) => {
182
+ if (filters.onlyHumanOwned && observation.note.user?.kind !== "human") {
183
+ return false;
184
+ }
185
+ if (!matchesSelectedTags(observation.tags, selectedTags)) {
186
+ return false;
187
+ }
188
+ if (search && !buildObservationSearchText(observation).includes(search)) {
189
+ return false;
190
+ }
191
+ return true;
192
+ });
193
+ const activity = (includeActivity ? payload.activity : []).filter((entry) => {
194
+ if (filters.onlyHumanOwned && entry.event.user?.kind !== "human") {
195
+ return false;
196
+ }
197
+ if (!matchesSelectedTags(entry.tags, selectedTags)) {
198
+ return false;
199
+ }
200
+ if (search && !buildActivitySearchText(entry).includes(search)) {
201
+ return false;
202
+ }
203
+ return true;
204
+ });
205
+ return psycheObservationCalendarPayloadSchema.parse({
206
+ ...payload,
207
+ observations,
208
+ activity,
209
+ availableTags: collectAvailableTags(observations, activity)
45
210
  });
46
211
  }
212
+ function buildTimelineEntries(payload) {
213
+ return [
214
+ ...payload.observations.map((observation) => ({
215
+ kind: "observation",
216
+ observedAt: observation.observedAt,
217
+ tags: observation.tags,
218
+ ownerLabel: observation.note.user?.displayName ?? observation.note.author ?? "",
219
+ title: summarizeText(observation.note.contentPlain || observation.note.contentMarkdown || "Observation"),
220
+ description: [
221
+ observation.note.author ? `Author: ${observation.note.author}` : "",
222
+ observation.linkedPatterns.length > 0
223
+ ? `Patterns: ${observation.linkedPatterns.map((pattern) => pattern.title).join(", ")}`
224
+ : "",
225
+ observation.linkedReports.length > 0
226
+ ? `Trigger reports: ${observation.linkedReports.map((report) => report.title).join(", ")}`
227
+ : "",
228
+ summarizeText(observation.note.contentPlain || observation.note.contentMarkdown, 240)
229
+ ]
230
+ .filter(Boolean)
231
+ .join("\n"),
232
+ observation
233
+ })),
234
+ ...payload.activity.map((activity) => ({
235
+ kind: "activity",
236
+ observedAt: activity.observedAt,
237
+ tags: activity.tags,
238
+ ownerLabel: activity.event.user?.displayName ?? activity.event.actor ?? "",
239
+ title: activity.event.title,
240
+ description: [
241
+ activity.event.description,
242
+ `Entity: ${ACTIVITY_ENTITY_LABELS[activity.event.entityType] ?? activity.event.entityType}`,
243
+ `Source: ${ACTIVITY_SOURCE_LABELS[activity.event.source] ?? activity.event.source}`,
244
+ activity.event.actor ? `Actor: ${activity.event.actor}` : ""
245
+ ]
246
+ .filter(Boolean)
247
+ .join("\n"),
248
+ activity
249
+ }))
250
+ ].sort(compareIso);
251
+ }
252
+ function csvEscape(value) {
253
+ const text = String(value ?? "");
254
+ if (/[",\n]/.test(text)) {
255
+ return `"${text.replaceAll('"', '""')}"`;
256
+ }
257
+ return text;
258
+ }
259
+ function toIcsDate(value) {
260
+ return value.replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
261
+ }
262
+ function escapeIcsText(value) {
263
+ return value
264
+ .replace(/\\/g, "\\\\")
265
+ .replace(/\n/g, "\\n")
266
+ .replace(/,/g, "\\,")
267
+ .replace(/;/g, "\\;");
268
+ }
269
+ function addMinutes(value, minutes) {
270
+ return new Date(new Date(value).getTime() + minutes * 60 * 1000).toISOString();
271
+ }
272
+ function buildMarkdownExport(payload, entries, filters) {
273
+ const sections = [
274
+ "# Forge self observation calendar",
275
+ "",
276
+ `Range: ${payload.from} to ${payload.to}`,
277
+ `Included observations: ${filters.includeObservations ?? true ? "yes" : "no"}`,
278
+ `Included Forge activity: ${filters.includeActivity ?? true ? "yes" : "no"}`,
279
+ filters.onlyHumanOwned ? "Ownership filter: human-only" : "Ownership filter: all owners",
280
+ filters.tags && filters.tags.length > 0
281
+ ? `Tag filter: ${normalizeTags(filters.tags).join(", ")}`
282
+ : "Tag filter: none",
283
+ filters.search?.trim() ? `Search filter: ${filters.search.trim()}` : "Search filter: none",
284
+ ""
285
+ ];
286
+ let currentDay = "";
287
+ for (const entry of entries) {
288
+ const dayKey = entry.observedAt.slice(0, 10);
289
+ if (dayKey !== currentDay) {
290
+ currentDay = dayKey;
291
+ sections.push(`## ${dayKey}`, "");
292
+ }
293
+ sections.push(`### ${entry.observedAt.slice(11, 16)} · ${entry.kind === "observation" ? "Observation" : "Forge activity"}`, `- Title: ${entry.title}`, entry.ownerLabel ? `- Owner: ${entry.ownerLabel}` : "- Owner: ", `- Tags: ${entry.tags.join(", ") || "None"}`, `- Details: ${entry.description.replace(/\n+/g, " | ")}`, "");
294
+ }
295
+ if (entries.length === 0) {
296
+ sections.push("No entries matched the selected filters.", "");
297
+ }
298
+ return sections.join("\n");
299
+ }
300
+ function buildCsvExport(entries) {
301
+ const rows = [
302
+ [
303
+ "observedAt",
304
+ "kind",
305
+ "title",
306
+ "description",
307
+ "owner",
308
+ "tags",
309
+ "entityType",
310
+ "eventType",
311
+ "source"
312
+ ].join(","),
313
+ ...entries.map((entry) => {
314
+ const activity = entry.kind === "activity" ? entry.activity.event : null;
315
+ return [
316
+ csvEscape(entry.observedAt),
317
+ csvEscape(entry.kind),
318
+ csvEscape(entry.title),
319
+ csvEscape(entry.description),
320
+ csvEscape(entry.ownerLabel),
321
+ csvEscape(entry.tags.join(" | ")),
322
+ csvEscape(activity?.entityType ?? ""),
323
+ csvEscape(activity?.eventType ?? ""),
324
+ csvEscape(activity?.source ?? "")
325
+ ].join(",");
326
+ })
327
+ ];
328
+ return rows.join("\n");
329
+ }
330
+ function buildIcsExport(entries) {
331
+ const now = toIcsDate(new Date().toISOString());
332
+ const lines = [
333
+ "BEGIN:VCALENDAR",
334
+ "VERSION:2.0",
335
+ "PRODID:-//Forge//Self Observation Calendar//EN",
336
+ "CALSCALE:GREGORIAN",
337
+ "METHOD:PUBLISH"
338
+ ];
339
+ for (const entry of entries) {
340
+ const summaryPrefix = entry.kind === "observation" ? "Observation" : "Forge activity";
341
+ lines.push("BEGIN:VEVENT", `UID:${entry.kind}-${entry.kind === "observation" ? entry.observation.id : entry.activity.id}@forge`, `DTSTAMP:${now}`, `DTSTART:${toIcsDate(entry.observedAt)}`, `DTEND:${toIcsDate(addMinutes(entry.observedAt, 15))}`, `SUMMARY:${escapeIcsText(`${summaryPrefix}: ${entry.title}`)}`, `DESCRIPTION:${escapeIcsText(entry.description)}`, `CATEGORIES:${escapeIcsText(entry.tags.join(","))}`, "END:VEVENT");
342
+ }
343
+ lines.push("END:VCALENDAR");
344
+ return lines.join("\r\n");
345
+ }
346
+ function buildExportFileName(format, from) {
347
+ const datePrefix = from.slice(0, 10);
348
+ const extension = format === "markdown"
349
+ ? "md"
350
+ : format === "json"
351
+ ? "json"
352
+ : format === "csv"
353
+ ? "csv"
354
+ : "ics";
355
+ return `forge-self-observation-${datePrefix}.${extension}`;
356
+ }
357
+ export function exportPsycheObservationCalendar(input) {
358
+ const payload = filterPsycheObservationCalendar(getPsycheObservationCalendar({
359
+ from: input.from,
360
+ to: input.to,
361
+ userIds: input.userIds
362
+ }), {
363
+ tags: input.tags,
364
+ includeObservations: input.includeObservations,
365
+ includeActivity: input.includeActivity,
366
+ onlyHumanOwned: input.onlyHumanOwned,
367
+ search: input.search
368
+ });
369
+ const entries = buildTimelineEntries(payload);
370
+ const format = input.format ?? "markdown";
371
+ const fileName = buildExportFileName(format, input.from);
372
+ if (format === "json") {
373
+ return {
374
+ body: Buffer.from(JSON.stringify({
375
+ filters: {
376
+ tags: normalizeTags(input.tags ?? []),
377
+ includeObservations: input.includeObservations ?? true,
378
+ includeActivity: input.includeActivity ?? true,
379
+ onlyHumanOwned: input.onlyHumanOwned ?? false,
380
+ search: input.search?.trim() || ""
381
+ },
382
+ calendar: payload
383
+ }, null, 2), "utf8"),
384
+ fileName,
385
+ mimeType: "application/json; charset=utf-8"
386
+ };
387
+ }
388
+ if (format === "csv") {
389
+ return {
390
+ body: Buffer.from(buildCsvExport(entries), "utf8"),
391
+ fileName,
392
+ mimeType: "text/csv; charset=utf-8"
393
+ };
394
+ }
395
+ if (format === "ics") {
396
+ return {
397
+ body: Buffer.from(buildIcsExport(entries), "utf8"),
398
+ fileName,
399
+ mimeType: "text/calendar; charset=utf-8"
400
+ };
401
+ }
402
+ return {
403
+ body: Buffer.from(buildMarkdownExport(payload, entries, {
404
+ tags: input.tags,
405
+ includeObservations: input.includeObservations,
406
+ includeActivity: input.includeActivity,
407
+ onlyHumanOwned: input.onlyHumanOwned,
408
+ search: input.search
409
+ }), "utf8"),
410
+ fileName,
411
+ mimeType: "text/markdown; charset=utf-8"
412
+ };
413
+ }