forge-openclaw-plugin 0.2.19 → 0.2.21
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 +133 -2
- package/dist/assets/board-_C6oMy5w.js +6 -0
- package/dist/assets/{board-8L3uX7_O.js.map → board-_C6oMy5w.js.map} +1 -1
- package/dist/assets/index-B4A6TooJ.js +63 -0
- package/dist/assets/index-B4A6TooJ.js.map +1 -0
- package/dist/assets/index-D6Xs_2mo.css +1 -0
- package/dist/assets/{motion-1GAqqi8M.js → motion-D4sZgCHd.js} +2 -2
- package/dist/assets/{motion-1GAqqi8M.js.map → motion-D4sZgCHd.js.map} +1 -1
- package/dist/assets/{table-DBGlgRjk.js → table-BWzTaky1.js} +2 -2
- package/dist/assets/{table-DBGlgRjk.js.map → table-BWzTaky1.js.map} +1 -1
- package/dist/assets/{ui-iTluWjC4.js → ui-BzK4azQb.js} +7 -7
- package/dist/assets/{ui-iTluWjC4.js.map → ui-BzK4azQb.js.map} +1 -1
- package/dist/assets/vendor-DT3pnAKJ.css +1 -0
- package/dist/assets/vendor-De38P6YR.js +729 -0
- package/dist/assets/vendor-De38P6YR.js.map +1 -0
- package/dist/assets/viz-C6hfyqzu.js +34 -0
- package/dist/assets/viz-C6hfyqzu.js.map +1 -0
- package/dist/index.html +9 -9
- package/dist/openclaw/parity.d.ts +1 -1
- package/dist/openclaw/parity.js +29 -2
- package/dist/openclaw/routes.js +207 -24
- package/dist/openclaw/tools.js +324 -35
- package/dist/server/app.js +2080 -92
- package/dist/server/db.js +3 -0
- package/dist/server/health.js +1284 -0
- package/dist/server/managers/platform/background-job-manager.js +138 -2
- package/dist/server/managers/platform/llm-manager.js +126 -0
- package/dist/server/managers/platform/openai-responses-provider.js +773 -0
- package/dist/server/managers/runtime.js +6 -1
- package/dist/server/openapi.js +718 -0
- package/dist/server/preferences-seeds.js +409 -0
- package/dist/server/preferences-types.js +368 -0
- package/dist/server/psyche-types.js +42 -18
- package/dist/server/repositories/activity-events.js +53 -4
- package/dist/server/repositories/calendar.js +89 -15
- package/dist/server/repositories/collaboration.js +8 -3
- package/dist/server/repositories/diagnostic-logs.js +243 -0
- package/dist/server/repositories/entity-ownership.js +92 -0
- package/dist/server/repositories/goals.js +7 -2
- package/dist/server/repositories/habits.js +122 -16
- package/dist/server/repositories/notes.js +119 -41
- package/dist/server/repositories/preferences.js +1765 -0
- package/dist/server/repositories/projects.js +18 -7
- package/dist/server/repositories/psyche.js +84 -27
- package/dist/server/repositories/rewards.js +112 -4
- package/dist/server/repositories/strategies.js +450 -0
- package/dist/server/repositories/tags.js +11 -6
- package/dist/server/repositories/task-runs.js +10 -2
- package/dist/server/repositories/tasks.js +99 -17
- package/dist/server/repositories/users.js +417 -0
- package/dist/server/repositories/wiki-memory.js +3366 -0
- package/dist/server/services/context.js +20 -18
- package/dist/server/services/dashboard.js +29 -6
- package/dist/server/services/entity-crud.js +21 -3
- package/dist/server/services/insights.js +9 -7
- package/dist/server/services/projects.js +2 -1
- package/dist/server/services/psyche.js +10 -9
- package/dist/server/types.js +594 -30
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/server/migrations/015_multi_user_and_strategies.sql +244 -0
- package/server/migrations/016_health_companion.sql +158 -0
- package/server/migrations/016_strategy_contracts_and_user_graph.sql +22 -0
- package/server/migrations/017_preferences.sql +131 -0
- package/server/migrations/018_preference_catalogs.sql +31 -0
- package/server/migrations/019_wiki_memory.sql +255 -0
- package/server/migrations/020_wiki_page_hierarchy.sql +11 -0
- package/server/migrations/021_hide_evidence_from_wiki_index.sql +3 -0
- package/server/migrations/022_wiki_ingest_background.sql +85 -0
- package/server/migrations/023_diagnostic_logs.sql +28 -0
- package/skills/forge-openclaw/SKILL.md +126 -34
- package/skills/forge-openclaw/entity_conversation_playbooks.md +337 -0
- package/skills/forge-openclaw/psyche_entity_playbooks.md +404 -0
- package/dist/assets/board-8L3uX7_O.js +0 -6
- package/dist/assets/index-Cj1IBH_w.js +0 -36
- package/dist/assets/index-Cj1IBH_w.js.map +0 -1
- package/dist/assets/index-DQT6EbuS.css +0 -1
- package/dist/assets/vendor-BvM2F9Dp.js +0 -503
- package/dist/assets/vendor-BvM2F9Dp.js.map +0 -1
- package/dist/assets/vendor-CRS-psbw.css +0 -1
- package/dist/assets/viz-CNeunkfu.js +0 -34
- package/dist/assets/viz-CNeunkfu.js.map +0 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { getDatabase, runInTransaction } from "../db.js";
|
|
3
3
|
import { recordActivityEvent } from "./activity-events.js";
|
|
4
|
+
import { decorateOwnedEntity, setEntityOwner } from "./entity-ownership.js";
|
|
4
5
|
import { filterDeletedEntities, filterDeletedIds, isEntityDeleted } from "./deleted-entities.js";
|
|
5
6
|
import { createLinkedNotes } from "./notes.js";
|
|
6
7
|
import { assertGoalRelations } from "../services/relations.js";
|
|
@@ -13,7 +14,7 @@ function readGoalTagIds(goalId) {
|
|
|
13
14
|
return filterDeletedIds("tag", rows.map((row) => row.tag_id));
|
|
14
15
|
}
|
|
15
16
|
function mapGoal(row) {
|
|
16
|
-
return goalSchema.parse({
|
|
17
|
+
return goalSchema.parse(decorateOwnedEntity("goal", {
|
|
17
18
|
id: row.id,
|
|
18
19
|
title: row.title,
|
|
19
20
|
description: row.description,
|
|
@@ -24,7 +25,7 @@ function mapGoal(row) {
|
|
|
24
25
|
createdAt: row.created_at,
|
|
25
26
|
updatedAt: row.updated_at,
|
|
26
27
|
tagIds: readGoalTagIds(row.id)
|
|
27
|
-
});
|
|
28
|
+
}));
|
|
28
29
|
}
|
|
29
30
|
function replaceGoalTags(goalId, tagIds) {
|
|
30
31
|
const database = getDatabase();
|
|
@@ -51,6 +52,7 @@ export function createGoal(input, activity) {
|
|
|
51
52
|
.prepare(`INSERT INTO goals (id, title, description, horizon, status, target_points, theme_color, created_at, updated_at)
|
|
52
53
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
53
54
|
.run(id, input.title, input.description, input.horizon, input.status, input.targetPoints, input.themeColor, now, now);
|
|
55
|
+
setEntityOwner("goal", id, input.userId);
|
|
54
56
|
replaceGoalTags(id, input.tagIds);
|
|
55
57
|
const goal = getGoalById(id);
|
|
56
58
|
createLinkedNotes(input.notes, { entityType: "goal", entityId: goal.id, anchorKey: null }, activity ?? { source: "ui", actor: null });
|
|
@@ -92,6 +94,9 @@ export function updateGoal(goalId, input, activity) {
|
|
|
92
94
|
WHERE id = ?`)
|
|
93
95
|
.run(next.title, next.description, next.horizon, next.status, next.targetPoints, next.themeColor, next.updatedAt, goalId);
|
|
94
96
|
replaceGoalTags(goalId, next.tagIds);
|
|
97
|
+
if (input.userId !== undefined) {
|
|
98
|
+
setEntityOwner("goal", goalId, input.userId);
|
|
99
|
+
}
|
|
95
100
|
const goal = getGoalById(goalId);
|
|
96
101
|
if (goal && activity) {
|
|
97
102
|
const statusChanged = current.status !== goal.status;
|
|
@@ -1,29 +1,39 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { getDatabase, runInTransaction } from "../db.js";
|
|
3
3
|
import { HttpError } from "../errors.js";
|
|
4
|
+
import { createGeneratedWorkoutFromHabit, parseGeneratedHealthEventTemplate } from "../health.js";
|
|
5
|
+
import { decorateOwnedEntity, inferFirstOwnedUserId, setEntityOwner } from "./entity-ownership.js";
|
|
4
6
|
import { getGoalById } from "./goals.js";
|
|
5
7
|
import { getProjectById } from "./projects.js";
|
|
6
8
|
import { getBehaviorById, getBehaviorPatternById, getBeliefEntryById, getModeProfileById, getPsycheValueById, getTriggerReportById } from "./psyche.js";
|
|
7
9
|
import { getTaskById } from "./tasks.js";
|
|
8
10
|
import { recordActivityEvent } from "./activity-events.js";
|
|
9
|
-
import { recordHabitCheckInReward } from "./rewards.js";
|
|
11
|
+
import { recordHabitCheckInReward, reverseLatestHabitCheckInReward } from "./rewards.js";
|
|
10
12
|
import { createHabitCheckInSchema, createHabitSchema, habitCheckInSchema, habitSchema, updateHabitSchema } from "../types.js";
|
|
11
13
|
function todayKey(now = new Date()) {
|
|
12
14
|
return now.toISOString().slice(0, 10);
|
|
13
15
|
}
|
|
14
16
|
function parseWeekDays(raw) {
|
|
15
17
|
const parsed = JSON.parse(raw);
|
|
16
|
-
return Array.isArray(parsed)
|
|
18
|
+
return Array.isArray(parsed)
|
|
19
|
+
? parsed.filter((value) => Number.isInteger(value) && value >= 0 && value <= 6)
|
|
20
|
+
: [];
|
|
17
21
|
}
|
|
18
22
|
function parseIdList(raw) {
|
|
19
23
|
const parsed = JSON.parse(raw);
|
|
20
|
-
return Array.isArray(parsed)
|
|
24
|
+
return Array.isArray(parsed)
|
|
25
|
+
? parsed.filter((value) => typeof value === "string" && value.trim().length > 0)
|
|
26
|
+
: [];
|
|
21
27
|
}
|
|
22
28
|
function uniqueIds(values) {
|
|
23
|
-
return [
|
|
29
|
+
return [
|
|
30
|
+
...new Set(values.filter((value) => typeof value === "string" && value.trim().length > 0))
|
|
31
|
+
];
|
|
24
32
|
}
|
|
25
33
|
function normalizeLinkedBehaviorIds(input) {
|
|
26
|
-
const fromArray = Array.isArray(input.linkedBehaviorIds)
|
|
34
|
+
const fromArray = Array.isArray(input.linkedBehaviorIds)
|
|
35
|
+
? input.linkedBehaviorIds
|
|
36
|
+
: [];
|
|
27
37
|
return uniqueIds([...fromArray, input.linkedBehaviorId ?? null]);
|
|
28
38
|
}
|
|
29
39
|
function validateExistingIds(ids, getById, code, label) {
|
|
@@ -56,7 +66,8 @@ function listCheckInsForHabit(habitId, limit = 14) {
|
|
|
56
66
|
return rows.map(mapCheckIn);
|
|
57
67
|
}
|
|
58
68
|
function isAligned(habit, checkIn) {
|
|
59
|
-
return (habit.polarity === "positive" && checkIn.status === "done") ||
|
|
69
|
+
return ((habit.polarity === "positive" && checkIn.status === "done") ||
|
|
70
|
+
(habit.polarity === "negative" && checkIn.status === "missed"));
|
|
60
71
|
}
|
|
61
72
|
function calculateCompletionRate(habit, checkIns) {
|
|
62
73
|
if (checkIns.length === 0) {
|
|
@@ -120,6 +131,7 @@ function mapHabit(row, checkIns = listCheckInsForHabit(row.id)) {
|
|
|
120
131
|
linkedBehaviorTitles: linkedBehaviors.map((behavior) => behavior.title),
|
|
121
132
|
rewardXp: row.reward_xp,
|
|
122
133
|
penaltyXp: row.penalty_xp,
|
|
134
|
+
generatedHealthEventTemplate: parseGeneratedHealthEventTemplate(row.generated_health_event_template_json),
|
|
123
135
|
createdAt: row.created_at,
|
|
124
136
|
updatedAt: row.updated_at,
|
|
125
137
|
lastCheckInAt: latestCheckIn?.createdAt ?? null,
|
|
@@ -129,8 +141,12 @@ function mapHabit(row, checkIns = listCheckInsForHabit(row.id)) {
|
|
|
129
141
|
dueToday: false,
|
|
130
142
|
checkIns
|
|
131
143
|
};
|
|
132
|
-
draft.dueToday = isHabitDueToday({
|
|
133
|
-
|
|
144
|
+
draft.dueToday = isHabitDueToday({
|
|
145
|
+
status: draft.status,
|
|
146
|
+
frequency: draft.frequency,
|
|
147
|
+
weekDays: draft.weekDays
|
|
148
|
+
}, latestCheckIn);
|
|
149
|
+
return habitSchema.parse(decorateOwnedEntity("habit", draft));
|
|
134
150
|
}
|
|
135
151
|
function getHabitRow(habitId) {
|
|
136
152
|
return getDatabase()
|
|
@@ -139,7 +155,7 @@ function getHabitRow(habitId) {
|
|
|
139
155
|
linked_goal_ids_json, linked_project_ids_json, linked_task_ids_json,
|
|
140
156
|
linked_value_ids_json, linked_pattern_ids_json, linked_behavior_ids_json,
|
|
141
157
|
linked_belief_ids_json, linked_mode_ids_json, linked_report_ids_json,
|
|
142
|
-
linked_behavior_id, reward_xp, penalty_xp, created_at, updated_at
|
|
158
|
+
linked_behavior_id, reward_xp, penalty_xp, generated_health_event_template_json, created_at, updated_at
|
|
143
159
|
FROM habits
|
|
144
160
|
WHERE id = ?`)
|
|
145
161
|
.get(habitId);
|
|
@@ -167,7 +183,7 @@ export function listHabits(filters = {}) {
|
|
|
167
183
|
linked_goal_ids_json, linked_project_ids_json, linked_task_ids_json,
|
|
168
184
|
linked_value_ids_json, linked_pattern_ids_json, linked_behavior_ids_json,
|
|
169
185
|
linked_belief_ids_json, linked_mode_ids_json, linked_report_ids_json,
|
|
170
|
-
linked_behavior_id, reward_xp, penalty_xp, created_at, updated_at
|
|
186
|
+
linked_behavior_id, reward_xp, penalty_xp, generated_health_event_template_json, created_at, updated_at
|
|
171
187
|
FROM habits
|
|
172
188
|
${whereSql}
|
|
173
189
|
ORDER BY
|
|
@@ -203,9 +219,24 @@ export function createHabit(input, activity) {
|
|
|
203
219
|
linked_goal_ids_json, linked_project_ids_json, linked_task_ids_json,
|
|
204
220
|
linked_value_ids_json, linked_pattern_ids_json, linked_behavior_ids_json,
|
|
205
221
|
linked_belief_ids_json, linked_mode_ids_json, linked_report_ids_json,
|
|
206
|
-
linked_behavior_id, reward_xp, penalty_xp, created_at, updated_at
|
|
207
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
208
|
-
.run(id, parsed.title, parsed.description, parsed.status, parsed.polarity, parsed.frequency, parsed.targetCount, JSON.stringify(parsed.weekDays), JSON.stringify(parsed.linkedGoalIds), JSON.stringify(parsed.linkedProjectIds), JSON.stringify(parsed.linkedTaskIds), JSON.stringify(parsed.linkedValueIds), JSON.stringify(parsed.linkedPatternIds), JSON.stringify(linkedBehaviorIds), JSON.stringify(parsed.linkedBeliefIds), JSON.stringify(parsed.linkedModeIds), JSON.stringify(parsed.linkedReportIds), linkedBehaviorIds[0] ?? null, parsed.rewardXp, parsed.penaltyXp, now, now);
|
|
222
|
+
linked_behavior_id, reward_xp, penalty_xp, generated_health_event_template_json, created_at, updated_at
|
|
223
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
224
|
+
.run(id, parsed.title, parsed.description, parsed.status, parsed.polarity, parsed.frequency, parsed.targetCount, JSON.stringify(parsed.weekDays), JSON.stringify(parsed.linkedGoalIds), JSON.stringify(parsed.linkedProjectIds), JSON.stringify(parsed.linkedTaskIds), JSON.stringify(parsed.linkedValueIds), JSON.stringify(parsed.linkedPatternIds), JSON.stringify(linkedBehaviorIds), JSON.stringify(parsed.linkedBeliefIds), JSON.stringify(parsed.linkedModeIds), JSON.stringify(parsed.linkedReportIds), linkedBehaviorIds[0] ?? null, parsed.rewardXp, parsed.penaltyXp, JSON.stringify(parsed.generatedHealthEventTemplate), now, now);
|
|
225
|
+
setEntityOwner("habit", id, parsed.userId ??
|
|
226
|
+
inferFirstOwnedUserId([
|
|
227
|
+
...parsed.linkedProjectIds.map((entityId) => ({
|
|
228
|
+
entityType: "project",
|
|
229
|
+
entityId
|
|
230
|
+
})),
|
|
231
|
+
...parsed.linkedGoalIds.map((entityId) => ({
|
|
232
|
+
entityType: "goal",
|
|
233
|
+
entityId
|
|
234
|
+
})),
|
|
235
|
+
...parsed.linkedTaskIds.map((entityId) => ({
|
|
236
|
+
entityType: "task",
|
|
237
|
+
entityId
|
|
238
|
+
}))
|
|
239
|
+
]));
|
|
209
240
|
const habit = getHabitById(id);
|
|
210
241
|
if (activity) {
|
|
211
242
|
recordActivityEvent({
|
|
@@ -232,7 +263,8 @@ export function updateHabit(habitId, input, activity) {
|
|
|
232
263
|
return undefined;
|
|
233
264
|
}
|
|
234
265
|
const parsed = updateHabitSchema.parse(input);
|
|
235
|
-
const nextLinkedBehaviorIds = parsed.linkedBehaviorIds !== undefined ||
|
|
266
|
+
const nextLinkedBehaviorIds = parsed.linkedBehaviorIds !== undefined ||
|
|
267
|
+
parsed.linkedBehaviorId !== undefined
|
|
236
268
|
? normalizeLinkedBehaviorIds({
|
|
237
269
|
linkedBehaviorIds: parsed.linkedBehaviorIds ?? current.linkedBehaviorIds,
|
|
238
270
|
linkedBehaviorId: parsed.linkedBehaviorId === undefined
|
|
@@ -257,9 +289,13 @@ export function updateHabit(habitId, input, activity) {
|
|
|
257
289
|
week_days_json = ?, linked_goal_ids_json = ?, linked_project_ids_json = ?, linked_task_ids_json = ?,
|
|
258
290
|
linked_value_ids_json = ?, linked_pattern_ids_json = ?, linked_behavior_ids_json = ?,
|
|
259
291
|
linked_belief_ids_json = ?, linked_mode_ids_json = ?, linked_report_ids_json = ?,
|
|
260
|
-
linked_behavior_id = ?, reward_xp = ?, penalty_xp = ?, updated_at = ?
|
|
292
|
+
linked_behavior_id = ?, reward_xp = ?, penalty_xp = ?, generated_health_event_template_json = ?, updated_at = ?
|
|
261
293
|
WHERE id = ?`)
|
|
262
|
-
.run(parsed.title ?? current.title, parsed.description ?? current.description, parsed.status ?? current.status, parsed.polarity ?? current.polarity, parsed.frequency ?? current.frequency, parsed.targetCount ?? current.targetCount, JSON.stringify(parsed.weekDays ?? current.weekDays), JSON.stringify(parsed.linkedGoalIds ?? current.linkedGoalIds), JSON.stringify(parsed.linkedProjectIds ?? current.linkedProjectIds), JSON.stringify(parsed.linkedTaskIds ?? current.linkedTaskIds), JSON.stringify(parsed.linkedValueIds ?? current.linkedValueIds), JSON.stringify(parsed.linkedPatternIds ?? current.linkedPatternIds), JSON.stringify(nextLinkedBehaviorIds), JSON.stringify(parsed.linkedBeliefIds ?? current.linkedBeliefIds), JSON.stringify(parsed.linkedModeIds ?? current.linkedModeIds), JSON.stringify(parsed.linkedReportIds ?? current.linkedReportIds), nextLinkedBehaviorIds[0] ?? null, parsed.rewardXp ?? current.rewardXp, parsed.penaltyXp ?? current.penaltyXp,
|
|
294
|
+
.run(parsed.title ?? current.title, parsed.description ?? current.description, parsed.status ?? current.status, parsed.polarity ?? current.polarity, parsed.frequency ?? current.frequency, parsed.targetCount ?? current.targetCount, JSON.stringify(parsed.weekDays ?? current.weekDays), JSON.stringify(parsed.linkedGoalIds ?? current.linkedGoalIds), JSON.stringify(parsed.linkedProjectIds ?? current.linkedProjectIds), JSON.stringify(parsed.linkedTaskIds ?? current.linkedTaskIds), JSON.stringify(parsed.linkedValueIds ?? current.linkedValueIds), JSON.stringify(parsed.linkedPatternIds ?? current.linkedPatternIds), JSON.stringify(nextLinkedBehaviorIds), JSON.stringify(parsed.linkedBeliefIds ?? current.linkedBeliefIds), JSON.stringify(parsed.linkedModeIds ?? current.linkedModeIds), JSON.stringify(parsed.linkedReportIds ?? current.linkedReportIds), nextLinkedBehaviorIds[0] ?? null, parsed.rewardXp ?? current.rewardXp, parsed.penaltyXp ?? current.penaltyXp, JSON.stringify(parsed.generatedHealthEventTemplate ??
|
|
295
|
+
current.generatedHealthEventTemplate), updatedAt, habitId);
|
|
296
|
+
if (parsed.userId !== undefined) {
|
|
297
|
+
setEntityOwner("habit", habitId, parsed.userId);
|
|
298
|
+
}
|
|
263
299
|
const habit = getHabitById(habitId);
|
|
264
300
|
if (activity) {
|
|
265
301
|
recordActivityEvent({
|
|
@@ -353,6 +389,76 @@ export function createHabitCheckIn(habitId, input, activity) {
|
|
|
353
389
|
deltaXp: reward.deltaXp
|
|
354
390
|
}
|
|
355
391
|
});
|
|
392
|
+
if (parsed.status === "done") {
|
|
393
|
+
const checkInId = existing?.id
|
|
394
|
+
? existing.id
|
|
395
|
+
: getDatabase()
|
|
396
|
+
.prepare(`SELECT id FROM habit_check_ins WHERE habit_id = ? AND date_key = ?`)
|
|
397
|
+
.get(habitId, parsed.dateKey)?.id;
|
|
398
|
+
if (checkInId) {
|
|
399
|
+
createGeneratedWorkoutFromHabit({
|
|
400
|
+
habitId: habit.id,
|
|
401
|
+
checkInId,
|
|
402
|
+
habitTitle: habit.title,
|
|
403
|
+
userId: habit.userId ?? "user_operator",
|
|
404
|
+
dateKey: parsed.dateKey,
|
|
405
|
+
template: habit.generatedHealthEventTemplate,
|
|
406
|
+
linkedEntities: [
|
|
407
|
+
...habit.linkedGoalIds.map((entityId) => ({
|
|
408
|
+
entityType: "goal",
|
|
409
|
+
entityId,
|
|
410
|
+
relationshipType: "habit_context"
|
|
411
|
+
})),
|
|
412
|
+
...habit.linkedProjectIds.map((entityId) => ({
|
|
413
|
+
entityType: "project",
|
|
414
|
+
entityId,
|
|
415
|
+
relationshipType: "habit_context"
|
|
416
|
+
})),
|
|
417
|
+
...habit.linkedTaskIds.map((entityId) => ({
|
|
418
|
+
entityType: "task",
|
|
419
|
+
entityId,
|
|
420
|
+
relationshipType: "habit_context"
|
|
421
|
+
}))
|
|
422
|
+
]
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return getHabitById(habitId);
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
export function deleteHabitCheckIn(habitId, dateKey, activity) {
|
|
430
|
+
const habit = getHabitById(habitId);
|
|
431
|
+
if (!habit) {
|
|
432
|
+
return undefined;
|
|
433
|
+
}
|
|
434
|
+
return runInTransaction(() => {
|
|
435
|
+
const existing = getDatabase()
|
|
436
|
+
.prepare(`SELECT id, habit_id, date_key, status, note, delta_xp, created_at, updated_at
|
|
437
|
+
FROM habit_check_ins
|
|
438
|
+
WHERE habit_id = ? AND date_key = ?`)
|
|
439
|
+
.get(habitId, dateKey);
|
|
440
|
+
if (!existing) {
|
|
441
|
+
return getHabitById(habitId);
|
|
442
|
+
}
|
|
443
|
+
getDatabase()
|
|
444
|
+
.prepare(`DELETE FROM habit_check_ins WHERE id = ?`)
|
|
445
|
+
.run(existing.id);
|
|
446
|
+
reverseLatestHabitCheckInReward(habit, dateKey, activity ?? { source: "ui", actor: null });
|
|
447
|
+
recordActivityEvent({
|
|
448
|
+
entityType: "habit",
|
|
449
|
+
entityId: habit.id,
|
|
450
|
+
eventType: "habit_check_in_deleted",
|
|
451
|
+
title: `Habit entry removed: ${habit.title}`,
|
|
452
|
+
description: "Habit check-in removed from the timeline.",
|
|
453
|
+
actor: activity?.actor ?? null,
|
|
454
|
+
source: activity?.source ?? "ui",
|
|
455
|
+
metadata: {
|
|
456
|
+
dateKey,
|
|
457
|
+
status: existing.status,
|
|
458
|
+
polarity: habit.polarity,
|
|
459
|
+
deltaXp: existing.delta_xp
|
|
460
|
+
}
|
|
461
|
+
});
|
|
356
462
|
return getHabitById(habitId);
|
|
357
463
|
});
|
|
358
464
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { getDatabase } from "../db.js";
|
|
3
3
|
import { recordActivityEvent } from "./activity-events.js";
|
|
4
|
-
import {
|
|
4
|
+
import { decorateOwnedEntity, setEntityOwner } from "./entity-ownership.js";
|
|
5
|
+
import { filterDeletedEntities, getDeletedEntityRecord, clearDeletedEntityRecord, isEntityDeleted } from "./deleted-entities.js";
|
|
5
6
|
import { recordEventLog } from "./event-log.js";
|
|
6
7
|
import { noteSchema, notesListQuerySchema, createNoteSchema, updateNoteSchema } from "../types.js";
|
|
8
|
+
import { deleteNoteWikiArtifacts, prepareNoteWikiFields, syncNoteWikiArtifacts } from "./wiki-memory.js";
|
|
7
9
|
function normalizeAnchorKey(anchorKey) {
|
|
8
10
|
return anchorKey.trim().length > 0 ? anchorKey : null;
|
|
9
11
|
}
|
|
@@ -46,6 +48,31 @@ function parseTagsJson(raw) {
|
|
|
46
48
|
return [];
|
|
47
49
|
}
|
|
48
50
|
}
|
|
51
|
+
function parseAliasesJson(raw) {
|
|
52
|
+
try {
|
|
53
|
+
const parsed = JSON.parse(raw);
|
|
54
|
+
return Array.isArray(parsed)
|
|
55
|
+
? Array.from(new Set(parsed
|
|
56
|
+
.filter((value) => typeof value === "string")
|
|
57
|
+
.map((value) => value.trim())
|
|
58
|
+
.filter(Boolean)))
|
|
59
|
+
: [];
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function parseFrontmatterJson(raw) {
|
|
66
|
+
try {
|
|
67
|
+
const parsed = JSON.parse(raw);
|
|
68
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
69
|
+
? parsed
|
|
70
|
+
: {};
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return {};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
49
76
|
function noteMatchesTextTerm(note, term) {
|
|
50
77
|
const normalized = term.trim().toLowerCase();
|
|
51
78
|
if (!normalized) {
|
|
@@ -102,7 +129,8 @@ function buildFtsQuery(query) {
|
|
|
102
129
|
}
|
|
103
130
|
function getNoteRow(noteId) {
|
|
104
131
|
return getDatabase()
|
|
105
|
-
.prepare(`SELECT id, content_markdown, content_plain, author, source, tags_json, destroy_at,
|
|
132
|
+
.prepare(`SELECT id, kind, title, slug, space_id, aliases_json, summary, content_markdown, content_plain, author, source, tags_json, destroy_at,
|
|
133
|
+
source_path, frontmatter_json, revision_hash, last_synced_at, parent_slug, index_order, show_in_index, created_at, updated_at
|
|
106
134
|
FROM notes
|
|
107
135
|
WHERE id = ?`)
|
|
108
136
|
.get(noteId);
|
|
@@ -127,18 +155,31 @@ function mapLinks(rows) {
|
|
|
127
155
|
}));
|
|
128
156
|
}
|
|
129
157
|
function mapNote(row, linkRows) {
|
|
130
|
-
return noteSchema.parse({
|
|
158
|
+
return noteSchema.parse(decorateOwnedEntity("note", {
|
|
131
159
|
id: row.id,
|
|
160
|
+
kind: row.kind,
|
|
161
|
+
title: row.title,
|
|
162
|
+
slug: row.slug,
|
|
163
|
+
spaceId: row.space_id,
|
|
164
|
+
parentSlug: row.parent_slug,
|
|
165
|
+
indexOrder: row.index_order,
|
|
166
|
+
showInIndex: row.show_in_index === 1,
|
|
167
|
+
aliases: parseAliasesJson(row.aliases_json),
|
|
168
|
+
summary: row.summary,
|
|
132
169
|
contentMarkdown: row.content_markdown,
|
|
133
170
|
contentPlain: row.content_plain,
|
|
134
171
|
author: row.author,
|
|
135
172
|
source: row.source,
|
|
173
|
+
sourcePath: row.source_path,
|
|
174
|
+
frontmatter: parseFrontmatterJson(row.frontmatter_json),
|
|
175
|
+
revisionHash: row.revision_hash,
|
|
176
|
+
lastSyncedAt: row.last_synced_at,
|
|
136
177
|
tags: parseTagsJson(row.tags_json),
|
|
137
178
|
destroyAt: row.destroy_at,
|
|
138
179
|
createdAt: row.created_at,
|
|
139
180
|
updatedAt: row.updated_at,
|
|
140
181
|
links: mapLinks(linkRows)
|
|
141
|
-
});
|
|
182
|
+
}));
|
|
142
183
|
}
|
|
143
184
|
function upsertSearchRow(noteId, contentPlain, author) {
|
|
144
185
|
getDatabase().prepare(`DELETE FROM notes_fts WHERE note_id = ?`).run(noteId);
|
|
@@ -151,7 +192,8 @@ function deleteSearchRow(noteId) {
|
|
|
151
192
|
}
|
|
152
193
|
function listAllNoteRows() {
|
|
153
194
|
return getDatabase()
|
|
154
|
-
.prepare(`SELECT id, content_markdown, content_plain, author, source, tags_json, destroy_at,
|
|
195
|
+
.prepare(`SELECT id, kind, title, slug, space_id, aliases_json, summary, content_markdown, content_plain, author, source, tags_json, destroy_at,
|
|
196
|
+
source_path, frontmatter_json, revision_hash, last_synced_at, parent_slug, index_order, show_in_index, created_at, updated_at
|
|
155
197
|
FROM notes
|
|
156
198
|
ORDER BY created_at DESC`)
|
|
157
199
|
.all();
|
|
@@ -279,6 +321,9 @@ export function listNotes(query = {}) {
|
|
|
279
321
|
linksByNoteId.set(link.note_id, current);
|
|
280
322
|
}
|
|
281
323
|
return filterDeletedEntities("note", rows
|
|
324
|
+
.filter((row) => parsed.kind ? row.kind === parsed.kind : true)
|
|
325
|
+
.filter((row) => parsed.spaceId ? row.space_id === parsed.spaceId : true)
|
|
326
|
+
.filter((row) => parsed.slug ? row.slug.toLowerCase() === parsed.slug.toLowerCase() : true)
|
|
282
327
|
.filter((row) => parsed.author
|
|
283
328
|
? (row.author ?? "")
|
|
284
329
|
.toLowerCase()
|
|
@@ -327,28 +372,59 @@ export function createNote(input, context) {
|
|
|
327
372
|
});
|
|
328
373
|
const now = new Date().toISOString();
|
|
329
374
|
const id = `note_${randomUUID().replaceAll("-", "").slice(0, 10)}`;
|
|
375
|
+
const wikiFields = prepareNoteWikiFields({
|
|
376
|
+
id,
|
|
377
|
+
contentMarkdown: parsed.contentMarkdown,
|
|
378
|
+
kind: parsed.kind,
|
|
379
|
+
title: parsed.title,
|
|
380
|
+
slug: parsed.slug,
|
|
381
|
+
spaceId: parsed.spaceId,
|
|
382
|
+
parentSlug: parsed.parentSlug,
|
|
383
|
+
indexOrder: parsed.indexOrder,
|
|
384
|
+
showInIndex: parsed.showInIndex,
|
|
385
|
+
aliases: parsed.aliases,
|
|
386
|
+
summary: parsed.summary,
|
|
387
|
+
userId: parsed.userId ?? null
|
|
388
|
+
});
|
|
330
389
|
const contentPlain = stripMarkdown(parsed.contentMarkdown);
|
|
331
390
|
getDatabase()
|
|
332
|
-
.prepare(`INSERT INTO notes (
|
|
333
|
-
|
|
334
|
-
|
|
391
|
+
.prepare(`INSERT INTO notes (
|
|
392
|
+
id, kind, title, slug, space_id, parent_slug, index_order, show_in_index, aliases_json, summary, content_markdown, content_plain, author, source, tags_json, destroy_at,
|
|
393
|
+
source_path, frontmatter_json, revision_hash, last_synced_at, created_at, updated_at
|
|
394
|
+
)
|
|
395
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
396
|
+
.run(id, wikiFields.kind, wikiFields.title, wikiFields.slug, wikiFields.spaceId, wikiFields.parentSlug, wikiFields.indexOrder, wikiFields.showInIndex ? 1 : 0, JSON.stringify(wikiFields.aliases), wikiFields.summary, parsed.contentMarkdown, contentPlain, parsed.author ?? context.actor ?? null, context.source, JSON.stringify(parsed.tags), parsed.destroyAt, parsed.sourcePath, JSON.stringify(parsed.frontmatter), parsed.revisionHash, parsed.lastSyncedAt ?? null, now, now);
|
|
335
397
|
insertLinks(id, parsed.links, now);
|
|
398
|
+
setEntityOwner("note", id, parsed.userId, parsed.author ?? context.actor ?? null);
|
|
336
399
|
clearDeletedEntityRecord("note", id);
|
|
337
400
|
upsertSearchRow(id, contentPlain, parsed.author ?? context.actor ?? null);
|
|
338
401
|
const note = getNoteById(id, { skipCleanup: true });
|
|
402
|
+
syncNoteWikiArtifacts(note);
|
|
339
403
|
recordNoteActivity(note, "note.created", "Note added", context);
|
|
340
|
-
return
|
|
404
|
+
return getNoteById(id, { skipCleanup: true });
|
|
341
405
|
}
|
|
342
406
|
export function createLinkedNotes(notes, entityLink, context) {
|
|
343
407
|
if (!notes || notes.length === 0) {
|
|
344
408
|
return [];
|
|
345
409
|
}
|
|
346
410
|
return notes.map((note) => createNote({
|
|
411
|
+
kind: "evidence",
|
|
412
|
+
title: "",
|
|
413
|
+
slug: "",
|
|
414
|
+
spaceId: "",
|
|
415
|
+
parentSlug: null,
|
|
416
|
+
indexOrder: 0,
|
|
417
|
+
showInIndex: false,
|
|
418
|
+
aliases: [],
|
|
419
|
+
summary: "",
|
|
347
420
|
contentMarkdown: note.contentMarkdown,
|
|
348
421
|
author: note.author,
|
|
349
422
|
tags: note.tags,
|
|
350
423
|
destroyAt: note.destroyAt,
|
|
351
|
-
links: [entityLink, ...note.links]
|
|
424
|
+
links: [entityLink, ...note.links],
|
|
425
|
+
sourcePath: "",
|
|
426
|
+
frontmatter: {},
|
|
427
|
+
revisionHash: ""
|
|
352
428
|
}, context));
|
|
353
429
|
}
|
|
354
430
|
export function updateNote(noteId, input, context) {
|
|
@@ -367,36 +443,50 @@ export function updateNote(noteId, input, context) {
|
|
|
367
443
|
const nextAuthor = patch.author === undefined ? existing.author : patch.author;
|
|
368
444
|
const nextTags = patch.tags ?? existing.tags;
|
|
369
445
|
const nextDestroyAt = patch.destroyAt === undefined ? existing.destroyAt : patch.destroyAt;
|
|
446
|
+
const wikiFields = prepareNoteWikiFields({
|
|
447
|
+
id: noteId,
|
|
448
|
+
contentMarkdown: nextMarkdown,
|
|
449
|
+
kind: patch.kind ?? existing.kind,
|
|
450
|
+
title: patch.title,
|
|
451
|
+
slug: patch.slug,
|
|
452
|
+
spaceId: patch.spaceId,
|
|
453
|
+
parentSlug: patch.parentSlug,
|
|
454
|
+
indexOrder: patch.indexOrder,
|
|
455
|
+
showInIndex: patch.showInIndex,
|
|
456
|
+
aliases: patch.aliases,
|
|
457
|
+
summary: patch.summary,
|
|
458
|
+
userId: patch.userId ?? existing.userId ?? null,
|
|
459
|
+
existing
|
|
460
|
+
});
|
|
461
|
+
const nextFrontmatter = patch.frontmatter === undefined ? existing.frontmatter : patch.frontmatter;
|
|
462
|
+
const nextSourcePath = patch.sourcePath === undefined ? existing.sourcePath : patch.sourcePath;
|
|
463
|
+
const nextRevisionHash = patch.revisionHash === undefined
|
|
464
|
+
? existing.revisionHash
|
|
465
|
+
: patch.revisionHash;
|
|
466
|
+
const nextLastSyncedAt = patch.lastSyncedAt === undefined
|
|
467
|
+
? existing.lastSyncedAt
|
|
468
|
+
: patch.lastSyncedAt;
|
|
370
469
|
const updatedAt = new Date().toISOString();
|
|
371
470
|
getDatabase()
|
|
372
471
|
.prepare(`UPDATE notes
|
|
373
|
-
SET
|
|
472
|
+
SET kind = ?, title = ?, slug = ?, space_id = ?, parent_slug = ?, index_order = ?, show_in_index = ?, aliases_json = ?, summary = ?, content_markdown = ?, content_plain = ?, author = ?,
|
|
473
|
+
tags_json = ?, destroy_at = ?, source_path = ?, frontmatter_json = ?, revision_hash = ?, last_synced_at = ?, updated_at = ?
|
|
374
474
|
WHERE id = ?`)
|
|
375
|
-
.run(nextMarkdown, nextPlain, nextAuthor, JSON.stringify(nextTags), nextDestroyAt, updatedAt, noteId);
|
|
475
|
+
.run(wikiFields.kind, wikiFields.title, wikiFields.slug, wikiFields.spaceId, wikiFields.parentSlug, wikiFields.indexOrder, wikiFields.showInIndex ? 1 : 0, JSON.stringify(wikiFields.aliases), wikiFields.summary, nextMarkdown, nextPlain, nextAuthor, JSON.stringify(nextTags), nextDestroyAt, nextSourcePath, JSON.stringify(nextFrontmatter), nextRevisionHash, nextLastSyncedAt, updatedAt, noteId);
|
|
376
476
|
if (patch.links) {
|
|
377
477
|
replaceLinks(noteId, patch.links, updatedAt);
|
|
378
478
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
clearDeletedEntityRecord("note", noteId);
|
|
382
|
-
}
|
|
383
|
-
else {
|
|
384
|
-
const details = describeNote(note);
|
|
385
|
-
upsertDeletedEntityRecord({
|
|
386
|
-
entityType: "note",
|
|
387
|
-
entityId: note.id,
|
|
388
|
-
title: details.title,
|
|
389
|
-
subtitle: details.subtitle,
|
|
390
|
-
snapshot: note,
|
|
391
|
-
deleteReason: "Note no longer has any linked entities.",
|
|
392
|
-
context
|
|
393
|
-
});
|
|
479
|
+
if (patch.userId !== undefined) {
|
|
480
|
+
setEntityOwner("note", noteId, patch.userId, nextAuthor ?? context.actor ?? null);
|
|
394
481
|
}
|
|
482
|
+
const note = getNoteByIdIncludingDeleted(noteId, { skipCleanup: true });
|
|
483
|
+
clearDeletedEntityRecord("note", noteId);
|
|
395
484
|
upsertSearchRow(noteId, nextPlain, nextAuthor);
|
|
396
485
|
if (nextDestroyAt && Date.parse(nextDestroyAt) <= Date.now()) {
|
|
397
486
|
deleteNoteInternal(noteId, { source: "system", actor: null }, "Ephemeral note expired");
|
|
398
487
|
return undefined;
|
|
399
488
|
}
|
|
489
|
+
syncNoteWikiArtifacts(note);
|
|
400
490
|
recordNoteActivity(note, "note.updated", "Note updated", context);
|
|
401
491
|
return getNoteById(noteId);
|
|
402
492
|
}
|
|
@@ -411,6 +501,7 @@ function deleteNoteInternal(noteId, context, title) {
|
|
|
411
501
|
getDatabase().prepare(`DELETE FROM note_links WHERE note_id = ?`).run(noteId);
|
|
412
502
|
getDatabase().prepare(`DELETE FROM notes WHERE id = ?`).run(noteId);
|
|
413
503
|
deleteSearchRow(noteId);
|
|
504
|
+
deleteNoteWikiArtifacts(existing);
|
|
414
505
|
clearDeletedEntityRecord("note", noteId);
|
|
415
506
|
recordNoteActivity(existing, "note.deleted", title, context);
|
|
416
507
|
return existing;
|
|
@@ -474,19 +565,6 @@ export function unlinkNotesForEntity(entityType, entityId, context) {
|
|
|
474
565
|
clearDeletedEntityRecord("note", row.note_id);
|
|
475
566
|
continue;
|
|
476
567
|
}
|
|
477
|
-
|
|
478
|
-
if (!note) {
|
|
479
|
-
continue;
|
|
480
|
-
}
|
|
481
|
-
const details = describeNote(note);
|
|
482
|
-
upsertDeletedEntityRecord({
|
|
483
|
-
entityType: "note",
|
|
484
|
-
entityId: note.id,
|
|
485
|
-
title: details.title,
|
|
486
|
-
subtitle: details.subtitle,
|
|
487
|
-
snapshot: { ...note, links: [] },
|
|
488
|
-
deleteReason: `All links were removed when ${entityType} ${entityId} was deleted.`,
|
|
489
|
-
context
|
|
490
|
-
});
|
|
568
|
+
clearDeletedEntityRecord("note", row.note_id);
|
|
491
569
|
}
|
|
492
570
|
}
|