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.
- package/README.md +114 -6
- package/dist/assets/board-CzgvdLO8.js +6 -0
- package/dist/assets/board-CzgvdLO8.js.map +1 -0
- package/dist/assets/favicon-BCHm9dUV.ico +0 -0
- package/dist/assets/index-8d_oM8fL.js +27 -0
- package/dist/assets/index-8d_oM8fL.js.map +1 -0
- package/dist/assets/index-D4A_bq8m.css +1 -0
- package/dist/assets/motion-STUd1O46.js +10 -0
- package/dist/assets/motion-STUd1O46.js.map +1 -0
- package/dist/assets/plus-jakarta-sans-latin-ext-wght-normal-DmpS2jIq.woff2 +0 -0
- package/dist/assets/plus-jakarta-sans-latin-wght-normal-eXO_dkmS.woff2 +0 -0
- package/dist/assets/plus-jakarta-sans-vietnamese-wght-normal-qRpaaN48.woff2 +0 -0
- package/dist/assets/sora-latin-ext-wght-normal-CawQDOvP.woff2 +0 -0
- package/dist/assets/sora-latin-wght-normal-DdqRvwsR.woff2 +0 -0
- package/dist/assets/space-grotesk-latin-500-normal-CNSSEhBt.woff +0 -0
- package/dist/assets/space-grotesk-latin-500-normal-lFbtlQH6.woff2 +0 -0
- package/dist/assets/space-grotesk-latin-700-normal-CwsQ-cCU.woff +0 -0
- package/dist/assets/space-grotesk-latin-700-normal-RjhwGPKo.woff2 +0 -0
- package/dist/assets/space-grotesk-latin-ext-500-normal-3dgZTiw9.woff +0 -0
- package/dist/assets/space-grotesk-latin-ext-500-normal-DUe3BAxM.woff2 +0 -0
- package/dist/assets/space-grotesk-latin-ext-700-normal-BQnZhY3m.woff2 +0 -0
- package/dist/assets/space-grotesk-latin-ext-700-normal-HVCqSBdx.woff +0 -0
- package/dist/assets/space-grotesk-vietnamese-500-normal-BTqKIpxg.woff +0 -0
- package/dist/assets/space-grotesk-vietnamese-500-normal-BmEvtly_.woff2 +0 -0
- package/dist/assets/space-grotesk-vietnamese-700-normal-DMty7AZE.woff2 +0 -0
- package/dist/assets/space-grotesk-vietnamese-700-normal-Duxec5Rn.woff +0 -0
- package/dist/assets/table-CtNlETLc.js +23 -0
- package/dist/assets/table-CtNlETLc.js.map +1 -0
- package/dist/assets/ui-ThzkR_oW.js +46 -0
- package/dist/assets/ui-ThzkR_oW.js.map +1 -0
- package/dist/assets/vendor-CRS-psbw.css +1 -0
- package/dist/assets/vendor-DyHAI6nk.js +423 -0
- package/dist/assets/vendor-DyHAI6nk.js.map +1 -0
- package/dist/assets/viz-BJuBCz_G.js +34 -0
- package/dist/assets/viz-BJuBCz_G.js.map +1 -0
- package/dist/favicon.ico +0 -0
- package/dist/favicon.png +0 -0
- package/dist/index.html +29 -0
- package/dist/openclaw/api-client.d.ts +8 -0
- package/dist/openclaw/api-client.js +31 -4
- package/dist/openclaw/local-runtime.d.ts +3 -0
- package/dist/openclaw/local-runtime.js +135 -0
- package/dist/openclaw/parity.d.ts +4 -4
- package/dist/openclaw/parity.js +23 -33
- package/dist/openclaw/plugin-entry-shared.d.ts +5 -3
- package/dist/openclaw/plugin-entry-shared.js +52 -10
- package/dist/openclaw/routes.d.ts +12 -3
- package/dist/openclaw/routes.js +156 -924
- package/dist/openclaw/tools.js +242 -1100
- package/dist/server/app.js +2450 -0
- package/dist/server/db.js +313 -0
- package/dist/server/e2e-server.js +20 -0
- package/dist/server/errors.js +15 -0
- package/dist/server/index.js +16 -0
- package/dist/server/managers/base.js +17 -0
- package/dist/server/managers/contracts.js +47 -0
- package/dist/server/managers/platform/api-gateway-manager.js +11 -0
- package/dist/server/managers/platform/audit-manager.js +15 -0
- package/dist/server/managers/platform/authentication-manager.js +56 -0
- package/dist/server/managers/platform/authorization-manager.js +56 -0
- package/dist/server/managers/platform/background-job-manager.js +10 -0
- package/dist/server/managers/platform/configuration-manager.js +33 -0
- package/dist/server/managers/platform/database-manager.js +14 -0
- package/dist/server/managers/platform/event-bus-manager.js +7 -0
- package/dist/server/managers/platform/external-service-manager.js +11 -0
- package/dist/server/managers/platform/health-manager.js +7 -0
- package/dist/server/managers/platform/migration-manager.js +8 -0
- package/dist/server/managers/platform/search-index-manager.js +4 -0
- package/dist/server/managers/platform/secrets-manager.js +19 -0
- package/dist/server/managers/platform/session-manager.js +121 -0
- package/dist/server/managers/platform/storage-manager.js +16 -0
- package/dist/server/managers/platform/token-manager.js +37 -0
- package/dist/server/managers/platform/transaction-manager.js +8 -0
- package/dist/server/managers/platform/trusted-network.js +39 -0
- package/dist/server/managers/runtime.js +56 -0
- package/dist/server/managers/type-guards.js +4 -0
- package/dist/server/openapi.js +3512 -0
- package/dist/server/psyche-types.js +395 -0
- package/dist/server/repositories/activity-events.js +157 -0
- package/dist/server/repositories/collaboration.js +497 -0
- package/dist/server/repositories/comments.js +176 -0
- package/dist/server/repositories/deleted-entities.js +192 -0
- package/dist/server/repositories/domains.js +30 -0
- package/dist/server/repositories/event-log.js +64 -0
- package/dist/server/repositories/goals.js +159 -0
- package/dist/server/repositories/projects.js +214 -0
- package/dist/server/repositories/psyche.js +1356 -0
- package/dist/server/repositories/rewards.js +675 -0
- package/dist/server/repositories/settings.js +399 -0
- package/dist/server/repositories/tags.js +160 -0
- package/dist/server/repositories/task-runs.js +488 -0
- package/dist/server/repositories/tasks.js +413 -0
- package/dist/server/services/context.js +214 -0
- package/dist/server/services/dashboard.js +170 -0
- package/dist/server/services/entity-crud.js +576 -0
- package/dist/server/services/gamification.js +215 -0
- package/dist/server/services/insights.js +91 -0
- package/dist/server/services/projects.js +75 -0
- package/dist/server/services/psyche.js +63 -0
- package/dist/server/services/relations.js +28 -0
- package/dist/server/services/reviews.js +88 -0
- package/dist/server/services/run-recovery.js +13 -0
- package/dist/server/services/tagging.js +49 -0
- package/dist/server/services/task-run-watchdog.js +92 -0
- package/dist/server/services/work-time.js +176 -0
- package/dist/server/types.js +999 -0
- package/dist/server/web.js +91 -0
- package/openclaw.plugin.json +22 -10
- package/package.json +17 -4
- package/server/migrations/001_core.sql +333 -0
- package/server/migrations/002_psyche.sql +241 -0
- package/server/migrations/003_timer_execution.sql +18 -0
- package/server/migrations/004_psyche_linked_entities.sql +5 -0
- package/server/migrations/005_adaptive_schemas.sql +157 -0
- package/server/migrations/006_psyche_auth_setting.sql +4 -0
- package/server/migrations/007_deleted_entities.sql +16 -0
- package/skills/forge-openclaw/SKILL.md +189 -275
package/dist/favicon.ico
ADDED
|
Binary file
|
package/dist/favicon.png
ADDED
|
Binary file
|
package/dist/index.html
ADDED
|
@@ -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
|
|
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 =
|
|
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(
|
|
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
|
|
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,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
|
|
2
|
+
export type ForgeSupportedPluginApiRoute = {
|
|
3
3
|
method: Uppercase<string>;
|
|
4
4
|
path: string;
|
|
5
|
-
|
|
5
|
+
purpose: "diagnostics" | "overview" | "operator_context" | "onboarding" | "psyche" | "xp" | "weekly_review" | "entities" | "work" | "insights";
|
|
6
6
|
};
|
|
7
|
-
export declare const
|
|
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
|
|
9
|
+
export declare function collectSupportedPluginApiRouteKeys(): Set<`${Uppercase<string>} ${string}`>;
|
package/dist/openclaw/parity.js
CHANGED
|
@@ -1,39 +1,29 @@
|
|
|
1
|
-
export const
|
|
2
|
-
{
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
},
|
|
7
|
-
{
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
},
|
|
12
|
-
{
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
38
|
-
return new Set(
|
|
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
|
|
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 = "
|
|
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 = "
|
|
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
|
-
|
|
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
|
-
|
|
61
|
+
origin: {
|
|
33
62
|
type: "string",
|
|
34
|
-
default:
|
|
35
|
-
description: "
|
|
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
|
-
|
|
58
|
-
label: "Forge
|
|
59
|
-
help: "
|
|
60
|
-
placeholder: "http://127.0.0.1
|
|
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
|
|
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
|
-
|
|
23
|
-
|
|
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;
|