forge-openclaw-plugin 0.2.28 → 0.2.30
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 +1 -1
- package/dist/assets/{board-DPFvZf-D.js → board-q8cfwaAW.js} +2 -2
- package/dist/assets/{board-DPFvZf-D.js.map → board-q8cfwaAW.js.map} +1 -1
- package/dist/assets/index-CPC6E84V.js +85 -0
- package/dist/assets/index-CPC6E84V.js.map +1 -0
- package/dist/assets/index-DiyKCDxL.css +1 -0
- package/dist/assets/{motion-Bvwc85ch.js → motion-DHfqFntt.js} +2 -2
- package/dist/assets/{motion-Bvwc85ch.js.map → motion-DHfqFntt.js.map} +1 -1
- package/dist/assets/{table-FJQTJvUR.js → table-DLweENXt.js} +2 -2
- package/dist/assets/{table-FJQTJvUR.js.map → table-DLweENXt.js.map} +1 -1
- package/dist/assets/{ui-GXFcgvSw.js → ui-BV0OYxkH.js} +2 -2
- package/dist/assets/{ui-GXFcgvSw.js.map → ui-BV0OYxkH.js.map} +1 -1
- package/dist/assets/{vendor-Cwf49UMz.js → vendor-OwcH20PM.js} +2 -2
- package/dist/assets/{vendor-Cwf49UMz.js.map → vendor-OwcH20PM.js.map} +1 -1
- package/dist/index.html +7 -7
- package/dist/server/server/migrations/044_macos_local_calendar_provider.sql +21 -0
- package/dist/server/server/src/app.js +113 -17
- package/dist/server/server/src/movement.js +151 -0
- package/dist/server/server/src/openapi.js +29 -1
- package/dist/server/server/src/repositories/calendar.js +144 -12
- package/dist/server/server/src/repositories/tasks.js +36 -17
- package/dist/server/server/src/services/calendar-runtime.js +613 -32
- package/dist/server/server/src/services/life-force.js +84 -52
- package/dist/server/server/src/services/macos-calendar-helper.js +748 -0
- package/dist/server/server/src/types.js +46 -2
- package/dist/server/src/lib/api-error.js +2 -0
- package/dist/server/src/lib/api.js +51 -2
- package/dist/server/src/lib/calendar-name-deduper.js +2 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/server/migrations/044_macos_local_calendar_provider.sql +21 -0
- package/skills/forge-openclaw/SKILL.md +40 -7
- package/skills/forge-openclaw/entity_conversation_playbooks.md +88 -5
- package/dist/assets/index-Auw3JrdE.css +0 -1
- package/dist/assets/index-D1H7myQH.js +0 -85
- package/dist/assets/index-D1H7myQH.js.map +0 -1
|
@@ -64,6 +64,12 @@ function mapCalendar(row) {
|
|
|
64
64
|
canWrite: Boolean(row.can_write),
|
|
65
65
|
selectedForSync: Boolean(row.selected_for_sync),
|
|
66
66
|
forgeManaged: Boolean(row.forge_managed),
|
|
67
|
+
sourceId: row.source_id,
|
|
68
|
+
sourceTitle: row.source_title,
|
|
69
|
+
sourceType: row.source_type,
|
|
70
|
+
calendarType: row.calendar_type,
|
|
71
|
+
hostCalendarId: row.host_calendar_id,
|
|
72
|
+
canonicalKey: row.canonical_key,
|
|
67
73
|
lastSyncedAt: row.last_synced_at,
|
|
68
74
|
createdAt: row.created_at,
|
|
69
75
|
updatedAt: row.updated_at
|
|
@@ -255,6 +261,13 @@ export function readEncryptedSecret(secretId) {
|
|
|
255
261
|
export function deleteEncryptedSecret(secretId) {
|
|
256
262
|
getDatabase().prepare(`DELETE FROM stored_secrets WHERE id = ?`).run(secretId);
|
|
257
263
|
}
|
|
264
|
+
export function isSupersededCalendarConnection(connectionId) {
|
|
265
|
+
const connection = getCalendarConnectionById(connectionId);
|
|
266
|
+
if (!connection) {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
return isSupersededConnection(connection);
|
|
270
|
+
}
|
|
258
271
|
export function listCalendarConnections() {
|
|
259
272
|
const rows = getDatabase()
|
|
260
273
|
.prepare(`SELECT id, provider, label, account_label, status, config_json, credentials_secret_id, forge_calendar_id,
|
|
@@ -264,6 +277,15 @@ export function listCalendarConnections() {
|
|
|
264
277
|
.all();
|
|
265
278
|
return rows.map(mapConnection);
|
|
266
279
|
}
|
|
280
|
+
function isSupersededConnection(connection) {
|
|
281
|
+
return (typeof connection.config.replacedByConnectionId === "string" &&
|
|
282
|
+
connection.config.replacedByConnectionId.trim().length > 0);
|
|
283
|
+
}
|
|
284
|
+
function activeConnectionIds() {
|
|
285
|
+
return new Set(listCalendarConnections()
|
|
286
|
+
.filter((connection) => !isSupersededConnection(connection))
|
|
287
|
+
.map((connection) => connection.id));
|
|
288
|
+
}
|
|
267
289
|
export function getCalendarConnectionById(connectionId) {
|
|
268
290
|
const row = getDatabase()
|
|
269
291
|
.prepare(`SELECT id, provider, label, account_label, status, config_json, credentials_secret_id, forge_calendar_id,
|
|
@@ -330,6 +352,70 @@ export function deleteExternalEventsForConnection(connectionId) {
|
|
|
330
352
|
}
|
|
331
353
|
return rows.map((row) => row.id);
|
|
332
354
|
}
|
|
355
|
+
export function rehomeCalendarConnectionReferences(input) {
|
|
356
|
+
return runInTransaction(() => {
|
|
357
|
+
const fromCalendars = listCalendars(input.fromConnectionId, {
|
|
358
|
+
includeUnselected: true
|
|
359
|
+
});
|
|
360
|
+
const toCalendars = listCalendars(input.toConnectionId, {
|
|
361
|
+
includeUnselected: true
|
|
362
|
+
});
|
|
363
|
+
const toForgeCalendar = toCalendars.find((calendar) => calendar.forgeManaged) ??
|
|
364
|
+
toCalendars.find((calendar) => calendar.canWrite) ??
|
|
365
|
+
null;
|
|
366
|
+
const toByCanonicalKey = new Map(toCalendars
|
|
367
|
+
.filter((calendar) => typeof calendar.canonicalKey === "string" &&
|
|
368
|
+
calendar.canonicalKey.trim().length > 0)
|
|
369
|
+
.map((calendar) => [calendar.canonicalKey, calendar]));
|
|
370
|
+
const mappedCalendarIds = new Map();
|
|
371
|
+
for (const fromCalendar of fromCalendars) {
|
|
372
|
+
const mapped = (fromCalendar.canonicalKey
|
|
373
|
+
? toByCanonicalKey.get(fromCalendar.canonicalKey)
|
|
374
|
+
: null) ??
|
|
375
|
+
(fromCalendar.forgeManaged ? toForgeCalendar : null) ??
|
|
376
|
+
null;
|
|
377
|
+
mappedCalendarIds.set(fromCalendar.id, mapped?.id ?? null);
|
|
378
|
+
}
|
|
379
|
+
const now = nowIso();
|
|
380
|
+
const forgeEventRows = getDatabase()
|
|
381
|
+
.prepare(`SELECT id, preferred_calendar_id
|
|
382
|
+
FROM forge_events
|
|
383
|
+
WHERE ownership = 'forge' AND preferred_connection_id = ?`)
|
|
384
|
+
.all(input.fromConnectionId);
|
|
385
|
+
const updateForgeEvent = getDatabase().prepare(`UPDATE forge_events
|
|
386
|
+
SET preferred_connection_id = ?, preferred_calendar_id = ?, updated_at = ?
|
|
387
|
+
WHERE id = ?`);
|
|
388
|
+
for (const row of forgeEventRows) {
|
|
389
|
+
const nextCalendarId = row.preferred_calendar_id
|
|
390
|
+
? (mappedCalendarIds.get(row.preferred_calendar_id) ?? toForgeCalendar?.id ?? null)
|
|
391
|
+
: (toForgeCalendar?.id ?? null);
|
|
392
|
+
updateForgeEvent.run(nextCalendarId ? input.toConnectionId : null, nextCalendarId, now, row.id);
|
|
393
|
+
}
|
|
394
|
+
const timeboxRows = getDatabase()
|
|
395
|
+
.prepare(`SELECT id, calendar_id
|
|
396
|
+
FROM task_timeboxes
|
|
397
|
+
WHERE connection_id = ?`)
|
|
398
|
+
.all(input.fromConnectionId);
|
|
399
|
+
const updateTimebox = getDatabase().prepare(`UPDATE task_timeboxes
|
|
400
|
+
SET connection_id = ?, calendar_id = ?, remote_event_id = NULL, updated_at = ?
|
|
401
|
+
WHERE id = ?`);
|
|
402
|
+
for (const row of timeboxRows) {
|
|
403
|
+
const nextCalendarId = row.calendar_id
|
|
404
|
+
? (mappedCalendarIds.get(row.calendar_id) ?? toForgeCalendar?.id ?? null)
|
|
405
|
+
: (toForgeCalendar?.id ?? null);
|
|
406
|
+
updateTimebox.run(nextCalendarId ? input.toConnectionId : null, nextCalendarId, now, row.id);
|
|
407
|
+
}
|
|
408
|
+
getDatabase()
|
|
409
|
+
.prepare(`DELETE FROM forge_event_sources
|
|
410
|
+
WHERE connection_id = ?
|
|
411
|
+
AND forge_event_id IN (
|
|
412
|
+
SELECT id
|
|
413
|
+
FROM forge_events
|
|
414
|
+
WHERE ownership = 'forge'
|
|
415
|
+
)`)
|
|
416
|
+
.run(input.fromConnectionId);
|
|
417
|
+
});
|
|
418
|
+
}
|
|
333
419
|
export function detachConnectionFromForgeEvents(connectionId) {
|
|
334
420
|
const now = nowIso();
|
|
335
421
|
getDatabase()
|
|
@@ -352,16 +438,23 @@ export function listCalendars(connectionId, options = {}) {
|
|
|
352
438
|
: "WHERE (selected_for_sync = 1 OR forge_managed = 1)";
|
|
353
439
|
const rows = getDatabase()
|
|
354
440
|
.prepare(`SELECT id, connection_id, remote_id, title, description, color, timezone, is_primary, can_write, selected_for_sync, forge_managed,
|
|
441
|
+
source_id, source_title, source_type, calendar_type, host_calendar_id, canonical_key,
|
|
355
442
|
last_synced_at, created_at, updated_at
|
|
356
443
|
FROM calendar_calendars
|
|
357
444
|
${connectionId ? `WHERE connection_id = ? ${visibilityClause}` : visibilityClause}
|
|
358
445
|
ORDER BY forge_managed DESC, title ASC`)
|
|
359
446
|
.all(...(connectionId ? [connectionId] : []));
|
|
360
|
-
|
|
447
|
+
const mapped = rows.map(mapCalendar);
|
|
448
|
+
if (connectionId) {
|
|
449
|
+
return mapped;
|
|
450
|
+
}
|
|
451
|
+
const activeIds = activeConnectionIds();
|
|
452
|
+
return mapped.filter((calendar) => activeIds.has(calendar.connectionId));
|
|
361
453
|
}
|
|
362
454
|
export function getCalendarById(calendarId) {
|
|
363
455
|
const row = getDatabase()
|
|
364
456
|
.prepare(`SELECT id, connection_id, remote_id, title, description, color, timezone, is_primary, can_write, selected_for_sync, forge_managed,
|
|
457
|
+
source_id, source_title, source_type, calendar_type, host_calendar_id, canonical_key,
|
|
365
458
|
last_synced_at, created_at, updated_at
|
|
366
459
|
FROM calendar_calendars
|
|
367
460
|
WHERE id = ?`)
|
|
@@ -371,6 +464,7 @@ export function getCalendarById(calendarId) {
|
|
|
371
464
|
function getDefaultWritableCalendar() {
|
|
372
465
|
const row = getDatabase()
|
|
373
466
|
.prepare(`SELECT id, connection_id, remote_id, title, description, color, timezone, is_primary, can_write, selected_for_sync, forge_managed,
|
|
467
|
+
source_id, source_title, source_type, calendar_type, host_calendar_id, canonical_key,
|
|
374
468
|
last_synced_at, created_at, updated_at
|
|
375
469
|
FROM calendar_calendars
|
|
376
470
|
WHERE can_write = 1
|
|
@@ -383,6 +477,7 @@ function getDefaultWritableCalendar() {
|
|
|
383
477
|
export function getCalendarByRemoteId(connectionId, remoteId) {
|
|
384
478
|
const row = getDatabase()
|
|
385
479
|
.prepare(`SELECT id, connection_id, remote_id, title, description, color, timezone, is_primary, can_write, selected_for_sync, forge_managed,
|
|
480
|
+
source_id, source_title, source_type, calendar_type, host_calendar_id, canonical_key,
|
|
386
481
|
last_synced_at, created_at, updated_at
|
|
387
482
|
FROM calendar_calendars
|
|
388
483
|
WHERE connection_id = ? AND remote_id = ?`)
|
|
@@ -395,18 +490,21 @@ export function upsertCalendarRecord(connectionId, input) {
|
|
|
395
490
|
if (existing) {
|
|
396
491
|
getDatabase()
|
|
397
492
|
.prepare(`UPDATE calendar_calendars
|
|
398
|
-
SET title = ?, description = ?, color = ?, timezone = ?, is_primary = ?, can_write = ?, selected_for_sync = ?, forge_managed = ?,
|
|
493
|
+
SET title = ?, description = ?, color = ?, timezone = ?, is_primary = ?, can_write = ?, selected_for_sync = ?, forge_managed = ?,
|
|
494
|
+
source_id = ?, source_title = ?, source_type = ?, calendar_type = ?, host_calendar_id = ?, canonical_key = ?,
|
|
495
|
+
last_synced_at = ?, updated_at = ?
|
|
399
496
|
WHERE id = ?`)
|
|
400
|
-
.run(input.title, input.description ?? existing.description, input.color ?? existing.color, normalizeTimezone(input.timezone ?? existing.timezone), input.isPrimary ? 1 : 0, input.canWrite === false ? 0 : 1, input.selectedForSync === false ? 0 : 1, input.forgeManaged ? 1 : 0, now, now, existing.id);
|
|
497
|
+
.run(input.title, input.description ?? existing.description, input.color ?? existing.color, normalizeTimezone(input.timezone ?? existing.timezone), input.isPrimary ? 1 : 0, input.canWrite === false ? 0 : 1, input.selectedForSync === false ? 0 : 1, input.forgeManaged ? 1 : 0, input.sourceId ?? existing.sourceId, input.sourceTitle ?? existing.sourceTitle, input.sourceType ?? existing.sourceType, input.calendarType ?? existing.calendarType, input.hostCalendarId ?? existing.hostCalendarId, input.canonicalKey ?? existing.canonicalKey ?? existing.remoteId, now, now, existing.id);
|
|
401
498
|
return getCalendarById(existing.id);
|
|
402
499
|
}
|
|
403
500
|
const id = `calendar_${randomUUID().replaceAll("-", "").slice(0, 10)}`;
|
|
404
501
|
getDatabase()
|
|
405
502
|
.prepare(`INSERT INTO calendar_calendars (
|
|
406
|
-
id, connection_id, remote_id, title, description, color, timezone, is_primary, can_write, selected_for_sync, forge_managed,
|
|
503
|
+
id, connection_id, remote_id, title, description, color, timezone, is_primary, can_write, selected_for_sync, forge_managed,
|
|
504
|
+
source_id, source_title, source_type, calendar_type, host_calendar_id, canonical_key, last_synced_at, created_at, updated_at
|
|
407
505
|
)
|
|
408
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
409
|
-
.run(id, connectionId, input.remoteId, input.title, input.description ?? "", input.color ?? "#7dd3fc", normalizeTimezone(input.timezone), input.isPrimary ? 1 : 0, input.canWrite === false ? 0 : 1, input.selectedForSync === false ? 0 : 1, input.forgeManaged ? 1 : 0, now, now, now);
|
|
506
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
507
|
+
.run(id, connectionId, input.remoteId, input.title, input.description ?? "", input.color ?? "#7dd3fc", normalizeTimezone(input.timezone), input.isPrimary ? 1 : 0, input.canWrite === false ? 0 : 1, input.selectedForSync === false ? 0 : 1, input.forgeManaged ? 1 : 0, input.sourceId ?? null, input.sourceTitle ?? null, input.sourceType ?? null, input.calendarType ?? null, input.hostCalendarId ?? null, input.canonicalKey ?? input.remoteId, now, now, now);
|
|
410
508
|
return getCalendarById(id);
|
|
411
509
|
}
|
|
412
510
|
export function listCalendarEvents(query) {
|
|
@@ -442,7 +540,12 @@ export function listCalendarEvents(query) {
|
|
|
442
540
|
WHERE ${clauses.join(" AND ")}
|
|
443
541
|
ORDER BY start_at ASC, title ASC`)
|
|
444
542
|
.all(...params);
|
|
445
|
-
|
|
543
|
+
const activeIds = activeConnectionIds();
|
|
544
|
+
return filterOwnedEntities("calendar_event", rows
|
|
545
|
+
.map(mapEvent)
|
|
546
|
+
.filter((event) => event.ownership !== "external" ||
|
|
547
|
+
event.connectionId === null ||
|
|
548
|
+
activeIds.has(event.connectionId)), query.userIds);
|
|
446
549
|
}
|
|
447
550
|
export function getCalendarEventById(eventId) {
|
|
448
551
|
const row = getDatabase()
|
|
@@ -556,10 +659,20 @@ export function upsertCalendarEventRecord(connectionId, input) {
|
|
|
556
659
|
calendarId: calendar.id,
|
|
557
660
|
remoteCalendarId: calendar.remoteId,
|
|
558
661
|
remoteEventId: input.remoteId,
|
|
559
|
-
remoteUid: typeof input.rawPayload?.uid === "string"
|
|
662
|
+
remoteUid: typeof input.rawPayload?.uid === "string"
|
|
663
|
+
? String(input.rawPayload.uid)
|
|
664
|
+
: typeof input.rawPayload?.externalId === "string"
|
|
665
|
+
? String(input.rawPayload.externalId)
|
|
666
|
+
: typeof input.rawPayload?.iCalUID === "string"
|
|
667
|
+
? String(input.rawPayload.iCalUID)
|
|
668
|
+
: typeof input.rawPayload?.iCalUId === "string"
|
|
669
|
+
? String(input.rawPayload.iCalUId)
|
|
670
|
+
: null,
|
|
560
671
|
recurrenceInstanceId: typeof input.rawPayload?.recurrenceid === "string"
|
|
561
672
|
? String(input.rawPayload.recurrenceid)
|
|
562
|
-
:
|
|
673
|
+
: typeof input.rawPayload?.occurrenceDate === "string"
|
|
674
|
+
? String(input.rawPayload.occurrenceDate)
|
|
675
|
+
: null,
|
|
563
676
|
isMasterRecurring: Boolean(input.rawPayload?.rrule),
|
|
564
677
|
remoteHref: input.remoteHref ?? null,
|
|
565
678
|
remoteEtag: input.remoteEtag ?? null,
|
|
@@ -585,10 +698,20 @@ export function upsertCalendarEventRecord(connectionId, input) {
|
|
|
585
698
|
calendarId: calendar.id,
|
|
586
699
|
remoteCalendarId: calendar.remoteId,
|
|
587
700
|
remoteEventId: input.remoteId,
|
|
588
|
-
remoteUid: typeof input.rawPayload?.uid === "string"
|
|
701
|
+
remoteUid: typeof input.rawPayload?.uid === "string"
|
|
702
|
+
? String(input.rawPayload.uid)
|
|
703
|
+
: typeof input.rawPayload?.externalId === "string"
|
|
704
|
+
? String(input.rawPayload.externalId)
|
|
705
|
+
: typeof input.rawPayload?.iCalUID === "string"
|
|
706
|
+
? String(input.rawPayload.iCalUID)
|
|
707
|
+
: typeof input.rawPayload?.iCalUId === "string"
|
|
708
|
+
? String(input.rawPayload.iCalUId)
|
|
709
|
+
: null,
|
|
589
710
|
recurrenceInstanceId: typeof input.rawPayload?.recurrenceid === "string"
|
|
590
711
|
? String(input.rawPayload.recurrenceid)
|
|
591
|
-
:
|
|
712
|
+
: typeof input.rawPayload?.occurrenceDate === "string"
|
|
713
|
+
? String(input.rawPayload.occurrenceDate)
|
|
714
|
+
: null,
|
|
592
715
|
isMasterRecurring: Boolean(input.rawPayload?.rrule),
|
|
593
716
|
remoteHref: input.remoteHref ?? null,
|
|
594
717
|
remoteEtag: input.remoteEtag ?? null,
|
|
@@ -931,7 +1054,10 @@ export function listTaskTimeboxes(query) {
|
|
|
931
1054
|
WHERE ${clauses.join(" AND ")}
|
|
932
1055
|
ORDER BY starts_at ASC`)
|
|
933
1056
|
.all(...params);
|
|
934
|
-
|
|
1057
|
+
const activeIds = activeConnectionIds();
|
|
1058
|
+
return filterOwnedEntities("task_timebox", rows
|
|
1059
|
+
.map(mapTimebox)
|
|
1060
|
+
.filter((timebox) => timebox.connectionId === null || activeIds.has(timebox.connectionId)), query.userIds);
|
|
935
1061
|
}
|
|
936
1062
|
export function getTaskTimeboxById(timeboxId) {
|
|
937
1063
|
const row = getDatabase()
|
|
@@ -1302,6 +1428,12 @@ export function getCalendarOverview(query) {
|
|
|
1302
1428
|
label: "Custom CalDAV",
|
|
1303
1429
|
supportsDedicatedForgeCalendar: true,
|
|
1304
1430
|
connectionHelp: "Use an account-level CalDAV base URL, then let Forge discover the calendars before selecting sync and write targets."
|
|
1431
|
+
},
|
|
1432
|
+
{
|
|
1433
|
+
provider: "macos_local",
|
|
1434
|
+
label: "Calendars On This Mac",
|
|
1435
|
+
supportsDedicatedForgeCalendar: true,
|
|
1436
|
+
connectionHelp: "Use EventKit to access the calendars already configured in Calendar.app on this Mac. Forge replaces overlapping remote account connections instead of showing duplicate copies."
|
|
1305
1437
|
}
|
|
1306
1438
|
],
|
|
1307
1439
|
connections: listCalendarConnections().map(({ credentialsSecretId: _secret, ...connection }) => connection),
|
|
@@ -70,9 +70,9 @@ function nextSortOrder(status) {
|
|
|
70
70
|
.get(status);
|
|
71
71
|
return row.max_sort + 1;
|
|
72
72
|
}
|
|
73
|
-
function normalizeCompletedAt(status, existingCompletedAt) {
|
|
73
|
+
function normalizeCompletedAt(status, existingCompletedAt, overrideCompletedAt) {
|
|
74
74
|
if (status === "done") {
|
|
75
|
-
return existingCompletedAt ?? new Date().toISOString();
|
|
75
|
+
return overrideCompletedAt ?? existingCompletedAt ?? new Date().toISOString();
|
|
76
76
|
}
|
|
77
77
|
return null;
|
|
78
78
|
}
|
|
@@ -203,10 +203,32 @@ function updateTaskRecord(current, input, activity) {
|
|
|
203
203
|
const movedColumns = nextStatus !== current.status;
|
|
204
204
|
const nextSort = input.sortOrder ??
|
|
205
205
|
(movedColumns ? nextSortOrder(nextStatus) : current.sortOrder);
|
|
206
|
+
const completionRequirement = nextStatus === "done"
|
|
207
|
+
? getTaskCompletionRequirement(current, current.userId ?? undefined)
|
|
208
|
+
: null;
|
|
209
|
+
const applyCompletionWorkLogAdjustment = (desiredTodaySeconds, currentTodayCreditedSeconds) => {
|
|
210
|
+
const deltaMinutes = Math.round((desiredTodaySeconds - currentTodayCreditedSeconds) / 60);
|
|
211
|
+
if (deltaMinutes === 0) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const appliedDeltaMinutes = deltaMinutes;
|
|
215
|
+
createWorkAdjustment({
|
|
216
|
+
entityType: "task",
|
|
217
|
+
entityId: current.id,
|
|
218
|
+
deltaMinutes: appliedDeltaMinutes,
|
|
219
|
+
appliedDeltaMinutes,
|
|
220
|
+
note: desiredTodaySeconds <= 0
|
|
221
|
+
? "Completion log cleared for today"
|
|
222
|
+
: "Completion log adjusted for today"
|
|
223
|
+
}, {
|
|
224
|
+
actor: activity?.actor ?? null,
|
|
225
|
+
source: activity?.source ?? "ui"
|
|
226
|
+
});
|
|
227
|
+
};
|
|
206
228
|
if (current.status !== "done" &&
|
|
207
229
|
nextStatus === "done" &&
|
|
208
|
-
input.resolutionKind !== "split"
|
|
209
|
-
|
|
230
|
+
input.resolutionKind !== "split" &&
|
|
231
|
+
completionRequirement) {
|
|
210
232
|
if (input.enforceTodayWorkLog === true &&
|
|
211
233
|
completionRequirement.requiresWorkLog &&
|
|
212
234
|
input.completedTodayWorkSeconds === undefined) {
|
|
@@ -215,21 +237,18 @@ function updateTaskRecord(current, input, activity) {
|
|
|
215
237
|
todayCreditedSeconds: completionRequirement.todayCreditedSeconds
|
|
216
238
|
});
|
|
217
239
|
}
|
|
218
|
-
if (
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
entityType: "task",
|
|
222
|
-
entityId: current.id,
|
|
223
|
-
deltaMinutes: appliedDeltaMinutes,
|
|
224
|
-
appliedDeltaMinutes,
|
|
225
|
-
note: "Completion log for today"
|
|
226
|
-
}, {
|
|
227
|
-
actor: activity?.actor ?? null,
|
|
228
|
-
source: activity?.source ?? "ui"
|
|
229
|
-
});
|
|
240
|
+
if (input.completedTodayWorkSeconds !== undefined) {
|
|
241
|
+
const desiredTodaySeconds = Math.max(0, input.completedTodayWorkSeconds);
|
|
242
|
+
applyCompletionWorkLogAdjustment(desiredTodaySeconds, completionRequirement.todayCreditedSeconds);
|
|
230
243
|
}
|
|
231
244
|
}
|
|
232
|
-
|
|
245
|
+
else if (nextStatus === "done" &&
|
|
246
|
+
input.completedTodayWorkSeconds !== undefined &&
|
|
247
|
+
completionRequirement) {
|
|
248
|
+
const desiredTodaySeconds = Math.max(0, input.completedTodayWorkSeconds);
|
|
249
|
+
applyCompletionWorkLogAdjustment(desiredTodaySeconds, completionRequirement.todayCreditedSeconds);
|
|
250
|
+
}
|
|
251
|
+
const completedAt = normalizeCompletedAt(nextStatus, current.completedAt, input.completedAt);
|
|
233
252
|
const updatedAt = new Date().toISOString();
|
|
234
253
|
getDatabase()
|
|
235
254
|
.prepare(`UPDATE tasks
|