forge-openclaw-plugin 0.2.3 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/README.md +114 -6
  2. package/dist/assets/board-CzgvdLO8.js +6 -0
  3. package/dist/assets/board-CzgvdLO8.js.map +1 -0
  4. package/dist/assets/favicon-BCHm9dUV.ico +0 -0
  5. package/dist/assets/index-8d_oM8fL.js +27 -0
  6. package/dist/assets/index-8d_oM8fL.js.map +1 -0
  7. package/dist/assets/index-D4A_bq8m.css +1 -0
  8. package/dist/assets/motion-STUd1O46.js +10 -0
  9. package/dist/assets/motion-STUd1O46.js.map +1 -0
  10. package/dist/assets/plus-jakarta-sans-latin-ext-wght-normal-DmpS2jIq.woff2 +0 -0
  11. package/dist/assets/plus-jakarta-sans-latin-wght-normal-eXO_dkmS.woff2 +0 -0
  12. package/dist/assets/plus-jakarta-sans-vietnamese-wght-normal-qRpaaN48.woff2 +0 -0
  13. package/dist/assets/sora-latin-ext-wght-normal-CawQDOvP.woff2 +0 -0
  14. package/dist/assets/sora-latin-wght-normal-DdqRvwsR.woff2 +0 -0
  15. package/dist/assets/space-grotesk-latin-500-normal-CNSSEhBt.woff +0 -0
  16. package/dist/assets/space-grotesk-latin-500-normal-lFbtlQH6.woff2 +0 -0
  17. package/dist/assets/space-grotesk-latin-700-normal-CwsQ-cCU.woff +0 -0
  18. package/dist/assets/space-grotesk-latin-700-normal-RjhwGPKo.woff2 +0 -0
  19. package/dist/assets/space-grotesk-latin-ext-500-normal-3dgZTiw9.woff +0 -0
  20. package/dist/assets/space-grotesk-latin-ext-500-normal-DUe3BAxM.woff2 +0 -0
  21. package/dist/assets/space-grotesk-latin-ext-700-normal-BQnZhY3m.woff2 +0 -0
  22. package/dist/assets/space-grotesk-latin-ext-700-normal-HVCqSBdx.woff +0 -0
  23. package/dist/assets/space-grotesk-vietnamese-500-normal-BTqKIpxg.woff +0 -0
  24. package/dist/assets/space-grotesk-vietnamese-500-normal-BmEvtly_.woff2 +0 -0
  25. package/dist/assets/space-grotesk-vietnamese-700-normal-DMty7AZE.woff2 +0 -0
  26. package/dist/assets/space-grotesk-vietnamese-700-normal-Duxec5Rn.woff +0 -0
  27. package/dist/assets/table-CtNlETLc.js +23 -0
  28. package/dist/assets/table-CtNlETLc.js.map +1 -0
  29. package/dist/assets/ui-ThzkR_oW.js +46 -0
  30. package/dist/assets/ui-ThzkR_oW.js.map +1 -0
  31. package/dist/assets/vendor-CRS-psbw.css +1 -0
  32. package/dist/assets/vendor-DyHAI6nk.js +423 -0
  33. package/dist/assets/vendor-DyHAI6nk.js.map +1 -0
  34. package/dist/assets/viz-BJuBCz_G.js +34 -0
  35. package/dist/assets/viz-BJuBCz_G.js.map +1 -0
  36. package/dist/favicon.ico +0 -0
  37. package/dist/favicon.png +0 -0
  38. package/dist/index.html +29 -0
  39. package/dist/openclaw/api-client.d.ts +8 -0
  40. package/dist/openclaw/api-client.js +31 -4
  41. package/dist/openclaw/local-runtime.d.ts +3 -0
  42. package/dist/openclaw/local-runtime.js +135 -0
  43. package/dist/openclaw/parity.d.ts +4 -4
  44. package/dist/openclaw/parity.js +23 -33
  45. package/dist/openclaw/plugin-entry-shared.d.ts +5 -3
  46. package/dist/openclaw/plugin-entry-shared.js +52 -10
  47. package/dist/openclaw/routes.d.ts +12 -3
  48. package/dist/openclaw/routes.js +156 -924
  49. package/dist/openclaw/tools.js +242 -1100
  50. package/dist/server/app.js +2450 -0
  51. package/dist/server/db.js +313 -0
  52. package/dist/server/e2e-server.js +20 -0
  53. package/dist/server/errors.js +15 -0
  54. package/dist/server/index.js +16 -0
  55. package/dist/server/managers/base.js +17 -0
  56. package/dist/server/managers/contracts.js +47 -0
  57. package/dist/server/managers/platform/api-gateway-manager.js +11 -0
  58. package/dist/server/managers/platform/audit-manager.js +15 -0
  59. package/dist/server/managers/platform/authentication-manager.js +56 -0
  60. package/dist/server/managers/platform/authorization-manager.js +56 -0
  61. package/dist/server/managers/platform/background-job-manager.js +10 -0
  62. package/dist/server/managers/platform/configuration-manager.js +33 -0
  63. package/dist/server/managers/platform/database-manager.js +14 -0
  64. package/dist/server/managers/platform/event-bus-manager.js +7 -0
  65. package/dist/server/managers/platform/external-service-manager.js +11 -0
  66. package/dist/server/managers/platform/health-manager.js +7 -0
  67. package/dist/server/managers/platform/migration-manager.js +8 -0
  68. package/dist/server/managers/platform/search-index-manager.js +4 -0
  69. package/dist/server/managers/platform/secrets-manager.js +19 -0
  70. package/dist/server/managers/platform/session-manager.js +121 -0
  71. package/dist/server/managers/platform/storage-manager.js +16 -0
  72. package/dist/server/managers/platform/token-manager.js +37 -0
  73. package/dist/server/managers/platform/transaction-manager.js +8 -0
  74. package/dist/server/managers/platform/trusted-network.js +39 -0
  75. package/dist/server/managers/runtime.js +56 -0
  76. package/dist/server/managers/type-guards.js +4 -0
  77. package/dist/server/openapi.js +3512 -0
  78. package/dist/server/psyche-types.js +395 -0
  79. package/dist/server/repositories/activity-events.js +157 -0
  80. package/dist/server/repositories/collaboration.js +497 -0
  81. package/dist/server/repositories/comments.js +176 -0
  82. package/dist/server/repositories/deleted-entities.js +192 -0
  83. package/dist/server/repositories/domains.js +30 -0
  84. package/dist/server/repositories/event-log.js +64 -0
  85. package/dist/server/repositories/goals.js +159 -0
  86. package/dist/server/repositories/projects.js +214 -0
  87. package/dist/server/repositories/psyche.js +1356 -0
  88. package/dist/server/repositories/rewards.js +675 -0
  89. package/dist/server/repositories/settings.js +399 -0
  90. package/dist/server/repositories/tags.js +160 -0
  91. package/dist/server/repositories/task-runs.js +488 -0
  92. package/dist/server/repositories/tasks.js +413 -0
  93. package/dist/server/services/context.js +214 -0
  94. package/dist/server/services/dashboard.js +170 -0
  95. package/dist/server/services/entity-crud.js +576 -0
  96. package/dist/server/services/gamification.js +215 -0
  97. package/dist/server/services/insights.js +91 -0
  98. package/dist/server/services/projects.js +75 -0
  99. package/dist/server/services/psyche.js +63 -0
  100. package/dist/server/services/relations.js +28 -0
  101. package/dist/server/services/reviews.js +88 -0
  102. package/dist/server/services/run-recovery.js +13 -0
  103. package/dist/server/services/tagging.js +49 -0
  104. package/dist/server/services/task-run-watchdog.js +92 -0
  105. package/dist/server/services/work-time.js +176 -0
  106. package/dist/server/types.js +999 -0
  107. package/dist/server/web.js +91 -0
  108. package/openclaw.plugin.json +22 -10
  109. package/package.json +17 -4
  110. package/server/migrations/001_core.sql +333 -0
  111. package/server/migrations/002_psyche.sql +241 -0
  112. package/server/migrations/003_timer_execution.sql +18 -0
  113. package/server/migrations/004_psyche_linked_entities.sql +5 -0
  114. package/server/migrations/005_adaptive_schemas.sql +157 -0
  115. package/server/migrations/006_psyche_auth_setting.sql +4 -0
  116. package/server/migrations/007_deleted_entities.sql +16 -0
  117. package/skills/forge-openclaw/SKILL.md +189 -275
@@ -0,0 +1,313 @@
1
+ import { mkdir, readdir, readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { DatabaseSync } from "node:sqlite";
5
+ function nowIso() {
6
+ return new Date().toISOString();
7
+ }
8
+ function dateOffsetIso(days) {
9
+ const date = new Date();
10
+ date.setUTCDate(date.getUTCDate() + days);
11
+ return date.toISOString().slice(0, 10);
12
+ }
13
+ const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
14
+ const migrationsDir = path.join(projectRoot, "server", "migrations");
15
+ let dataRoot = process.cwd();
16
+ let seedDemoDataEnabled = false;
17
+ let db = null;
18
+ let transactionDepth = 0;
19
+ let savepointCounter = 0;
20
+ function getDataDir() {
21
+ return path.join(dataRoot, "data");
22
+ }
23
+ function getDatabasePath() {
24
+ return path.join(getDataDir(), "forge.sqlite");
25
+ }
26
+ export function getDatabase() {
27
+ if (!db) {
28
+ db = new DatabaseSync(getDatabasePath());
29
+ db.exec("PRAGMA foreign_keys = ON;");
30
+ db.exec("PRAGMA busy_timeout = 5000;");
31
+ db.exec("PRAGMA synchronous = FULL;");
32
+ db.prepare("PRAGMA journal_mode = WAL;").get();
33
+ }
34
+ return db;
35
+ }
36
+ export function runInTransaction(operation) {
37
+ const database = getDatabase();
38
+ const isNested = transactionDepth > 0;
39
+ const savepointName = isNested ? `forge_sp_${++savepointCounter}` : null;
40
+ if (isNested) {
41
+ database.exec(`SAVEPOINT ${savepointName}`);
42
+ }
43
+ else {
44
+ database.exec("BEGIN IMMEDIATE");
45
+ }
46
+ transactionDepth += 1;
47
+ try {
48
+ const result = operation();
49
+ if (isNested) {
50
+ database.exec(`RELEASE SAVEPOINT ${savepointName}`);
51
+ }
52
+ else {
53
+ database.exec("COMMIT");
54
+ }
55
+ return result;
56
+ }
57
+ catch (error) {
58
+ if (isNested) {
59
+ database.exec(`ROLLBACK TO SAVEPOINT ${savepointName}`);
60
+ database.exec(`RELEASE SAVEPOINT ${savepointName}`);
61
+ }
62
+ else {
63
+ database.exec("ROLLBACK");
64
+ }
65
+ throw error;
66
+ }
67
+ finally {
68
+ transactionDepth = Math.max(0, transactionDepth - 1);
69
+ }
70
+ }
71
+ export function configureDatabase(options = {}) {
72
+ if (options.dataRoot) {
73
+ dataRoot = path.resolve(options.dataRoot);
74
+ closeDatabase();
75
+ }
76
+ if (typeof options.seedDemoData === "boolean") {
77
+ seedDemoDataEnabled = options.seedDemoData;
78
+ }
79
+ }
80
+ async function listMigrationFiles() {
81
+ const files = await readdir(migrationsDir);
82
+ return files.filter((file) => file.endsWith(".sql")).sort();
83
+ }
84
+ function countRows(tableName) {
85
+ const row = getDatabase().prepare(`SELECT COUNT(*) as count FROM ${tableName}`).get();
86
+ return row.count;
87
+ }
88
+ function seedData() {
89
+ if (countRows("goals") > 0) {
90
+ return;
91
+ }
92
+ const database = getDatabase();
93
+ const now = nowIso();
94
+ const insertGoal = database.prepare(`
95
+ INSERT INTO goals (id, title, description, horizon, status, target_points, theme_color, created_at, updated_at)
96
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
97
+ `);
98
+ const insertTag = database.prepare(`
99
+ INSERT INTO tags (id, name, kind, color, description, created_at)
100
+ VALUES (?, ?, ?, ?, ?, ?)
101
+ `);
102
+ const insertGoalTag = database.prepare(`
103
+ INSERT INTO goal_tags (goal_id, tag_id)
104
+ VALUES (?, ?)
105
+ `);
106
+ const insertProject = database.prepare(`
107
+ INSERT INTO projects (id, goal_id, title, description, status, theme_color, target_points, created_at, updated_at)
108
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
109
+ `);
110
+ const insertTask = database.prepare(`
111
+ INSERT INTO tasks (
112
+ id, title, description, status, priority, owner, goal_id, project_id, due_date, effort, energy, points, sort_order, completed_at, created_at, updated_at
113
+ )
114
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
115
+ `);
116
+ const insertTaskTag = database.prepare(`
117
+ INSERT INTO task_tags (task_id, tag_id)
118
+ VALUES (?, ?)
119
+ `);
120
+ const goals = [
121
+ {
122
+ id: "goal_be_a_good_person",
123
+ title: "Be a good person",
124
+ description: "Live in a way that is kind, honest, and helpful to other people.",
125
+ horizon: "lifetime",
126
+ status: "active",
127
+ targetPoints: 1000,
128
+ themeColor: "#f5efe6"
129
+ },
130
+ {
131
+ id: "goal_build_forge",
132
+ title: "Build Forge into a premium operating system",
133
+ description: "Turn Forge into a sharp, trustworthy life system with strong daily execution.",
134
+ horizon: "year",
135
+ status: "active",
136
+ targetPoints: 720,
137
+ themeColor: "#9dc4ff"
138
+ },
139
+ {
140
+ id: "goal_train_body",
141
+ title: "Train with consistency",
142
+ description: "Keep health, training, and recovery visible in the weekly operating rhythm.",
143
+ horizon: "quarter",
144
+ status: "active",
145
+ targetPoints: 360,
146
+ themeColor: "#f4b97a"
147
+ }
148
+ ];
149
+ for (const goal of goals) {
150
+ insertGoal.run(goal.id, goal.title, goal.description, goal.horizon, goal.status, goal.targetPoints, goal.themeColor, now, now);
151
+ }
152
+ const tags = [
153
+ ["tag_vitality", "Vitality", "value", "#f59e0b", "Health, training, and physical energy."],
154
+ ["tag_deep_work", "Deep Work", "execution", "#8b5cf6", "Protected focus and cognitively demanding work."],
155
+ ["tag_relationships", "Relationships", "value", "#ef4444", "Important human connection and maintenance."],
156
+ ["tag_systems", "Systems", "category", "#14b8a6", "Operational scaffolding, review, and maintenance."],
157
+ ["tag_craft", "Craft", "category", "#60a5fa", "Making the product sharper and more intentional."],
158
+ ["tag_recovery", "Recovery", "execution", "#22c55e", "Recovery, decompression, and reset work."]
159
+ ];
160
+ for (const [id, name, kind, color, description] of tags) {
161
+ insertTag.run(id, name, kind, color, description, now);
162
+ }
163
+ insertGoalTag.run("goal_be_a_good_person", "tag_relationships");
164
+ insertGoalTag.run("goal_build_forge", "tag_craft");
165
+ insertGoalTag.run("goal_build_forge", "tag_systems");
166
+ insertGoalTag.run("goal_train_body", "tag_vitality");
167
+ insertGoalTag.run("goal_train_body", "tag_recovery");
168
+ insertProject.run("project_relationships_ritual", "goal_be_a_good_person", "Keep the relationship ritual visible", "Protect simple weekly actions that maintain important relationships and personal integrity.", "active", "#fb7185", 90, now, now);
169
+ insertProject.run("project_forge_mobile", "goal_build_forge", "Ship the Forge flagship workflow", "Tighten the main execution loop, Kanban, and OpenClaw collaboration surface.", "active", "#7dd3fc", 240, now, now);
170
+ insertProject.run("project_strength_cycle", "goal_train_body", "Run the current strength cycle", "Keep the training block visible with recovery and progression.", "active", "#f59e0b", 120, now, now);
171
+ const tasks = [
172
+ {
173
+ id: "task_flagship_review",
174
+ title: "Review the Forge flagship flow",
175
+ description: "Walk Overview, Today, Kanban, and Psyche to identify friction before the next pass.",
176
+ status: "focus",
177
+ priority: "high",
178
+ owner: "Albert",
179
+ goalId: "goal_build_forge",
180
+ projectId: "project_forge_mobile",
181
+ dueDate: dateOffsetIso(1),
182
+ effort: "deep",
183
+ energy: "high",
184
+ points: 55,
185
+ sortOrder: 100,
186
+ completedAt: null
187
+ },
188
+ {
189
+ id: "task_plugin_surface",
190
+ title: "Slim the OpenClaw plugin surface",
191
+ description: "Keep the plugin focused on overview, batch entities, insights, and UI entry.",
192
+ status: "in_progress",
193
+ priority: "high",
194
+ owner: "Albert",
195
+ goalId: "goal_build_forge",
196
+ projectId: "project_forge_mobile",
197
+ dueDate: dateOffsetIso(0),
198
+ effort: "deep",
199
+ energy: "high",
200
+ points: 34,
201
+ sortOrder: 200,
202
+ completedAt: null
203
+ },
204
+ {
205
+ id: "task_weekly_review",
206
+ title: "Prepare the weekly review ritual",
207
+ description: "Make sure the review captures drift, signals, and visible wins.",
208
+ status: "backlog",
209
+ priority: "medium",
210
+ owner: "Albert",
211
+ goalId: "goal_be_a_good_person",
212
+ projectId: "project_relationships_ritual",
213
+ dueDate: dateOffsetIso(3),
214
+ effort: "deep",
215
+ energy: "steady",
216
+ points: 21,
217
+ sortOrder: 300,
218
+ completedAt: null
219
+ },
220
+ {
221
+ id: "task_strength_session",
222
+ title: "Complete the lower-body strength session",
223
+ description: "Keep the training cycle alive with one deliberate session.",
224
+ status: "blocked",
225
+ priority: "medium",
226
+ owner: "Albert",
227
+ goalId: "goal_train_body",
228
+ projectId: "project_strength_cycle",
229
+ dueDate: dateOffsetIso(-1),
230
+ effort: "deep",
231
+ energy: "steady",
232
+ points: 18,
233
+ sortOrder: 400,
234
+ completedAt: null
235
+ },
236
+ {
237
+ id: "task_recovery_walk",
238
+ title: "Take the recovery walk",
239
+ description: "Short reset to keep energy stable after the work block.",
240
+ status: "done",
241
+ priority: "low",
242
+ owner: "Albert",
243
+ goalId: "goal_train_body",
244
+ projectId: "project_strength_cycle",
245
+ dueDate: dateOffsetIso(-2),
246
+ effort: "light",
247
+ energy: "low",
248
+ points: 60,
249
+ sortOrder: 500,
250
+ completedAt: now
251
+ }
252
+ ];
253
+ for (const task of tasks) {
254
+ insertTask.run(task.id, task.title, task.description, task.status, task.priority, task.owner, task.goalId, task.projectId, task.dueDate, task.effort, task.energy, task.points, task.sortOrder, task.completedAt, now, now);
255
+ }
256
+ const taskTags = [
257
+ ["task_flagship_review", "tag_deep_work"],
258
+ ["task_flagship_review", "tag_craft"],
259
+ ["task_plugin_surface", "tag_systems"],
260
+ ["task_plugin_surface", "tag_craft"],
261
+ ["task_weekly_review", "tag_relationships"],
262
+ ["task_strength_session", "tag_vitality"],
263
+ ["task_recovery_walk", "tag_recovery"]
264
+ ];
265
+ for (const [taskId, tagId] of taskTags) {
266
+ insertTaskTag.run(taskId, tagId);
267
+ }
268
+ }
269
+ export async function initializeDatabase() {
270
+ await mkdir(getDataDir(), { recursive: true });
271
+ const database = getDatabase();
272
+ const migrationFiles = await listMigrationFiles();
273
+ database.exec(`
274
+ CREATE TABLE IF NOT EXISTS migrations (
275
+ id TEXT PRIMARY KEY,
276
+ applied_at TEXT NOT NULL
277
+ );
278
+ `);
279
+ const appliedRows = database
280
+ .prepare("SELECT id FROM migrations ORDER BY id")
281
+ .all();
282
+ const applied = new Set(appliedRows.map((row) => row.id));
283
+ for (const file of migrationFiles) {
284
+ if (applied.has(file)) {
285
+ continue;
286
+ }
287
+ const sql = await readFile(path.join(migrationsDir, file), "utf8");
288
+ database.exec("BEGIN");
289
+ try {
290
+ database.exec(sql);
291
+ database
292
+ .prepare("INSERT INTO migrations (id, applied_at) VALUES (?, ?)")
293
+ .run(file, nowIso());
294
+ database.exec("COMMIT");
295
+ }
296
+ catch (error) {
297
+ database.exec("ROLLBACK");
298
+ throw error;
299
+ }
300
+ }
301
+ if (seedDemoDataEnabled) {
302
+ seedData();
303
+ }
304
+ }
305
+ export function configureDatabaseSeeding(enabled) {
306
+ seedDemoDataEnabled = enabled;
307
+ }
308
+ export function closeDatabase() {
309
+ if (db) {
310
+ db.close();
311
+ db = null;
312
+ }
313
+ }
@@ -0,0 +1,20 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ import { mkdtemp } from "node:fs/promises";
4
+ import { buildServer } from "./app.js";
5
+ import { closeDatabase } from "./db.js";
6
+ const port = Number(process.env.PORT ?? 4317);
7
+ const host = process.env.HOST ?? "127.0.0.1";
8
+ const dataRoot = process.env.FORGE_E2E_DATA_ROOT ?? (await mkdtemp(path.join(os.tmpdir(), "forge-e2e-")));
9
+ const app = await buildServer({ dataRoot });
10
+ const close = async () => {
11
+ await app.close();
12
+ closeDatabase();
13
+ };
14
+ process.on("SIGINT", () => {
15
+ void close();
16
+ });
17
+ process.on("SIGTERM", () => {
18
+ void close();
19
+ });
20
+ await app.listen({ port, host });
@@ -0,0 +1,15 @@
1
+ export class HttpError extends Error {
2
+ statusCode;
3
+ code;
4
+ details;
5
+ constructor(statusCode, code, message, details) {
6
+ super(message);
7
+ this.name = "HttpError";
8
+ this.statusCode = statusCode;
9
+ this.code = code;
10
+ this.details = details;
11
+ }
12
+ }
13
+ export function isHttpError(error) {
14
+ return error instanceof HttpError;
15
+ }
@@ -0,0 +1,16 @@
1
+ import { buildServer } from "./app.js";
2
+ import { closeDatabase } from "./db.js";
3
+ const port = Number(process.env.PORT ?? 4317);
4
+ const host = process.env.HOST ?? "0.0.0.0";
5
+ const app = await buildServer();
6
+ const close = async () => {
7
+ await app.close();
8
+ closeDatabase();
9
+ };
10
+ process.on("SIGINT", () => {
11
+ void close();
12
+ });
13
+ process.on("SIGTERM", () => {
14
+ void close();
15
+ });
16
+ await app.listen({ port, host });
@@ -0,0 +1,17 @@
1
+ export class AbstractManager {
2
+ }
3
+ export class AbstractAuthAwareManager extends AbstractManager {
4
+ getNow(context) {
5
+ return context.now ?? new Date();
6
+ }
7
+ }
8
+ export class AbstractAuditedManager extends AbstractAuthAwareManager {
9
+ }
10
+ export class AbstractVersionedManager extends AbstractAuditedManager {
11
+ }
12
+ export class AbstractReadModelManager extends AbstractAuthAwareManager {
13
+ }
14
+ export class AbstractExternalManager extends AbstractManager {
15
+ }
16
+ export class AbstractWritableManager extends AbstractAuditedManager {
17
+ }
@@ -0,0 +1,47 @@
1
+ export class ManagerError extends Error {
2
+ code;
3
+ statusCode;
4
+ details;
5
+ constructor(message, code, statusCode, details) {
6
+ super(message);
7
+ this.code = code;
8
+ this.statusCode = statusCode;
9
+ this.details = details;
10
+ this.name = "ManagerError";
11
+ }
12
+ }
13
+ export class AuthRequiredError extends ManagerError {
14
+ constructor(message = "Authentication is required", details) {
15
+ super(message, "auth_required", 401, details);
16
+ }
17
+ }
18
+ export class AuthorizationDeniedError extends ManagerError {
19
+ constructor(message = "Authorization denied", details) {
20
+ super(message, "authorization_denied", 403, details);
21
+ }
22
+ }
23
+ export class InsufficientScopeError extends ManagerError {
24
+ constructor(message = "Insufficient scope", details) {
25
+ super(message, "insufficient_scope", 403, details);
26
+ }
27
+ }
28
+ export class ApprovalRequiredError extends ManagerError {
29
+ constructor(message = "Approval is required", details) {
30
+ super(message, "approval_required", 409, details);
31
+ }
32
+ }
33
+ export class ConcurrencyConflictError extends ManagerError {
34
+ constructor(message = "Concurrency conflict", details) {
35
+ super(message, "concurrency_conflict", 409, details);
36
+ }
37
+ }
38
+ export class ProviderUnavailableError extends ManagerError {
39
+ constructor(message = "Provider unavailable", details) {
40
+ super(message, "provider_unavailable", 503, details);
41
+ }
42
+ }
43
+ export class ValidationFailedError extends ManagerError {
44
+ constructor(message = "Validation failed", details) {
45
+ super(message, "validation_failed", 400, details);
46
+ }
47
+ }
@@ -0,0 +1,11 @@
1
+ import { AbstractExternalManager } from "../base.js";
2
+ export class ApiGatewayManager extends AbstractExternalManager {
3
+ name = "ApiGatewayManager";
4
+ config = null;
5
+ configure(config) {
6
+ this.config = config;
7
+ }
8
+ getConfig() {
9
+ return this.config;
10
+ }
11
+ }
@@ -0,0 +1,15 @@
1
+ import { AbstractAuditedManager } from "../base.js";
2
+ import { recordEventLog } from "../../repositories/event-log.js";
3
+ export class AuditManager extends AbstractAuditedManager {
4
+ name = "AuditManager";
5
+ record(eventKind, entityType, entityId, context, metadata = {}) {
6
+ recordEventLog({
7
+ eventKind,
8
+ entityType,
9
+ entityId,
10
+ actor: context.actor ?? null,
11
+ source: context.source,
12
+ metadata
13
+ });
14
+ }
15
+ }
@@ -0,0 +1,56 @@
1
+ import { AbstractManager } from "../base.js";
2
+ function readSingleHeaderValue(value) {
3
+ if (Array.isArray(value)) {
4
+ return value[0] ?? null;
5
+ }
6
+ return typeof value === "string" ? value : null;
7
+ }
8
+ function normalizeSource(value) {
9
+ if (value === "ui" || value === "agent" || value === "openclaw" || value === "system") {
10
+ return value;
11
+ }
12
+ return null;
13
+ }
14
+ export class AuthenticationManager extends AbstractManager {
15
+ sessionManager;
16
+ tokenManager;
17
+ name = "AuthenticationManager";
18
+ constructor(sessionManager, tokenManager) {
19
+ super();
20
+ this.sessionManager = sessionManager;
21
+ this.tokenManager = tokenManager;
22
+ }
23
+ authenticate(headers) {
24
+ const sourceHeader = readSingleHeaderValue(headers["x-forge-source"]);
25
+ const actorHeader = readSingleHeaderValue(headers["x-forge-actor"]);
26
+ const bearer = this.parseBearerToken(headers);
27
+ const token = bearer ? this.tokenManager.verifyBearerToken(bearer) : null;
28
+ const session = this.sessionManager.readSessionFromHeaders(headers);
29
+ const sourceOverride = normalizeSource(sourceHeader);
30
+ const actor = token?.agentLabel ?? session?.actorLabel ?? actorHeader ?? null;
31
+ const source = token ? sourceOverride ?? "agent" : sourceOverride ?? (session ? "ui" : "ui");
32
+ return {
33
+ now: new Date(),
34
+ correlationId: null,
35
+ requestId: null,
36
+ origin: readSingleHeaderValue(headers.origin),
37
+ host: readSingleHeaderValue(headers.host),
38
+ ip: null,
39
+ actor,
40
+ source,
41
+ token,
42
+ session
43
+ };
44
+ }
45
+ parseBearerToken(headers) {
46
+ const raw = readSingleHeaderValue(headers.authorization);
47
+ if (!raw) {
48
+ return null;
49
+ }
50
+ const [scheme, token] = raw.trim().split(/\s+/, 2);
51
+ if (scheme?.toLowerCase() !== "bearer" || !token) {
52
+ return null;
53
+ }
54
+ return token;
55
+ }
56
+ }
@@ -0,0 +1,56 @@
1
+ import { AbstractManager } from "../base.js";
2
+ import { AuthRequiredError, InsufficientScopeError } from "../contracts.js";
3
+ export class AuthorizationManager extends AbstractManager {
4
+ name = "AuthorizationManager";
5
+ requireAuthenticatedOperator(context, detail) {
6
+ if (context.session) {
7
+ return;
8
+ }
9
+ throw new AuthRequiredError("An authenticated operator session is required.", detail);
10
+ }
11
+ requireAuthenticatedActor(context, detail) {
12
+ if (context.session || context.token) {
13
+ return;
14
+ }
15
+ throw new AuthRequiredError("Authentication is required for this operation.", detail);
16
+ }
17
+ requireTokenScope(context, scope, detail) {
18
+ if (context.session) {
19
+ return;
20
+ }
21
+ if (!context.token) {
22
+ throw new AuthRequiredError("A token or operator session is required.", {
23
+ requiredScope: scope,
24
+ ...(detail ?? {})
25
+ });
26
+ }
27
+ if (!context.token.scopes.includes(scope)) {
28
+ throw new InsufficientScopeError(`This operation requires the ${scope} scope.`, {
29
+ requiredScope: scope,
30
+ scopes: context.token.scopes,
31
+ ...(detail ?? {})
32
+ });
33
+ }
34
+ }
35
+ requireAnyTokenScope(context, scopes, detail) {
36
+ if (context.session) {
37
+ return;
38
+ }
39
+ if (!context.token) {
40
+ throw new AuthRequiredError("A token or operator session is required.", {
41
+ requiredScopes: scopes,
42
+ ...(detail ?? {})
43
+ });
44
+ }
45
+ if (!scopes.some((scope) => context.token?.scopes.includes(scope))) {
46
+ throw new InsufficientScopeError(`This operation requires one of: ${scopes.join(", ")}.`, {
47
+ requiredScopes: scopes,
48
+ scopes: context.token.scopes,
49
+ ...(detail ?? {})
50
+ });
51
+ }
52
+ }
53
+ canManageOperator(context) {
54
+ return Boolean(context.session);
55
+ }
56
+ }
@@ -0,0 +1,10 @@
1
+ import { AbstractManager } from "../base.js";
2
+ export class BackgroundJobManager extends AbstractManager {
3
+ name = "BackgroundJobManager";
4
+ start() {
5
+ return;
6
+ }
7
+ stop() {
8
+ return;
9
+ }
10
+ }
@@ -0,0 +1,33 @@
1
+ import { AbstractManager } from "../base.js";
2
+ export class ConfigurationManager extends AbstractManager {
3
+ env;
4
+ name = "ConfigurationManager";
5
+ constructor(env = process.env) {
6
+ super();
7
+ this.env = env;
8
+ }
9
+ readRuntimeConfig(overrides = {}) {
10
+ return {
11
+ host: this.env.HOST?.trim() || "0.0.0.0",
12
+ port: Number(this.env.PORT ?? 4317),
13
+ basePath: this.normalizeBasePath(this.env.FORGE_BASE_PATH ?? "/forge/"),
14
+ dataRoot: overrides.dataRoot ? overrides.dataRoot : this.env.FORGE_DATA_ROOT?.trim() || null,
15
+ sessionCookieName: this.env.FORGE_OPERATOR_SESSION_COOKIE?.trim() || "forge_operator_session",
16
+ sessionTtlSeconds: Math.max(3600, Number(this.env.FORGE_OPERATOR_SESSION_TTL_SECONDS ?? 60 * 60 * 24 * 7)),
17
+ allowedOrigins: [
18
+ /^https?:\/\/localhost(?::\d+)?$/i,
19
+ /^https?:\/\/127\.0\.0\.1(?::\d+)?$/i,
20
+ /^https?:\/\/\[::1\](?::\d+)?$/i,
21
+ /^https?:\/\/[a-z0-9-]+(?:\.[a-z0-9-]+)*\.ts\.net(?::\d+)?$/i,
22
+ /^https?:\/\/100\.(?:\d{1,3})\.(?:\d{1,3})\.(?:\d{1,3})(?::\d+)?$/i
23
+ ]
24
+ };
25
+ }
26
+ normalizeBasePath(value) {
27
+ if (!value || value === "/") {
28
+ return "/";
29
+ }
30
+ const withLeadingSlash = value.startsWith("/") ? value : `/${value}`;
31
+ return withLeadingSlash.endsWith("/") ? withLeadingSlash : `${withLeadingSlash}/`;
32
+ }
33
+ }
@@ -0,0 +1,14 @@
1
+ import { AbstractManager } from "../base.js";
2
+ import { closeDatabase, configureDatabase, getDatabase } from "../../db.js";
3
+ export class DatabaseManager extends AbstractManager {
4
+ name = "DatabaseManager";
5
+ configure(dataRoot) {
6
+ configureDatabase({ dataRoot: dataRoot ?? undefined });
7
+ }
8
+ getConnection() {
9
+ return getDatabase();
10
+ }
11
+ close() {
12
+ closeDatabase();
13
+ }
14
+ }
@@ -0,0 +1,7 @@
1
+ import { AbstractManager } from "../base.js";
2
+ export class EventBusManager extends AbstractManager {
3
+ name = "EventBusManager";
4
+ publish(_event) {
5
+ return;
6
+ }
7
+ }
@@ -0,0 +1,11 @@
1
+ import { AbstractManager } from "../base.js";
2
+ export class ExternalServiceManager extends AbstractManager {
3
+ name = "ExternalServiceManager";
4
+ providers = new Map();
5
+ register(name, metadata) {
6
+ this.providers.set(name, metadata);
7
+ }
8
+ list() {
9
+ return [...this.providers.entries()].map(([name, metadata]) => ({ name, ...metadata }));
10
+ }
11
+ }
@@ -0,0 +1,7 @@
1
+ import { AbstractManager } from "../base.js";
2
+ export class HealthManager extends AbstractManager {
3
+ name = "HealthManager";
4
+ summarize(input) {
5
+ return input;
6
+ }
7
+ }
@@ -0,0 +1,8 @@
1
+ import { AbstractManager } from "../base.js";
2
+ import { initializeDatabase } from "../../db.js";
3
+ export class MigrationManager extends AbstractManager {
4
+ name = "MigrationManager";
5
+ async initialize() {
6
+ await initializeDatabase();
7
+ }
8
+ }