forge-openclaw-plugin 0.2.15 → 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 +39 -4
- package/dist/assets/{board-C_m78kvK.js → board-8L3uX7_O.js} +2 -2
- package/dist/assets/{board-C_m78kvK.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-CpZvZumD.js → motion-1GAqqi8M.js} +2 -2
- package/dist/assets/{motion-CpZvZumD.js.map → motion-1GAqqi8M.js.map} +1 -1
- package/dist/assets/{table-DtyXTw03.js → table-DBGlgRjk.js} +2 -2
- package/dist/assets/{table-DtyXTw03.js.map → table-DBGlgRjk.js.map} +1 -1
- package/dist/assets/{ui-BXbpiKyS.js → ui-iTluWjC4.js} +2 -2
- package/dist/assets/{ui-BXbpiKyS.js.map → ui-iTluWjC4.js.map} +1 -1
- package/dist/assets/{vendor-QBH6qVEe.js → vendor-BvM2F9Dp.js} +151 -81
- package/dist/assets/vendor-BvM2F9Dp.js.map +1 -0
- package/dist/assets/{viz-w-IMeueL.js → viz-CNeunkfu.js} +2 -2
- package/dist/assets/{viz-w-IMeueL.js.map → viz-CNeunkfu.js.map} +1 -1
- package/dist/index.html +8 -8
- package/dist/openclaw/local-runtime.js +142 -9
- package/dist/openclaw/parity.js +1 -0
- package/dist/openclaw/plugin-entry-shared.js +7 -1
- package/dist/openclaw/routes.js +7 -0
- package/dist/openclaw/tools.js +198 -16
- package/dist/server/app.js +2615 -251
- package/dist/server/managers/platform/secrets-manager.js +44 -1
- package/dist/server/managers/runtime.js +3 -1
- package/dist/server/openapi.js +2212 -170
- package/dist/server/repositories/calendar.js +1101 -0
- package/dist/server/repositories/deleted-entities.js +10 -2
- package/dist/server/repositories/habits.js +358 -0
- package/dist/server/repositories/notes.js +161 -28
- package/dist/server/repositories/projects.js +45 -13
- package/dist/server/repositories/rewards.js +176 -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/context.js +16 -6
- package/dist/server/services/dashboard.js +6 -3
- package/dist/server/services/entity-crud.js +116 -3
- package/dist/server/services/gamification.js +66 -18
- package/dist/server/services/insights.js +2 -1
- package/dist/server/services/projects.js +32 -8
- package/dist/server/services/reviews.js +17 -2
- package/dist/server/services/work-time.js +27 -0
- package/dist/server/types.js +1069 -45
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/server/migrations/003_habits.sql +30 -0
- package/server/migrations/004_habit_links.sql +8 -0
- package/server/migrations/005_habit_psyche_links.sql +24 -0
- 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 +130 -10
- package/skills/forge-openclaw/cron_jobs.md +395 -0
- package/dist/assets/index-BWtLtXwb.js +0 -36
- package/dist/assets/index-BWtLtXwb.js.map +0 -1
- package/dist/assets/index-Dp5GXY_z.css +0 -1
- package/dist/assets/vendor-QBH6qVEe.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-
|
|
17
|
-
<link rel="modulepreload" crossorigin href="/forge/assets/vendor-
|
|
18
|
-
<link rel="modulepreload" crossorigin href="/forge/assets/motion-
|
|
19
|
-
<link rel="modulepreload" crossorigin href="/forge/assets/ui-
|
|
20
|
-
<link rel="modulepreload" crossorigin href="/forge/assets/table-
|
|
21
|
-
<link rel="modulepreload" crossorigin href="/forge/assets/viz-
|
|
22
|
-
<link rel="modulepreload" crossorigin href="/forge/assets/board-
|
|
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-
|
|
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>
|
|
@@ -47,6 +47,25 @@ function applyPortToConfig(config, port, portSource) {
|
|
|
47
47
|
config.webAppUrl = buildForgeWebAppUrl(config.origin, port);
|
|
48
48
|
config.portSource = portSource;
|
|
49
49
|
}
|
|
50
|
+
function getExpectedDataRoot(config) {
|
|
51
|
+
return config.dataRoot.trim().length > 0 ? path.resolve(config.dataRoot) : null;
|
|
52
|
+
}
|
|
53
|
+
function isExpectedDataRoot(expectedDataRoot, actualDataRoot) {
|
|
54
|
+
if (!expectedDataRoot) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
if (!actualDataRoot) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
return path.resolve(actualDataRoot) === expectedDataRoot;
|
|
61
|
+
}
|
|
62
|
+
function formatRuntimeDataRootMismatch(config, expectedDataRoot, actualDataRoot) {
|
|
63
|
+
return [
|
|
64
|
+
`Forge is already responding on ${config.baseUrl}, but it is using storage root ${actualDataRoot ?? "(unknown)"}.`,
|
|
65
|
+
`The OpenClaw plugin is configured to use ${expectedDataRoot}.`,
|
|
66
|
+
"Restart the plugin-managed runtime or stop the conflicting Forge server so the configured dataRoot can take over."
|
|
67
|
+
].join(" ");
|
|
68
|
+
}
|
|
50
69
|
async function writePreferredPortState(config, port) {
|
|
51
70
|
const statePath = getPreferredPortStatePath(config.origin);
|
|
52
71
|
await mkdir(path.dirname(statePath), { recursive: true });
|
|
@@ -272,6 +291,43 @@ async function isForgeHealthy(config, timeoutMs) {
|
|
|
272
291
|
clearTimeout(timeout);
|
|
273
292
|
}
|
|
274
293
|
}
|
|
294
|
+
async function probeForgeRuntime(config, timeoutMs) {
|
|
295
|
+
const controller = new AbortController();
|
|
296
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
297
|
+
try {
|
|
298
|
+
const response = await fetch(new URL("/api/v1/health", config.baseUrl), {
|
|
299
|
+
method: "GET",
|
|
300
|
+
headers: {
|
|
301
|
+
accept: "application/json",
|
|
302
|
+
"x-forge-runtime-probe": "1"
|
|
303
|
+
},
|
|
304
|
+
signal: controller.signal
|
|
305
|
+
});
|
|
306
|
+
if (!response.ok) {
|
|
307
|
+
return { healthy: false, pid: null, storageRoot: null, basePath: null };
|
|
308
|
+
}
|
|
309
|
+
const payload = (await response.json());
|
|
310
|
+
return {
|
|
311
|
+
healthy: true,
|
|
312
|
+
pid: typeof payload.runtime?.pid === "number" && Number.isFinite(payload.runtime.pid) ? Math.trunc(payload.runtime.pid) : null,
|
|
313
|
+
storageRoot: typeof payload.runtime?.storageRoot === "string" ? path.resolve(payload.runtime.storageRoot) : null,
|
|
314
|
+
basePath: typeof payload.runtime?.basePath === "string" ? payload.runtime.basePath : null
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
return { healthy: false, pid: null, storageRoot: null, basePath: null };
|
|
319
|
+
}
|
|
320
|
+
finally {
|
|
321
|
+
clearTimeout(timeout);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
async function adoptManagedRuntimeState(config, probe) {
|
|
325
|
+
if (probe.pid === null || !processExists(probe.pid)) {
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
await writeRuntimeState(config, probe.pid);
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
275
331
|
async function spawnManagedRuntime(config, plan) {
|
|
276
332
|
const isPackagedServer = isPackagedServerPlan(plan);
|
|
277
333
|
const args = isPackagedServer ? [plan.entryFile] : [plan.entryFile, path.join(plan.packageRoot, "server", "src", "index.ts")];
|
|
@@ -353,7 +409,13 @@ export async function ensureForgeRuntimeReady(config) {
|
|
|
353
409
|
if (!isLocalOrigin(config.origin)) {
|
|
354
410
|
return;
|
|
355
411
|
}
|
|
356
|
-
|
|
412
|
+
const expectedDataRoot = getExpectedDataRoot(config);
|
|
413
|
+
const initialProbe = await probeForgeRuntime(config, HEALTHCHECK_TIMEOUT_MS);
|
|
414
|
+
if (initialProbe.healthy && isExpectedDataRoot(expectedDataRoot, initialProbe.storageRoot)) {
|
|
415
|
+
const existingState = await readRuntimeState(config);
|
|
416
|
+
if (!existingState) {
|
|
417
|
+
await adoptManagedRuntimeState(config, initialProbe);
|
|
418
|
+
}
|
|
357
419
|
return;
|
|
358
420
|
}
|
|
359
421
|
const savedState = await readRuntimeState(config);
|
|
@@ -361,12 +423,29 @@ export async function ensureForgeRuntimeReady(config) {
|
|
|
361
423
|
await clearRuntimeState(config);
|
|
362
424
|
}
|
|
363
425
|
else if (savedState && processExists(savedState.pid)) {
|
|
426
|
+
if (initialProbe.healthy && !isExpectedDataRoot(expectedDataRoot, initialProbe.storageRoot)) {
|
|
427
|
+
await stopForgeRuntime(config);
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
try {
|
|
431
|
+
await waitForRuntime(config, EXISTING_RUNTIME_GRACE_MS, null);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
catch {
|
|
435
|
+
await stopForgeRuntime(config);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
else if (initialProbe.healthy) {
|
|
440
|
+
if (!isExpectedDataRoot(expectedDataRoot, initialProbe.storageRoot)) {
|
|
441
|
+
throw new Error(formatRuntimeDataRootMismatch(config, expectedDataRoot, initialProbe.storageRoot));
|
|
442
|
+
}
|
|
364
443
|
try {
|
|
365
444
|
await waitForRuntime(config, EXISTING_RUNTIME_GRACE_MS, null);
|
|
366
445
|
return;
|
|
367
446
|
}
|
|
368
447
|
catch {
|
|
369
|
-
|
|
448
|
+
// There is no plugin-managed pid to stop here; fall through into normal startup handling.
|
|
370
449
|
}
|
|
371
450
|
}
|
|
372
451
|
const key = runtimeKey(config);
|
|
@@ -378,14 +457,16 @@ export async function ensureForgeRuntimeReady(config) {
|
|
|
378
457
|
return;
|
|
379
458
|
}
|
|
380
459
|
startupPromise = (async () => {
|
|
381
|
-
|
|
460
|
+
const probeBeforeStart = await probeForgeRuntime(config, HEALTHCHECK_TIMEOUT_MS);
|
|
461
|
+
if (probeBeforeStart.healthy && isExpectedDataRoot(expectedDataRoot, probeBeforeStart.storageRoot)) {
|
|
382
462
|
return;
|
|
383
463
|
}
|
|
384
464
|
startupRuntimeKey = runtimeKey(config);
|
|
385
465
|
if (!(await isPortAvailable("127.0.0.1", config.port))) {
|
|
386
466
|
await relocateLocalRuntimePort(config);
|
|
387
467
|
startupRuntimeKey = runtimeKey(config);
|
|
388
|
-
|
|
468
|
+
const probeAfterRelocation = await probeForgeRuntime(config, HEALTHCHECK_TIMEOUT_MS);
|
|
469
|
+
if (probeAfterRelocation.healthy && isExpectedDataRoot(expectedDataRoot, probeAfterRelocation.storageRoot)) {
|
|
389
470
|
return;
|
|
390
471
|
}
|
|
391
472
|
}
|
|
@@ -394,6 +475,10 @@ export async function ensureForgeRuntimeReady(config) {
|
|
|
394
475
|
await spawnManagedRuntime(config, plan);
|
|
395
476
|
}
|
|
396
477
|
await waitForRuntime(config, STARTUP_TIMEOUT_MS, managedRuntimeChild?.pid ?? null);
|
|
478
|
+
const probeAfterStart = await probeForgeRuntime(config, HEALTHCHECK_TIMEOUT_MS);
|
|
479
|
+
if (!probeAfterStart.healthy || !isExpectedDataRoot(expectedDataRoot, probeAfterStart.storageRoot)) {
|
|
480
|
+
throw new Error(formatRuntimeDataRootMismatch(config, expectedDataRoot, probeAfterStart.storageRoot));
|
|
481
|
+
}
|
|
397
482
|
})().finally(() => {
|
|
398
483
|
startupPromise = null;
|
|
399
484
|
startupRuntimeKey = null;
|
|
@@ -411,8 +496,26 @@ export async function startForgeRuntime(config) {
|
|
|
411
496
|
baseUrl: config.baseUrl
|
|
412
497
|
};
|
|
413
498
|
}
|
|
414
|
-
const
|
|
415
|
-
|
|
499
|
+
const expectedDataRoot = getExpectedDataRoot(config);
|
|
500
|
+
const probe = await probeForgeRuntime(config, HEALTHCHECK_TIMEOUT_MS);
|
|
501
|
+
let existingState = await readRuntimeState(config);
|
|
502
|
+
if (!existingState && probe.healthy && isExpectedDataRoot(expectedDataRoot, probe.storageRoot)) {
|
|
503
|
+
const adopted = await adoptManagedRuntimeState(config, probe);
|
|
504
|
+
if (adopted) {
|
|
505
|
+
existingState = await readRuntimeState(config);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
if (probe.healthy && !isExpectedDataRoot(expectedDataRoot, probe.storageRoot)) {
|
|
509
|
+
return {
|
|
510
|
+
ok: false,
|
|
511
|
+
started: false,
|
|
512
|
+
managed: Boolean(existingState),
|
|
513
|
+
message: formatRuntimeDataRootMismatch(config, expectedDataRoot, probe.storageRoot),
|
|
514
|
+
pid: existingState?.pid ?? null,
|
|
515
|
+
baseUrl: config.baseUrl
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
if (!existingState && probe.healthy) {
|
|
416
519
|
return {
|
|
417
520
|
ok: true,
|
|
418
521
|
started: false,
|
|
@@ -422,7 +525,7 @@ export async function startForgeRuntime(config) {
|
|
|
422
525
|
baseUrl: config.baseUrl
|
|
423
526
|
};
|
|
424
527
|
}
|
|
425
|
-
if (existingState && processExists(existingState.pid) &&
|
|
528
|
+
if (existingState && processExists(existingState.pid) && probe.healthy) {
|
|
426
529
|
return {
|
|
427
530
|
ok: true,
|
|
428
531
|
started: false,
|
|
@@ -517,8 +620,16 @@ export async function stopForgeRuntime(config) {
|
|
|
517
620
|
};
|
|
518
621
|
}
|
|
519
622
|
export async function getForgeRuntimeStatus(config) {
|
|
520
|
-
const
|
|
521
|
-
const
|
|
623
|
+
const expectedDataRoot = getExpectedDataRoot(config);
|
|
624
|
+
const probe = await probeForgeRuntime(config, HEALTHCHECK_TIMEOUT_MS);
|
|
625
|
+
const healthy = probe.healthy;
|
|
626
|
+
let state = await readRuntimeState(config);
|
|
627
|
+
if (!state && healthy && isExpectedDataRoot(expectedDataRoot, probe.storageRoot)) {
|
|
628
|
+
const adopted = await adoptManagedRuntimeState(config, probe);
|
|
629
|
+
if (adopted) {
|
|
630
|
+
state = await readRuntimeState(config);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
522
633
|
const pid = state?.pid ?? null;
|
|
523
634
|
const managed = Boolean(state);
|
|
524
635
|
const running = healthy || (pid !== null && processExists(pid));
|
|
@@ -548,6 +659,17 @@ export async function getForgeRuntimeStatus(config) {
|
|
|
548
659
|
};
|
|
549
660
|
}
|
|
550
661
|
if (healthy && managed) {
|
|
662
|
+
if (!isExpectedDataRoot(expectedDataRoot, probe.storageRoot)) {
|
|
663
|
+
return {
|
|
664
|
+
ok: false,
|
|
665
|
+
running: true,
|
|
666
|
+
healthy: true,
|
|
667
|
+
managed: true,
|
|
668
|
+
message: formatRuntimeDataRootMismatch(config, expectedDataRoot, probe.storageRoot),
|
|
669
|
+
pid,
|
|
670
|
+
baseUrl: config.baseUrl
|
|
671
|
+
};
|
|
672
|
+
}
|
|
551
673
|
return {
|
|
552
674
|
ok: true,
|
|
553
675
|
running: true,
|
|
@@ -559,6 +681,17 @@ export async function getForgeRuntimeStatus(config) {
|
|
|
559
681
|
};
|
|
560
682
|
}
|
|
561
683
|
if (healthy) {
|
|
684
|
+
if (!isExpectedDataRoot(expectedDataRoot, probe.storageRoot)) {
|
|
685
|
+
return {
|
|
686
|
+
ok: false,
|
|
687
|
+
running: true,
|
|
688
|
+
healthy: true,
|
|
689
|
+
managed: false,
|
|
690
|
+
message: formatRuntimeDataRootMismatch(config, expectedDataRoot, probe.storageRoot),
|
|
691
|
+
pid: null,
|
|
692
|
+
baseUrl: config.baseUrl
|
|
693
|
+
};
|
|
694
|
+
}
|
|
562
695
|
return {
|
|
563
696
|
ok: true,
|
|
564
697
|
running: true,
|
package/dist/openclaw/parity.js
CHANGED
|
@@ -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" },
|
|
@@ -40,6 +40,12 @@ function normalizeTimeout(value, fallback) {
|
|
|
40
40
|
}
|
|
41
41
|
return Math.min(120_000, Math.max(1000, Math.round(value)));
|
|
42
42
|
}
|
|
43
|
+
function normalizeDataRoot(value) {
|
|
44
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
45
|
+
return "";
|
|
46
|
+
}
|
|
47
|
+
return path.resolve(value.trim());
|
|
48
|
+
}
|
|
43
49
|
function isLocalOrigin(origin) {
|
|
44
50
|
try {
|
|
45
51
|
return LOCAL_HOSTNAMES.has(new URL(origin).hostname.toLowerCase());
|
|
@@ -80,7 +86,7 @@ export function resolveForgePluginConfig(pluginConfig) {
|
|
|
80
86
|
baseUrl: buildForgeBaseUrl(origin, port),
|
|
81
87
|
webAppUrl: buildForgeWebAppUrl(origin, port),
|
|
82
88
|
portSource: hasConfiguredPort ? "configured" : preferredPort !== null ? "preferred" : "default",
|
|
83
|
-
dataRoot:
|
|
89
|
+
dataRoot: normalizeDataRoot(raw.dataRoot),
|
|
84
90
|
apiToken: typeof raw.apiToken === "string" ? raw.apiToken.trim() : "",
|
|
85
91
|
actorLabel: normalizeString(raw.actorLabel, "aurel"),
|
|
86
92
|
timeoutMs: normalizeTimeout(raw.timeoutMs, 15_000)
|
package/dist/openclaw/routes.js
CHANGED
|
@@ -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",
|
package/dist/openclaw/tools.js
CHANGED
|
@@ -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" &&
|
|
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)
|
|
64
|
-
|
|
65
|
-
|
|
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"
|
|
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`.
|
|
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({
|
|
@@ -238,6 +262,34 @@ export function registerForgePluginTools(api, config) {
|
|
|
238
262
|
method: "POST",
|
|
239
263
|
path: "/api/v1/entities/restore"
|
|
240
264
|
});
|
|
265
|
+
registerWriteTool(api, config, {
|
|
266
|
+
name: "forge_grant_reward_bonus",
|
|
267
|
+
label: "Forge Grant Reward Bonus",
|
|
268
|
+
description: "Grant an explicit manual XP bonus or penalty with provenance. Use only for auditable operator judgement beyond the normal task-run and habit reward flows.",
|
|
269
|
+
parameters: Type.Object({
|
|
270
|
+
entityType: Type.String({ minLength: 1 }),
|
|
271
|
+
entityId: Type.String({ minLength: 1 }),
|
|
272
|
+
deltaXp: Type.Number(),
|
|
273
|
+
reasonTitle: Type.String({ minLength: 1 }),
|
|
274
|
+
reasonSummary: optionalString(),
|
|
275
|
+
metadata: Type.Optional(Type.Record(Type.String(), Type.Any()))
|
|
276
|
+
}),
|
|
277
|
+
method: "POST",
|
|
278
|
+
path: "/api/v1/rewards/bonus"
|
|
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
|
+
});
|
|
241
293
|
registerWriteTool(api, config, {
|
|
242
294
|
name: "forge_post_insight",
|
|
243
295
|
label: "Forge Post Insight",
|
|
@@ -275,7 +327,7 @@ export function registerForgePluginTools(api, config) {
|
|
|
275
327
|
registerWriteTool(api, config, {
|
|
276
328
|
name: "forge_log_work",
|
|
277
329
|
label: "Forge Log Work",
|
|
278
|
-
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.",
|
|
279
331
|
parameters: Type.Object({
|
|
280
332
|
taskId: optionalString(),
|
|
281
333
|
title: optionalString(),
|
|
@@ -290,7 +342,8 @@ export function registerForgePluginTools(api, config) {
|
|
|
290
342
|
effort: optionalString(),
|
|
291
343
|
energy: optionalString(),
|
|
292
344
|
points: Type.Optional(Type.Integer({ minimum: 5, maximum: 500 })),
|
|
293
|
-
tagIds: Type.Optional(Type.Array(Type.String()))
|
|
345
|
+
tagIds: Type.Optional(Type.Array(Type.String())),
|
|
346
|
+
closeoutNote: Type.Optional(noteInputSchema())
|
|
294
347
|
}),
|
|
295
348
|
method: "POST",
|
|
296
349
|
path: "/api/v1/operator/log-work"
|
|
@@ -304,6 +357,7 @@ export function registerForgePluginTools(api, config) {
|
|
|
304
357
|
actor: Type.String({ minLength: 1 }),
|
|
305
358
|
timerMode: Type.Optional(Type.Union([Type.Literal("planned"), Type.Literal("unlimited")])),
|
|
306
359
|
plannedDurationSeconds: Type.Optional(Type.Union([Type.Integer({ minimum: 60, maximum: 86400 }), Type.Null()])),
|
|
360
|
+
overrideReason: optionalNullableString(),
|
|
307
361
|
isCurrent: Type.Optional(Type.Boolean()),
|
|
308
362
|
leaseTtlSeconds: Type.Optional(Type.Integer({ minimum: 1, maximum: 14400 })),
|
|
309
363
|
note: Type.Optional(Type.String())
|
|
@@ -317,6 +371,7 @@ export function registerForgePluginTools(api, config) {
|
|
|
317
371
|
actor: typed.actor,
|
|
318
372
|
timerMode: typed.timerMode,
|
|
319
373
|
plannedDurationSeconds: typed.plannedDurationSeconds,
|
|
374
|
+
overrideReason: typed.overrideReason,
|
|
320
375
|
isCurrent: typed.isCurrent,
|
|
321
376
|
leaseTtlSeconds: typed.leaseTtlSeconds,
|
|
322
377
|
note: typed.note
|
|
@@ -369,11 +424,12 @@ export function registerForgePluginTools(api, config) {
|
|
|
369
424
|
api.registerTool({
|
|
370
425
|
name: "forge_complete_task_run",
|
|
371
426
|
label: "Forge Complete Task Run",
|
|
372
|
-
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.",
|
|
373
428
|
parameters: Type.Object({
|
|
374
429
|
taskRunId: Type.String({ minLength: 1 }),
|
|
375
430
|
actor: optionalString(),
|
|
376
|
-
note: Type.Optional(Type.String())
|
|
431
|
+
note: Type.Optional(Type.String()),
|
|
432
|
+
closeoutNote: Type.Optional(noteInputSchema())
|
|
377
433
|
}),
|
|
378
434
|
async execute(_toolCallId, params) {
|
|
379
435
|
const typed = params;
|
|
@@ -382,7 +438,8 @@ export function registerForgePluginTools(api, config) {
|
|
|
382
438
|
path: `/api/v1/task-runs/${typed.taskRunId}/complete`,
|
|
383
439
|
body: {
|
|
384
440
|
actor: typed.actor,
|
|
385
|
-
note: typed.note
|
|
441
|
+
note: typed.note,
|
|
442
|
+
closeoutNote: typed.closeoutNote
|
|
386
443
|
}
|
|
387
444
|
}));
|
|
388
445
|
}
|
|
@@ -390,11 +447,12 @@ export function registerForgePluginTools(api, config) {
|
|
|
390
447
|
api.registerTool({
|
|
391
448
|
name: "forge_release_task_run",
|
|
392
449
|
label: "Forge Release Task Run",
|
|
393
|
-
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.",
|
|
394
451
|
parameters: Type.Object({
|
|
395
452
|
taskRunId: Type.String({ minLength: 1 }),
|
|
396
453
|
actor: optionalString(),
|
|
397
|
-
note: Type.Optional(Type.String())
|
|
454
|
+
note: Type.Optional(Type.String()),
|
|
455
|
+
closeoutNote: Type.Optional(noteInputSchema())
|
|
398
456
|
}),
|
|
399
457
|
async execute(_toolCallId, params) {
|
|
400
458
|
const typed = params;
|
|
@@ -403,9 +461,133 @@ export function registerForgePluginTools(api, config) {
|
|
|
403
461
|
path: `/api/v1/task-runs/${typed.taskRunId}/release`,
|
|
404
462
|
body: {
|
|
405
463
|
actor: typed.actor,
|
|
406
|
-
note: typed.note
|
|
464
|
+
note: typed.note,
|
|
465
|
+
closeoutNote: typed.closeoutNote
|
|
407
466
|
}
|
|
408
467
|
}));
|
|
409
468
|
}
|
|
410
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
|
+
});
|
|
411
593
|
}
|