forge-openclaw-plugin 0.2.61 → 0.2.66

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 (31) hide show
  1. package/README.md +28 -5
  2. package/dist/assets/{board-DThHV1D8.js → board-DFNV9VAZ.js} +1 -1
  3. package/dist/assets/index-CFZxwOFB.js +90 -0
  4. package/dist/assets/{index-7gvVCqnV.css → index-lFN9z5op.css} +1 -1
  5. package/dist/assets/{motion-BtTJtHCw.js → motion-CXdn34ih.js} +1 -1
  6. package/dist/assets/{table-Bnw6pcwN.js → table-CEq3bTDv.js} +1 -1
  7. package/dist/assets/{ui-CnVxFkj0.js → ui-g7FaEglG.js} +1 -1
  8. package/dist/assets/{vendor-BgZ3YrRd.js → vendor-BcOHGipZ.js} +236 -216
  9. package/dist/companion-iroh/darwin-arm64/forge-companion-iroh +0 -0
  10. package/dist/companion-iroh/darwin-x64/forge-companion-iroh +0 -0
  11. package/dist/companion-iroh/linux-x64/forge-companion-iroh +0 -0
  12. package/dist/companion-iroh-src/Cargo.lock +4559 -0
  13. package/dist/companion-iroh-src/Cargo.toml +37 -0
  14. package/dist/companion-iroh-src/src/lib.rs +279 -0
  15. package/dist/companion-iroh-src/src/main.rs +478 -0
  16. package/dist/companion-iroh-src/src/protocol.rs +129 -0
  17. package/dist/index.html +7 -7
  18. package/dist/server/server/src/app.js +163 -18
  19. package/dist/server/server/src/discovery-advertiser.js +13 -0
  20. package/dist/server/server/src/health.js +18 -3
  21. package/dist/server/server/src/movement.js +16 -1
  22. package/dist/server/server/src/openapi.js +12 -2
  23. package/dist/server/server/src/services/companion-iroh.js +425 -0
  24. package/dist/server/server/src/services/life-force.js +166 -25
  25. package/dist/server/server/src/web.js +88 -12
  26. package/openclaw.plugin.json +1 -1
  27. package/package.json +3 -3
  28. package/skills/forge-openclaw/SKILL.md +44 -2
  29. package/skills/forge-openclaw/entity_conversation_playbooks.md +217 -17
  30. package/skills/forge-openclaw/psyche_entity_playbooks.md +59 -0
  31. package/dist/assets/index-_Cn6Prym.js +0 -90
@@ -23,6 +23,73 @@ const LIFE_FORCE_STAT_LABELS = {
23
23
  composure: "Composure",
24
24
  flow: "Flow"
25
25
  };
26
+ const AGENT_ACTOR_PATTERN = /\b(codex|hermes|openclaw|agent|bot)\b|aurel\s+the\s+bot/i;
27
+ const PASSIVE_CALENDAR_PATTERN = /\b(vacation|vacances?|cong[eé]s?|absence|holiday|out\s+of\s+office|ooo|away|leave|off)\b/i;
28
+ function isAgentAuthoredActivity(input) {
29
+ if (input.source === "agent") {
30
+ return true;
31
+ }
32
+ return AGENT_ACTOR_PATTERN.test(input.actor ?? "");
33
+ }
34
+ function agentSupervisionMultiplier(input) {
35
+ if (!isAgentAuthoredActivity(input)) {
36
+ return 1;
37
+ }
38
+ return input.goalLinked ? 0.05 : 0.15;
39
+ }
40
+ function noteApMultiplier(input) {
41
+ const author = input.author?.trim().toLowerCase() ?? "";
42
+ if (input.source === "system" && author === "movement sync") {
43
+ return 0;
44
+ }
45
+ return isAgentAuthoredActivity({ actor: input.author, source: input.source })
46
+ ? 0.15
47
+ : 1;
48
+ }
49
+ function calendarCategoriesText(raw) {
50
+ try {
51
+ const parsed = JSON.parse(raw);
52
+ return Array.isArray(parsed) ? parsed.join(" ") : "";
53
+ }
54
+ catch {
55
+ return "";
56
+ }
57
+ }
58
+ function isPassiveCalendarContainer(row, profile) {
59
+ const hasManualOrCustomApProfile = profile?.sourceMethod === "manual" ||
60
+ (profile?.metadata.customSustainRateApPerHour !== null &&
61
+ profile?.metadata.customSustainRateApPerHour !== undefined);
62
+ if (hasManualOrCustomApProfile) {
63
+ return false;
64
+ }
65
+ const durationHours = Math.max(0, (Date.parse(row.end_at) - Date.parse(row.start_at)) / 3_600_000);
66
+ const searchable = [
67
+ row.title,
68
+ row.event_type,
69
+ calendarCategoriesText(row.categories_json)
70
+ ].join(" ");
71
+ if (row.availability === "free") {
72
+ return true;
73
+ }
74
+ if (row.is_all_day === 1) {
75
+ return true;
76
+ }
77
+ return durationHours >= 12 && PASSIVE_CALENDAR_PATTERN.test(searchable);
78
+ }
79
+ function scaleContributionAp(contribution, multiplier, reason) {
80
+ if (multiplier >= 1) {
81
+ return contribution;
82
+ }
83
+ return {
84
+ ...contribution,
85
+ totalAp: Number((contribution.totalAp * Math.max(0, multiplier)).toFixed(4)),
86
+ why: `${contribution.why} ${reason}`,
87
+ metadata: {
88
+ ...(contribution.metadata ?? {}),
89
+ personalApMultiplier: multiplier
90
+ }
91
+ };
92
+ }
26
93
  const CALENDAR_ACTIVITY_PRESETS = {
27
94
  deep_work: {
28
95
  title: "Deep work",
@@ -987,6 +1054,7 @@ function readTaskRunRows(range, userId) {
987
1054
  task_runs.timed_out_at,
988
1055
  task_runs.updated_at,
989
1056
  tasks.title AS task_title,
1057
+ tasks.goal_id AS task_goal_id,
990
1058
  task_runs.planned_duration_seconds,
991
1059
  tasks.planned_duration_seconds AS task_expected_duration_seconds
992
1060
  FROM task_runs
@@ -1103,7 +1171,39 @@ function getOrCreateDaySnapshot(userId, date) {
1103
1171
  WHERE user_id = ? AND date_key = ?`)
1104
1172
  .get(userId, range.dateKey);
1105
1173
  if (existing) {
1106
- return existing;
1174
+ const profile = ensureLifeForceProfile(userId);
1175
+ const template = readWeekdayTemplate(userId, date.getUTCDay());
1176
+ const sleepRecoveryMultiplier = computeSleepRecoveryMultiplier(userId, date);
1177
+ const fatigueDebtCarry = computeFatigueDebtCarry(userId, date);
1178
+ const readinessMultiplier = profile.readiness_multiplier;
1179
+ const dailyBudgetAp = Math.max(40, Math.round(profile.base_daily_ap *
1180
+ computeLifeForceMultiplier(profile) *
1181
+ sleepRecoveryMultiplier *
1182
+ readinessMultiplier) - fatigueDebtCarry);
1183
+ const derivedChanged = Math.abs(existing.daily_budget_ap - dailyBudgetAp) > 0.01 ||
1184
+ Math.abs(existing.sleep_recovery_multiplier - sleepRecoveryMultiplier) > 0.001 ||
1185
+ Math.abs(existing.readiness_multiplier - readinessMultiplier) > 0.001 ||
1186
+ Math.abs(existing.fatigue_debt_carry - fatigueDebtCarry) > 0.01;
1187
+ if (!derivedChanged) {
1188
+ return existing;
1189
+ }
1190
+ const points = normalizeCurveToBudget(parseCurvePoints(template.points_json), dailyBudgetAp);
1191
+ const updatedAt = nowIso();
1192
+ getDatabase()
1193
+ .prepare(`UPDATE life_force_day_snapshots
1194
+ SET daily_budget_ap = ?,
1195
+ sleep_recovery_multiplier = ?,
1196
+ readiness_multiplier = ?,
1197
+ fatigue_debt_carry = ?,
1198
+ points_json = ?,
1199
+ updated_at = ?
1200
+ WHERE id = ?`)
1201
+ .run(dailyBudgetAp, sleepRecoveryMultiplier, readinessMultiplier, fatigueDebtCarry, JSON.stringify(points), updatedAt, existing.id);
1202
+ return getDatabase()
1203
+ .prepare(`SELECT *
1204
+ FROM life_force_day_snapshots
1205
+ WHERE id = ?`)
1206
+ .get(existing.id);
1107
1207
  }
1108
1208
  const profile = ensureLifeForceProfile(userId);
1109
1209
  const template = readWeekdayTemplate(userId, date.getUTCDay());
@@ -1190,8 +1290,11 @@ function readTodayAdjustmentRows(userId, range) {
1190
1290
  work_adjustments.entity_id,
1191
1291
  work_adjustments.applied_delta_minutes,
1192
1292
  work_adjustments.note,
1293
+ work_adjustments.actor,
1294
+ work_adjustments.source,
1193
1295
  work_adjustments.created_at,
1194
- tasks.planned_duration_seconds
1296
+ tasks.planned_duration_seconds,
1297
+ tasks.goal_id AS task_goal_id
1195
1298
  FROM work_adjustments
1196
1299
  LEFT JOIN tasks
1197
1300
  ON work_adjustments.entity_type = 'task'
@@ -1216,7 +1319,11 @@ function readTodayAdjustmentApByTaskId(userId, range, lifeForceProfile) {
1216
1319
  id: row.entity_id,
1217
1320
  plannedDurationSeconds: row.planned_duration_seconds
1218
1321
  }, lifeForceProfile);
1219
- const deltaAp = rateToTotalAp(profile.sustainRateApPerHour, row.applied_delta_minutes * 60);
1322
+ const deltaAp = rateToTotalAp(profile.sustainRateApPerHour, row.applied_delta_minutes * 60) * agentSupervisionMultiplier({
1323
+ actor: row.actor,
1324
+ source: row.source,
1325
+ goalLinked: Boolean(row.task_goal_id)
1326
+ });
1220
1327
  totals.set(row.entity_id, (totals.get(row.entity_id) ?? 0) + deltaAp);
1221
1328
  }
1222
1329
  return totals;
@@ -1301,7 +1408,7 @@ function buildWorkAdjustmentContributions(userId, range, lifeForceProfile) {
1301
1408
  plannedDurationSeconds: row.planned_duration_seconds
1302
1409
  }, lifeForceProfile);
1303
1410
  const totalAp = rateToTotalAp(profile.sustainRateApPerHour, row.applied_delta_minutes * 60);
1304
- return {
1411
+ return scaleContributionAp({
1305
1412
  entityType: "task",
1306
1413
  entityId: row.entity_id,
1307
1414
  eventKind: "work_adjustment",
@@ -1315,9 +1422,15 @@ function buildWorkAdjustmentContributions(userId, range, lifeForceProfile) {
1315
1422
  role: "background",
1316
1423
  metadata: {
1317
1424
  adjustmentId: row.id,
1318
- appliedDeltaMinutes: row.applied_delta_minutes
1425
+ appliedDeltaMinutes: row.applied_delta_minutes,
1426
+ actor: row.actor,
1427
+ source: row.source
1319
1428
  }
1320
- };
1429
+ }, agentSupervisionMultiplier({
1430
+ actor: row.actor,
1431
+ source: row.source,
1432
+ goalLinked: Boolean(row.task_goal_id)
1433
+ }), "Agent-authored manual work is charged as light supervision AP instead of full human effort.");
1321
1434
  });
1322
1435
  }
1323
1436
  function buildTaskRunContributions(userId, range, now, lifeForceProfile) {
@@ -1336,7 +1449,7 @@ function buildTaskRunContributions(userId, range, now, lifeForceProfile) {
1336
1449
  const totalAp = rateToTotalAp(profile.sustainRateApPerHour, seconds);
1337
1450
  const startsAt = new Date(Math.max(range.startMs, Date.parse(row.claimed_at))).toISOString();
1338
1451
  const endsAt = new Date(Math.min(range.endMs, terminalRunMs(row, now))).toISOString();
1339
- const contribution = {
1452
+ const contribution = scaleContributionAp({
1340
1453
  entityType: "task",
1341
1454
  entityId: row.task_id,
1342
1455
  eventKind: "task_run",
@@ -1348,8 +1461,11 @@ function buildTaskRunContributions(userId, range, now, lifeForceProfile) {
1348
1461
  startsAt,
1349
1462
  endsAt,
1350
1463
  role: row.is_current === 1 ? "primary" : "secondary",
1351
- metadata: { taskRunId: row.id }
1352
- };
1464
+ metadata: { taskRunId: row.id, actor: row.actor }
1465
+ }, agentSupervisionMultiplier({
1466
+ actor: row.actor,
1467
+ goalLinked: Boolean(row.task_goal_id)
1468
+ }), "Agent-authored task runs are charged as supervision AP for the human user.");
1353
1469
  contributions.push(contribution);
1354
1470
  const existing = totalsByTaskId.get(row.task_id) ?? { todayAp: 0, totalAp: 0 };
1355
1471
  existing.todayAp += totalAp;
@@ -1372,6 +1488,8 @@ function buildNoteContributions(userId, range, now, lifeForceProfile) {
1372
1488
  .prepare(`SELECT
1373
1489
  notes.id,
1374
1490
  notes.title,
1491
+ notes.author,
1492
+ notes.source,
1375
1493
  notes.created_at,
1376
1494
  GROUP_CONCAT(
1377
1495
  CASE
@@ -1399,19 +1517,34 @@ function buildNoteContributions(userId, range, now, lifeForceProfile) {
1399
1517
  .filter(Boolean);
1400
1518
  return !linkedTaskIds.some((taskId) => (taskRunWindowsByTaskId.get(taskId) ?? []).some((window) => createdAtMs >= window.startMs && createdAtMs <= window.endMs));
1401
1519
  })
1402
- .map((row) => ({
1403
- entityType: "note",
1404
- entityId: row.id,
1405
- eventKind: "note_created",
1406
- sourceKind: "note",
1407
- totalAp: noteProfile.totalCostAp,
1408
- rateApPerHour: null,
1409
- title: row.title || "Note",
1410
- why: "Standalone capture takes a small impulse of activation and focus.",
1411
- startsAt: row.created_at,
1412
- endsAt: row.created_at,
1413
- role: "background"
1414
- }));
1520
+ .flatMap((row) => {
1521
+ const multiplier = noteApMultiplier({
1522
+ author: row.author,
1523
+ source: row.source
1524
+ });
1525
+ if (multiplier <= 0) {
1526
+ return [];
1527
+ }
1528
+ return [
1529
+ scaleContributionAp({
1530
+ entityType: "note",
1531
+ entityId: row.id,
1532
+ eventKind: "note_created",
1533
+ sourceKind: "note",
1534
+ totalAp: noteProfile.totalCostAp,
1535
+ rateApPerHour: null,
1536
+ title: row.title || "Note",
1537
+ why: "Standalone capture takes a small impulse of activation and focus.",
1538
+ startsAt: row.created_at,
1539
+ endsAt: row.created_at,
1540
+ role: "background",
1541
+ metadata: {
1542
+ author: row.author,
1543
+ source: row.source
1544
+ }
1545
+ }, multiplier, "Agent-authored notes are charged as a small monitoring impulse instead of full personal work.")
1546
+ ];
1547
+ });
1415
1548
  }
1416
1549
  catch {
1417
1550
  return [];
@@ -1652,8 +1785,10 @@ function readCalendarEventLifeForceRows(range) {
1652
1785
  forge_events.title,
1653
1786
  forge_events.start_at,
1654
1787
  forge_events.end_at,
1788
+ forge_events.is_all_day,
1655
1789
  forge_events.availability,
1656
1790
  forge_events.event_type,
1791
+ forge_events.categories_json,
1657
1792
  COUNT(forge_event_links.id) AS link_count
1658
1793
  FROM forge_events
1659
1794
  LEFT JOIN forge_event_links
@@ -1666,8 +1801,10 @@ function readCalendarEventLifeForceRows(range) {
1666
1801
  forge_events.title,
1667
1802
  forge_events.start_at,
1668
1803
  forge_events.end_at,
1804
+ forge_events.is_all_day,
1669
1805
  forge_events.availability,
1670
- forge_events.event_type`)
1806
+ forge_events.event_type,
1807
+ forge_events.categories_json`)
1671
1808
  .all(range.from, range.to);
1672
1809
  }
1673
1810
  catch {
@@ -1973,11 +2110,15 @@ function buildCalendarDrains(rows, now, range, lifeForceProfile, blockingWindows
1973
2110
  const plannedDrains = [];
1974
2111
  try {
1975
2112
  for (const row of rows) {
1976
- const calendarProfile = buildEffectiveProfile(readEntityActionProfile("calendar_event", row.id, {
2113
+ const storedProfile = readEntityActionProfile("calendar_event", row.id, {
1977
2114
  profileKey: `calendar_event_${row.id}`,
1978
2115
  title: row.title,
1979
2116
  entityType: "calendar_event"
1980
- }) ??
2117
+ });
2118
+ if (isPassiveCalendarContainer(row, storedProfile)) {
2119
+ continue;
2120
+ }
2121
+ const calendarProfile = buildEffectiveProfile(storedProfile ??
1981
2122
  buildCalendarEventActionProfile({
1982
2123
  eventId: row.id,
1983
2124
  title: row.title,
@@ -1,5 +1,5 @@
1
- import { request as httpRequest } from "node:http";
2
- import { request as httpsRequest } from "node:https";
1
+ import { Agent as HttpAgent, request as httpRequest } from "node:http";
2
+ import { Agent as HttpsAgent, request as httpsRequest } from "node:https";
3
3
  import { spawn } from "node:child_process";
4
4
  import { existsSync } from "node:fs";
5
5
  import { access, readFile } from "node:fs/promises";
@@ -80,7 +80,8 @@ function buildManagedDevWebLaunch(input) {
80
80
  };
81
81
  }
82
82
  const host = input.env.FORGE_DEV_WEB_HOST?.trim() || "127.0.0.1";
83
- const port = input.env.FORGE_DEV_WEB_PORT?.trim() || getDefaultDevWebOriginPort(input.origin);
83
+ const port = input.env.FORGE_DEV_WEB_PORT?.trim() ||
84
+ getDefaultDevWebOriginPort(input.origin);
84
85
  return {
85
86
  command: process.execPath,
86
87
  args: [viteCliPath, "--host", host, "--port", port],
@@ -136,15 +137,23 @@ function parseRequestTarget(requestPath) {
136
137
  function copyProxyHeaders(response, reply) {
137
138
  for (const [name, value] of response.headers) {
138
139
  const lowerName = name.toLowerCase();
139
- if (lowerName === "connection" ||
140
- lowerName === "content-length" ||
141
- lowerName === "keep-alive" ||
142
- lowerName === "transfer-encoding") {
140
+ if (hopByHopHeaders.has(lowerName)) {
143
141
  continue;
144
142
  }
145
143
  reply.header(name, value);
146
144
  }
147
145
  }
146
+ const hopByHopHeaders = new Set([
147
+ "connection",
148
+ "content-length",
149
+ "keep-alive",
150
+ "proxy-authenticate",
151
+ "proxy-authorization",
152
+ "te",
153
+ "trailer",
154
+ "transfer-encoding",
155
+ "upgrade"
156
+ ]);
148
157
  function buildDevWebTarget(origin, pathname, search) {
149
158
  const target = new URL(pathname.startsWith("/") ? pathname.slice(1) : pathname, origin);
150
159
  target.search = search;
@@ -163,6 +172,72 @@ async function proxyDevAsset(input) {
163
172
  }
164
173
  return Buffer.from(await response.arrayBuffer());
165
174
  }
175
+ export function createKeepAliveDevAssetProxy() {
176
+ const httpAgent = new HttpAgent({
177
+ keepAlive: true,
178
+ maxFreeSockets: 8,
179
+ maxSockets: 32
180
+ });
181
+ const httpsAgent = new HttpsAgent({
182
+ keepAlive: true,
183
+ maxFreeSockets: 8,
184
+ maxSockets: 32
185
+ });
186
+ return {
187
+ fetch(input) {
188
+ const target = buildDevWebTarget(input.origin, input.pathname, input.search);
189
+ const isHttps = target.protocol === "https:";
190
+ const request = isHttps ? httpsRequest : httpRequest;
191
+ const agent = isHttps ? httpsAgent : httpAgent;
192
+ return new Promise((resolve, reject) => {
193
+ const proxyRequest = request(target, {
194
+ agent,
195
+ headers: {
196
+ Accept: "*/*",
197
+ Host: target.host
198
+ },
199
+ method: "GET"
200
+ }, (response) => {
201
+ input.reply.code(response.statusCode ?? 502);
202
+ for (const [name, value] of Object.entries(response.headers)) {
203
+ if (!value || hopByHopHeaders.has(name.toLowerCase())) {
204
+ continue;
205
+ }
206
+ input.reply.header(name, value);
207
+ }
208
+ if (!response.headers["cache-control"]) {
209
+ input.reply.header("Cache-Control", "no-store, max-age=0, must-revalidate");
210
+ }
211
+ const chunks = [];
212
+ response.on("data", (chunk) => {
213
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
214
+ });
215
+ response.on("end", () => {
216
+ resolve(Buffer.concat(chunks));
217
+ });
218
+ response.on("error", reject);
219
+ });
220
+ proxyRequest.on("error", reject);
221
+ proxyRequest.end();
222
+ });
223
+ },
224
+ close() {
225
+ httpAgent.destroy();
226
+ httpsAgent.destroy();
227
+ }
228
+ };
229
+ }
230
+ function createDevAssetProxy(fetchImpl) {
231
+ if (fetchImpl !== fetch) {
232
+ return {
233
+ fetch(input) {
234
+ return proxyDevAsset({ ...input, fetchImpl });
235
+ },
236
+ close() { }
237
+ };
238
+ }
239
+ return createKeepAliveDevAssetProxy();
240
+ }
166
241
  function writeProxyUpgradeResponse(socket, response) {
167
242
  const statusCode = response.statusCode ?? 101;
168
243
  const statusMessage = response.statusMessage ?? "Switching Protocols";
@@ -344,12 +419,11 @@ async function serveAsset(requestPath, reply, options) {
344
419
  : await options.devWebRuntime.ensureReady();
345
420
  if (devWebOrigin) {
346
421
  try {
347
- return await proxyDevAsset({
422
+ return await options.devAssetProxy.fetch({
348
423
  origin: devWebOrigin,
349
424
  pathname: normalizedRequestPath,
350
425
  search: requestTarget.search,
351
- reply,
352
- fetchImpl: options.fetchImpl
426
+ reply
353
427
  });
354
428
  }
355
429
  catch {
@@ -393,8 +467,10 @@ async function serveAsset(requestPath, reply, options) {
393
467
  export async function registerWebRoutes(app, options = {}) {
394
468
  const devWebRuntime = options.devWebRuntime ?? createManagedDevWebRuntime();
395
469
  const fetchImpl = options.fetchImpl ?? fetch;
470
+ const devAssetProxy = options.devAssetProxy ?? createDevAssetProxy(fetchImpl);
396
471
  app.addHook("onClose", async () => {
397
472
  await devWebRuntime.stop();
473
+ devAssetProxy.close();
398
474
  });
399
475
  app.server.on("upgrade", (request, socket, head) => {
400
476
  void (async () => {
@@ -406,6 +482,6 @@ export async function registerWebRoutes(app, options = {}) {
406
482
  });
407
483
  })();
408
484
  });
409
- app.get("/", async (_request, reply) => serveAsset("/", reply, { devWebRuntime, fetchImpl }));
410
- app.get("/*", async (request, reply) => serveAsset(request.url, reply, { devWebRuntime, fetchImpl }));
485
+ app.get("/", async (_request, reply) => serveAsset("/", reply, { devWebRuntime, devAssetProxy }));
486
+ app.get("/*", async (request, reply) => serveAsset(request.url, reply, { devWebRuntime, devAssetProxy }));
411
487
  }
@@ -2,7 +2,7 @@
2
2
  "id": "forge-openclaw-plugin",
3
3
  "name": "Forge",
4
4
  "description": "Curated OpenClaw adapter for the Forge collaboration API, UI entrypoint, and localhost auto-start runtime.",
5
- "version": "0.2.61",
5
+ "version": "0.2.66",
6
6
  "activation": {
7
7
  "onStartup": true,
8
8
  "onCapabilities": [
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "forge-openclaw-plugin",
3
- "version": "0.2.61",
3
+ "version": "0.2.66",
4
4
  "description": "Curated OpenClaw adapter for the Forge collaboration API, UI entrypoint, and localhost auto-start runtime.",
5
5
  "type": "module",
6
- "license": "MIT",
6
+ "license": "Apache-2.0",
7
7
  "private": false,
8
8
  "repository": {
9
9
  "type": "git",
@@ -103,7 +103,7 @@
103
103
  "axios": "^1.16.0",
104
104
  "fast-xml-parser": "^5.7.1",
105
105
  "follow-redirects": "^1.16.0",
106
- "hono": "4.12.14",
106
+ "hono": "^4.12.18",
107
107
  "ip-address": "^10.2.0",
108
108
  "uuid": "^14.0.0"
109
109
  },
@@ -89,8 +89,11 @@ guessing.
89
89
  exact `/api/v1/*` route or OpenClaw `/forge/v1/*` mirror published in onboarding.
90
90
  Life Force may be keyed as `lifeForce` and as the entity-style alias `life_force`;
91
91
  both point to the same `/api/v1/life-force/*` route family.
92
- - The specialized route-key tool schemas include the exact route-key to method/path
93
- map. When a route key's exact path contains placeholders such as `:id`,
92
+ - The live onboarding `methodRoutes` map and the specialized route-key tool schemas
93
+ include the exact route-key to method/path map. Use `methodRoutes` as the
94
+ route-key-to-`METHOD /api/v1/...` source of truth when checking specialized
95
+ methods, especially POST aggregate reads such as Movement `selection` and DELETE
96
+ repair paths. When a route key's exact path contains placeholders such as `:id`,
94
97
  `:weekday`, `:runId`, or `:nodeId`, pass those values in `pathParams` using the
95
98
  placeholder names exactly. Do not place IDs inside `routeKey`, invent a raw route
96
99
  string, or ask the user to choose an endpoint when the lane already selects one. If
@@ -98,6 +101,40 @@ guessing.
98
101
  call and treat the disagreement as a Forge contract bug to fix, not as a reason to
99
102
  guess a nearby route.
100
103
 
104
+ Concrete route-key examples for internal use:
105
+
106
+ - Movement all-time read:
107
+ `{"routeKey":"allTime","query":{"userIds":["user_operator"]}}`
108
+ - Movement timeline read:
109
+ `{"routeKey":"timeline","query":{"from":"2026-05-01T00:00:00.000Z","to":"2026-05-06T23:59:59.999Z","userIds":["user_operator"]}}`
110
+ - Movement selection aggregate:
111
+ `{"routeKey":"selection","query":{"from":"2026-05-01T00:00:00.000Z","to":"2026-05-14T23:59:59.999Z","placeIds":["place_home"],"userIds":["user_operator"]}}`
112
+ - Movement trip detail:
113
+ `{"routeKey":"tripDetail","pathParams":{"id":"trip_123"}}`
114
+ - Movement missing-stay correction:
115
+ first `{"routeKey":"userBoxPreflight","body":{"kind":"stay","startedAt":"2026-05-06T13:00:00.000Z","endedAt":"2026-05-06T15:00:00.000Z","placeLabel":"Home","userId":"user_operator"}}`,
116
+ then `{"routeKey":"userBoxCreate","body":{"kind":"stay","startedAt":"2026-05-06T13:00:00.000Z","endedAt":"2026-05-06T15:00:00.000Z","placeLabel":"Home","userId":"user_operator","note":"Manual correction after reviewing the timeline."}}`
117
+ - Life Force overview:
118
+ `{"routeKey":"overview"}`
119
+ - Life Force profile edit:
120
+ `{"routeKey":"profile","body":{"baselineDailyAp":24,"recoveryNotes":"Clinic-admin days need a lower expected afternoon load."}}`
121
+ - Life Force weekday template edit:
122
+ `{"routeKey":"weekdayTemplate","pathParams":{"weekday":"monday"},"body":{"points":[{"hour":13,"freeAp":-4}]}}`
123
+ - Life Force fatigue signal:
124
+ `{"routeKey":"fatigueSignal","body":{"signal":"tired","intensity":7,"note":"Sharp post-lunch dip after clinic admin."}}`
125
+ - Workbench flow catalog:
126
+ `{"routeKey":"listFlows","query":{"includeArchived":false}}`
127
+ - Workbench box catalog:
128
+ `{"routeKey":"boxCatalog"}`
129
+ - Workbench run detail:
130
+ `{"routeKey":"runDetail","pathParams":{"id":"flow_research_digest","runId":"run_123"}}`
131
+ - Workbench published output:
132
+ `{"routeKey":"publishedOutput","pathParams":{"id":"flow_research_digest"}}`
133
+ - Workbench latest node output:
134
+ `{"routeKey":"latestNodeOutput","pathParams":{"id":"flow_research_digest","nodeId":"node_summary"}}`
135
+ - Workbench run execution:
136
+ `{"routeKey":"runFlow","pathParams":{"id":"flow_research_digest"},"body":{"input":{"topic":"question flow quality"}}}`
137
+
101
138
  Preferences rule:
102
139
 
103
140
  - When the user wants to understand or refine taste, ranking, recommendation fit, or “what Forge thinks I like”, treat that as the Preferences surface rather than a normal planning intake.
@@ -136,6 +173,11 @@ Health rule:
136
173
 
137
174
  - Sleep and sports records are first-class health surfaces, not generic notes or tasks.
138
175
  - Use `forge_get_sleep_overview` and `forge_get_sports_overview` for review and trend reading.
176
+ - In `forge_get_agent_onboarding.entityRouteModel.readModelOnlySurfaces`, the health
177
+ overview routes are published under both the plain names `sleepOverview` and
178
+ `sportsOverview` and the entity-style aliases `sleep_overview` and
179
+ `sports_overview`. Treat those as read-only overview surfaces, not batch CRUD
180
+ entities.
139
181
  - Use the shared batch entity tools for ordinary `sleep_session` and `workout_session` create, update, delete, and search work. Do not force agents into a large one-route-per-entity mental model when the batch routes already cover the record cleanly.
140
182
  - Use `forge_update_sleep_session` and `forge_update_workout_session` only when the job is reflective enrichment on one existing health record after review, such as attaching notes, tags, mood, meaning, or Forge links.
141
183
  - Habit-generated workouts and imported HealthKit workouts belong to the same workout record model, so do not invent a separate storage path for sport sessions.