forge-openclaw-plugin 0.2.26 → 0.2.27
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 +59 -3
- package/dist/assets/{board-ta0rUHOf.js → board-C6jCchjI.js} +2 -2
- package/dist/assets/{board-ta0rUHOf.js.map → board-C6jCchjI.js.map} +1 -1
- package/dist/assets/index-DVvS8iiU.css +1 -0
- package/dist/assets/index-zYB-9Dfo.js +85 -0
- package/dist/assets/index-zYB-9Dfo.js.map +1 -0
- package/dist/assets/knowledge-graph-layout.worker-DRvzPxhP.js +2 -0
- package/dist/assets/knowledge-graph-layout.worker-DRvzPxhP.js.map +1 -0
- package/dist/assets/{motion-fBKPB6yw.js → motion-DFHrH2rd.js} +2 -2
- package/dist/assets/{motion-fBKPB6yw.js.map → motion-DFHrH2rd.js.map} +1 -1
- package/dist/assets/{table-C-IGTQni.js → table-ZL7Di_u3.js} +2 -2
- package/dist/assets/{table-C-IGTQni.js.map → table-ZL7Di_u3.js.map} +1 -1
- package/dist/assets/{ui-DInOpaYF.js → ui-CKNPpz7q.js} +2 -2
- package/dist/assets/{ui-DInOpaYF.js.map → ui-CKNPpz7q.js.map} +1 -1
- package/dist/assets/vendor-DoNZuFhn.js +1247 -0
- package/dist/assets/vendor-DoNZuFhn.js.map +1 -0
- package/dist/index.html +7 -7
- package/dist/openclaw/local-runtime.js +16 -0
- package/dist/openclaw/routes.d.ts +27 -0
- package/dist/openclaw/routes.js +16 -12
- package/dist/server/server/migrations/037_workbench_public_inputs_and_run_inputs.sql +5 -0
- package/dist/server/server/migrations/038_data_management_settings.sql +11 -0
- package/dist/server/server/migrations/039_life_force_and_action_points.sql +114 -0
- package/dist/server/server/migrations/040_screen_time_domain.sql +89 -0
- package/dist/server/server/migrations/041_companion_source_states.sql +21 -0
- package/dist/server/server/migrations/042_movement_boxes.sql +47 -0
- package/dist/server/server/migrations/043_movement_box_overlap_overrides.sql +26 -0
- package/dist/server/server/src/app.js +1684 -117
- package/dist/server/server/src/connectors/box-registry.js +44 -9
- package/dist/server/server/src/data-management-types.js +107 -0
- package/dist/server/server/src/db.js +68 -4
- package/dist/server/server/src/demo-data.js +2 -2
- package/dist/server/server/src/health.js +702 -18
- package/dist/server/server/src/managers/platform/llm-manager.js +7 -4
- package/dist/server/server/src/managers/platform/mock-workbench-provider.js +149 -0
- package/dist/server/server/src/managers/platform/secrets-manager.js +18 -1
- package/dist/server/server/src/managers/runtime.js +9 -0
- package/dist/server/server/src/movement.js +1971 -112
- package/dist/server/server/src/openapi.js +489 -1
- package/dist/server/server/src/psyche-types.js +9 -1
- package/dist/server/server/src/repositories/activity-events.js +8 -0
- package/dist/server/server/src/repositories/ai-connectors.js +522 -74
- package/dist/server/server/src/repositories/habits.js +37 -1
- package/dist/server/server/src/repositories/model-settings.js +13 -3
- package/dist/server/server/src/repositories/notes.js +3 -0
- package/dist/server/server/src/repositories/settings.js +380 -18
- package/dist/server/server/src/repositories/tasks.js +170 -10
- package/dist/server/server/src/runtime-data-root.js +82 -0
- package/dist/server/server/src/screen-time.js +802 -0
- package/dist/server/server/src/services/data-management.js +788 -0
- package/dist/server/server/src/services/entity-crud.js +205 -2
- package/dist/server/server/src/services/knowledge-graph.js +1455 -0
- package/dist/server/server/src/services/life-force-model.js +197 -0
- package/dist/server/server/src/services/life-force.js +1270 -0
- package/dist/server/server/src/services/psyche-observation-calendar.js +383 -16
- package/dist/server/server/src/types.js +286 -13
- package/dist/server/server/src/web.js +228 -13
- package/dist/server/src/components/customization/utility-widgets.js +136 -27
- package/dist/server/src/components/ui/info-tooltip.js +25 -0
- package/dist/server/src/components/workbench-boxes/calendar/calendar-boxes.js +78 -0
- package/dist/server/src/components/workbench-boxes/goals/goals-boxes.js +62 -0
- package/dist/server/src/components/workbench-boxes/habits/habits-boxes.js +62 -0
- package/dist/server/src/components/workbench-boxes/health/health-boxes.js +63 -8
- package/dist/server/src/components/workbench-boxes/insights/insights-boxes.js +50 -0
- package/dist/server/src/components/workbench-boxes/kanban/kanban-boxes.js +62 -54
- package/dist/server/src/components/workbench-boxes/movement/movement-boxes.js +18 -8
- package/dist/server/src/components/workbench-boxes/notes/notes-boxes.js +56 -38
- package/dist/server/src/components/workbench-boxes/overview/overview-boxes.js +65 -0
- package/dist/server/src/components/workbench-boxes/preferences/preferences-boxes.js +78 -0
- package/dist/server/src/components/workbench-boxes/projects/projects-boxes.js +35 -30
- package/dist/server/src/components/workbench-boxes/psyche/psyche-boxes.js +88 -0
- package/dist/server/src/components/workbench-boxes/questionnaires/questionnaires-boxes.js +61 -0
- package/dist/server/src/components/workbench-boxes/review/review-boxes.js +53 -0
- package/dist/server/src/components/workbench-boxes/shared/define-workbench-box.js +3 -1
- package/dist/server/src/components/workbench-boxes/shared/generic-node-view.js +39 -3
- package/dist/server/src/components/workbench-boxes/strategies/strategies-boxes.js +62 -0
- package/dist/server/src/components/workbench-boxes/tasks/tasks-boxes.js +76 -0
- package/dist/server/src/components/workbench-boxes/today/today-boxes.js +47 -32
- package/dist/server/src/components/workbench-boxes/wiki/wiki-boxes.js +60 -0
- package/dist/server/src/lib/api.js +280 -21
- package/dist/server/src/lib/data-management-types.js +1 -0
- package/dist/server/src/lib/entity-visuals.js +279 -0
- package/dist/server/src/lib/knowledge-graph-types.js +276 -0
- package/dist/server/src/lib/knowledge-graph.js +470 -0
- package/dist/server/src/lib/schemas.js +4 -0
- package/dist/server/src/lib/snapshot-normalizer.js +43 -1
- package/dist/server/src/lib/workbench/contracts.js +229 -0
- package/dist/server/src/lib/workbench/nodes.js +200 -0
- package/dist/server/src/lib/workbench/registry.js +52 -5
- package/dist/server/src/lib/workbench/runtime.js +254 -38
- package/dist/server/src/lib/workbench/tool-catalog.js +68 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/server/migrations/037_workbench_public_inputs_and_run_inputs.sql +5 -0
- package/server/migrations/038_data_management_settings.sql +11 -0
- package/server/migrations/039_life_force_and_action_points.sql +114 -0
- package/server/migrations/040_screen_time_domain.sql +89 -0
- package/server/migrations/041_companion_source_states.sql +21 -0
- package/server/migrations/042_movement_boxes.sql +47 -0
- package/server/migrations/043_movement_box_overlap_overrides.sql +26 -0
- package/skills/forge-openclaw/SKILL.md +24 -11
- package/skills/forge-openclaw/entity_conversation_playbooks.md +210 -34
- package/skills/forge-openclaw/psyche_entity_playbooks.md +113 -17
- package/dist/assets/index-Ro0ZF_az.css +0 -1
- package/dist/assets/index-ytlpSj23.js +0 -79
- package/dist/assets/index-ytlpSj23.js.map +0 -1
- package/dist/assets/vendor-lE3tZJcC.js +0 -876
- package/dist/assets/vendor-lE3tZJcC.js.map +0 -1
|
@@ -225,6 +225,39 @@ function getHabitRow(habitId) {
|
|
|
225
225
|
WHERE id = ?`)
|
|
226
226
|
.get(habitId);
|
|
227
227
|
}
|
|
228
|
+
function compareDateDesc(left, right) {
|
|
229
|
+
return new Date(right ?? 0).getTime() - new Date(left ?? 0).getTime();
|
|
230
|
+
}
|
|
231
|
+
function compareDateAsc(left, right) {
|
|
232
|
+
return new Date(left ?? 0).getTime() - new Date(right ?? 0).getTime();
|
|
233
|
+
}
|
|
234
|
+
function sortHabits(habits, orderBy) {
|
|
235
|
+
const nextHabits = [...habits];
|
|
236
|
+
nextHabits.sort((left, right) => {
|
|
237
|
+
if (orderBy === "name") {
|
|
238
|
+
return (left.title.localeCompare(right.title, undefined, { sensitivity: "base" }) ||
|
|
239
|
+
compareDateDesc(left.createdAt, right.createdAt));
|
|
240
|
+
}
|
|
241
|
+
if (orderBy === "streak") {
|
|
242
|
+
return (right.streakCount - left.streakCount ||
|
|
243
|
+
Number(right.dueToday) - Number(left.dueToday) ||
|
|
244
|
+
left.title.localeCompare(right.title, undefined, { sensitivity: "base" }));
|
|
245
|
+
}
|
|
246
|
+
if (orderBy === "created_at") {
|
|
247
|
+
return (compareDateDesc(left.createdAt, right.createdAt) ||
|
|
248
|
+
left.title.localeCompare(right.title, undefined, { sensitivity: "base" }));
|
|
249
|
+
}
|
|
250
|
+
if (orderBy === "updated_at") {
|
|
251
|
+
return (compareDateDesc(left.updatedAt, right.updatedAt) ||
|
|
252
|
+
left.title.localeCompare(right.title, undefined, { sensitivity: "base" }));
|
|
253
|
+
}
|
|
254
|
+
return (Number(right.dueToday) - Number(left.dueToday) ||
|
|
255
|
+
compareDateAsc(left.lastCheckInAt, right.lastCheckInAt) ||
|
|
256
|
+
compareDateDesc(left.updatedAt, right.updatedAt) ||
|
|
257
|
+
left.title.localeCompare(right.title, undefined, { sensitivity: "base" }));
|
|
258
|
+
});
|
|
259
|
+
return nextHabits;
|
|
260
|
+
}
|
|
228
261
|
export function listHabits(filters = {}) {
|
|
229
262
|
const parsed = filters;
|
|
230
263
|
const whereClauses = [];
|
|
@@ -257,7 +290,10 @@ export function listHabits(filters = {}) {
|
|
|
257
290
|
${limitSql}`)
|
|
258
291
|
.all(...params);
|
|
259
292
|
const habits = filterDeletedEntities("habit", rows.map((row) => mapHabit(row)));
|
|
260
|
-
|
|
293
|
+
const filteredHabits = parsed.dueToday
|
|
294
|
+
? habits.filter((habit) => habit.dueToday)
|
|
295
|
+
: habits;
|
|
296
|
+
return sortHabits(filteredHabits, parsed.orderBy);
|
|
261
297
|
}
|
|
262
298
|
export function getHabitById(habitId) {
|
|
263
299
|
if (isEntityDeleted("habit", habitId)) {
|
|
@@ -5,6 +5,7 @@ import { deleteEncryptedSecret, readEncryptedSecret, storeEncryptedSecret } from
|
|
|
5
5
|
import { upsertWikiLlmProfile } from "./wiki-memory.js";
|
|
6
6
|
export const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
|
|
7
7
|
export const DEFAULT_OPENAI_CODEX_BASE_URL = "https://chatgpt.com/backend-api";
|
|
8
|
+
export const DEFAULT_MOCK_LLM_BASE_URL = "mock://workbench";
|
|
8
9
|
export const FORGE_MANAGED_WIKI_PROFILE_ID = "wiki_llm_forge_managed";
|
|
9
10
|
export const FORGE_DEFAULT_AGENT_ID = "agt_forge_default";
|
|
10
11
|
function parseMetadata(value) {
|
|
@@ -26,6 +27,9 @@ export function defaultBaseUrlForProvider(provider) {
|
|
|
26
27
|
if (provider === "openai-compatible") {
|
|
27
28
|
return "http://127.0.0.1:11434/v1";
|
|
28
29
|
}
|
|
30
|
+
if (provider === "mock") {
|
|
31
|
+
return DEFAULT_MOCK_LLM_BASE_URL;
|
|
32
|
+
}
|
|
29
33
|
return DEFAULT_OPENAI_BASE_URL;
|
|
30
34
|
}
|
|
31
35
|
function buildConnectionAgentId(connectionId) {
|
|
@@ -36,7 +40,9 @@ export function buildConnectionAgentIdentity(connection) {
|
|
|
36
40
|
? "Chat agent backed by OpenAI Codex OAuth."
|
|
37
41
|
: connection.provider === "openai-compatible"
|
|
38
42
|
? "Chat agent backed by a local or OpenAI-compatible endpoint."
|
|
39
|
-
:
|
|
43
|
+
: connection.provider === "mock"
|
|
44
|
+
? "Chat agent backed by Forge's deterministic mock workflow runtime."
|
|
45
|
+
: "Chat agent backed by the OpenAI API.";
|
|
40
46
|
return {
|
|
41
47
|
id: connection.agentId,
|
|
42
48
|
label: connection.agentLabel,
|
|
@@ -52,7 +58,8 @@ export function buildConnectionAgentIdentity(connection) {
|
|
|
52
58
|
};
|
|
53
59
|
}
|
|
54
60
|
function mapConnection(row) {
|
|
55
|
-
const hasStoredCredential =
|
|
61
|
+
const hasStoredCredential = row.provider === "mock" ||
|
|
62
|
+
(Boolean(row.secret_id) && Boolean(readEncryptedSecret(row.secret_id)));
|
|
56
63
|
return aiModelConnectionSchema.parse({
|
|
57
64
|
id: row.id,
|
|
58
65
|
label: row.label,
|
|
@@ -122,7 +129,10 @@ export function upsertAiModelConnection(input, secrets, options = {}) {
|
|
|
122
129
|
defaultBaseUrlForProvider(provider);
|
|
123
130
|
let secretId = existing?.secret_id ?? null;
|
|
124
131
|
let accountLabel = existing?.account_label ?? null;
|
|
125
|
-
if (parsed.
|
|
132
|
+
if (parsed.provider === "mock") {
|
|
133
|
+
secretId = null;
|
|
134
|
+
}
|
|
135
|
+
else if (parsed.apiKey?.trim()) {
|
|
126
136
|
secretId =
|
|
127
137
|
secretId ?? `mdl_secret_${randomUUID().replaceAll("-", "").slice(0, 10)}`;
|
|
128
138
|
storeEncryptedSecret(secretId, secrets.sealJson({
|
|
@@ -10,6 +10,9 @@ function normalizeAnchorKey(anchorKey) {
|
|
|
10
10
|
return anchorKey.trim().length > 0 ? anchorKey : null;
|
|
11
11
|
}
|
|
12
12
|
function normalizeLinks(links) {
|
|
13
|
+
if (!links) {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
13
16
|
const seen = new Set();
|
|
14
17
|
return links.filter((link) => {
|
|
15
18
|
const key = `${link.entityType}:${link.entityId}:${link.anchorKey ?? ""}`;
|
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
2
|
-
import {
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { getDatabase, getEffectiveDataRoot, runInTransaction } from "../db.js";
|
|
3
5
|
import { logForgeDebug } from "../debug.js";
|
|
4
6
|
import { recordActivityEvent } from "./activity-events.js";
|
|
5
7
|
import { recordEventLog } from "./event-log.js";
|
|
6
8
|
import { resolveGoogleCalendarOauthPublicConfig } from "../services/google-calendar-oauth-config.js";
|
|
7
9
|
import { buildConnectionAgentIdentity, FORGE_DEFAULT_AGENT_ID, listAiModelConnections, syncForgeManagedWikiProfile } from "./model-settings.js";
|
|
8
10
|
import { createAgentTokenSchema, agentIdentitySchema, customThemeSchema, settingsPayloadSchema, updateSettingsSchema } from "../types.js";
|
|
11
|
+
const settingsFileSchema = settingsPayloadSchema.deepPartial();
|
|
12
|
+
let settingsFileSyncDepth = 0;
|
|
13
|
+
let lastSettingsFileStatus = {
|
|
14
|
+
path: path.join(getEffectiveDataRoot(), "forge.json"),
|
|
15
|
+
exists: false,
|
|
16
|
+
valid: false,
|
|
17
|
+
syncState: "uninitialized",
|
|
18
|
+
parseError: null,
|
|
19
|
+
overrideKeys: []
|
|
20
|
+
};
|
|
9
21
|
function boolFromInt(value) {
|
|
10
22
|
return value === 1;
|
|
11
23
|
}
|
|
@@ -25,7 +37,9 @@ function normalizeMicrosoftTenantId(value) {
|
|
|
25
37
|
}
|
|
26
38
|
function normalizeMicrosoftRedirectUri(value) {
|
|
27
39
|
const trimmed = value?.trim();
|
|
28
|
-
return trimmed && trimmed.length > 0
|
|
40
|
+
return trimmed && trimmed.length > 0
|
|
41
|
+
? trimmed
|
|
42
|
+
: defaultMicrosoftRedirectUri();
|
|
29
43
|
}
|
|
30
44
|
function logCalendarSettingsDebug(message, details) {
|
|
31
45
|
const serialized = Object.entries(details)
|
|
@@ -52,6 +66,237 @@ function parseCustomThemeJson(raw) {
|
|
|
52
66
|
return null;
|
|
53
67
|
}
|
|
54
68
|
}
|
|
69
|
+
function getForgeSettingsFilePath() {
|
|
70
|
+
return path.join(getEffectiveDataRoot(), "forge.json");
|
|
71
|
+
}
|
|
72
|
+
function writeForgeSettingsFileSnapshot(payload) {
|
|
73
|
+
const filePath = getForgeSettingsFilePath();
|
|
74
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
75
|
+
writeFileSync(filePath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
|
76
|
+
lastSettingsFileStatus = {
|
|
77
|
+
path: filePath,
|
|
78
|
+
exists: true,
|
|
79
|
+
valid: true,
|
|
80
|
+
syncState: "mirrored_from_database",
|
|
81
|
+
parseError: null,
|
|
82
|
+
overrideKeys: []
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function readForgeSettingsFile() {
|
|
86
|
+
const filePath = getForgeSettingsFilePath();
|
|
87
|
+
if (!existsSync(filePath)) {
|
|
88
|
+
return {
|
|
89
|
+
filePath,
|
|
90
|
+
exists: false,
|
|
91
|
+
valid: false,
|
|
92
|
+
settings: null,
|
|
93
|
+
parseError: null
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
const raw = readFileSync(filePath, "utf8");
|
|
98
|
+
const parsed = JSON.parse(raw);
|
|
99
|
+
const settings = settingsFileSchema.parse(parsed);
|
|
100
|
+
return {
|
|
101
|
+
filePath,
|
|
102
|
+
exists: true,
|
|
103
|
+
valid: true,
|
|
104
|
+
settings,
|
|
105
|
+
parseError: null
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
return {
|
|
110
|
+
filePath,
|
|
111
|
+
exists: true,
|
|
112
|
+
valid: false,
|
|
113
|
+
settings: null,
|
|
114
|
+
parseError: error instanceof Error ? error.message : String(error)
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function toSettingsFileOverrideInput(input) {
|
|
119
|
+
const next = {};
|
|
120
|
+
if (input.profile) {
|
|
121
|
+
next.profile = {};
|
|
122
|
+
if (input.profile.operatorName !== undefined) {
|
|
123
|
+
next.profile.operatorName = input.profile.operatorName;
|
|
124
|
+
}
|
|
125
|
+
if (input.profile.operatorEmail !== undefined) {
|
|
126
|
+
next.profile.operatorEmail = input.profile.operatorEmail;
|
|
127
|
+
}
|
|
128
|
+
if (input.profile.operatorTitle !== undefined) {
|
|
129
|
+
next.profile.operatorTitle = input.profile.operatorTitle;
|
|
130
|
+
}
|
|
131
|
+
if (Object.keys(next.profile).length === 0) {
|
|
132
|
+
delete next.profile;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (input.notifications) {
|
|
136
|
+
next.notifications = {};
|
|
137
|
+
if (input.notifications.goalDriftAlerts !== undefined) {
|
|
138
|
+
next.notifications.goalDriftAlerts = input.notifications.goalDriftAlerts;
|
|
139
|
+
}
|
|
140
|
+
if (input.notifications.dailyQuestReminders !== undefined) {
|
|
141
|
+
next.notifications.dailyQuestReminders =
|
|
142
|
+
input.notifications.dailyQuestReminders;
|
|
143
|
+
}
|
|
144
|
+
if (input.notifications.achievementCelebrations !== undefined) {
|
|
145
|
+
next.notifications.achievementCelebrations =
|
|
146
|
+
input.notifications.achievementCelebrations;
|
|
147
|
+
}
|
|
148
|
+
if (Object.keys(next.notifications).length === 0) {
|
|
149
|
+
delete next.notifications;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (input.execution) {
|
|
153
|
+
next.execution = {};
|
|
154
|
+
if (input.execution.maxActiveTasks !== undefined) {
|
|
155
|
+
next.execution.maxActiveTasks = input.execution.maxActiveTasks;
|
|
156
|
+
}
|
|
157
|
+
if (input.execution.timeAccountingMode !== undefined) {
|
|
158
|
+
next.execution.timeAccountingMode = input.execution.timeAccountingMode;
|
|
159
|
+
}
|
|
160
|
+
if (Object.keys(next.execution).length === 0) {
|
|
161
|
+
delete next.execution;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (input.themePreference !== undefined) {
|
|
165
|
+
next.themePreference = input.themePreference;
|
|
166
|
+
}
|
|
167
|
+
if (input.customTheme !== undefined) {
|
|
168
|
+
next.customTheme = input.customTheme;
|
|
169
|
+
}
|
|
170
|
+
if (input.localePreference !== undefined) {
|
|
171
|
+
next.localePreference = input.localePreference;
|
|
172
|
+
}
|
|
173
|
+
if (input.security?.psycheAuthRequired !== undefined) {
|
|
174
|
+
next.security = {
|
|
175
|
+
psycheAuthRequired: input.security.psycheAuthRequired
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
if (input.calendarProviders) {
|
|
179
|
+
next.calendarProviders = {};
|
|
180
|
+
if (input.calendarProviders.google) {
|
|
181
|
+
next.calendarProviders.google = {};
|
|
182
|
+
if (input.calendarProviders.google.clientId !== undefined) {
|
|
183
|
+
next.calendarProviders.google.clientId =
|
|
184
|
+
input.calendarProviders.google.clientId;
|
|
185
|
+
}
|
|
186
|
+
if (input.calendarProviders.google.clientSecret !== undefined) {
|
|
187
|
+
next.calendarProviders.google.clientSecret =
|
|
188
|
+
input.calendarProviders.google.clientSecret;
|
|
189
|
+
}
|
|
190
|
+
if (Object.keys(next.calendarProviders.google).length === 0) {
|
|
191
|
+
delete next.calendarProviders.google;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (input.calendarProviders.microsoft) {
|
|
195
|
+
next.calendarProviders.microsoft = {};
|
|
196
|
+
if (input.calendarProviders.microsoft.clientId !== undefined) {
|
|
197
|
+
next.calendarProviders.microsoft.clientId =
|
|
198
|
+
input.calendarProviders.microsoft.clientId;
|
|
199
|
+
}
|
|
200
|
+
if (input.calendarProviders.microsoft.tenantId !== undefined) {
|
|
201
|
+
next.calendarProviders.microsoft.tenantId =
|
|
202
|
+
input.calendarProviders.microsoft.tenantId;
|
|
203
|
+
}
|
|
204
|
+
if (input.calendarProviders.microsoft.redirectUri !== undefined) {
|
|
205
|
+
next.calendarProviders.microsoft.redirectUri =
|
|
206
|
+
input.calendarProviders.microsoft.redirectUri;
|
|
207
|
+
}
|
|
208
|
+
if (Object.keys(next.calendarProviders.microsoft).length === 0) {
|
|
209
|
+
delete next.calendarProviders.microsoft;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (Object.keys(next.calendarProviders).length === 0) {
|
|
213
|
+
delete next.calendarProviders;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (input.modelSettings?.forgeAgent) {
|
|
217
|
+
next.modelSettings = {
|
|
218
|
+
forgeAgent: {}
|
|
219
|
+
};
|
|
220
|
+
if (input.modelSettings.forgeAgent.basicChat) {
|
|
221
|
+
next.modelSettings.forgeAgent.basicChat = {};
|
|
222
|
+
if (input.modelSettings.forgeAgent.basicChat.connectionId !== undefined) {
|
|
223
|
+
next.modelSettings.forgeAgent.basicChat.connectionId =
|
|
224
|
+
input.modelSettings.forgeAgent.basicChat.connectionId;
|
|
225
|
+
}
|
|
226
|
+
if (input.modelSettings.forgeAgent.basicChat.model !== undefined) {
|
|
227
|
+
next.modelSettings.forgeAgent.basicChat.model =
|
|
228
|
+
input.modelSettings.forgeAgent.basicChat.model;
|
|
229
|
+
}
|
|
230
|
+
if (Object.keys(next.modelSettings.forgeAgent.basicChat).length === 0) {
|
|
231
|
+
delete next.modelSettings.forgeAgent.basicChat;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (input.modelSettings.forgeAgent.wiki) {
|
|
235
|
+
next.modelSettings.forgeAgent.wiki = {};
|
|
236
|
+
if (input.modelSettings.forgeAgent.wiki.connectionId !== undefined) {
|
|
237
|
+
next.modelSettings.forgeAgent.wiki.connectionId =
|
|
238
|
+
input.modelSettings.forgeAgent.wiki.connectionId;
|
|
239
|
+
}
|
|
240
|
+
if (input.modelSettings.forgeAgent.wiki.model !== undefined) {
|
|
241
|
+
next.modelSettings.forgeAgent.wiki.model =
|
|
242
|
+
input.modelSettings.forgeAgent.wiki.model;
|
|
243
|
+
}
|
|
244
|
+
if (Object.keys(next.modelSettings.forgeAgent.wiki).length === 0) {
|
|
245
|
+
delete next.modelSettings.forgeAgent.wiki;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (Object.keys(next.modelSettings.forgeAgent).length === 0) {
|
|
249
|
+
delete next.modelSettings;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return next;
|
|
253
|
+
}
|
|
254
|
+
function listOverrideKeys(input) {
|
|
255
|
+
const keys = [];
|
|
256
|
+
const pushNestedKeys = (prefix, value) => {
|
|
257
|
+
for (const [key, nestedValue] of Object.entries(value)) {
|
|
258
|
+
if (nestedValue &&
|
|
259
|
+
typeof nestedValue === "object" &&
|
|
260
|
+
!Array.isArray(nestedValue)) {
|
|
261
|
+
pushNestedKeys(`${prefix}.${key}`, nestedValue);
|
|
262
|
+
}
|
|
263
|
+
else if (nestedValue !== undefined) {
|
|
264
|
+
keys.push(`${prefix}.${key}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
for (const [key, value] of Object.entries(input)) {
|
|
269
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
270
|
+
keys.push(key);
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
pushNestedKeys(key, value);
|
|
274
|
+
}
|
|
275
|
+
return keys.sort();
|
|
276
|
+
}
|
|
277
|
+
function pickComparableOverrideSubset(source, template) {
|
|
278
|
+
const picked = {};
|
|
279
|
+
for (const [key, templateValue] of Object.entries(template)) {
|
|
280
|
+
if (templateValue === undefined) {
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
const sourceValue = source[key];
|
|
284
|
+
if (templateValue &&
|
|
285
|
+
typeof templateValue === "object" &&
|
|
286
|
+
!Array.isArray(templateValue) &&
|
|
287
|
+
sourceValue &&
|
|
288
|
+
typeof sourceValue === "object" &&
|
|
289
|
+
!Array.isArray(sourceValue)) {
|
|
290
|
+
const nested = pickComparableOverrideSubset(sourceValue, templateValue);
|
|
291
|
+
if (Object.keys(nested).length > 0) {
|
|
292
|
+
picked[key] = nested;
|
|
293
|
+
}
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
picked[key] = sourceValue;
|
|
297
|
+
}
|
|
298
|
+
return picked;
|
|
299
|
+
}
|
|
55
300
|
function mapAgent(row) {
|
|
56
301
|
return agentIdentitySchema.parse({
|
|
57
302
|
id: row.id,
|
|
@@ -218,7 +463,7 @@ export function isPsycheAuthRequired() {
|
|
|
218
463
|
.get();
|
|
219
464
|
return row ? boolFromInt(row.psyche_auth_required) : false;
|
|
220
465
|
}
|
|
221
|
-
|
|
466
|
+
function buildSettingsPayloadFromDatabase() {
|
|
222
467
|
const row = readSettingsRow();
|
|
223
468
|
const connections = listAiModelConnections();
|
|
224
469
|
const googleConfig = resolveGoogleCalendarOauthPublicConfig(process.env, {
|
|
@@ -264,7 +509,8 @@ export function getSettings() {
|
|
|
264
509
|
lastAuditAt: row.last_audit_at,
|
|
265
510
|
storageMode: "local-first",
|
|
266
511
|
activeSessions: 1,
|
|
267
|
-
tokenCount: listAgentTokens().filter((token) => token.status === "active")
|
|
512
|
+
tokenCount: listAgentTokens().filter((token) => token.status === "active")
|
|
513
|
+
.length,
|
|
268
514
|
psycheAuthRequired: boolFromInt(row.psyche_auth_required)
|
|
269
515
|
},
|
|
270
516
|
calendarProviders: {
|
|
@@ -290,14 +536,18 @@ export function getSettings() {
|
|
|
290
536
|
connectionLabel: basicChatConnection?.label ?? null,
|
|
291
537
|
provider: basicChatConnection?.provider ?? null,
|
|
292
538
|
baseUrl: basicChatConnection?.baseUrl ?? null,
|
|
293
|
-
model: row.forge_basic_chat_model?.trim() ||
|
|
539
|
+
model: row.forge_basic_chat_model?.trim() ||
|
|
540
|
+
basicChatConnection?.model ||
|
|
541
|
+
"gpt-5.4-mini"
|
|
294
542
|
},
|
|
295
543
|
wiki: {
|
|
296
544
|
connectionId: wikiConnection?.id ?? null,
|
|
297
545
|
connectionLabel: wikiConnection?.label ?? null,
|
|
298
546
|
provider: wikiConnection?.provider ?? null,
|
|
299
547
|
baseUrl: wikiConnection?.baseUrl ?? null,
|
|
300
|
-
model: row.forge_wiki_model?.trim() ||
|
|
548
|
+
model: row.forge_wiki_model?.trim() ||
|
|
549
|
+
wikiConnection?.model ||
|
|
550
|
+
"gpt-5.4-mini"
|
|
301
551
|
}
|
|
302
552
|
},
|
|
303
553
|
connections,
|
|
@@ -313,10 +563,99 @@ export function getSettings() {
|
|
|
313
563
|
agentTokens: listAgentTokens()
|
|
314
564
|
});
|
|
315
565
|
}
|
|
316
|
-
|
|
566
|
+
function reconcileSettingsFileWithDatabase(current) {
|
|
567
|
+
const fileState = readForgeSettingsFile();
|
|
568
|
+
if (!fileState.exists) {
|
|
569
|
+
writeForgeSettingsFileSnapshot(current);
|
|
570
|
+
lastSettingsFileStatus = {
|
|
571
|
+
path: fileState.filePath,
|
|
572
|
+
exists: true,
|
|
573
|
+
valid: true,
|
|
574
|
+
syncState: "created_from_database",
|
|
575
|
+
parseError: null,
|
|
576
|
+
overrideKeys: []
|
|
577
|
+
};
|
|
578
|
+
return current;
|
|
579
|
+
}
|
|
580
|
+
if (!fileState.valid || !fileState.settings) {
|
|
581
|
+
lastSettingsFileStatus = {
|
|
582
|
+
path: fileState.filePath,
|
|
583
|
+
exists: true,
|
|
584
|
+
valid: false,
|
|
585
|
+
syncState: "invalid",
|
|
586
|
+
parseError: fileState.parseError,
|
|
587
|
+
overrideKeys: []
|
|
588
|
+
};
|
|
589
|
+
return current;
|
|
590
|
+
}
|
|
591
|
+
const overrideInput = toSettingsFileOverrideInput(fileState.settings);
|
|
592
|
+
const overrideKeys = listOverrideKeys(overrideInput);
|
|
593
|
+
let next = current;
|
|
594
|
+
const currentOverride = pickComparableOverrideSubset(toSettingsFileOverrideInput(current), overrideInput);
|
|
595
|
+
const overridesDiffer = JSON.stringify(currentOverride) !== JSON.stringify(overrideInput);
|
|
596
|
+
if (overrideKeys.length > 0 && overridesDiffer) {
|
|
597
|
+
next = updateSettingsInternal(overrideInput, {
|
|
598
|
+
mirrorSettingsFile: false
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
const serialized = `${JSON.stringify(next, null, 2)}\n`;
|
|
602
|
+
let syncState = overrideKeys.length > 0 && overridesDiffer
|
|
603
|
+
? "applied_file_overrides"
|
|
604
|
+
: "up_to_date";
|
|
605
|
+
try {
|
|
606
|
+
const existing = readFileSync(fileState.filePath, "utf8");
|
|
607
|
+
if (existing !== serialized) {
|
|
608
|
+
mkdirSync(path.dirname(fileState.filePath), { recursive: true });
|
|
609
|
+
writeFileSync(fileState.filePath, serialized, "utf8");
|
|
610
|
+
if (syncState !== "applied_file_overrides") {
|
|
611
|
+
syncState = "mirrored_from_database";
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
catch {
|
|
616
|
+
mkdirSync(path.dirname(fileState.filePath), { recursive: true });
|
|
617
|
+
writeFileSync(fileState.filePath, serialized, "utf8");
|
|
618
|
+
if (syncState !== "applied_file_overrides") {
|
|
619
|
+
syncState = "mirrored_from_database";
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
lastSettingsFileStatus = {
|
|
623
|
+
path: fileState.filePath,
|
|
624
|
+
exists: true,
|
|
625
|
+
valid: true,
|
|
626
|
+
syncState,
|
|
627
|
+
parseError: null,
|
|
628
|
+
overrideKeys
|
|
629
|
+
};
|
|
630
|
+
return next;
|
|
631
|
+
}
|
|
632
|
+
export function getSettingsFileStatus() {
|
|
633
|
+
return {
|
|
634
|
+
...lastSettingsFileStatus,
|
|
635
|
+
path: getForgeSettingsFilePath()
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
export function mirrorSettingsFileFromCurrentState() {
|
|
639
|
+
const current = buildSettingsPayloadFromDatabase();
|
|
640
|
+
writeForgeSettingsFileSnapshot(current);
|
|
641
|
+
return current;
|
|
642
|
+
}
|
|
643
|
+
export function getSettings() {
|
|
644
|
+
if (settingsFileSyncDepth > 0) {
|
|
645
|
+
return buildSettingsPayloadFromDatabase();
|
|
646
|
+
}
|
|
647
|
+
settingsFileSyncDepth += 1;
|
|
648
|
+
try {
|
|
649
|
+
return reconcileSettingsFileWithDatabase(buildSettingsPayloadFromDatabase());
|
|
650
|
+
}
|
|
651
|
+
finally {
|
|
652
|
+
settingsFileSyncDepth = Math.max(0, settingsFileSyncDepth - 1);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
function updateSettingsInternal(input, options = {}) {
|
|
317
656
|
const parsed = updateSettingsSchema.parse(input);
|
|
318
657
|
return runInTransaction(() => {
|
|
319
|
-
const current =
|
|
658
|
+
const current = buildSettingsPayloadFromDatabase();
|
|
320
659
|
const now = new Date().toISOString();
|
|
321
660
|
const nextGoogleClientId = parsed.calendarProviders?.google?.clientId?.trim() ??
|
|
322
661
|
current.calendarProviders.google.storedClientId;
|
|
@@ -339,18 +678,25 @@ export function updateSettings(input, options = {}) {
|
|
|
339
678
|
operatorTitle: parsed.profile?.operatorTitle ?? current.profile.operatorTitle
|
|
340
679
|
},
|
|
341
680
|
notifications: {
|
|
342
|
-
goalDriftAlerts: parsed.notifications?.goalDriftAlerts ??
|
|
343
|
-
|
|
344
|
-
|
|
681
|
+
goalDriftAlerts: parsed.notifications?.goalDriftAlerts ??
|
|
682
|
+
current.notifications.goalDriftAlerts,
|
|
683
|
+
dailyQuestReminders: parsed.notifications?.dailyQuestReminders ??
|
|
684
|
+
current.notifications.dailyQuestReminders,
|
|
685
|
+
achievementCelebrations: parsed.notifications?.achievementCelebrations ??
|
|
686
|
+
current.notifications.achievementCelebrations
|
|
345
687
|
},
|
|
346
688
|
execution: {
|
|
347
689
|
maxActiveTasks: parsed.execution?.maxActiveTasks ?? current.execution.maxActiveTasks,
|
|
348
|
-
timeAccountingMode: parsed.execution?.timeAccountingMode ??
|
|
690
|
+
timeAccountingMode: parsed.execution?.timeAccountingMode ??
|
|
691
|
+
current.execution.timeAccountingMode
|
|
349
692
|
},
|
|
350
693
|
themePreference: parsed.themePreference ?? current.themePreference,
|
|
351
|
-
customTheme: parsed.customTheme === undefined
|
|
694
|
+
customTheme: parsed.customTheme === undefined
|
|
695
|
+
? (current.customTheme ?? null)
|
|
696
|
+
: parsed.customTheme,
|
|
352
697
|
localePreference: parsed.localePreference ?? current.localePreference,
|
|
353
|
-
psycheAuthRequired: parsed.security?.psycheAuthRequired ??
|
|
698
|
+
psycheAuthRequired: parsed.security?.psycheAuthRequired ??
|
|
699
|
+
current.security.psycheAuthRequired,
|
|
354
700
|
calendarProviders: {
|
|
355
701
|
google: resolveGoogleCalendarOauthPublicConfig(process.env, {
|
|
356
702
|
clientId: nextGoogleClientId,
|
|
@@ -371,14 +717,15 @@ export function updateSettings(input, options = {}) {
|
|
|
371
717
|
connectionId: parsed.modelSettings?.forgeAgent?.basicChat?.connectionId !==
|
|
372
718
|
undefined
|
|
373
719
|
? normalizeModelConnectionId(parsed.modelSettings.forgeAgent.basicChat.connectionId)
|
|
374
|
-
: current.modelSettings.forgeAgent.basicChat.connectionId ??
|
|
720
|
+
: (current.modelSettings.forgeAgent.basicChat.connectionId ??
|
|
721
|
+
""),
|
|
375
722
|
model: parsed.modelSettings?.forgeAgent?.basicChat?.model?.trim() ||
|
|
376
723
|
current.modelSettings.forgeAgent.basicChat.model
|
|
377
724
|
},
|
|
378
725
|
wiki: {
|
|
379
726
|
connectionId: parsed.modelSettings?.forgeAgent?.wiki?.connectionId !== undefined
|
|
380
727
|
? normalizeModelConnectionId(parsed.modelSettings.forgeAgent.wiki.connectionId)
|
|
381
|
-
: current.modelSettings.forgeAgent.wiki.connectionId ?? "",
|
|
728
|
+
: (current.modelSettings.forgeAgent.wiki.connectionId ?? ""),
|
|
382
729
|
model: parsed.modelSettings?.forgeAgent?.wiki?.model?.trim() ||
|
|
383
730
|
current.modelSettings.forgeAgent.wiki.model
|
|
384
731
|
}
|
|
@@ -431,7 +778,17 @@ export function updateSettings(input, options = {}) {
|
|
|
431
778
|
}
|
|
432
779
|
});
|
|
433
780
|
}
|
|
434
|
-
|
|
781
|
+
const updated = buildSettingsPayloadFromDatabase();
|
|
782
|
+
if (options.mirrorSettingsFile !== false) {
|
|
783
|
+
writeForgeSettingsFileSnapshot(updated);
|
|
784
|
+
}
|
|
785
|
+
return updated;
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
export function updateSettings(input, options = {}) {
|
|
789
|
+
return updateSettingsInternal(input, {
|
|
790
|
+
...options,
|
|
791
|
+
mirrorSettingsFile: true
|
|
435
792
|
});
|
|
436
793
|
}
|
|
437
794
|
export function createAgentToken(input, activity) {
|
|
@@ -477,6 +834,7 @@ export function createAgentToken(input, activity) {
|
|
|
477
834
|
}
|
|
478
835
|
});
|
|
479
836
|
}
|
|
837
|
+
mirrorSettingsFileFromCurrentState();
|
|
480
838
|
return {
|
|
481
839
|
token,
|
|
482
840
|
tokenSummary
|
|
@@ -514,6 +872,7 @@ export function rotateAgentToken(tokenId, activity) {
|
|
|
514
872
|
source: activity.source
|
|
515
873
|
});
|
|
516
874
|
}
|
|
875
|
+
mirrorSettingsFileFromCurrentState();
|
|
517
876
|
return {
|
|
518
877
|
token,
|
|
519
878
|
tokenSummary
|
|
@@ -549,6 +908,7 @@ export function revokeAgentToken(tokenId, activity) {
|
|
|
549
908
|
source: activity.source
|
|
550
909
|
});
|
|
551
910
|
}
|
|
911
|
+
mirrorSettingsFileFromCurrentState();
|
|
552
912
|
return tokenSummary;
|
|
553
913
|
});
|
|
554
914
|
}
|
|
@@ -580,6 +940,8 @@ export function verifyAgentToken(token) {
|
|
|
580
940
|
if (!row || row.revoked_at) {
|
|
581
941
|
return null;
|
|
582
942
|
}
|
|
583
|
-
getDatabase()
|
|
943
|
+
getDatabase()
|
|
944
|
+
.prepare(`UPDATE agent_tokens SET last_used_at = ?, updated_at = ? WHERE id = ?`)
|
|
945
|
+
.run(new Date().toISOString(), new Date().toISOString(), row.id);
|
|
584
946
|
return mapToken(row);
|
|
585
947
|
}
|