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.
Files changed (36) hide show
  1. package/README.md +1 -1
  2. package/dist/assets/{board-DPFvZf-D.js → board-q8cfwaAW.js} +2 -2
  3. package/dist/assets/{board-DPFvZf-D.js.map → board-q8cfwaAW.js.map} +1 -1
  4. package/dist/assets/index-CPC6E84V.js +85 -0
  5. package/dist/assets/index-CPC6E84V.js.map +1 -0
  6. package/dist/assets/index-DiyKCDxL.css +1 -0
  7. package/dist/assets/{motion-Bvwc85ch.js → motion-DHfqFntt.js} +2 -2
  8. package/dist/assets/{motion-Bvwc85ch.js.map → motion-DHfqFntt.js.map} +1 -1
  9. package/dist/assets/{table-FJQTJvUR.js → table-DLweENXt.js} +2 -2
  10. package/dist/assets/{table-FJQTJvUR.js.map → table-DLweENXt.js.map} +1 -1
  11. package/dist/assets/{ui-GXFcgvSw.js → ui-BV0OYxkH.js} +2 -2
  12. package/dist/assets/{ui-GXFcgvSw.js.map → ui-BV0OYxkH.js.map} +1 -1
  13. package/dist/assets/{vendor-Cwf49UMz.js → vendor-OwcH20PM.js} +2 -2
  14. package/dist/assets/{vendor-Cwf49UMz.js.map → vendor-OwcH20PM.js.map} +1 -1
  15. package/dist/index.html +7 -7
  16. package/dist/server/server/migrations/044_macos_local_calendar_provider.sql +21 -0
  17. package/dist/server/server/src/app.js +113 -17
  18. package/dist/server/server/src/movement.js +151 -0
  19. package/dist/server/server/src/openapi.js +29 -1
  20. package/dist/server/server/src/repositories/calendar.js +144 -12
  21. package/dist/server/server/src/repositories/tasks.js +36 -17
  22. package/dist/server/server/src/services/calendar-runtime.js +613 -32
  23. package/dist/server/server/src/services/life-force.js +84 -52
  24. package/dist/server/server/src/services/macos-calendar-helper.js +748 -0
  25. package/dist/server/server/src/types.js +46 -2
  26. package/dist/server/src/lib/api-error.js +2 -0
  27. package/dist/server/src/lib/api.js +51 -2
  28. package/dist/server/src/lib/calendar-name-deduper.js +2 -0
  29. package/openclaw.plugin.json +1 -1
  30. package/package.json +1 -1
  31. package/server/migrations/044_macos_local_calendar_provider.sql +21 -0
  32. package/skills/forge-openclaw/SKILL.md +40 -7
  33. package/skills/forge-openclaw/entity_conversation_playbooks.md +88 -5
  34. package/dist/assets/index-Auw3JrdE.css +0 -1
  35. package/dist/assets/index-D1H7myQH.js +0 -85
  36. package/dist/assets/index-D1H7myQH.js.map +0 -1
@@ -234,6 +234,15 @@ function computeUncoveredSeconds(window, blockingWindows) {
234
234
  coveredMs += activeEndMs - activeStartMs;
235
235
  return Math.max(0, totalSeconds - Math.floor(coveredMs / 1000));
236
236
  }
237
+ function containsInstant(window, instantIso) {
238
+ const instantMs = Date.parse(instantIso);
239
+ return (Number.isFinite(instantMs) &&
240
+ Date.parse(window.startAt) <= instantMs &&
241
+ Date.parse(window.endAt) > instantMs);
242
+ }
243
+ function isInstantCovered(instantIso, blockingWindows) {
244
+ return blockingWindows.some((window) => containsInstant(window, instantIso));
245
+ }
237
246
  function nowIso() {
238
247
  return new Date().toISOString();
239
248
  }
@@ -1635,6 +1644,36 @@ function readTaskTimeboxLifeForceRows(userId, range) {
1635
1644
  return [];
1636
1645
  }
1637
1646
  }
1647
+ function readCalendarEventLifeForceRows(range) {
1648
+ try {
1649
+ return getDatabase()
1650
+ .prepare(`SELECT
1651
+ forge_events.id,
1652
+ forge_events.title,
1653
+ forge_events.start_at,
1654
+ forge_events.end_at,
1655
+ forge_events.availability,
1656
+ forge_events.event_type,
1657
+ COUNT(forge_event_links.id) AS link_count
1658
+ FROM forge_events
1659
+ LEFT JOIN forge_event_links
1660
+ ON forge_event_links.forge_event_id = forge_events.id
1661
+ WHERE forge_events.deleted_at IS NULL
1662
+ AND forge_events.end_at > ?
1663
+ AND forge_events.start_at < ?
1664
+ GROUP BY
1665
+ forge_events.id,
1666
+ forge_events.title,
1667
+ forge_events.start_at,
1668
+ forge_events.end_at,
1669
+ forge_events.availability,
1670
+ forge_events.event_type`)
1671
+ .all(range.from, range.to);
1672
+ }
1673
+ catch {
1674
+ return [];
1675
+ }
1676
+ }
1638
1677
  function readWorkBlockTemplateLifeForceRows(userId) {
1639
1678
  try {
1640
1679
  return getDatabase()
@@ -1717,7 +1756,7 @@ function buildWorkBlockProfile(input) {
1717
1756
  endMinute
1718
1757
  });
1719
1758
  }
1720
- function buildTimeboxAndWorkBlockDrains(userId, range, now, lifeForceProfile, activeTaskRunTaskIds, actualSourceWindows) {
1759
+ function buildTimeboxAndWorkBlockDrains(userId, range, now, lifeForceProfile, activeTaskRunTaskIds, actualSourceWindows, calendarEventWindows) {
1721
1760
  const actualContributions = [];
1722
1761
  const plannedDrains = [];
1723
1762
  const activeDrains = [];
@@ -1756,11 +1795,12 @@ function buildTimeboxAndWorkBlockDrains(userId, range, now, lifeForceProfile, ac
1756
1795
  startsAt: row.starts_at,
1757
1796
  endsAt: row.ends_at
1758
1797
  });
1798
+ const higherPriorityWindows = [...actualSourceWindows, ...calendarEventWindows];
1759
1799
  const elapsedWindow = {
1760
1800
  startAt: row.starts_at,
1761
1801
  endAt: new Date(Math.min(now.getTime(), Date.parse(row.ends_at))).toISOString()
1762
1802
  };
1763
- const elapsedSeconds = computeUncoveredSeconds(elapsedWindow, actualSourceWindows);
1803
+ const elapsedSeconds = computeUncoveredSeconds(elapsedWindow, higherPriorityWindows);
1764
1804
  if (elapsedSeconds > 0) {
1765
1805
  actualContributions.push({
1766
1806
  entityType: "task_timebox",
@@ -1778,7 +1818,15 @@ function buildTimeboxAndWorkBlockDrains(userId, range, now, lifeForceProfile, ac
1778
1818
  }
1779
1819
  const remainingStartMs = Math.max(now.getTime(), Date.parse(row.starts_at));
1780
1820
  const remainingEndMs = Math.min(range.endMs, Date.parse(row.ends_at));
1781
- const remainingSeconds = Math.max(0, Math.floor((remainingEndMs - remainingStartMs) / 1000));
1821
+ const remainingWindow = remainingEndMs > remainingStartMs
1822
+ ? {
1823
+ startAt: new Date(remainingStartMs).toISOString(),
1824
+ endAt: new Date(remainingEndMs).toISOString()
1825
+ }
1826
+ : null;
1827
+ const remainingSeconds = remainingWindow
1828
+ ? computeUncoveredSeconds(remainingWindow, higherPriorityWindows)
1829
+ : 0;
1782
1830
  if (remainingSeconds > 0) {
1783
1831
  plannedDrains.push({
1784
1832
  entityType: "task_timebox",
@@ -1789,15 +1837,15 @@ function buildTimeboxAndWorkBlockDrains(userId, range, now, lifeForceProfile, ac
1789
1837
  rateApPerHour: profile.sustainRateApPerHour,
1790
1838
  title: row.title,
1791
1839
  why: "Planned task timeboxes forecast how much Action Point throughput is still booked today.",
1792
- startsAt: row.starts_at,
1793
- endsAt: row.ends_at,
1840
+ startsAt: remainingWindow?.startAt ?? row.starts_at,
1841
+ endsAt: remainingWindow?.endAt ?? row.ends_at,
1794
1842
  role: "secondary"
1795
1843
  });
1796
1844
  }
1797
1845
  if (Date.parse(row.starts_at) <= now.getTime() &&
1798
1846
  Date.parse(row.ends_at) > now.getTime() &&
1799
1847
  !activeTaskRunTaskIds.has(row.task_id) &&
1800
- !actualSourceWindows.some((window) => overlapsWindow(row.starts_at, row.ends_at, window.startAt, window.endAt))) {
1848
+ !isInstantCovered(now.toISOString(), higherPriorityWindows)) {
1801
1849
  activeDrains.push({
1802
1850
  entityType: "task_timebox",
1803
1851
  entityId: row.id,
@@ -1819,8 +1867,11 @@ function buildTimeboxAndWorkBlockDrains(userId, range, now, lifeForceProfile, ac
1819
1867
  startAt: block.start_at,
1820
1868
  endAt: block.end_at
1821
1869
  });
1822
- const overlapsTimebox = timeboxes.some((timebox) => timebox.status !== "cancelled" &&
1823
- overlapsWindow(block.start_at, block.end_at, timebox.starts_at, timebox.ends_at));
1870
+ const higherPriorityWindows = [
1871
+ ...actualSourceWindows,
1872
+ ...calendarEventWindows,
1873
+ ...timeboxWindows
1874
+ ];
1824
1875
  const profile = buildEffectiveProfile(buildWorkBlockProfile({
1825
1876
  templateId: block.id,
1826
1877
  title: block.title,
@@ -1832,11 +1883,8 @@ function buildTimeboxAndWorkBlockDrains(userId, range, now, lifeForceProfile, ac
1832
1883
  startAt: block.start_at,
1833
1884
  endAt: new Date(Math.min(now.getTime(), Date.parse(block.end_at))).toISOString()
1834
1885
  };
1835
- const elapsedSeconds = computeUncoveredSeconds(elapsedWindow, [
1836
- ...actualSourceWindows,
1837
- ...timeboxWindows
1838
- ]);
1839
- if (elapsedSeconds > 0 && !overlapsTimebox) {
1886
+ const elapsedSeconds = computeUncoveredSeconds(elapsedWindow, higherPriorityWindows);
1887
+ if (elapsedSeconds > 0) {
1840
1888
  actualContributions.push({
1841
1889
  entityType: "work_block",
1842
1890
  entityId: block.instance_id,
@@ -1857,8 +1905,16 @@ function buildTimeboxAndWorkBlockDrains(userId, range, now, lifeForceProfile, ac
1857
1905
  }
1858
1906
  const remainingStartMs = Math.max(now.getTime(), Date.parse(block.start_at));
1859
1907
  const remainingEndMs = Math.min(range.endMs, Date.parse(block.end_at));
1860
- const remainingSeconds = Math.max(0, Math.floor((remainingEndMs - remainingStartMs) / 1000));
1861
- if (remainingSeconds > 0 && !overlapsTimebox) {
1908
+ const remainingWindow = remainingEndMs > remainingStartMs
1909
+ ? {
1910
+ startAt: new Date(remainingStartMs).toISOString(),
1911
+ endAt: new Date(remainingEndMs).toISOString()
1912
+ }
1913
+ : null;
1914
+ const remainingSeconds = remainingWindow
1915
+ ? computeUncoveredSeconds(remainingWindow, higherPriorityWindows)
1916
+ : 0;
1917
+ if (remainingSeconds > 0) {
1862
1918
  plannedDrains.push({
1863
1919
  entityType: "work_block",
1864
1920
  entityId: block.instance_id,
@@ -1868,8 +1924,8 @@ function buildTimeboxAndWorkBlockDrains(userId, range, now, lifeForceProfile, ac
1868
1924
  rateApPerHour: profile.sustainRateApPerHour,
1869
1925
  title: block.title,
1870
1926
  why: "Work blocks act as planning containers and forecast background load when no richer task plan exists.",
1871
- startsAt: block.start_at,
1872
- endsAt: block.end_at,
1927
+ startsAt: remainingWindow?.startAt ?? block.start_at,
1928
+ endsAt: remainingWindow?.endAt ?? block.end_at,
1873
1929
  role: "background",
1874
1930
  metadata: {
1875
1931
  templateId: block.id,
@@ -1879,9 +1935,8 @@ function buildTimeboxAndWorkBlockDrains(userId, range, now, lifeForceProfile, ac
1879
1935
  }
1880
1936
  if (Date.parse(block.start_at) <= now.getTime() &&
1881
1937
  Date.parse(block.end_at) > now.getTime() &&
1882
- !overlapsTimebox &&
1883
1938
  activeTaskRunTaskIds.size === 0 &&
1884
- !actualSourceWindows.some((window) => overlapsWindow(block.start_at, block.end_at, window.startAt, window.endAt))) {
1939
+ !isInstantCovered(now.toISOString(), higherPriorityWindows)) {
1885
1940
  activeDrains.push({
1886
1941
  entityType: "work_block",
1887
1942
  entityId: block.instance_id,
@@ -1909,35 +1964,12 @@ function buildTimeboxAndWorkBlockDrains(userId, range, now, lifeForceProfile, ac
1909
1964
  workBlockWindows
1910
1965
  };
1911
1966
  }
1912
- function buildCalendarDrains(userId, now, range, lifeForceProfile, blockingWindows) {
1967
+ function buildCalendarDrains(rows, now, range, lifeForceProfile, blockingWindows) {
1913
1968
  const actualContributions = [];
1914
1969
  const nowIsoValue = now.toISOString();
1915
1970
  const activeDrains = [];
1916
1971
  const plannedDrains = [];
1917
1972
  try {
1918
- const rows = getDatabase()
1919
- .prepare(`SELECT
1920
- forge_events.id,
1921
- forge_events.title,
1922
- forge_events.start_at,
1923
- forge_events.end_at,
1924
- forge_events.availability,
1925
- forge_events.event_type,
1926
- COUNT(forge_event_links.id) AS link_count
1927
- FROM forge_events
1928
- LEFT JOIN forge_event_links
1929
- ON forge_event_links.forge_event_id = forge_events.id
1930
- WHERE forge_events.deleted_at IS NULL
1931
- AND forge_events.end_at > ?
1932
- AND forge_events.start_at < ?
1933
- GROUP BY
1934
- forge_events.id,
1935
- forge_events.title,
1936
- forge_events.start_at,
1937
- forge_events.end_at,
1938
- forge_events.availability,
1939
- forge_events.event_type`)
1940
- .all(range.from, range.to);
1941
1973
  for (const row of rows) {
1942
1974
  const calendarProfile = buildEffectiveProfile(readEntityActionProfile("calendar_event", row.id, {
1943
1975
  profileKey: `calendar_event_${row.id}`,
@@ -1952,7 +1984,6 @@ function buildCalendarDrains(userId, now, range, lifeForceProfile, blockingWindo
1952
1984
  startAt: row.start_at,
1953
1985
  endAt: row.end_at
1954
1986
  }), lifeForceProfile);
1955
- const overlapsBlockingWindow = blockingWindows.some((window) => overlapsWindow(row.start_at, row.end_at, window.startAt, window.endAt));
1956
1987
  const elapsedWindow = {
1957
1988
  startAt: row.start_at,
1958
1989
  endAt: new Date(Math.min(now.getTime(), Date.parse(row.end_at))).toISOString()
@@ -1993,7 +2024,7 @@ function buildCalendarDrains(userId, now, range, lifeForceProfile, blockingWindo
1993
2024
  }
1994
2025
  if (row.start_at <= nowIsoValue &&
1995
2026
  row.end_at > nowIsoValue &&
1996
- !overlapsBlockingWindow) {
2027
+ !isInstantCovered(nowIsoValue, blockingWindows)) {
1997
2028
  activeDrains.push({
1998
2029
  entityType: "calendar_event",
1999
2030
  entityId: row.id,
@@ -2172,14 +2203,15 @@ export function buildLifeForcePayload(now = new Date(), userIds) {
2172
2203
  startAt: entry.startsAt,
2173
2204
  endAt: entry.endsAt
2174
2205
  }));
2206
+ const calendarRows = readCalendarEventLifeForceRows(range);
2207
+ const calendarEventWindows = calendarRows.map((row) => ({
2208
+ startAt: row.start_at,
2209
+ endAt: row.end_at
2210
+ }));
2175
2211
  const activeTaskRunTaskIds = new Set(taskRuns.activeDrains.map((entry) => entry.entityId));
2176
- const plannedContainers = buildTimeboxAndWorkBlockDrains(user.id, range, now, profile, activeTaskRunTaskIds, actualSourceWindows);
2177
- const calendarBlockingWindows = [
2178
- ...actualSourceWindows,
2179
- ...plannedContainers.timeboxWindows,
2180
- ...plannedContainers.workBlockWindows
2181
- ];
2182
- const calendarDrains = buildCalendarDrains(user.id, now, range, profile, calendarBlockingWindows);
2212
+ const plannedContainers = buildTimeboxAndWorkBlockDrains(user.id, range, now, profile, activeTaskRunTaskIds, actualSourceWindows, calendarEventWindows);
2213
+ const calendarBlockingWindows = [...actualSourceWindows];
2214
+ const calendarDrains = buildCalendarDrains(calendarRows, now, range, profile, calendarBlockingWindows);
2183
2215
  const contributions = [
2184
2216
  ...taskRuns.contributions,
2185
2217
  ...adjustments,