forge-openclaw-plugin 0.2.26 → 0.2.28
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/README.md +60 -3
- package/dist/assets/{board-ta0rUHOf.js → board-DPFvZf-D.js} +2 -2
- package/dist/assets/{board-ta0rUHOf.js.map → board-DPFvZf-D.js.map} +1 -1
- package/dist/assets/index-Auw3JrdE.css +1 -0
- package/dist/assets/index-D1H7myQH.js +85 -0
- package/dist/assets/index-D1H7myQH.js.map +1 -0
- package/dist/assets/knowledge-graph-layout.worker-DRvzPxhP.js +2 -0
- package/dist/assets/knowledge-graph-layout.worker-DRvzPxhP.js.map +1 -0
- package/dist/assets/{motion-fBKPB6yw.js → motion-Bvwc85ch.js} +2 -2
- package/dist/assets/{motion-fBKPB6yw.js.map → motion-Bvwc85ch.js.map} +1 -1
- package/dist/assets/{table-C-IGTQni.js → table-FJQTJvUR.js} +2 -2
- package/dist/assets/{table-C-IGTQni.js.map → table-FJQTJvUR.js.map} +1 -1
- package/dist/assets/{ui-DInOpaYF.js → ui-GXFcgvSw.js} +2 -2
- package/dist/assets/{ui-DInOpaYF.js.map → ui-GXFcgvSw.js.map} +1 -1
- package/dist/assets/vendor-Cwf49UMz.js +1247 -0
- package/dist/assets/vendor-Cwf49UMz.js.map +1 -0
- package/dist/index.html +7 -7
- package/dist/openclaw/local-runtime.js +16 -0
- package/dist/openclaw/routes.d.ts +27 -0
- package/dist/openclaw/routes.js +16 -12
- package/dist/server/server/migrations/037_workbench_public_inputs_and_run_inputs.sql +5 -0
- package/dist/server/server/migrations/038_data_management_settings.sql +11 -0
- package/dist/server/server/migrations/039_life_force_and_action_points.sql +114 -0
- package/dist/server/server/migrations/040_screen_time_domain.sql +89 -0
- package/dist/server/server/migrations/041_companion_source_states.sql +21 -0
- package/dist/server/server/migrations/042_movement_boxes.sql +47 -0
- package/dist/server/server/migrations/043_movement_box_overlap_overrides.sql +26 -0
- package/dist/server/server/src/app.js +1900 -91
- package/dist/server/server/src/connectors/box-registry.js +44 -9
- package/dist/server/server/src/data-management-types.js +107 -0
- package/dist/server/server/src/db.js +68 -4
- package/dist/server/server/src/demo-data.js +2 -2
- package/dist/server/server/src/health.js +702 -18
- package/dist/server/server/src/managers/platform/llm-manager.js +7 -4
- package/dist/server/server/src/managers/platform/mock-workbench-provider.js +149 -0
- package/dist/server/server/src/managers/platform/secrets-manager.js +18 -1
- package/dist/server/server/src/managers/runtime.js +9 -0
- package/dist/server/server/src/movement.js +1971 -112
- package/dist/server/server/src/openapi.js +1390 -105
- package/dist/server/server/src/psyche-types.js +9 -1
- package/dist/server/server/src/repositories/activity-events.js +8 -0
- package/dist/server/server/src/repositories/ai-connectors.js +522 -74
- package/dist/server/server/src/repositories/calendar.js +151 -0
- package/dist/server/server/src/repositories/habits.js +37 -1
- package/dist/server/server/src/repositories/model-settings.js +13 -3
- package/dist/server/server/src/repositories/notes.js +3 -0
- package/dist/server/server/src/repositories/settings.js +380 -18
- package/dist/server/server/src/repositories/tasks.js +170 -10
- package/dist/server/server/src/runtime-data-root.js +82 -0
- package/dist/server/server/src/screen-time.js +802 -0
- package/dist/server/server/src/services/data-management.js +788 -0
- package/dist/server/server/src/services/entity-crud.js +205 -2
- package/dist/server/server/src/services/knowledge-graph.js +1455 -0
- package/dist/server/server/src/services/life-force-model.js +217 -0
- package/dist/server/server/src/services/life-force.js +2506 -0
- package/dist/server/server/src/services/psyche-observation-calendar.js +383 -16
- package/dist/server/server/src/types.js +307 -14
- package/dist/server/server/src/web.js +228 -13
- package/dist/server/src/components/customization/utility-widgets.js +136 -27
- package/dist/server/src/components/ui/info-tooltip.js +25 -0
- package/dist/server/src/components/workbench-boxes/calendar/calendar-boxes.js +78 -0
- package/dist/server/src/components/workbench-boxes/goals/goals-boxes.js +62 -0
- package/dist/server/src/components/workbench-boxes/habits/habits-boxes.js +62 -0
- package/dist/server/src/components/workbench-boxes/health/health-boxes.js +63 -8
- package/dist/server/src/components/workbench-boxes/insights/insights-boxes.js +50 -0
- package/dist/server/src/components/workbench-boxes/kanban/kanban-boxes.js +62 -54
- package/dist/server/src/components/workbench-boxes/movement/movement-boxes.js +18 -8
- package/dist/server/src/components/workbench-boxes/notes/notes-boxes.js +56 -38
- package/dist/server/src/components/workbench-boxes/overview/overview-boxes.js +65 -0
- package/dist/server/src/components/workbench-boxes/preferences/preferences-boxes.js +78 -0
- package/dist/server/src/components/workbench-boxes/projects/projects-boxes.js +35 -30
- package/dist/server/src/components/workbench-boxes/psyche/psyche-boxes.js +88 -0
- package/dist/server/src/components/workbench-boxes/questionnaires/questionnaires-boxes.js +61 -0
- package/dist/server/src/components/workbench-boxes/review/review-boxes.js +53 -0
- package/dist/server/src/components/workbench-boxes/shared/define-workbench-box.js +3 -1
- package/dist/server/src/components/workbench-boxes/shared/generic-node-view.js +39 -3
- package/dist/server/src/components/workbench-boxes/strategies/strategies-boxes.js +62 -0
- package/dist/server/src/components/workbench-boxes/tasks/tasks-boxes.js +76 -0
- package/dist/server/src/components/workbench-boxes/today/today-boxes.js +47 -32
- package/dist/server/src/components/workbench-boxes/wiki/wiki-boxes.js +60 -0
- package/dist/server/src/lib/api.js +280 -21
- package/dist/server/src/lib/data-management-types.js +1 -0
- package/dist/server/src/lib/entity-visuals.js +279 -0
- package/dist/server/src/lib/knowledge-graph-types.js +276 -0
- package/dist/server/src/lib/knowledge-graph.js +470 -0
- package/dist/server/src/lib/schemas.js +4 -0
- package/dist/server/src/lib/snapshot-normalizer.js +45 -1
- package/dist/server/src/lib/workbench/contracts.js +229 -0
- package/dist/server/src/lib/workbench/nodes.js +200 -0
- package/dist/server/src/lib/workbench/registry.js +52 -5
- package/dist/server/src/lib/workbench/runtime.js +254 -38
- package/dist/server/src/lib/workbench/tool-catalog.js +68 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/server/migrations/037_workbench_public_inputs_and_run_inputs.sql +5 -0
- package/server/migrations/038_data_management_settings.sql +11 -0
- package/server/migrations/039_life_force_and_action_points.sql +114 -0
- package/server/migrations/040_screen_time_domain.sql +89 -0
- package/server/migrations/041_companion_source_states.sql +21 -0
- package/server/migrations/042_movement_boxes.sql +47 -0
- package/server/migrations/043_movement_box_overlap_overrides.sql +26 -0
- package/skills/forge-openclaw/SKILL.md +41 -11
- package/skills/forge-openclaw/entity_conversation_playbooks.md +448 -34
- package/skills/forge-openclaw/psyche_entity_playbooks.md +170 -17
- package/dist/assets/index-Ro0ZF_az.css +0 -1
- package/dist/assets/index-ytlpSj23.js +0 -79
- package/dist/assets/index-ytlpSj23.js.map +0 -1
- package/dist/assets/vendor-lE3tZJcC.js +0 -876
- package/dist/assets/vendor-lE3tZJcC.js.map +0 -1
|
@@ -1,31 +1,147 @@
|
|
|
1
1
|
import { psycheObservationCalendarPayloadSchema } from "../psyche-types.js";
|
|
2
|
-
import {
|
|
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
|
-
|
|
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
|
|
8
|
-
for (const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|