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
Binary file
Binary file
@@ -0,0 +1,29 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta
6
+ name="viewport"
7
+ content="width=device-width, initial-scale=1.0, viewport-fit=cover"
8
+ />
9
+ <title>Forge</title>
10
+ <meta
11
+ name="description"
12
+ content="Forge is a premium life operating system for turning long-horizon goals into daily motion."
13
+ />
14
+ <link rel="icon" type="image/png" href="/forge/assets/favicon-BCHm9dUV.ico" />
15
+ <link rel="alternate icon" href="/forge/assets/favicon-BCHm9dUV.ico" />
16
+ <script type="module" crossorigin src="/forge/assets/index-8d_oM8fL.js"></script>
17
+ <link rel="modulepreload" crossorigin href="/forge/assets/vendor-DyHAI6nk.js">
18
+ <link rel="modulepreload" crossorigin href="/forge/assets/motion-STUd1O46.js">
19
+ <link rel="modulepreload" crossorigin href="/forge/assets/ui-ThzkR_oW.js">
20
+ <link rel="modulepreload" crossorigin href="/forge/assets/table-CtNlETLc.js">
21
+ <link rel="modulepreload" crossorigin href="/forge/assets/viz-BJuBCz_G.js">
22
+ <link rel="modulepreload" crossorigin href="/forge/assets/board-CzgvdLO8.js">
23
+ <link rel="stylesheet" crossorigin href="/forge/assets/vendor-CRS-psbw.css">
24
+ <link rel="stylesheet" crossorigin href="/forge/assets/index-D4A_bq8m.css">
25
+ </head>
26
+ <body class="bg-canvas text-ink antialiased">
27
+ <div id="root"></div>
28
+ </body>
29
+ </html>
@@ -1,7 +1,10 @@
1
1
  import type { IncomingMessage, ServerResponse } from "node:http";
2
2
  export type ForgeHttpMethod = "GET" | "POST" | "PATCH" | "PUT" | "DELETE";
3
3
  export type ForgePluginConfig = {
4
+ origin: string;
5
+ port: number;
4
6
  baseUrl: string;
7
+ webAppUrl: string;
5
8
  apiToken: string;
6
9
  actorLabel: string;
7
10
  timeoutMs: number;
@@ -17,6 +20,7 @@ export type CallForgeApiArgs = {
17
20
  idempotencyKey?: string | null;
18
21
  extraHeaders?: Record<string, string | null | undefined>;
19
22
  };
23
+ export type CallConfiguredForgeApiArgs = Omit<CallForgeApiArgs, "baseUrl" | "apiToken" | "actorLabel" | "timeoutMs">;
20
24
  export type ForgeProxyResponse = {
21
25
  status: number;
22
26
  body: unknown;
@@ -26,8 +30,11 @@ export declare class ForgePluginError extends Error {
26
30
  readonly code: string;
27
31
  constructor(status: number, code: string, message: string);
28
32
  }
33
+ export declare function buildForgeBaseUrl(origin: string, port: number): string;
34
+ export declare function buildForgeWebAppUrl(origin: string, port: number): string;
29
35
  export declare function canBootstrapOperatorSession(baseUrl: string): boolean;
30
36
  export declare function callForgeApi(args: CallForgeApiArgs): Promise<ForgeProxyResponse>;
37
+ export declare function callConfiguredForgeApi(config: ForgePluginConfig, args: CallConfiguredForgeApiArgs): Promise<ForgeProxyResponse>;
31
38
  export declare function readJsonRequestBody(request: IncomingMessage, options?: {
32
39
  maxBytes?: number;
33
40
  emptyObject?: boolean;
@@ -35,6 +42,7 @@ export declare function readJsonRequestBody(request: IncomingMessage, options?:
35
42
  export declare function readSingleHeaderValue(headers: IncomingMessage["headers"], name: string): string | null;
36
43
  export declare function requireApiToken(config: ForgePluginConfig): void;
37
44
  export declare function writeJsonResponse(response: ServerResponse, status: number, body: unknown): void;
45
+ export declare function writeRedirectResponse(response: ServerResponse, location: string): void;
38
46
  export declare function writeForgeProxyResponse(response: ServerResponse, result: ForgeProxyResponse): void;
39
47
  export declare function writePluginError(response: ServerResponse, error: unknown): void;
40
48
  export declare function expectForgeSuccess(result: ForgeProxyResponse): unknown;
@@ -1,4 +1,5 @@
1
1
  import packageJson from "../../package.json" with { type: "json" };
2
+ import { ensureForgeRuntimeReady } from "./local-runtime.js";
2
3
  const DEFAULT_REQUEST_BODY_LIMIT = 256_000;
3
4
  const DEFAULT_RESPONSE_BODY_LIMIT = 2_000_000;
4
5
  const operatorSessionCookies = new Map();
@@ -15,9 +16,20 @@ export class ForgePluginError extends Error {
15
16
  function normalizeBaseUrl(baseUrl) {
16
17
  return baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
17
18
  }
18
- function normalizeOrigin(baseUrl) {
19
+ function normalizeOriginUrl(baseUrl) {
19
20
  return new URL(normalizeBaseUrl(baseUrl)).origin;
20
21
  }
22
+ export function buildForgeBaseUrl(origin, port) {
23
+ const url = new URL(origin.endsWith("/") ? origin : `${origin}/`);
24
+ url.port = String(port);
25
+ url.pathname = "/";
26
+ url.search = "";
27
+ url.hash = "";
28
+ return url.origin;
29
+ }
30
+ export function buildForgeWebAppUrl(origin, port) {
31
+ return `${buildForgeBaseUrl(origin, port)}/forge/`;
32
+ }
21
33
  function isTailscaleIpv4(hostname) {
22
34
  const match = hostname.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/);
23
35
  if (!match) {
@@ -120,7 +132,7 @@ function buildRequestHeaders(args) {
120
132
  return headers;
121
133
  }
122
134
  async function ensureOperatorSessionCookie(baseUrl, timeoutMs) {
123
- const origin = normalizeOrigin(baseUrl);
135
+ const origin = normalizeOriginUrl(baseUrl);
124
136
  const cached = operatorSessionCookies.get(origin);
125
137
  if (cached) {
126
138
  return cached;
@@ -165,6 +177,16 @@ async function ensureOperatorSessionCookie(baseUrl, timeoutMs) {
165
177
  export async function callForgeApi(args) {
166
178
  return callForgeApiInternal(args, false);
167
179
  }
180
+ export async function callConfiguredForgeApi(config, args) {
181
+ await ensureForgeRuntimeReady(config);
182
+ return callForgeApi({
183
+ baseUrl: config.baseUrl,
184
+ apiToken: config.apiToken,
185
+ actorLabel: config.actorLabel,
186
+ timeoutMs: config.timeoutMs,
187
+ ...args
188
+ });
189
+ }
168
190
  async function callForgeApiInternal(args, retriedWithFreshSession) {
169
191
  const controller = new AbortController();
170
192
  const timeoutMs = Math.max(1000, args.timeoutMs ?? 15_000);
@@ -183,7 +205,7 @@ async function callForgeApiInternal(args, retriedWithFreshSession) {
183
205
  signal: controller.signal
184
206
  });
185
207
  if (!args.apiToken && sessionCookie && response.status === 401 && !retriedWithFreshSession) {
186
- operatorSessionCookies.delete(normalizeOrigin(args.baseUrl));
208
+ operatorSessionCookies.delete(normalizeOriginUrl(args.baseUrl));
187
209
  return callForgeApiInternal(args, true);
188
210
  }
189
211
  const body = await readResponseBody(response);
@@ -242,7 +264,7 @@ export function readSingleHeaderValue(headers, name) {
242
264
  }
243
265
  export function requireApiToken(config) {
244
266
  if (config.apiToken.trim().length === 0 && !canBootstrapOperatorSession(config.baseUrl)) {
245
- throw new ForgePluginError(401, "forge_plugin_token_required", "Forge apiToken is required for remote mutating plugin routes that cannot use local or Tailscale operator-session bootstrap");
267
+ throw new ForgePluginError(401, "forge_plugin_token_required", "Forge apiToken is required for remote plugin mutations when this target cannot use local or Tailscale operator-session bootstrap");
246
268
  }
247
269
  }
248
270
  export function writeJsonResponse(response, status, body) {
@@ -250,6 +272,11 @@ export function writeJsonResponse(response, status, body) {
250
272
  response.setHeader("content-type", "application/json; charset=utf-8");
251
273
  response.end(JSON.stringify(body));
252
274
  }
275
+ export function writeRedirectResponse(response, location) {
276
+ response.statusCode = 302;
277
+ response.setHeader("location", location);
278
+ response.end("");
279
+ }
253
280
  export function writeForgeProxyResponse(response, result) {
254
281
  writeJsonResponse(response, result.status, result.body);
255
282
  }
@@ -0,0 +1,3 @@
1
+ import type { ForgePluginConfig } from "./api-client.js";
2
+ export declare function ensureForgeRuntimeReady(config: ForgePluginConfig): Promise<void>;
3
+ export declare function primeForgeRuntime(config: ForgePluginConfig): void;
@@ -0,0 +1,135 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ const LOCAL_HOSTNAMES = new Set(["127.0.0.1", "localhost", "::1"]);
6
+ const STARTUP_TIMEOUT_MS = 15_000;
7
+ const HEALTHCHECK_TIMEOUT_MS = 1_500;
8
+ const HEALTHCHECK_INTERVAL_MS = 250;
9
+ let managedRuntimeChild = null;
10
+ let managedRuntimeKey = null;
11
+ let startupPromise = null;
12
+ function runtimeKey(config) {
13
+ return `${config.origin}:${config.port}`;
14
+ }
15
+ function isLocalOrigin(origin) {
16
+ try {
17
+ return LOCAL_HOSTNAMES.has(new URL(origin).hostname.toLowerCase());
18
+ }
19
+ catch {
20
+ return false;
21
+ }
22
+ }
23
+ function getCurrentModuleRoot() {
24
+ return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
25
+ }
26
+ function resolveLaunchPlan() {
27
+ const moduleRoot = getCurrentModuleRoot();
28
+ // Published or linked plugin package runtime.
29
+ const packagedEntry = path.join(moduleRoot, "dist", "server", "index.js");
30
+ const packagedMigrations = path.join(moduleRoot, "server", "migrations");
31
+ if (existsSync(packagedEntry) && existsSync(packagedMigrations)) {
32
+ return {
33
+ packageRoot: moduleRoot,
34
+ entryFile: packagedEntry
35
+ };
36
+ }
37
+ // Source-tree fallback for local development before packaging.
38
+ const repoRoot = moduleRoot;
39
+ const sourceEntry = path.join(repoRoot, "server", "src", "index.ts");
40
+ const tsxCli = path.join(repoRoot, "node_modules", "tsx", "dist", "cli.mjs");
41
+ if (existsSync(sourceEntry) && existsSync(tsxCli)) {
42
+ return {
43
+ packageRoot: repoRoot,
44
+ entryFile: tsxCli
45
+ };
46
+ }
47
+ return null;
48
+ }
49
+ async function isForgeHealthy(config, timeoutMs) {
50
+ const controller = new AbortController();
51
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
52
+ try {
53
+ const response = await fetch(new URL("/api/v1/health", config.baseUrl), {
54
+ method: "GET",
55
+ headers: {
56
+ accept: "application/json"
57
+ },
58
+ signal: controller.signal
59
+ });
60
+ return response.ok;
61
+ }
62
+ catch {
63
+ return false;
64
+ }
65
+ finally {
66
+ clearTimeout(timeout);
67
+ }
68
+ }
69
+ function spawnManagedRuntime(config, plan) {
70
+ const isPackagedServer = plan.entryFile.endsWith(path.join("dist", "server", "index.js"));
71
+ const args = isPackagedServer ? [plan.entryFile] : [plan.entryFile, path.join(plan.packageRoot, "server", "src", "index.ts")];
72
+ const child = spawn(process.execPath, args, {
73
+ cwd: plan.packageRoot,
74
+ env: {
75
+ ...process.env,
76
+ HOST: "127.0.0.1",
77
+ PORT: String(config.port),
78
+ FORGE_BASE_PATH: "/forge/"
79
+ },
80
+ stdio: "ignore",
81
+ detached: true
82
+ });
83
+ child.unref();
84
+ child.once("exit", () => {
85
+ if (managedRuntimeChild === child) {
86
+ managedRuntimeChild = null;
87
+ managedRuntimeKey = null;
88
+ }
89
+ });
90
+ managedRuntimeChild = child;
91
+ managedRuntimeKey = runtimeKey(config);
92
+ }
93
+ async function waitForRuntime(config, timeoutMs) {
94
+ const deadline = Date.now() + timeoutMs;
95
+ while (Date.now() < deadline) {
96
+ if (await isForgeHealthy(config, HEALTHCHECK_TIMEOUT_MS)) {
97
+ return;
98
+ }
99
+ await new Promise((resolve) => setTimeout(resolve, HEALTHCHECK_INTERVAL_MS));
100
+ }
101
+ throw new Error(`Forge local runtime did not become healthy at ${config.baseUrl} within ${timeoutMs}ms`);
102
+ }
103
+ export async function ensureForgeRuntimeReady(config) {
104
+ if (!isLocalOrigin(config.origin)) {
105
+ return;
106
+ }
107
+ if (await isForgeHealthy(config, HEALTHCHECK_TIMEOUT_MS)) {
108
+ return;
109
+ }
110
+ const key = runtimeKey(config);
111
+ if (startupPromise && managedRuntimeKey === key) {
112
+ return startupPromise;
113
+ }
114
+ const plan = resolveLaunchPlan();
115
+ if (!plan) {
116
+ return;
117
+ }
118
+ startupPromise = (async () => {
119
+ if (await isForgeHealthy(config, HEALTHCHECK_TIMEOUT_MS)) {
120
+ return;
121
+ }
122
+ if (!managedRuntimeChild || managedRuntimeKey !== key || managedRuntimeChild.killed) {
123
+ spawnManagedRuntime(config, plan);
124
+ }
125
+ await waitForRuntime(config, STARTUP_TIMEOUT_MS);
126
+ })().finally(() => {
127
+ startupPromise = null;
128
+ });
129
+ return startupPromise;
130
+ }
131
+ export function primeForgeRuntime(config) {
132
+ void ensureForgeRuntimeReady(config).catch(() => {
133
+ // Keep plugin registration non-blocking. Failures surface on first real call.
134
+ });
135
+ }
@@ -1,9 +1,9 @@
1
1
  export type ApiRouteKey = `${Uppercase<string>} ${string}`;
2
- export type ForgePluginRouteExclusion = {
2
+ export type ForgeSupportedPluginApiRoute = {
3
3
  method: Uppercase<string>;
4
4
  path: string;
5
- reason: "browser-session-telemetry" | "legacy-alias" | "operator-token-bootstrap" | "sse-forwarding";
5
+ purpose: "diagnostics" | "overview" | "operator_context" | "onboarding" | "psyche" | "xp" | "weekly_review" | "entities" | "work" | "insights";
6
6
  };
7
- export declare const FORGE_PLUGIN_ROUTE_EXCLUSIONS: ForgePluginRouteExclusion[];
7
+ export declare const FORGE_SUPPORTED_PLUGIN_API_ROUTES: ForgeSupportedPluginApiRoute[];
8
8
  export declare function makeApiRouteKey(method: string, path: string): ApiRouteKey;
9
- export declare function collectExcludedApiRouteKeys(): Set<`${Uppercase<string>} ${string}`>;
9
+ export declare function collectSupportedPluginApiRouteKeys(): Set<`${Uppercase<string>} ${string}`>;
@@ -1,39 +1,29 @@
1
- export const FORGE_PLUGIN_ROUTE_EXCLUSIONS = [
2
- {
3
- method: "GET",
4
- path: "/api/v1/campaigns",
5
- reason: "legacy-alias"
6
- },
7
- {
8
- method: "POST",
9
- path: "/api/v1/settings/tokens",
10
- reason: "operator-token-bootstrap"
11
- },
12
- {
13
- method: "POST",
14
- path: "/api/v1/settings/tokens/:id/rotate",
15
- reason: "operator-token-bootstrap"
16
- },
17
- {
18
- method: "POST",
19
- path: "/api/v1/settings/tokens/:id/revoke",
20
- reason: "operator-token-bootstrap"
21
- },
22
- {
23
- method: "POST",
24
- path: "/api/v1/session-events",
25
- reason: "browser-session-telemetry"
26
- },
27
- {
28
- method: "GET",
29
- path: "/api/v1/events/stream",
30
- reason: "sse-forwarding"
31
- }
1
+ export const FORGE_SUPPORTED_PLUGIN_API_ROUTES = [
2
+ { method: "GET", path: "/api/v1/health", purpose: "diagnostics" },
3
+ { method: "GET", path: "/api/v1/operator/overview", purpose: "overview" },
4
+ { method: "GET", path: "/api/v1/operator/context", purpose: "operator_context" },
5
+ { method: "GET", path: "/api/v1/agents/onboarding", purpose: "onboarding" },
6
+ { method: "GET", path: "/api/v1/psyche/overview", purpose: "psyche" },
7
+ { method: "GET", path: "/api/v1/metrics/xp", purpose: "xp" },
8
+ { method: "GET", path: "/api/v1/reviews/weekly", purpose: "weekly_review" },
9
+ { method: "POST", path: "/api/v1/entities/search", purpose: "entities" },
10
+ { method: "POST", path: "/api/v1/entities/create", purpose: "entities" },
11
+ { method: "POST", path: "/api/v1/entities/update", purpose: "entities" },
12
+ { method: "POST", path: "/api/v1/entities/delete", purpose: "entities" },
13
+ { method: "POST", path: "/api/v1/entities/restore", purpose: "entities" },
14
+ { method: "POST", path: "/api/v1/operator/log-work", purpose: "work" },
15
+ { method: "POST", path: "/api/v1/tasks/:id/runs", purpose: "work" },
16
+ { method: "GET", path: "/api/v1/task-runs", purpose: "work" },
17
+ { method: "POST", path: "/api/v1/task-runs/:id/heartbeat", purpose: "work" },
18
+ { method: "POST", path: "/api/v1/task-runs/:id/focus", purpose: "work" },
19
+ { method: "POST", path: "/api/v1/task-runs/:id/complete", purpose: "work" },
20
+ { method: "POST", path: "/api/v1/task-runs/:id/release", purpose: "work" },
21
+ { method: "POST", path: "/api/v1/insights", purpose: "insights" }
32
22
  ];
33
23
  export function makeApiRouteKey(method, path) {
34
24
  const normalizedPath = path.replaceAll(/\{([^}]+)\}/g, ":$1");
35
25
  return `${method.toUpperCase()} ${normalizedPath}`;
36
26
  }
37
- export function collectExcludedApiRouteKeys() {
38
- return new Set(FORGE_PLUGIN_ROUTE_EXCLUSIONS.map((route) => makeApiRouteKey(route.method, route.path)));
27
+ export function collectSupportedPluginApiRouteKeys() {
28
+ return new Set(FORGE_SUPPORTED_PLUGIN_API_ROUTES.map((route) => makeApiRouteKey(route.method, route.path)));
39
29
  }
@@ -1,8 +1,10 @@
1
- import type { ForgePluginConfig } from "./api-client.js";
1
+ import { type ForgePluginConfig } from "./api-client.js";
2
2
  import type { ForgePluginConfigSchema, ForgePluginRegistrationApi } from "./plugin-sdk-types.js";
3
- export declare const FORGE_PLUGIN_ID = "forge";
3
+ export declare const FORGE_PLUGIN_ID = "forge-openclaw-plugin";
4
4
  export declare const FORGE_PLUGIN_NAME = "Forge";
5
- export declare const FORGE_PLUGIN_DESCRIPTION = "Thin OpenClaw adapter for the live Forge /api/v1 collaboration API.";
5
+ export declare const FORGE_PLUGIN_DESCRIPTION = "Curated OpenClaw adapter for the Forge collaboration API, UI entrypoint, and localhost auto-start runtime.";
6
+ export declare const DEFAULT_FORGE_ORIGIN = "http://127.0.0.1";
7
+ export declare const DEFAULT_FORGE_PORT = 4317;
6
8
  export declare function resolveForgePluginConfig(pluginConfig: unknown): ForgePluginConfig;
7
9
  export declare const forgePluginConfigSchema: ForgePluginConfigSchema;
8
10
  export declare function registerForgePlugin(api: ForgePluginRegistrationApi): void;
@@ -1,11 +1,35 @@
1
+ import { buildForgeBaseUrl, buildForgeWebAppUrl } from "./api-client.js";
2
+ import { primeForgeRuntime } from "./local-runtime.js";
1
3
  import { registerForgePluginCli, registerForgePluginRoutes } from "./routes.js";
2
4
  import { registerForgePluginTools } from "./tools.js";
3
- export const FORGE_PLUGIN_ID = "forge";
5
+ export const FORGE_PLUGIN_ID = "forge-openclaw-plugin";
4
6
  export const FORGE_PLUGIN_NAME = "Forge";
5
- export const FORGE_PLUGIN_DESCRIPTION = "Thin OpenClaw adapter for the live Forge /api/v1 collaboration API.";
7
+ export const FORGE_PLUGIN_DESCRIPTION = "Curated OpenClaw adapter for the Forge collaboration API, UI entrypoint, and localhost auto-start runtime.";
8
+ export const DEFAULT_FORGE_ORIGIN = "http://127.0.0.1";
9
+ export const DEFAULT_FORGE_PORT = 4317;
6
10
  function normalizeString(value, fallback) {
7
11
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : fallback;
8
12
  }
13
+ function normalizeOrigin(value, fallback) {
14
+ const candidate = normalizeString(value, fallback);
15
+ try {
16
+ const url = new URL(candidate);
17
+ url.port = "";
18
+ url.pathname = "/";
19
+ url.search = "";
20
+ url.hash = "";
21
+ return url.origin;
22
+ }
23
+ catch {
24
+ return fallback;
25
+ }
26
+ }
27
+ function normalizePort(value, fallback) {
28
+ if (typeof value !== "number" || !Number.isFinite(value)) {
29
+ return fallback;
30
+ }
31
+ return Math.min(65535, Math.max(1, Math.round(value)));
32
+ }
9
33
  function normalizeTimeout(value, fallback) {
10
34
  if (typeof value !== "number" || !Number.isFinite(value)) {
11
35
  return fallback;
@@ -14,8 +38,13 @@ function normalizeTimeout(value, fallback) {
14
38
  }
15
39
  export function resolveForgePluginConfig(pluginConfig) {
16
40
  const raw = (pluginConfig ?? {});
41
+ const origin = normalizeOrigin(raw.origin, DEFAULT_FORGE_ORIGIN);
42
+ const port = normalizePort(raw.port, DEFAULT_FORGE_PORT);
17
43
  return {
18
- baseUrl: normalizeString(raw.baseUrl, "http://127.0.0.1:3017"),
44
+ origin,
45
+ port,
46
+ baseUrl: buildForgeBaseUrl(origin, port),
47
+ webAppUrl: buildForgeWebAppUrl(origin, port),
19
48
  apiToken: typeof raw.apiToken === "string" ? raw.apiToken.trim() : "",
20
49
  actorLabel: normalizeString(raw.actorLabel, "aurel"),
21
50
  timeoutMs: normalizeTimeout(raw.timeoutMs, 15_000)
@@ -29,10 +58,17 @@ export const forgePluginConfigSchema = {
29
58
  type: "object",
30
59
  additionalProperties: false,
31
60
  properties: {
32
- baseUrl: {
61
+ origin: {
33
62
  type: "string",
34
- default: "http://127.0.0.1:3017",
35
- description: "Base URL of the live Forge API bridge."
63
+ default: DEFAULT_FORGE_ORIGIN,
64
+ description: "Forge protocol and host without the port. Example: http://127.0.0.1. Localhost targets auto-start the bundled Forge runtime."
65
+ },
66
+ port: {
67
+ type: "integer",
68
+ default: DEFAULT_FORGE_PORT,
69
+ minimum: 1,
70
+ maximum: 65535,
71
+ description: "Forge server port. Override this when your local machine uses a different port."
36
72
  },
37
73
  apiToken: {
38
74
  type: "string",
@@ -54,10 +90,15 @@ export const forgePluginConfigSchema = {
54
90
  }
55
91
  },
56
92
  uiHints: {
57
- baseUrl: {
58
- label: "Forge Base URL",
59
- help: "Base URL of the live Forge API bridge.",
60
- placeholder: "http://127.0.0.1:3017"
93
+ origin: {
94
+ label: "Forge Origin",
95
+ help: "Protocol and host for Forge without the port. Example: http://127.0.0.1. Localhost targets auto-start Forge.",
96
+ placeholder: "http://127.0.0.1"
97
+ },
98
+ port: {
99
+ label: "Forge Port",
100
+ help: "Forge server port. Change this if your local machine uses another port.",
101
+ placeholder: "4317"
61
102
  },
62
103
  apiToken: {
63
104
  label: "Forge API Token",
@@ -79,6 +120,7 @@ export const forgePluginConfigSchema = {
79
120
  };
80
121
  export function registerForgePlugin(api) {
81
122
  const config = resolveForgePluginConfig(api.pluginConfig);
123
+ primeForgeRuntime(config);
82
124
  registerForgePluginRoutes(api, config);
83
125
  registerForgePluginCli(api, config);
84
126
  registerForgePluginTools(api, config);
@@ -2,7 +2,8 @@ import { type ForgeHttpMethod, type ForgePluginConfig } from "./api-client.js";
2
2
  import { type ApiRouteKey } from "./parity.js";
3
3
  import type { ForgePluginCliApi, ForgePluginRouteApi, ForgeRegisteredHttpRoute } from "./plugin-sdk-types.js";
4
4
  type PluginRouteMatch = NonNullable<ForgeRegisteredHttpRoute["match"]>;
5
- type RouteOperation = {
5
+ type ProxyRouteOperation = {
6
+ kind?: "proxy";
6
7
  method: ForgeHttpMethod;
7
8
  pattern: RegExp;
8
9
  upstreamPath: string;
@@ -10,6 +11,12 @@ type RouteOperation = {
10
11
  requiresToken?: boolean;
11
12
  requestBody?: "json";
12
13
  };
14
+ type UiRedirectRouteOperation = {
15
+ kind: "ui_redirect";
16
+ method: "GET";
17
+ pattern: RegExp;
18
+ };
19
+ type RouteOperation = ProxyRouteOperation | UiRedirectRouteOperation;
13
20
  type RouteGroup = {
14
21
  path: string;
15
22
  match: PluginRouteMatch;
@@ -18,9 +25,11 @@ type RouteGroup = {
18
25
  export declare const FORGE_PLUGIN_ROUTE_GROUPS: RouteGroup[];
19
26
  export declare function collectMirroredApiRouteKeys(): Set<ApiRouteKey>;
20
27
  export declare function buildRouteParityReport(pathMap: Record<string, Record<string, unknown>>): {
28
+ supported: `${Uppercase<string>} ${string}`[];
21
29
  mirrored: `${Uppercase<string>} ${string}`[];
22
- excluded: `${Uppercase<string>} ${string}`[];
23
- uncovered: `${Uppercase<string>} ${string}`[];
30
+ missingFromPlugin: `${Uppercase<string>} ${string}`[];
31
+ missingFromOpenApi: `${Uppercase<string>} ${string}`[];
32
+ unexpectedMirrors: `${Uppercase<string>} ${string}`[];
24
33
  };
25
34
  export declare function registerForgePluginRoutes(api: ForgePluginRouteApi, config: ForgePluginConfig): void;
26
35
  export declare function registerForgePluginCli(api: ForgePluginCliApi, config: ForgePluginConfig): void;