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.
Files changed (56) hide show
  1. package/README.md +36 -4
  2. package/dist/assets/{board-2KevHCI0.js → board-8L3uX7_O.js} +2 -2
  3. package/dist/assets/{board-2KevHCI0.js.map → board-8L3uX7_O.js.map} +1 -1
  4. package/dist/assets/index-Cj1IBH_w.js +36 -0
  5. package/dist/assets/index-Cj1IBH_w.js.map +1 -0
  6. package/dist/assets/index-DQT6EbuS.css +1 -0
  7. package/dist/assets/{motion-q19HPmWs.js → motion-1GAqqi8M.js} +2 -2
  8. package/dist/assets/{motion-q19HPmWs.js.map → motion-1GAqqi8M.js.map} +1 -1
  9. package/dist/assets/{table-BDMHBY4a.js → table-DBGlgRjk.js} +2 -2
  10. package/dist/assets/{table-BDMHBY4a.js.map → table-DBGlgRjk.js.map} +1 -1
  11. package/dist/assets/{ui-CQ_AsFs8.js → ui-iTluWjC4.js} +2 -2
  12. package/dist/assets/{ui-CQ_AsFs8.js.map → ui-iTluWjC4.js.map} +1 -1
  13. package/dist/assets/{vendor-5HifrnRK.js → vendor-BvM2F9Dp.js} +139 -84
  14. package/dist/assets/vendor-BvM2F9Dp.js.map +1 -0
  15. package/dist/assets/{viz-CQzkRnTu.js → viz-CNeunkfu.js} +2 -2
  16. package/dist/assets/{viz-CQzkRnTu.js.map → viz-CNeunkfu.js.map} +1 -1
  17. package/dist/index.html +8 -8
  18. package/dist/openclaw/parity.js +1 -0
  19. package/dist/openclaw/routes.js +7 -0
  20. package/dist/openclaw/tools.js +183 -16
  21. package/dist/server/app.js +2509 -263
  22. package/dist/server/managers/platform/secrets-manager.js +44 -1
  23. package/dist/server/managers/runtime.js +3 -1
  24. package/dist/server/openapi.js +2037 -172
  25. package/dist/server/repositories/calendar.js +1101 -0
  26. package/dist/server/repositories/deleted-entities.js +10 -2
  27. package/dist/server/repositories/notes.js +161 -28
  28. package/dist/server/repositories/projects.js +45 -13
  29. package/dist/server/repositories/rewards.js +114 -6
  30. package/dist/server/repositories/settings.js +47 -5
  31. package/dist/server/repositories/task-runs.js +46 -10
  32. package/dist/server/repositories/tasks.js +25 -9
  33. package/dist/server/repositories/weekly-reviews.js +109 -0
  34. package/dist/server/repositories/work-adjustments.js +105 -0
  35. package/dist/server/services/calendar-runtime.js +1301 -0
  36. package/dist/server/services/entity-crud.js +94 -3
  37. package/dist/server/services/projects.js +32 -8
  38. package/dist/server/services/reviews.js +15 -1
  39. package/dist/server/services/work-time.js +27 -0
  40. package/dist/server/types.js +934 -49
  41. package/openclaw.plugin.json +1 -1
  42. package/package.json +1 -1
  43. package/server/migrations/006_work_adjustments.sql +14 -0
  44. package/server/migrations/007_weekly_review_closures.sql +17 -0
  45. package/server/migrations/008_calendar_execution.sql +147 -0
  46. package/server/migrations/009_true_calendar_events.sql +195 -0
  47. package/server/migrations/010_calendar_selection_state.sql +6 -0
  48. package/server/migrations/011_calendar_timezone_backfill.sql +11 -0
  49. package/server/migrations/012_work_block_ranges.sql +7 -0
  50. package/server/migrations/013_microsoft_local_auth_settings.sql +8 -0
  51. package/server/migrations/014_note_tags_and_ephemeral.sql +8 -0
  52. package/skills/forge-openclaw/SKILL.md +117 -11
  53. package/dist/assets/index-CDYW4WDH.js +0 -36
  54. package/dist/assets/index-CDYW4WDH.js.map +0 -1
  55. package/dist/assets/index-yroQr6YZ.css +0 -1
  56. package/dist/assets/vendor-5HifrnRK.js.map +0 -1
package/dist/index.html CHANGED
@@ -13,15 +13,15 @@
13
13
  />
14
14
  <link rel="icon" type="image/png" href="/forge/assets/favicon-BCHm9dUV.ico" />
15
15
  <link rel="alternate icon" href="/forge/assets/favicon-BCHm9dUV.ico" />
16
- <script type="module" crossorigin src="/forge/assets/index-CDYW4WDH.js"></script>
17
- <link rel="modulepreload" crossorigin href="/forge/assets/vendor-5HifrnRK.js">
18
- <link rel="modulepreload" crossorigin href="/forge/assets/motion-q19HPmWs.js">
19
- <link rel="modulepreload" crossorigin href="/forge/assets/ui-CQ_AsFs8.js">
20
- <link rel="modulepreload" crossorigin href="/forge/assets/table-BDMHBY4a.js">
21
- <link rel="modulepreload" crossorigin href="/forge/assets/viz-CQzkRnTu.js">
22
- <link rel="modulepreload" crossorigin href="/forge/assets/board-2KevHCI0.js">
16
+ <script type="module" crossorigin src="/forge/assets/index-Cj1IBH_w.js"></script>
17
+ <link rel="modulepreload" crossorigin href="/forge/assets/vendor-BvM2F9Dp.js">
18
+ <link rel="modulepreload" crossorigin href="/forge/assets/motion-1GAqqi8M.js">
19
+ <link rel="modulepreload" crossorigin href="/forge/assets/ui-iTluWjC4.js">
20
+ <link rel="modulepreload" crossorigin href="/forge/assets/table-DBGlgRjk.js">
21
+ <link rel="modulepreload" crossorigin href="/forge/assets/viz-CNeunkfu.js">
22
+ <link rel="modulepreload" crossorigin href="/forge/assets/board-8L3uX7_O.js">
23
23
  <link rel="stylesheet" crossorigin href="/forge/assets/vendor-CRS-psbw.css">
24
- <link rel="stylesheet" crossorigin href="/forge/assets/index-yroQr6YZ.css">
24
+ <link rel="stylesheet" crossorigin href="/forge/assets/index-DQT6EbuS.css">
25
25
  </head>
26
26
  <body class="bg-canvas text-ink antialiased">
27
27
  <div id="root"></div>
@@ -12,6 +12,7 @@ export const FORGE_SUPPORTED_PLUGIN_API_ROUTES = [
12
12
  { method: "POST", path: "/api/v1/entities/delete", purpose: "entities" },
13
13
  { method: "POST", path: "/api/v1/entities/restore", purpose: "entities" },
14
14
  { method: "POST", path: "/api/v1/operator/log-work", purpose: "work" },
15
+ { method: "POST", path: "/api/v1/work-adjustments", purpose: "work" },
15
16
  { method: "POST", path: "/api/v1/tasks/:id/runs", purpose: "work" },
16
17
  { method: "GET", path: "/api/v1/task-runs", purpose: "work" },
17
18
  { method: "POST", path: "/api/v1/task-runs/:id/heartbeat", purpose: "work" },
@@ -140,6 +140,13 @@ export const FORGE_PLUGIN_ROUTE_GROUPS = [
140
140
  requiresToken: true,
141
141
  target: (_match, url) => passthroughSearch("/api/v1/operator/log-work", url)
142
142
  }),
143
+ exact("/forge/v1/work-adjustments", {
144
+ method: "POST",
145
+ upstreamPath: "/api/v1/work-adjustments",
146
+ requestBody: "json",
147
+ requiresToken: true,
148
+ target: (_match, url) => passthroughSearch("/api/v1/work-adjustments", url)
149
+ }),
143
150
  exact("/forge/v1/insights", {
144
151
  method: "POST",
145
152
  upstreamPath: "/api/v1/insights",
@@ -31,6 +31,17 @@ const emptyObjectSchema = Type.Object({});
31
31
  const optionalString = () => Type.Optional(Type.String());
32
32
  const optionalNullableString = () => Type.Optional(Type.Union([Type.String(), Type.Null()]));
33
33
  const optionalDeleteMode = () => Type.Optional(Type.Union([Type.Literal("soft"), Type.Literal("hard")]));
34
+ const noteInputSchema = () => Type.Object({
35
+ contentMarkdown: Type.String({ minLength: 1 }),
36
+ author: optionalNullableString(),
37
+ tags: Type.Optional(Type.Array(Type.String())),
38
+ destroyAt: optionalNullableString(),
39
+ links: Type.Optional(Type.Array(Type.Object({
40
+ entityType: Type.String({ minLength: 1 }),
41
+ entityId: Type.String({ minLength: 1 }),
42
+ anchorKey: optionalNullableString()
43
+ })))
44
+ });
34
45
  async function resolveUiEntrypoint(config) {
35
46
  let webAppUrl = config.webAppUrl;
36
47
  try {
@@ -57,14 +68,27 @@ async function resolveUiEntrypoint(config) {
57
68
  }
58
69
  async function resolveCurrentWork(config) {
59
70
  const payload = await runRead(config, "/api/v1/operator/context");
60
- const context = typeof payload === "object" && payload !== null && "context" in payload && typeof payload.context === "object" && payload.context !== null
71
+ const context = typeof payload === "object" &&
72
+ payload !== null &&
73
+ "context" in payload &&
74
+ typeof payload.context === "object" &&
75
+ payload.context !== null
61
76
  ? payload.context
62
77
  : null;
63
- const recentTaskRuns = Array.isArray(context?.recentTaskRuns) ? context.recentTaskRuns : [];
64
- const activeTaskRuns = recentTaskRuns.filter((run) => typeof run === "object" && run !== null && "status" in run && run.status === "active");
65
- const focusTasks = Array.isArray(context?.focusTasks) ? context.focusTasks : [];
78
+ const recentTaskRuns = Array.isArray(context?.recentTaskRuns)
79
+ ? context.recentTaskRuns
80
+ : [];
81
+ const activeTaskRuns = recentTaskRuns.filter((run) => typeof run === "object" &&
82
+ run !== null &&
83
+ "status" in run &&
84
+ run.status === "active");
85
+ const focusTasks = Array.isArray(context?.focusTasks)
86
+ ? context.focusTasks
87
+ : [];
66
88
  return {
67
- generatedAt: typeof context?.generatedAt === "string" ? context.generatedAt : new Date().toISOString(),
89
+ generatedAt: typeof context?.generatedAt === "string"
90
+ ? context.generatedAt
91
+ : new Date().toISOString(),
68
92
  activeTaskRuns,
69
93
  focusTasks,
70
94
  recommendedNextTask: context?.recommendedNextTask ?? null,
@@ -178,7 +202,7 @@ export function registerForgePluginTools(api, config) {
178
202
  registerWriteTool(api, config, {
179
203
  name: "forge_create_entities",
180
204
  label: "Create Forge Entities",
181
- description: "Create one or more Forge entities through the ordered batch workflow. Pass `operations` as an array. Each operation must include `entityType` and full `data`. Batch several creates together in one call when possible.",
205
+ description: "Create one or more Forge entities through the ordered batch workflow. Pass `operations` as an array. Each operation must include `entityType` and full `data`. This is the preferred create path for planning, Psyche, and calendar records including calendar_event, work_block_template, and task_timebox.",
182
206
  parameters: Type.Object({
183
207
  atomic: Type.Optional(Type.Boolean()),
184
208
  operations: Type.Array(Type.Object({
@@ -193,7 +217,7 @@ export function registerForgePluginTools(api, config) {
193
217
  registerWriteTool(api, config, {
194
218
  name: "forge_update_entities",
195
219
  label: "Update Forge Entities",
196
- description: "Update one or more Forge entities through the ordered batch workflow. Pass `operations` as an array. Each operation must include `entityType`, `id`, and `patch`.",
220
+ description: "Update one or more Forge entities through the ordered batch workflow. Pass `operations` as an array. Each operation must include `entityType`, `id`, and `patch`. This is the preferred update path for calendar_event, work_block_template, and task_timebox too; Forge runs calendar sync side effects downstream.",
197
221
  parameters: Type.Object({
198
222
  atomic: Type.Optional(Type.Boolean()),
199
223
  operations: Type.Array(Type.Object({
@@ -209,7 +233,7 @@ export function registerForgePluginTools(api, config) {
209
233
  registerWriteTool(api, config, {
210
234
  name: "forge_delete_entities",
211
235
  label: "Delete Forge Entities",
212
- description: "Delete Forge entities in one batch request. Pass `operations` as an array with `entityType` and `id`. Delete defaults to soft mode unless hard is requested explicitly.",
236
+ description: "Delete Forge entities in one batch request. Pass `operations` as an array with `entityType` and `id`. Delete defaults to soft mode unless hard is requested explicitly. Calendar-domain deletes still run their downstream removal logic, including remote calendar projection cleanup for calendar_event.",
213
237
  parameters: Type.Object({
214
238
  atomic: Type.Optional(Type.Boolean()),
215
239
  operations: Type.Array(Type.Object({
@@ -253,6 +277,19 @@ export function registerForgePluginTools(api, config) {
253
277
  method: "POST",
254
278
  path: "/api/v1/rewards/bonus"
255
279
  });
280
+ registerWriteTool(api, config, {
281
+ name: "forge_adjust_work_minutes",
282
+ label: "Forge Adjust Work Minutes",
283
+ description: "Add or remove tracked work minutes on an existing task or project without creating a live task run. Forge applies symmetric XP changes when the total crosses reward buckets.",
284
+ parameters: Type.Object({
285
+ entityType: Type.Union([Type.Literal("task"), Type.Literal("project")]),
286
+ entityId: Type.String({ minLength: 1 }),
287
+ deltaMinutes: Type.Integer(),
288
+ note: optionalString()
289
+ }),
290
+ method: "POST",
291
+ path: "/api/v1/work-adjustments"
292
+ });
256
293
  registerWriteTool(api, config, {
257
294
  name: "forge_post_insight",
258
295
  label: "Forge Post Insight",
@@ -290,7 +327,7 @@ export function registerForgePluginTools(api, config) {
290
327
  registerWriteTool(api, config, {
291
328
  name: "forge_log_work",
292
329
  label: "Forge Log Work",
293
- description: "Log retroactive work or mark an existing task as completed through the operator work-log flow. Use this when the user already did the work and wants truthful evidence plus XP.",
330
+ description: "Log retroactive work or mark an existing task as completed through the operator work-log flow. Use this when the user already did the work and wants truthful evidence plus XP. Prefer closeoutNote when the summary should survive as a real linked note.",
294
331
  parameters: Type.Object({
295
332
  taskId: optionalString(),
296
333
  title: optionalString(),
@@ -305,7 +342,8 @@ export function registerForgePluginTools(api, config) {
305
342
  effort: optionalString(),
306
343
  energy: optionalString(),
307
344
  points: Type.Optional(Type.Integer({ minimum: 5, maximum: 500 })),
308
- tagIds: Type.Optional(Type.Array(Type.String()))
345
+ tagIds: Type.Optional(Type.Array(Type.String())),
346
+ closeoutNote: Type.Optional(noteInputSchema())
309
347
  }),
310
348
  method: "POST",
311
349
  path: "/api/v1/operator/log-work"
@@ -319,6 +357,7 @@ export function registerForgePluginTools(api, config) {
319
357
  actor: Type.String({ minLength: 1 }),
320
358
  timerMode: Type.Optional(Type.Union([Type.Literal("planned"), Type.Literal("unlimited")])),
321
359
  plannedDurationSeconds: Type.Optional(Type.Union([Type.Integer({ minimum: 60, maximum: 86400 }), Type.Null()])),
360
+ overrideReason: optionalNullableString(),
322
361
  isCurrent: Type.Optional(Type.Boolean()),
323
362
  leaseTtlSeconds: Type.Optional(Type.Integer({ minimum: 1, maximum: 14400 })),
324
363
  note: Type.Optional(Type.String())
@@ -332,6 +371,7 @@ export function registerForgePluginTools(api, config) {
332
371
  actor: typed.actor,
333
372
  timerMode: typed.timerMode,
334
373
  plannedDurationSeconds: typed.plannedDurationSeconds,
374
+ overrideReason: typed.overrideReason,
335
375
  isCurrent: typed.isCurrent,
336
376
  leaseTtlSeconds: typed.leaseTtlSeconds,
337
377
  note: typed.note
@@ -384,11 +424,12 @@ export function registerForgePluginTools(api, config) {
384
424
  api.registerTool({
385
425
  name: "forge_complete_task_run",
386
426
  label: "Forge Complete Task Run",
387
- description: "Finish an active task run as completed work and let Forge award the appropriate completion rewards.",
427
+ description: "Finish an active task run as completed work and let Forge award the appropriate completion rewards. Prefer closeoutNote when the work summary should become a real linked note.",
388
428
  parameters: Type.Object({
389
429
  taskRunId: Type.String({ minLength: 1 }),
390
430
  actor: optionalString(),
391
- note: Type.Optional(Type.String())
431
+ note: Type.Optional(Type.String()),
432
+ closeoutNote: Type.Optional(noteInputSchema())
392
433
  }),
393
434
  async execute(_toolCallId, params) {
394
435
  const typed = params;
@@ -397,7 +438,8 @@ export function registerForgePluginTools(api, config) {
397
438
  path: `/api/v1/task-runs/${typed.taskRunId}/complete`,
398
439
  body: {
399
440
  actor: typed.actor,
400
- note: typed.note
441
+ note: typed.note,
442
+ closeoutNote: typed.closeoutNote
401
443
  }
402
444
  }));
403
445
  }
@@ -405,11 +447,12 @@ export function registerForgePluginTools(api, config) {
405
447
  api.registerTool({
406
448
  name: "forge_release_task_run",
407
449
  label: "Forge Release Task Run",
408
- description: "Stop an active task run without completing it. Use this to truthfully stop current work.",
450
+ description: "Stop an active task run without completing it. Use this to truthfully stop current work. Prefer closeoutNote when blockers or handoff context should become a real linked note.",
409
451
  parameters: Type.Object({
410
452
  taskRunId: Type.String({ minLength: 1 }),
411
453
  actor: optionalString(),
412
- note: Type.Optional(Type.String())
454
+ note: Type.Optional(Type.String()),
455
+ closeoutNote: Type.Optional(noteInputSchema())
413
456
  }),
414
457
  async execute(_toolCallId, params) {
415
458
  const typed = params;
@@ -418,9 +461,133 @@ export function registerForgePluginTools(api, config) {
418
461
  path: `/api/v1/task-runs/${typed.taskRunId}/release`,
419
462
  body: {
420
463
  actor: typed.actor,
421
- note: typed.note
464
+ note: typed.note,
465
+ closeoutNote: typed.closeoutNote
422
466
  }
423
467
  }));
424
468
  }
425
469
  });
470
+ registerReadTool(api, config, {
471
+ name: "forge_get_calendar_overview",
472
+ label: "Forge Calendar Overview",
473
+ description: "Read the calendar domain in one response: provider metadata, connected calendars, Forge-native events, mirrored events, recurring work blocks, and task timeboxes.",
474
+ parameters: Type.Object({
475
+ from: optionalString(),
476
+ to: optionalString()
477
+ }),
478
+ path: (params) => {
479
+ const search = new URLSearchParams();
480
+ if (typeof params.from === "string" && params.from.trim().length > 0) {
481
+ search.set("from", params.from);
482
+ }
483
+ if (typeof params.to === "string" && params.to.trim().length > 0) {
484
+ search.set("to", params.to);
485
+ }
486
+ const suffix = search.size > 0 ? `?${search.toString()}` : "";
487
+ return `/api/v1/calendar/overview${suffix}`;
488
+ }
489
+ });
490
+ registerWriteTool(api, config, {
491
+ name: "forge_connect_calendar_provider",
492
+ label: "Forge Connect Calendar Provider",
493
+ description: "Create a Google, Apple, Exchange Online, or custom CalDAV calendar connection. Use this only for explicit provider-connection requests after discovery choices are known.",
494
+ parameters: Type.Object({
495
+ provider: Type.Union([
496
+ Type.Literal("google"),
497
+ Type.Literal("apple"),
498
+ Type.Literal("caldav"),
499
+ Type.Literal("microsoft")
500
+ ]),
501
+ label: Type.String({ minLength: 1 }),
502
+ username: optionalString(),
503
+ clientId: optionalString(),
504
+ clientSecret: optionalString(),
505
+ refreshToken: optionalString(),
506
+ password: optionalString(),
507
+ serverUrl: optionalString(),
508
+ authSessionId: optionalString(),
509
+ selectedCalendarUrls: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
510
+ forgeCalendarUrl: optionalString(),
511
+ createForgeCalendar: Type.Optional(Type.Boolean())
512
+ }),
513
+ method: "POST",
514
+ path: "/api/v1/calendar/connections"
515
+ });
516
+ api.registerTool({
517
+ name: "forge_sync_calendar_connection",
518
+ label: "Forge Sync Calendar Connection",
519
+ description: "Pull and push changes for one connected calendar provider.",
520
+ parameters: Type.Object({
521
+ connectionId: Type.String({ minLength: 1 })
522
+ }),
523
+ async execute(_toolCallId, params) {
524
+ const typed = params;
525
+ return jsonResult(await runWrite(config, {
526
+ method: "POST",
527
+ path: `/api/v1/calendar/connections/${typed.connectionId}/sync`,
528
+ body: {}
529
+ }));
530
+ }
531
+ });
532
+ registerWriteTool(api, config, {
533
+ name: "forge_create_work_block_template",
534
+ label: "Forge Create Work Block",
535
+ description: "Create a recurring work-block template such as Main Activity, Secondary Activity, Third Activity, Rest, Holiday, or Custom. This is a planning helper; agents can also use forge_create_entities with entityType work_block_template.",
536
+ parameters: Type.Object({
537
+ title: Type.String({ minLength: 1 }),
538
+ kind: Type.Union([
539
+ Type.Literal("main_activity"),
540
+ Type.Literal("secondary_activity"),
541
+ Type.Literal("third_activity"),
542
+ Type.Literal("rest"),
543
+ Type.Literal("holiday"),
544
+ Type.Literal("custom")
545
+ ]),
546
+ color: Type.String({ minLength: 1 }),
547
+ timezone: Type.String({ minLength: 1 }),
548
+ weekDays: Type.Array(Type.Integer({ minimum: 0, maximum: 6 })),
549
+ startMinute: Type.Integer({ minimum: 0, maximum: 1440 }),
550
+ endMinute: Type.Integer({ minimum: 0, maximum: 1440 }),
551
+ startsOn: Type.Optional(Type.Union([Type.String({ minLength: 1 }), Type.Null()])),
552
+ endsOn: Type.Optional(Type.Union([Type.String({ minLength: 1 }), Type.Null()])),
553
+ blockingState: Type.Union([
554
+ Type.Literal("allowed"),
555
+ Type.Literal("blocked")
556
+ ])
557
+ }),
558
+ method: "POST",
559
+ path: "/api/v1/calendar/work-block-templates"
560
+ });
561
+ registerWriteTool(api, config, {
562
+ name: "forge_recommend_task_timeboxes",
563
+ label: "Forge Recommend Task Timeboxes",
564
+ description: "Suggest future task timeboxes that fit the current calendar rules and current schedule.",
565
+ parameters: Type.Object({
566
+ taskId: Type.String({ minLength: 1 }),
567
+ from: optionalString(),
568
+ to: optionalString(),
569
+ limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 24 }))
570
+ }),
571
+ method: "POST",
572
+ path: "/api/v1/calendar/timeboxes/recommend"
573
+ });
574
+ registerWriteTool(api, config, {
575
+ name: "forge_create_task_timebox",
576
+ label: "Forge Create Task Timebox",
577
+ description: "Create a planned task timebox directly in Forge's calendar domain. This is a planning helper; agents can also use forge_create_entities with entityType task_timebox.",
578
+ parameters: Type.Object({
579
+ taskId: Type.String({ minLength: 1 }),
580
+ projectId: optionalNullableString(),
581
+ title: Type.String({ minLength: 1 }),
582
+ startsAt: Type.String({ minLength: 1 }),
583
+ endsAt: Type.String({ minLength: 1 }),
584
+ source: Type.Optional(Type.Union([
585
+ Type.Literal("manual"),
586
+ Type.Literal("suggested"),
587
+ Type.Literal("live_run")
588
+ ]))
589
+ }),
590
+ method: "POST",
591
+ path: "/api/v1/calendar/timeboxes"
592
+ });
426
593
  }