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.
- package/README.md +28 -5
- package/dist/assets/{board-DThHV1D8.js → board-DFNV9VAZ.js} +1 -1
- package/dist/assets/index-CFZxwOFB.js +90 -0
- package/dist/assets/{index-7gvVCqnV.css → index-lFN9z5op.css} +1 -1
- package/dist/assets/{motion-BtTJtHCw.js → motion-CXdn34ih.js} +1 -1
- package/dist/assets/{table-Bnw6pcwN.js → table-CEq3bTDv.js} +1 -1
- package/dist/assets/{ui-CnVxFkj0.js → ui-g7FaEglG.js} +1 -1
- package/dist/assets/{vendor-BgZ3YrRd.js → vendor-BcOHGipZ.js} +236 -216
- package/dist/companion-iroh/darwin-arm64/forge-companion-iroh +0 -0
- package/dist/companion-iroh/darwin-x64/forge-companion-iroh +0 -0
- package/dist/companion-iroh/linux-x64/forge-companion-iroh +0 -0
- package/dist/companion-iroh-src/Cargo.lock +4559 -0
- package/dist/companion-iroh-src/Cargo.toml +37 -0
- package/dist/companion-iroh-src/src/lib.rs +279 -0
- package/dist/companion-iroh-src/src/main.rs +478 -0
- package/dist/companion-iroh-src/src/protocol.rs +129 -0
- package/dist/index.html +7 -7
- package/dist/server/server/src/app.js +163 -18
- package/dist/server/server/src/discovery-advertiser.js +13 -0
- package/dist/server/server/src/health.js +18 -3
- package/dist/server/server/src/movement.js +16 -1
- package/dist/server/server/src/openapi.js +12 -2
- package/dist/server/server/src/services/companion-iroh.js +425 -0
- package/dist/server/server/src/services/life-force.js +166 -25
- package/dist/server/server/src/web.js +88 -12
- package/openclaw.plugin.json +1 -1
- package/package.json +3 -3
- package/skills/forge-openclaw/SKILL.md +44 -2
- package/skills/forge-openclaw/entity_conversation_playbooks.md +217 -17
- package/skills/forge-openclaw/psyche_entity_playbooks.md +59 -0
- 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
|
-
|
|
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
|
-
.
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
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
|
|
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() ||
|
|
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
|
|
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
|
|
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,
|
|
410
|
-
app.get("/*", async (request, reply) => serveAsset(request.url, reply, { devWebRuntime,
|
|
485
|
+
app.get("/", async (_request, reply) => serveAsset("/", reply, { devWebRuntime, devAssetProxy }));
|
|
486
|
+
app.get("/*", async (request, reply) => serveAsset(request.url, reply, { devWebRuntime, devAssetProxy }));
|
|
411
487
|
}
|
package/openclaw.plugin.json
CHANGED
|
@@ -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.
|
|
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.
|
|
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": "
|
|
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.
|
|
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
|
|
93
|
-
|
|
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.
|