forge-openclaw-plugin 0.2.18 → 0.2.19
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 +36 -4
- package/dist/assets/{board-2KevHCI0.js → board-8L3uX7_O.js} +2 -2
- package/dist/assets/{board-2KevHCI0.js.map → board-8L3uX7_O.js.map} +1 -1
- package/dist/assets/index-Cj1IBH_w.js +36 -0
- package/dist/assets/index-Cj1IBH_w.js.map +1 -0
- package/dist/assets/index-DQT6EbuS.css +1 -0
- package/dist/assets/{motion-q19HPmWs.js → motion-1GAqqi8M.js} +2 -2
- package/dist/assets/{motion-q19HPmWs.js.map → motion-1GAqqi8M.js.map} +1 -1
- package/dist/assets/{table-BDMHBY4a.js → table-DBGlgRjk.js} +2 -2
- package/dist/assets/{table-BDMHBY4a.js.map → table-DBGlgRjk.js.map} +1 -1
- package/dist/assets/{ui-CQ_AsFs8.js → ui-iTluWjC4.js} +2 -2
- package/dist/assets/{ui-CQ_AsFs8.js.map → ui-iTluWjC4.js.map} +1 -1
- package/dist/assets/{vendor-5HifrnRK.js → vendor-BvM2F9Dp.js} +139 -84
- package/dist/assets/vendor-BvM2F9Dp.js.map +1 -0
- package/dist/assets/{viz-CQzkRnTu.js → viz-CNeunkfu.js} +2 -2
- package/dist/assets/{viz-CQzkRnTu.js.map → viz-CNeunkfu.js.map} +1 -1
- package/dist/index.html +8 -8
- package/dist/openclaw/parity.js +1 -0
- package/dist/openclaw/routes.js +7 -0
- package/dist/openclaw/tools.js +183 -16
- package/dist/server/app.js +2509 -263
- package/dist/server/managers/platform/secrets-manager.js +44 -1
- package/dist/server/managers/runtime.js +3 -1
- package/dist/server/openapi.js +2037 -172
- package/dist/server/repositories/calendar.js +1101 -0
- package/dist/server/repositories/deleted-entities.js +10 -2
- package/dist/server/repositories/notes.js +161 -28
- package/dist/server/repositories/projects.js +45 -13
- package/dist/server/repositories/rewards.js +114 -6
- package/dist/server/repositories/settings.js +47 -5
- package/dist/server/repositories/task-runs.js +46 -10
- package/dist/server/repositories/tasks.js +25 -9
- package/dist/server/repositories/weekly-reviews.js +109 -0
- package/dist/server/repositories/work-adjustments.js +105 -0
- package/dist/server/services/calendar-runtime.js +1301 -0
- package/dist/server/services/entity-crud.js +94 -3
- package/dist/server/services/projects.js +32 -8
- package/dist/server/services/reviews.js +15 -1
- package/dist/server/services/work-time.js +27 -0
- package/dist/server/types.js +934 -49
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/server/migrations/006_work_adjustments.sql +14 -0
- package/server/migrations/007_weekly_review_closures.sql +17 -0
- package/server/migrations/008_calendar_execution.sql +147 -0
- package/server/migrations/009_true_calendar_events.sql +195 -0
- package/server/migrations/010_calendar_selection_state.sql +6 -0
- package/server/migrations/011_calendar_timezone_backfill.sql +11 -0
- package/server/migrations/012_work_block_ranges.sql +7 -0
- package/server/migrations/013_microsoft_local_auth_settings.sql +8 -0
- package/server/migrations/014_note_tags_and_ephemeral.sql +8 -0
- package/skills/forge-openclaw/SKILL.md +117 -11
- package/dist/assets/index-CDYW4WDH.js +0 -36
- package/dist/assets/index-CDYW4WDH.js.map +0 -1
- package/dist/assets/index-yroQr6YZ.css +0 -1
- package/dist/assets/vendor-5HifrnRK.js.map +0 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getDatabase, runInTransaction } from "../db.js";
|
|
2
2
|
import { createInsight, deleteInsight, getInsightById, listInsights, updateInsight } from "../repositories/collaboration.js";
|
|
3
|
+
import { createCalendarEvent, createTaskTimebox, createWorkBlockTemplate, deleteCalendarEvent, deleteTaskTimebox, deleteWorkBlockTemplate, getCalendarEventById, getTaskTimeboxById, getWorkBlockTemplateById, listCalendarEvents, listTaskTimeboxes, listWorkBlockTemplates, updateCalendarEvent, updateTaskTimebox, updateWorkBlockTemplate } from "../repositories/calendar.js";
|
|
3
4
|
import { createNote, deleteNote, getNoteById, listNotes, unlinkNotesForEntity, updateNote } from "../repositories/notes.js";
|
|
4
5
|
import { createBehaviorPatternSchema, createBehaviorSchema, createBeliefEntrySchema, createEmotionDefinitionSchema, createEventTypeSchema, createModeGuideSessionSchema, createModeProfileSchema, createPsycheValueSchema, createTriggerReportSchema, updateBehaviorPatternSchema, updateBehaviorSchema, updateBeliefEntrySchema, updateEmotionDefinitionSchema, updateEventTypeSchema, updateModeGuideSessionSchema, updateModeProfileSchema, updatePsycheValueSchema, updateTriggerReportSchema } from "../psyche-types.js";
|
|
5
6
|
import { buildSettingsBinPayload, cascadeSoftDeleteAnchoredCollaboration, clearDeletedEntityRecord, getDeletedEntityRecord, listDeletedEntities, restoreAnchoredCollaboration, restoreDeletedEntityRecord, upsertDeletedEntityRecord } from "../repositories/deleted-entities.js";
|
|
@@ -9,7 +10,11 @@ import { createBehavior, createBehaviorPattern, createBeliefEntry, createEmotion
|
|
|
9
10
|
import { createProject, deleteProject, getProjectById, listProjects, updateProject } from "../repositories/projects.js";
|
|
10
11
|
import { createTag, deleteTag, getTagById, listTags, updateTag } from "../repositories/tags.js";
|
|
11
12
|
import { createTask, deleteTask, getTaskById, listTasks, updateTask } from "../repositories/tasks.js";
|
|
12
|
-
import { createGoalSchema, createHabitSchema, createInsightSchema, createNoteSchema, createProjectSchema, createTagSchema, createTaskSchema, updateGoalSchema, updateHabitSchema, updateInsightSchema, updateNoteSchema, updateProjectSchema, updateTagSchema, updateTaskSchema } from "../types.js";
|
|
13
|
+
import { createCalendarEventSchema, createGoalSchema, createHabitSchema, createInsightSchema, createNoteSchema, createProjectSchema, createTaskTimeboxSchema, createTagSchema, createTaskSchema, createWorkBlockTemplateSchema, updateCalendarEventSchema, updateGoalSchema, updateHabitSchema, updateInsightSchema, updateNoteSchema, updateProjectSchema, updateTaskTimeboxSchema, updateTagSchema, updateTaskSchema, updateWorkBlockTemplateSchema } from "../types.js";
|
|
14
|
+
const ENTITY_CALENDAR_LIST_RANGE = {
|
|
15
|
+
from: "1970-01-01T00:00:00.000Z",
|
|
16
|
+
to: "2100-01-01T00:00:00.000Z"
|
|
17
|
+
};
|
|
13
18
|
class AtomicBatchRollback extends Error {
|
|
14
19
|
index;
|
|
15
20
|
code;
|
|
@@ -26,6 +31,8 @@ const CRUD_ENTITY_CAPABILITIES = {
|
|
|
26
31
|
goal: {
|
|
27
32
|
entityType: "goal",
|
|
28
33
|
routeBase: "/api/v1/goals",
|
|
34
|
+
deleteMode: "soft_default",
|
|
35
|
+
inBin: true,
|
|
29
36
|
list: () => listGoals(),
|
|
30
37
|
get: (id) => getGoalById(id),
|
|
31
38
|
create: (data, context) => createGoal(data, context),
|
|
@@ -35,6 +42,8 @@ const CRUD_ENTITY_CAPABILITIES = {
|
|
|
35
42
|
project: {
|
|
36
43
|
entityType: "project",
|
|
37
44
|
routeBase: "/api/v1/projects",
|
|
45
|
+
deleteMode: "soft_default",
|
|
46
|
+
inBin: true,
|
|
38
47
|
list: () => listProjects(),
|
|
39
48
|
get: (id) => getProjectById(id),
|
|
40
49
|
create: (data, context) => createProject(data, context),
|
|
@@ -44,6 +53,8 @@ const CRUD_ENTITY_CAPABILITIES = {
|
|
|
44
53
|
task: {
|
|
45
54
|
entityType: "task",
|
|
46
55
|
routeBase: "/api/v1/tasks",
|
|
56
|
+
deleteMode: "soft_default",
|
|
57
|
+
inBin: true,
|
|
47
58
|
list: () => listTasks(),
|
|
48
59
|
get: (id) => getTaskById(id),
|
|
49
60
|
create: (data, context) => createTask(data, context),
|
|
@@ -53,6 +64,8 @@ const CRUD_ENTITY_CAPABILITIES = {
|
|
|
53
64
|
habit: {
|
|
54
65
|
entityType: "habit",
|
|
55
66
|
routeBase: "/api/v1/habits",
|
|
67
|
+
deleteMode: "soft_default",
|
|
68
|
+
inBin: true,
|
|
56
69
|
list: () => listHabits(),
|
|
57
70
|
get: (id) => getHabitById(id),
|
|
58
71
|
create: (data, context) => createHabit(data, context),
|
|
@@ -62,6 +75,8 @@ const CRUD_ENTITY_CAPABILITIES = {
|
|
|
62
75
|
tag: {
|
|
63
76
|
entityType: "tag",
|
|
64
77
|
routeBase: "/api/v1/tags",
|
|
78
|
+
deleteMode: "soft_default",
|
|
79
|
+
inBin: true,
|
|
65
80
|
list: () => listTags(),
|
|
66
81
|
get: (id) => getTagById(id),
|
|
67
82
|
create: (data, context) => createTag(data, context),
|
|
@@ -71,6 +86,8 @@ const CRUD_ENTITY_CAPABILITIES = {
|
|
|
71
86
|
note: {
|
|
72
87
|
entityType: "note",
|
|
73
88
|
routeBase: "/api/v1/notes",
|
|
89
|
+
deleteMode: "soft_default",
|
|
90
|
+
inBin: true,
|
|
74
91
|
list: () => listNotes(),
|
|
75
92
|
get: (id) => getNoteById(id),
|
|
76
93
|
create: (data, context) => createNote(data, context),
|
|
@@ -80,15 +97,52 @@ const CRUD_ENTITY_CAPABILITIES = {
|
|
|
80
97
|
insight: {
|
|
81
98
|
entityType: "insight",
|
|
82
99
|
routeBase: "/api/v1/insights",
|
|
100
|
+
deleteMode: "soft_default",
|
|
101
|
+
inBin: true,
|
|
83
102
|
list: () => listInsights(),
|
|
84
103
|
get: (id) => getInsightById(id),
|
|
85
104
|
create: (data, context) => createInsight(data, context),
|
|
86
105
|
update: (id, patch, context) => updateInsight(id, patch, context),
|
|
87
106
|
hardDelete: (id, context) => deleteInsight(id, context)
|
|
88
107
|
},
|
|
108
|
+
calendar_event: {
|
|
109
|
+
entityType: "calendar_event",
|
|
110
|
+
routeBase: "/api/v1/calendar/events",
|
|
111
|
+
deleteMode: "immediate",
|
|
112
|
+
inBin: false,
|
|
113
|
+
list: () => listCalendarEvents(ENTITY_CALENDAR_LIST_RANGE),
|
|
114
|
+
get: (id) => getCalendarEventById(id),
|
|
115
|
+
create: (data) => createCalendarEvent(data),
|
|
116
|
+
update: (id, patch) => updateCalendarEvent(id, patch),
|
|
117
|
+
hardDelete: (id) => deleteCalendarEvent(id)
|
|
118
|
+
},
|
|
119
|
+
work_block_template: {
|
|
120
|
+
entityType: "work_block_template",
|
|
121
|
+
routeBase: "/api/v1/calendar/work-block-templates",
|
|
122
|
+
deleteMode: "immediate",
|
|
123
|
+
inBin: false,
|
|
124
|
+
list: () => listWorkBlockTemplates(),
|
|
125
|
+
get: (id) => getWorkBlockTemplateById(id),
|
|
126
|
+
create: (data) => createWorkBlockTemplate(data),
|
|
127
|
+
update: (id, patch) => updateWorkBlockTemplate(id, patch),
|
|
128
|
+
hardDelete: (id) => deleteWorkBlockTemplate(id)
|
|
129
|
+
},
|
|
130
|
+
task_timebox: {
|
|
131
|
+
entityType: "task_timebox",
|
|
132
|
+
routeBase: "/api/v1/calendar/timeboxes",
|
|
133
|
+
deleteMode: "immediate",
|
|
134
|
+
inBin: false,
|
|
135
|
+
list: () => listTaskTimeboxes(ENTITY_CALENDAR_LIST_RANGE),
|
|
136
|
+
get: (id) => getTaskTimeboxById(id),
|
|
137
|
+
create: (data) => createTaskTimebox(data),
|
|
138
|
+
update: (id, patch) => updateTaskTimebox(id, patch),
|
|
139
|
+
hardDelete: (id) => deleteTaskTimebox(id)
|
|
140
|
+
},
|
|
89
141
|
psyche_value: {
|
|
90
142
|
entityType: "psyche_value",
|
|
91
143
|
routeBase: "/api/v1/psyche/values",
|
|
144
|
+
deleteMode: "soft_default",
|
|
145
|
+
inBin: true,
|
|
92
146
|
list: () => listPsycheValues(),
|
|
93
147
|
get: (id) => getPsycheValueById(id),
|
|
94
148
|
create: (data, context) => createPsycheValue(data, context),
|
|
@@ -98,6 +152,8 @@ const CRUD_ENTITY_CAPABILITIES = {
|
|
|
98
152
|
behavior_pattern: {
|
|
99
153
|
entityType: "behavior_pattern",
|
|
100
154
|
routeBase: "/api/v1/psyche/patterns",
|
|
155
|
+
deleteMode: "soft_default",
|
|
156
|
+
inBin: true,
|
|
101
157
|
list: () => listBehaviorPatterns(),
|
|
102
158
|
get: (id) => getBehaviorPatternById(id),
|
|
103
159
|
create: (data, context) => createBehaviorPattern(data, context),
|
|
@@ -107,6 +163,8 @@ const CRUD_ENTITY_CAPABILITIES = {
|
|
|
107
163
|
behavior: {
|
|
108
164
|
entityType: "behavior",
|
|
109
165
|
routeBase: "/api/v1/psyche/behaviors",
|
|
166
|
+
deleteMode: "soft_default",
|
|
167
|
+
inBin: true,
|
|
110
168
|
list: () => listBehaviors(),
|
|
111
169
|
get: (id) => getBehaviorById(id),
|
|
112
170
|
create: (data, context) => createBehavior(data, context),
|
|
@@ -116,6 +174,8 @@ const CRUD_ENTITY_CAPABILITIES = {
|
|
|
116
174
|
belief_entry: {
|
|
117
175
|
entityType: "belief_entry",
|
|
118
176
|
routeBase: "/api/v1/psyche/beliefs",
|
|
177
|
+
deleteMode: "soft_default",
|
|
178
|
+
inBin: true,
|
|
119
179
|
list: () => listBeliefEntries(),
|
|
120
180
|
get: (id) => getBeliefEntryById(id),
|
|
121
181
|
create: (data, context) => createBeliefEntry(data, context),
|
|
@@ -125,6 +185,8 @@ const CRUD_ENTITY_CAPABILITIES = {
|
|
|
125
185
|
mode_profile: {
|
|
126
186
|
entityType: "mode_profile",
|
|
127
187
|
routeBase: "/api/v1/psyche/modes",
|
|
188
|
+
deleteMode: "soft_default",
|
|
189
|
+
inBin: true,
|
|
128
190
|
list: () => listModeProfiles(),
|
|
129
191
|
get: (id) => getModeProfileById(id),
|
|
130
192
|
create: (data, context) => createModeProfile(data, context),
|
|
@@ -134,6 +196,8 @@ const CRUD_ENTITY_CAPABILITIES = {
|
|
|
134
196
|
mode_guide_session: {
|
|
135
197
|
entityType: "mode_guide_session",
|
|
136
198
|
routeBase: "/api/v1/psyche/mode-guides",
|
|
199
|
+
deleteMode: "soft_default",
|
|
200
|
+
inBin: true,
|
|
137
201
|
list: () => listModeGuideSessions(200),
|
|
138
202
|
get: (id) => getModeGuideSessionById(id),
|
|
139
203
|
create: (data, context) => createModeGuideSession(data, context),
|
|
@@ -143,6 +207,8 @@ const CRUD_ENTITY_CAPABILITIES = {
|
|
|
143
207
|
event_type: {
|
|
144
208
|
entityType: "event_type",
|
|
145
209
|
routeBase: "/api/v1/psyche/event-types",
|
|
210
|
+
deleteMode: "soft_default",
|
|
211
|
+
inBin: true,
|
|
146
212
|
list: () => listEventTypes(),
|
|
147
213
|
get: (id) => getEventTypeById(id),
|
|
148
214
|
create: (data, context) => createEventType(data, context),
|
|
@@ -152,6 +218,8 @@ const CRUD_ENTITY_CAPABILITIES = {
|
|
|
152
218
|
emotion_definition: {
|
|
153
219
|
entityType: "emotion_definition",
|
|
154
220
|
routeBase: "/api/v1/psyche/emotions",
|
|
221
|
+
deleteMode: "soft_default",
|
|
222
|
+
inBin: true,
|
|
155
223
|
list: () => listEmotionDefinitions(),
|
|
156
224
|
get: (id) => getEmotionDefinitionById(id),
|
|
157
225
|
create: (data, context) => createEmotionDefinition(data, context),
|
|
@@ -161,6 +229,8 @@ const CRUD_ENTITY_CAPABILITIES = {
|
|
|
161
229
|
trigger_report: {
|
|
162
230
|
entityType: "trigger_report",
|
|
163
231
|
routeBase: "/api/v1/psyche/reports",
|
|
232
|
+
deleteMode: "soft_default",
|
|
233
|
+
inBin: true,
|
|
164
234
|
list: () => listTriggerReports(200),
|
|
165
235
|
get: (id) => getTriggerReportById(id),
|
|
166
236
|
create: (data, context) => createTriggerReport(data, context),
|
|
@@ -173,8 +243,8 @@ export function getCrudEntityCapabilityMatrix() {
|
|
|
173
243
|
entityType: capability.entityType,
|
|
174
244
|
routeBase: capability.routeBase,
|
|
175
245
|
pluginExposed: true,
|
|
176
|
-
deleteMode:
|
|
177
|
-
inBin:
|
|
246
|
+
deleteMode: capability.deleteMode,
|
|
247
|
+
inBin: capability.inBin
|
|
178
248
|
}));
|
|
179
249
|
}
|
|
180
250
|
function getCapability(entityType) {
|
|
@@ -188,6 +258,9 @@ const CREATE_ENTITY_SCHEMAS = {
|
|
|
188
258
|
tag: createTagSchema,
|
|
189
259
|
note: createNoteSchema,
|
|
190
260
|
insight: createInsightSchema,
|
|
261
|
+
calendar_event: createCalendarEventSchema,
|
|
262
|
+
work_block_template: createWorkBlockTemplateSchema,
|
|
263
|
+
task_timebox: createTaskTimeboxSchema,
|
|
191
264
|
psyche_value: createPsycheValueSchema,
|
|
192
265
|
behavior_pattern: createBehaviorPatternSchema,
|
|
193
266
|
behavior: createBehaviorSchema,
|
|
@@ -206,6 +279,9 @@ const UPDATE_ENTITY_SCHEMAS = {
|
|
|
206
279
|
tag: updateTagSchema,
|
|
207
280
|
note: updateNoteSchema,
|
|
208
281
|
insight: updateInsightSchema,
|
|
282
|
+
calendar_event: updateCalendarEventSchema,
|
|
283
|
+
work_block_template: updateWorkBlockTemplateSchema,
|
|
284
|
+
task_timebox: updateTaskTimeboxSchema,
|
|
209
285
|
psyche_value: updatePsycheValueSchema,
|
|
210
286
|
behavior_pattern: updateBehaviorPatternSchema,
|
|
211
287
|
behavior: updateBehaviorSchema,
|
|
@@ -344,6 +420,17 @@ function matchesLinkedTo(entityType, entity, linkedTo) {
|
|
|
344
420
|
link.entityId === linkedTo.id));
|
|
345
421
|
case "insight":
|
|
346
422
|
return entity.entityType === linkedTo.entityType && entity.entityId === linkedTo.id;
|
|
423
|
+
case "calendar_event":
|
|
424
|
+
return (Array.isArray(entity.links) &&
|
|
425
|
+
entity.links.some((link) => typeof link === "object" &&
|
|
426
|
+
link !== null &&
|
|
427
|
+
"entityType" in link &&
|
|
428
|
+
"entityId" in link &&
|
|
429
|
+
link.entityType === linkedTo.entityType &&
|
|
430
|
+
link.entityId === linkedTo.id));
|
|
431
|
+
case "task_timebox":
|
|
432
|
+
return ((linkedTo.entityType === "task" && entity.taskId === linkedTo.id) ||
|
|
433
|
+
(linkedTo.entityType === "project" && entity.projectId === linkedTo.id));
|
|
347
434
|
case "psyche_value":
|
|
348
435
|
return ((linkedTo.entityType === "goal" && Array.isArray(entity.linkedGoalIds) && entity.linkedGoalIds.includes(linkedTo.id)) ||
|
|
349
436
|
(linkedTo.entityType === "project" && Array.isArray(entity.linkedProjectIds) && entity.linkedProjectIds.includes(linkedTo.id)) ||
|
|
@@ -412,6 +499,10 @@ function purgeAnchoredCollaboration(entityType, entityId) {
|
|
|
412
499
|
export function deleteEntity(entityType, id, options, context) {
|
|
413
500
|
const capability = getCapability(entityType);
|
|
414
501
|
const mode = options.mode ?? "soft";
|
|
502
|
+
if (capability.deleteMode === "immediate") {
|
|
503
|
+
clearDeletedEntityRecord(entityType, id);
|
|
504
|
+
return capability.hardDelete(id, context);
|
|
505
|
+
}
|
|
415
506
|
const existing = capability.get(id);
|
|
416
507
|
if (!existing) {
|
|
417
508
|
const deleted = getDeletedEntityRecord(entityType, id);
|
|
@@ -3,6 +3,7 @@ import { getGoalById, listGoals } from "../repositories/goals.js";
|
|
|
3
3
|
import { buildNotesSummaryByEntity } from "../repositories/notes.js";
|
|
4
4
|
import { listProjects } from "../repositories/projects.js";
|
|
5
5
|
import { listTasks } from "../repositories/tasks.js";
|
|
6
|
+
import { listProjectWorkAdjustmentSecondsMap } from "../repositories/work-adjustments.js";
|
|
6
7
|
import { emptyTaskTimeSummary } from "./work-time.js";
|
|
7
8
|
import { projectBoardPayloadSchema, projectSummarySchema } from "../types.js";
|
|
8
9
|
function projectTaskSummary(tasks) {
|
|
@@ -33,25 +34,48 @@ function projectTaskSummary(tasks) {
|
|
|
33
34
|
nextTaskId: nextTask?.id ?? null,
|
|
34
35
|
nextTaskTitle: nextTask?.title ?? null,
|
|
35
36
|
momentumLabel,
|
|
36
|
-
time: tasks.reduce((summary, task) =>
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
time: tasks.reduce((summary, task) => {
|
|
38
|
+
const liveTrackedSeconds = task.time.liveTrackedSeconds ?? 0;
|
|
39
|
+
const liveCreditedSeconds = task.time.liveCreditedSeconds ?? 0;
|
|
40
|
+
const manualAdjustedSeconds = task.time.manualAdjustedSeconds ?? 0;
|
|
41
|
+
return {
|
|
42
|
+
totalTrackedSeconds: summary.totalTrackedSeconds + task.time.totalTrackedSeconds,
|
|
43
|
+
totalCreditedSeconds: Math.round((summary.totalCreditedSeconds + task.time.totalCreditedSeconds) * 100) / 100,
|
|
44
|
+
liveTrackedSeconds: summary.liveTrackedSeconds + liveTrackedSeconds,
|
|
45
|
+
liveCreditedSeconds: Math.round((summary.liveCreditedSeconds + liveCreditedSeconds) * 100) / 100,
|
|
46
|
+
manualAdjustedSeconds: summary.manualAdjustedSeconds + manualAdjustedSeconds,
|
|
47
|
+
activeRunCount: summary.activeRunCount + task.time.activeRunCount,
|
|
48
|
+
hasCurrentRun: summary.hasCurrentRun || task.time.hasCurrentRun,
|
|
49
|
+
currentRunId: summary.currentRunId ?? task.time.currentRunId
|
|
50
|
+
};
|
|
51
|
+
}, emptyTaskTimeSummary())
|
|
43
52
|
};
|
|
44
53
|
}
|
|
45
54
|
export function listProjectSummaries(filters = {}) {
|
|
46
55
|
const goals = new Map(listGoals().map((goal) => [goal.id, goal]));
|
|
47
56
|
const tasks = listTasks();
|
|
57
|
+
const projectAdjustmentSeconds = listProjectWorkAdjustmentSecondsMap();
|
|
48
58
|
return listProjects(filters).map((project) => {
|
|
49
59
|
const goal = goals.get(project.goalId);
|
|
50
60
|
const projectTasks = tasks.filter((task) => task.projectId === project.id);
|
|
61
|
+
const taskSummary = projectTaskSummary(projectTasks);
|
|
62
|
+
const projectAdjustmentSecondsTotal = projectAdjustmentSeconds.get(project.id) ?? 0;
|
|
63
|
+
const manualAdjustedSeconds = (taskSummary.time.manualAdjustedSeconds ?? 0) + projectAdjustmentSecondsTotal;
|
|
64
|
+
const time = {
|
|
65
|
+
totalTrackedSeconds: Math.max(0, taskSummary.time.totalTrackedSeconds + projectAdjustmentSecondsTotal),
|
|
66
|
+
totalCreditedSeconds: Math.round(Math.max(0, taskSummary.time.totalCreditedSeconds + projectAdjustmentSecondsTotal) * 100) / 100,
|
|
67
|
+
liveTrackedSeconds: taskSummary.time.liveTrackedSeconds ?? 0,
|
|
68
|
+
liveCreditedSeconds: taskSummary.time.liveCreditedSeconds ?? 0,
|
|
69
|
+
manualAdjustedSeconds,
|
|
70
|
+
activeRunCount: taskSummary.time.activeRunCount,
|
|
71
|
+
hasCurrentRun: taskSummary.time.hasCurrentRun,
|
|
72
|
+
currentRunId: taskSummary.time.currentRunId
|
|
73
|
+
};
|
|
51
74
|
return projectSummarySchema.parse({
|
|
52
75
|
...project,
|
|
53
76
|
goalTitle: goal?.title ?? "Unknown life goal",
|
|
54
|
-
...
|
|
77
|
+
...taskSummary,
|
|
78
|
+
time
|
|
55
79
|
});
|
|
56
80
|
});
|
|
57
81
|
}
|
|
@@ -2,9 +2,10 @@ import { listActivityEvents } from "../repositories/activity-events.js";
|
|
|
2
2
|
import { listGoals } from "../repositories/goals.js";
|
|
3
3
|
import { listHabits } from "../repositories/habits.js";
|
|
4
4
|
import { listTasks } from "../repositories/tasks.js";
|
|
5
|
+
import { getWeeklyReviewClosure } from "../repositories/weekly-reviews.js";
|
|
5
6
|
import { buildGamificationProfile } from "./gamification.js";
|
|
6
7
|
import { weeklyReviewPayloadSchema } from "../types.js";
|
|
7
|
-
function startOfWeek(date) {
|
|
8
|
+
export function startOfWeek(date) {
|
|
8
9
|
const clone = new Date(date);
|
|
9
10
|
const day = clone.getDay();
|
|
10
11
|
const delta = day === 0 ? -6 : 1 - day;
|
|
@@ -20,6 +21,9 @@ function addDays(date, days) {
|
|
|
20
21
|
function formatRange(start, end) {
|
|
21
22
|
return `${start.toLocaleDateString("en-US", { month: "short", day: "numeric" })} - ${end.toLocaleDateString("en-US", { month: "short", day: "numeric" })}`;
|
|
22
23
|
}
|
|
24
|
+
function toDateOnly(date) {
|
|
25
|
+
return date.toISOString().slice(0, 10);
|
|
26
|
+
}
|
|
23
27
|
function dailyBuckets(tasks, start) {
|
|
24
28
|
return Array.from({ length: 7 }, (_, index) => {
|
|
25
29
|
const current = addDays(start, index);
|
|
@@ -40,6 +44,8 @@ export function getWeeklyReviewPayload(now = new Date()) {
|
|
|
40
44
|
const gamification = buildGamificationProfile(goals, tasks, listHabits(), now);
|
|
41
45
|
const weekStart = startOfWeek(now);
|
|
42
46
|
const weekEnd = addDays(weekStart, 6);
|
|
47
|
+
const weekKey = toDateOnly(weekStart);
|
|
48
|
+
const closure = getWeeklyReviewClosure(weekKey);
|
|
43
49
|
const weekTasks = tasks.filter((task) => task.updatedAt >= weekStart.toISOString() && task.updatedAt <= addDays(weekEnd, 1).toISOString());
|
|
44
50
|
const completedTasks = weekTasks.filter((task) => task.completedAt !== null);
|
|
45
51
|
const buckets = dailyBuckets(tasks, weekStart);
|
|
@@ -62,6 +68,9 @@ export function getWeeklyReviewPayload(now = new Date()) {
|
|
|
62
68
|
return weeklyReviewPayloadSchema.parse({
|
|
63
69
|
generatedAt: now.toISOString(),
|
|
64
70
|
windowLabel: formatRange(weekStart, weekEnd),
|
|
71
|
+
weekKey,
|
|
72
|
+
weekStartDate: weekKey,
|
|
73
|
+
weekEndDate: toDateOnly(weekEnd),
|
|
65
74
|
momentumSummary: {
|
|
66
75
|
totalXp,
|
|
67
76
|
focusHours: buckets.reduce((sum, bucket) => sum + bucket.focusHours, 0),
|
|
@@ -84,6 +93,11 @@ export function getWeeklyReviewPayload(now = new Date()) {
|
|
|
84
93
|
title: "Review Completion Bonus",
|
|
85
94
|
summary: "Finalizing the review locks the current cycle into evidence.",
|
|
86
95
|
rewardXp: 250
|
|
96
|
+
},
|
|
97
|
+
completion: {
|
|
98
|
+
finalized: closure !== null,
|
|
99
|
+
finalizedAt: closure?.createdAt ?? null,
|
|
100
|
+
finalizedBy: closure?.actor ?? null
|
|
87
101
|
}
|
|
88
102
|
});
|
|
89
103
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getDatabase } from "../db.js";
|
|
2
|
+
import { listTaskWorkAdjustmentSecondsMap } from "../repositories/work-adjustments.js";
|
|
2
3
|
function readTimeAccountingMode() {
|
|
3
4
|
try {
|
|
4
5
|
const row = getDatabase()
|
|
@@ -132,6 +133,9 @@ export function computeWorkTime(now = new Date()) {
|
|
|
132
133
|
const existing = taskSummaries.get(timing.row.task_id) ?? {
|
|
133
134
|
totalTrackedSeconds: 0,
|
|
134
135
|
totalCreditedSeconds: 0,
|
|
136
|
+
liveTrackedSeconds: 0,
|
|
137
|
+
liveCreditedSeconds: 0,
|
|
138
|
+
manualAdjustedSeconds: 0,
|
|
135
139
|
activeRunCount: 0,
|
|
136
140
|
hasCurrentRun: false,
|
|
137
141
|
currentRunId: null
|
|
@@ -139,11 +143,28 @@ export function computeWorkTime(now = new Date()) {
|
|
|
139
143
|
taskSummaries.set(timing.row.task_id, {
|
|
140
144
|
totalTrackedSeconds: existing.totalTrackedSeconds + elapsedWallSeconds,
|
|
141
145
|
totalCreditedSeconds: roundCreditedSeconds(existing.totalCreditedSeconds + creditedSeconds),
|
|
146
|
+
liveTrackedSeconds: existing.liveTrackedSeconds + elapsedWallSeconds,
|
|
147
|
+
liveCreditedSeconds: roundCreditedSeconds(existing.liveCreditedSeconds + creditedSeconds),
|
|
148
|
+
manualAdjustedSeconds: existing.manualAdjustedSeconds,
|
|
142
149
|
activeRunCount: existing.activeRunCount + (timing.row.status === "active" ? 1 : 0),
|
|
143
150
|
hasCurrentRun: existing.hasCurrentRun || isCurrent,
|
|
144
151
|
currentRunId: isCurrent ? timing.row.id : existing.currentRunId
|
|
145
152
|
});
|
|
146
153
|
}
|
|
154
|
+
const adjustmentSecondsByTaskId = listTaskWorkAdjustmentSecondsMap();
|
|
155
|
+
for (const [taskId, adjustmentSeconds] of adjustmentSecondsByTaskId.entries()) {
|
|
156
|
+
const existing = taskSummaries.get(taskId) ?? emptyTaskTimeSummary();
|
|
157
|
+
taskSummaries.set(taskId, {
|
|
158
|
+
totalTrackedSeconds: Math.max(0, existing.totalTrackedSeconds + adjustmentSeconds),
|
|
159
|
+
totalCreditedSeconds: roundCreditedSeconds(Math.max(0, existing.totalCreditedSeconds + adjustmentSeconds)),
|
|
160
|
+
liveTrackedSeconds: existing.liveTrackedSeconds,
|
|
161
|
+
liveCreditedSeconds: existing.liveCreditedSeconds,
|
|
162
|
+
manualAdjustedSeconds: existing.manualAdjustedSeconds + adjustmentSeconds,
|
|
163
|
+
activeRunCount: existing.activeRunCount,
|
|
164
|
+
hasCurrentRun: existing.hasCurrentRun,
|
|
165
|
+
currentRunId: existing.currentRunId
|
|
166
|
+
});
|
|
167
|
+
}
|
|
147
168
|
return {
|
|
148
169
|
mode,
|
|
149
170
|
runMetrics,
|
|
@@ -154,6 +175,9 @@ export function emptyTaskTimeSummary() {
|
|
|
154
175
|
return {
|
|
155
176
|
totalTrackedSeconds: 0,
|
|
156
177
|
totalCreditedSeconds: 0,
|
|
178
|
+
liveTrackedSeconds: 0,
|
|
179
|
+
liveCreditedSeconds: 0,
|
|
180
|
+
manualAdjustedSeconds: 0,
|
|
157
181
|
activeRunCount: 0,
|
|
158
182
|
hasCurrentRun: false,
|
|
159
183
|
currentRunId: null
|
|
@@ -168,6 +192,9 @@ export function sumTaskTimeSummaries(taskIds, summaries) {
|
|
|
168
192
|
return {
|
|
169
193
|
totalTrackedSeconds: accumulator.totalTrackedSeconds + summary.totalTrackedSeconds,
|
|
170
194
|
totalCreditedSeconds: roundCreditedSeconds(accumulator.totalCreditedSeconds + summary.totalCreditedSeconds),
|
|
195
|
+
liveTrackedSeconds: accumulator.liveTrackedSeconds + summary.liveTrackedSeconds,
|
|
196
|
+
liveCreditedSeconds: roundCreditedSeconds(accumulator.liveCreditedSeconds + summary.liveCreditedSeconds),
|
|
197
|
+
manualAdjustedSeconds: accumulator.manualAdjustedSeconds + summary.manualAdjustedSeconds,
|
|
171
198
|
activeRunCount: accumulator.activeRunCount + summary.activeRunCount,
|
|
172
199
|
hasCurrentRun: accumulator.hasCurrentRun || summary.hasCurrentRun,
|
|
173
200
|
currentRunId: accumulator.currentRunId ?? summary.currentRunId
|