forge-openclaw-plugin 0.2.60 → 0.2.65
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 +121 -51
- package/dist/assets/{board-B1V3M__K.js → board-DUwMfZvN.js} +1 -1
- package/dist/assets/index-B9VOpR7r.css +1 -0
- package/dist/assets/index-DoHjjze2.js +90 -0
- package/dist/assets/{motion-CltSTItx.js → motion-Crg3QyXD.js} +1 -1
- package/dist/assets/{table-B-VrSFx8.js → table-CTlDeYRs.js} +1 -1
- package/dist/assets/{ui-DUqM4jkt.js → ui-CJPaElbj.js} +1 -1
- package/dist/assets/{vendor-C0otBhgu.js → vendor-BdrT2htV.js} +217 -207
- 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/gamification-previews/dark-fantasy-item-trophy-tasks-anvil-marathon.webp +0 -0
- package/dist/gamification-previews/dark-fantasy-item-trophy-xp-levels-the-first-heat.webp +0 -0
- package/dist/gamification-previews/dark-fantasy-item-unlock-streaks-molten-crown-fire.webp +0 -0
- package/dist/gamification-previews/dark-fantasy-mascot.webp +0 -0
- package/dist/gamification-previews/dramatic-smithie-item-trophy-tasks-anvil-marathon.webp +0 -0
- package/dist/gamification-previews/dramatic-smithie-item-trophy-xp-levels-the-first-heat.webp +0 -0
- package/dist/gamification-previews/dramatic-smithie-item-unlock-streaks-molten-crown-fire.webp +0 -0
- package/dist/gamification-previews/dramatic-smithie-mascot.webp +0 -0
- package/dist/gamification-previews/mind-locksmith-item-trophy-tasks-anvil-marathon.webp +0 -0
- package/dist/gamification-previews/mind-locksmith-item-trophy-xp-levels-the-first-heat.webp +0 -0
- package/dist/gamification-previews/mind-locksmith-item-unlock-streaks-molten-crown-fire.webp +0 -0
- package/dist/gamification-previews/mind-locksmith-mascot.webp +0 -0
- package/dist/index.html +7 -7
- package/dist/openclaw/parity.js +27 -0
- package/dist/openclaw/plugin-entry-shared.js +2 -2
- package/dist/openclaw/plugin-sdk-types.d.ts +2 -1
- package/dist/openclaw/routes.d.ts +4 -0
- package/dist/openclaw/routes.js +112 -3
- package/dist/openclaw/tools.js +32 -4
- package/dist/server/server/migrations/059_data_backup_retention.sql +2 -0
- package/dist/server/server/src/app.js +288 -61
- package/dist/server/server/src/data-management-types.js +2 -0
- package/dist/server/server/src/discovery-advertiser.js +13 -0
- package/dist/server/server/src/health.js +58 -3
- package/dist/server/server/src/movement.js +16 -1
- package/dist/server/server/src/openapi.js +410 -9
- package/dist/server/server/src/repositories/rewards.js +60 -0
- package/dist/server/server/src/services/companion-iroh.js +425 -0
- package/dist/server/server/src/services/data-management.js +32 -2
- package/dist/server/server/src/services/doctor.js +762 -0
- package/dist/server/server/src/services/gamification.js +75 -3
- package/dist/server/server/src/services/life-force.js +166 -25
- package/dist/server/server/src/web.js +88 -12
- package/dist/server/src/lib/api.js +9 -0
- package/dist/server/src/lib/gamification-catalog.js +1 -1
- package/openclaw.plugin.json +85 -3
- package/package.json +10 -6
- package/server/migrations/059_data_backup_retention.sql +2 -0
- package/skills/forge-openclaw/SKILL.md +80 -19
- package/skills/forge-openclaw/entity_conversation_playbooks.md +283 -25
- package/skills/forge-openclaw/psyche_entity_playbooks.md +82 -0
- package/dist/assets/index-BwKAPo98.css +0 -1
- package/dist/assets/index-Dy7c-dRY.js +0 -90
|
@@ -1,10 +1,41 @@
|
|
|
1
1
|
import { getDatabase } from "../db.js";
|
|
2
2
|
import { enqueueGamificationCelebration, getGamificationEquipment, insertGamificationUnlock, listGamificationDailyActivity, listGamificationUnlocks, listUnseenGamificationCelebrations, replaceGamificationDailyActivity, upsertGamificationEquipment } from "../repositories/gamification.js";
|
|
3
|
-
import { getDailyAmbientXp, listRewardRules } from "../repositories/rewards.js";
|
|
3
|
+
import { getDailyAmbientXp, listRewardRules, recordEntityCreationReward } from "../repositories/rewards.js";
|
|
4
4
|
import { getDefaultUser, listUsers, listUsersByIds } from "../repositories/users.js";
|
|
5
5
|
import { GAMIFICATION_CATALOG, GAMIFICATION_STREAK_AWAY_DAY_KEYS, GAMIFICATION_STREAK_POWER_DAY_KEYS } from "../../../src/lib/gamification-catalog.js";
|
|
6
6
|
import { achievementSignalSchema, gamificationCatalogPayloadSchema, gamificationProfileSchema, milestoneRewardSchema, rewardLedgerEventSchema } from "../types.js";
|
|
7
7
|
const XP_CURVE_VERSION = "smith-forge";
|
|
8
|
+
const ENTITY_CREATION_REWARD_SOURCES = [
|
|
9
|
+
{ entityType: "goal", tableName: "goals", titleColumn: "title" },
|
|
10
|
+
{ entityType: "project", tableName: "projects", titleColumn: "title" },
|
|
11
|
+
{ entityType: "strategy", tableName: "strategies", titleColumn: "title" },
|
|
12
|
+
{ entityType: "task", tableName: "tasks", titleColumn: "title" },
|
|
13
|
+
{ entityType: "habit", tableName: "habits", titleColumn: "title" },
|
|
14
|
+
{ entityType: "note", tableName: "notes", titleColumn: "content_plain" },
|
|
15
|
+
{ entityType: "tag", tableName: "tags", titleColumn: "name" },
|
|
16
|
+
{ entityType: "calendar_event", tableName: "calendar_events", titleColumn: "title" },
|
|
17
|
+
{
|
|
18
|
+
entityType: "work_block_template",
|
|
19
|
+
tableName: "work_block_templates",
|
|
20
|
+
titleColumn: "title"
|
|
21
|
+
},
|
|
22
|
+
{ entityType: "task_timebox", tableName: "task_timeboxes", titleColumn: "title" },
|
|
23
|
+
{
|
|
24
|
+
entityType: "questionnaire_instrument",
|
|
25
|
+
tableName: "questionnaire_instruments",
|
|
26
|
+
titleColumn: "title"
|
|
27
|
+
},
|
|
28
|
+
{ entityType: "psyche_value", tableName: "psyche_values", titleColumn: "title" },
|
|
29
|
+
{
|
|
30
|
+
entityType: "behavior_pattern",
|
|
31
|
+
tableName: "behavior_patterns",
|
|
32
|
+
titleColumn: "title"
|
|
33
|
+
},
|
|
34
|
+
{ entityType: "behavior", tableName: "psyche_behaviors", titleColumn: "title" },
|
|
35
|
+
{ entityType: "belief_entry", tableName: "belief_entries", titleColumn: "statement" },
|
|
36
|
+
{ entityType: "mode_profile", tableName: "mode_profiles", titleColumn: "title" },
|
|
37
|
+
{ entityType: "trigger_report", tableName: "trigger_reports", titleColumn: "title" }
|
|
38
|
+
];
|
|
8
39
|
function startOfWeek(date) {
|
|
9
40
|
const clone = new Date(date);
|
|
10
41
|
const day = clone.getDay();
|
|
@@ -209,6 +240,42 @@ function loadScopedRewardEvents(scope) {
|
|
|
209
240
|
? true
|
|
210
241
|
: event.ownerUserId !== null && scopeUserIds.has(event.ownerUserId));
|
|
211
242
|
}
|
|
243
|
+
function syncEntityCreationRewards(scope) {
|
|
244
|
+
const database = getDatabase();
|
|
245
|
+
const scopeUserIds = [...new Set(scope.userIds)];
|
|
246
|
+
const scopePlaceholders = scopeUserIds.map(() => "?").join(", ");
|
|
247
|
+
for (const source of ENTITY_CREATION_REWARD_SOURCES) {
|
|
248
|
+
const scopedWhere = scopeUserIds.length > 0
|
|
249
|
+
? `WHERE (
|
|
250
|
+
entity_owners.user_id IN (${scopePlaceholders})
|
|
251
|
+
OR (entity_owners.user_id IS NULL AND ? IS NOT NULL)
|
|
252
|
+
)`
|
|
253
|
+
: "";
|
|
254
|
+
const params = scopeUserIds.length > 0
|
|
255
|
+
? [source.entityType, ...scopeUserIds, scopeUserIds[0] ?? null]
|
|
256
|
+
: [source.entityType];
|
|
257
|
+
const rows = database
|
|
258
|
+
.prepare(`SELECT
|
|
259
|
+
${source.tableName}.id AS id,
|
|
260
|
+
${source.tableName}.${source.titleColumn} AS title,
|
|
261
|
+
${source.tableName}.created_at AS created_at
|
|
262
|
+
FROM ${source.tableName}
|
|
263
|
+
LEFT JOIN entity_owners
|
|
264
|
+
ON entity_owners.entity_type = ?
|
|
265
|
+
AND entity_owners.entity_id = ${source.tableName}.id
|
|
266
|
+
${scopedWhere}`)
|
|
267
|
+
.all(...params);
|
|
268
|
+
for (const row of rows) {
|
|
269
|
+
recordEntityCreationReward({
|
|
270
|
+
entityType: source.entityType,
|
|
271
|
+
entityId: row.id,
|
|
272
|
+
title: row.title,
|
|
273
|
+
source: "system",
|
|
274
|
+
createdAt: row.created_at
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
212
279
|
function isQualifyingStreakReward(event) {
|
|
213
280
|
return (event.deltaXp > 0 &&
|
|
214
281
|
event.reversedByRewardId === null &&
|
|
@@ -245,8 +312,9 @@ function syncDailyActivity(userId, scopedRewards, timezone) {
|
|
|
245
312
|
}
|
|
246
313
|
function calculateStreakFromActivity(activeDateKeys, now, timezone) {
|
|
247
314
|
const today = dateKeyInTimezone(now, timezone);
|
|
315
|
+
const yesterday = subtractDaysFromDateKey(today, 1);
|
|
248
316
|
let streak = 0;
|
|
249
|
-
let cursor = today;
|
|
317
|
+
let cursor = activeDateKeys.has(today) ? today : yesterday;
|
|
250
318
|
while (activeDateKeys.has(cursor)) {
|
|
251
319
|
streak += 1;
|
|
252
320
|
cursor = subtractDaysFromDateKey(cursor, 1);
|
|
@@ -297,8 +365,11 @@ function calculateMissedDays(activeDateKeys, now, timezone) {
|
|
|
297
365
|
if (!latest || latest === today) {
|
|
298
366
|
return { missedDays: 0, lastActiveDateKey: latest };
|
|
299
367
|
}
|
|
368
|
+
if (latest === subtractDaysFromDateKey(today, 1)) {
|
|
369
|
+
return { missedDays: 0, lastActiveDateKey: latest };
|
|
370
|
+
}
|
|
300
371
|
return {
|
|
301
|
-
missedDays: Math.max(0, daysBetweenDateKeys(latest, today)),
|
|
372
|
+
missedDays: Math.max(0, daysBetweenDateKeys(latest, today) - 1),
|
|
302
373
|
lastActiveDateKey: latest
|
|
303
374
|
};
|
|
304
375
|
}
|
|
@@ -746,6 +817,7 @@ function syncCatalog(input) {
|
|
|
746
817
|
function buildGamificationState(goals, tasks, habits, options = {}) {
|
|
747
818
|
const now = options.now ?? new Date();
|
|
748
819
|
const scope = resolveGamificationScope(options.userIds);
|
|
820
|
+
syncEntityCreationRewards(scope);
|
|
749
821
|
const scopedRewards = loadScopedRewardEvents(scope);
|
|
750
822
|
const timezone = resolveTimezone();
|
|
751
823
|
const primaryUserId = scope.userIds[0] ?? "aggregate";
|
|
@@ -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
|
}
|
|
@@ -1515,6 +1515,15 @@ export function getOperatorOverview() {
|
|
|
1515
1515
|
export function getSettings() {
|
|
1516
1516
|
return request("/api/v1/settings");
|
|
1517
1517
|
}
|
|
1518
|
+
export function getForgeDoctor() {
|
|
1519
|
+
return request("/api/v1/doctor");
|
|
1520
|
+
}
|
|
1521
|
+
export function applyForgeDoctorFixes(input) {
|
|
1522
|
+
return request("/api/v1/doctor/fixes", {
|
|
1523
|
+
method: "POST",
|
|
1524
|
+
body: JSON.stringify(input)
|
|
1525
|
+
});
|
|
1526
|
+
}
|
|
1518
1527
|
export function saveAiModelConnection(input) {
|
|
1519
1528
|
return request("/api/v1/settings/models/connections", {
|
|
1520
1529
|
method: "POST",
|
|
@@ -222,7 +222,7 @@ const PSYCHE_TROPHIES = [
|
|
|
222
222
|
trophy("psyche", "Value Blade", allOf(metric("psycheValueCount", 10), metric("goalLinkedTaskCompletionCount", 100)), "Create 10 values and complete 100 goal-linked tasks.", "Values and work started cutting in the same direction."),
|
|
223
223
|
trophy("psyche", "Shadow Temper", allOf(metric("modeProfileCount", 12), metric("triggerReportRichCount", 50)), "Create 12 modes and 50 rich trigger reports.", "Shadow material became usable steel."),
|
|
224
224
|
trophy("psyche", "Inner Forge", allOf(metric("psycheValueCount", 12), metric("behaviorPatternCount", 25), metric("beliefFlexibleAlternativeCount", 30)), "Create 12 values, 25 patterns, and 30 flexible beliefs.", "A full inner forge takes shape."),
|
|
225
|
-
trophy("psyche", "Schema Bell", metric("questionnaireRunCount",
|
|
225
|
+
trophy("psyche", "Schema Bell", metric("questionnaireRunCount", 40), "Complete 40 questionnaire runs.", "Structured self-observation rang the bell repeatedly."),
|
|
226
226
|
trophy("psyche", "Mode Guide", metric("modeGuideSessionCount", 5), "Complete 5 mode guide sessions.", "Guided mode work became an actual practice."),
|
|
227
227
|
trophy("psyche", "Repair Script", metric("behaviorCount", 10), "Create 10 Psyche behaviors.", "Behaviors now carry repair plans, not just names."),
|
|
228
228
|
trophy("psyche", "Flexible Self", metric("beliefFlexibleAlternativeCount", 50), "Create 50 beliefs with flexible alternatives.", "A trophy for not letting old beliefs remain iron cages."),
|