forge-openclaw-plugin 0.2.44 → 0.2.47
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/dist/assets/index-BejDHw1R.js +91 -0
- package/dist/assets/index-BejDHw1R.js.map +1 -0
- package/dist/index.html +1 -1
- package/dist/openclaw/api-client.d.ts +1 -0
- package/dist/openclaw/plugin-entry-shared.js +13 -0
- package/dist/openclaw/session-bootstrap.d.ts +11 -0
- package/dist/openclaw/session-bootstrap.js +151 -13
- package/dist/server/server/migrations/050_agent_token_bootstrap_policy.sql +2 -0
- package/dist/server/server/migrations/051_agent_token_scope_policy.sql +2 -0
- package/dist/server/server/src/app.js +193 -43
- package/dist/server/server/src/managers/platform/authentication-manager.js +5 -0
- package/dist/server/server/src/managers/platform/session-manager.js +5 -0
- package/dist/server/server/src/openapi.js +59 -0
- package/dist/server/server/src/repositories/settings.js +26 -6
- package/dist/server/server/src/types.js +83 -1
- package/dist/server/src/lib/schemas.js +16 -1
- package/openclaw.plugin.json +10 -1
- package/package.json +1 -1
- package/server/migrations/050_agent_token_bootstrap_policy.sql +2 -0
- package/server/migrations/051_agent_token_scope_policy.sql +2 -0
- package/dist/assets/index-s24CefIb.js +0 -91
- package/dist/assets/index-s24CefIb.js.map +0 -1
package/dist/index.html
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
/>
|
|
14
14
|
<link rel="icon" type="image/png" href="/forge/assets/favicon-BCHm9dUV.ico" />
|
|
15
15
|
<link rel="alternate icon" href="/forge/assets/favicon-BCHm9dUV.ico" />
|
|
16
|
-
<script type="module" crossorigin src="/forge/assets/index-
|
|
16
|
+
<script type="module" crossorigin src="/forge/assets/index-BejDHw1R.js"></script>
|
|
17
17
|
<link rel="modulepreload" crossorigin href="/forge/assets/vendor-D_NZFJze.js">
|
|
18
18
|
<link rel="modulepreload" crossorigin href="/forge/assets/board-CAszQU7Y.js">
|
|
19
19
|
<link rel="modulepreload" crossorigin href="/forge/assets/ui-B5MjRjKe.js">
|
|
@@ -55,6 +55,9 @@ function normalizeDataRoot(value) {
|
|
|
55
55
|
function normalizeOptionalString(value) {
|
|
56
56
|
return typeof value === "string" ? value.trim() : "";
|
|
57
57
|
}
|
|
58
|
+
function normalizeBoolean(value, fallback) {
|
|
59
|
+
return typeof value === "boolean" ? value : fallback;
|
|
60
|
+
}
|
|
58
61
|
function isLocalOrigin(origin) {
|
|
59
62
|
try {
|
|
60
63
|
return LOCAL_HOSTNAMES.has(new URL(origin).hostname.toLowerCase());
|
|
@@ -98,6 +101,7 @@ export function resolveForgePluginConfig(pluginConfig) {
|
|
|
98
101
|
dataRoot: normalizeDataRoot(raw.dataRoot),
|
|
99
102
|
apiToken: typeof raw.apiToken === "string" ? raw.apiToken.trim() : "",
|
|
100
103
|
actorLabel: normalizeOptionalString(raw.actorLabel),
|
|
104
|
+
injectBootstrapContext: normalizeBoolean(raw.injectBootstrapContext, true),
|
|
101
105
|
timeoutMs: normalizeTimeout(raw.timeoutMs, 15_000)
|
|
102
106
|
};
|
|
103
107
|
}
|
|
@@ -136,6 +140,11 @@ export const forgePluginConfigSchema = {
|
|
|
136
140
|
default: DEFAULT_OPENCLAW_ACTOR_LABEL,
|
|
137
141
|
description: "Optional acting user label recorded in Forge provenance headers. Leave blank to inherit the local operator session label automatically."
|
|
138
142
|
},
|
|
143
|
+
injectBootstrapContext: {
|
|
144
|
+
type: "boolean",
|
|
145
|
+
default: true,
|
|
146
|
+
description: "Whether OpenClaw should inject a preseeded Forge BOOTSTRAP.md file into new agent sessions. Disable this to conserve context or model-token budget."
|
|
147
|
+
},
|
|
139
148
|
timeoutMs: {
|
|
140
149
|
type: "integer",
|
|
141
150
|
default: 15000,
|
|
@@ -173,6 +182,10 @@ export const forgePluginConfigSchema = {
|
|
|
173
182
|
help: "Optional acting user label for provenance. Leave blank to inherit the local operator session label, or set one when a child agent should announce itself under a specific user.",
|
|
174
183
|
placeholder: "Inherited from Forge operator session"
|
|
175
184
|
},
|
|
185
|
+
injectBootstrapContext: {
|
|
186
|
+
label: "Inject Bootstrap Context",
|
|
187
|
+
help: "Enabled by default. Turn this off when you want OpenClaw sessions to start without a preseeded Forge BOOTSTRAP.md context file to conserve token budget."
|
|
188
|
+
},
|
|
176
189
|
timeoutMs: {
|
|
177
190
|
label: "Request Timeout (ms)",
|
|
178
191
|
help: "Maximum time to wait before the plugin aborts an upstream Forge request.",
|
|
@@ -62,7 +62,18 @@ type ForgeOperatorOverview = {
|
|
|
62
62
|
warnings?: string[];
|
|
63
63
|
operator?: ForgeOperatorContext | null;
|
|
64
64
|
};
|
|
65
|
+
type ForgeBootstrapPolicy = {
|
|
66
|
+
mode: "disabled" | "active_only" | "scoped" | "full";
|
|
67
|
+
goalsLimit: number;
|
|
68
|
+
projectsLimit: number;
|
|
69
|
+
tasksLimit: number;
|
|
70
|
+
habitsLimit: number;
|
|
71
|
+
strategiesLimit: number;
|
|
72
|
+
peoplePageLimit: number;
|
|
73
|
+
includePeoplePages: boolean;
|
|
74
|
+
};
|
|
65
75
|
type ForgeSessionBootstrapPayload = {
|
|
76
|
+
bootstrapPolicy: ForgeBootstrapPolicy;
|
|
66
77
|
overview: ForgeOperatorOverview | null;
|
|
67
78
|
goals: ForgeGoalRecord[];
|
|
68
79
|
projects: ForgeProjectRecord[];
|
|
@@ -3,6 +3,16 @@ import { isAgentBootstrapEvent } from "openclaw/plugin-sdk/hook-runtime";
|
|
|
3
3
|
import { callConfiguredForgeApi, expectForgeSuccess } from "./api-client.js";
|
|
4
4
|
const FORGE_SESSION_BOOTSTRAP_PATH = ".forge/generated/FORGE_SESSION_BOOTSTRAP.md";
|
|
5
5
|
const FORGE_SESSION_BOOTSTRAP_NAME = "forge-session-bootstrap";
|
|
6
|
+
const DEFAULT_BOOTSTRAP_POLICY = {
|
|
7
|
+
mode: "active_only",
|
|
8
|
+
goalsLimit: 5,
|
|
9
|
+
projectsLimit: 8,
|
|
10
|
+
tasksLimit: 10,
|
|
11
|
+
habitsLimit: 6,
|
|
12
|
+
strategiesLimit: 4,
|
|
13
|
+
peoplePageLimit: 4,
|
|
14
|
+
includePeoplePages: true
|
|
15
|
+
};
|
|
6
16
|
function isRecord(value) {
|
|
7
17
|
return typeof value === "object" && value !== null;
|
|
8
18
|
}
|
|
@@ -12,6 +22,33 @@ function asArray(value) {
|
|
|
12
22
|
function cleanInline(value) {
|
|
13
23
|
return (value ?? "").replace(/\s+/g, " ").trim();
|
|
14
24
|
}
|
|
25
|
+
function clampBudget(value, fallback, max) {
|
|
26
|
+
const numeric = typeof value === "number" ? value : Number(value);
|
|
27
|
+
return Math.min(Math.max(Number.isFinite(numeric) ? numeric : fallback, 0), max);
|
|
28
|
+
}
|
|
29
|
+
function sanitizeBootstrapPolicy(value) {
|
|
30
|
+
if (!isRecord(value)) {
|
|
31
|
+
return { ...DEFAULT_BOOTSTRAP_POLICY };
|
|
32
|
+
}
|
|
33
|
+
const mode = value.mode === "disabled" ||
|
|
34
|
+
value.mode === "active_only" ||
|
|
35
|
+
value.mode === "scoped" ||
|
|
36
|
+
value.mode === "full"
|
|
37
|
+
? value.mode
|
|
38
|
+
: DEFAULT_BOOTSTRAP_POLICY.mode;
|
|
39
|
+
return {
|
|
40
|
+
mode,
|
|
41
|
+
goalsLimit: clampBudget(value.goalsLimit, DEFAULT_BOOTSTRAP_POLICY.goalsLimit, 100),
|
|
42
|
+
projectsLimit: clampBudget(value.projectsLimit, DEFAULT_BOOTSTRAP_POLICY.projectsLimit, 100),
|
|
43
|
+
tasksLimit: clampBudget(value.tasksLimit, DEFAULT_BOOTSTRAP_POLICY.tasksLimit, 100),
|
|
44
|
+
habitsLimit: clampBudget(value.habitsLimit, DEFAULT_BOOTSTRAP_POLICY.habitsLimit, 100),
|
|
45
|
+
strategiesLimit: clampBudget(value.strategiesLimit, DEFAULT_BOOTSTRAP_POLICY.strategiesLimit, 100),
|
|
46
|
+
peoplePageLimit: clampBudget(value.peoplePageLimit, DEFAULT_BOOTSTRAP_POLICY.peoplePageLimit, 50),
|
|
47
|
+
includePeoplePages: typeof value.includePeoplePages === "boolean"
|
|
48
|
+
? value.includePeoplePages
|
|
49
|
+
: DEFAULT_BOOTSTRAP_POLICY.includePeoplePages
|
|
50
|
+
};
|
|
51
|
+
}
|
|
15
52
|
function excerpt(value, maxLength) {
|
|
16
53
|
const normalized = cleanInline(value);
|
|
17
54
|
if (!normalized) {
|
|
@@ -105,6 +142,7 @@ export function buildForgeSessionBootstrapContext(payload) {
|
|
|
105
142
|
if (payload.overview?.generatedAt) {
|
|
106
143
|
lines.push(`Generated at: ${payload.overview.generatedAt}`, "");
|
|
107
144
|
}
|
|
145
|
+
lines.push(`Bootstrap mode: ${payload.bootstrapPolicy.mode.replaceAll("_", " ")}`, "");
|
|
108
146
|
if (overview) {
|
|
109
147
|
lines.push("## Current Forge Snapshot", "");
|
|
110
148
|
lines.push(`- Active projects in operator view: ${overview.activeProjects?.length ?? 0}`);
|
|
@@ -177,38 +215,138 @@ async function readForgePayload(config, pathName) {
|
|
|
177
215
|
});
|
|
178
216
|
return expectForgeSuccess(result);
|
|
179
217
|
}
|
|
218
|
+
function withQuery(pathName, query) {
|
|
219
|
+
const search = new URLSearchParams();
|
|
220
|
+
for (const [key, value] of Object.entries(query)) {
|
|
221
|
+
if (value === undefined) {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
search.set(key, String(value));
|
|
225
|
+
}
|
|
226
|
+
const encoded = search.toString();
|
|
227
|
+
return encoded ? `${pathName}?${encoded}` : pathName;
|
|
228
|
+
}
|
|
229
|
+
function resolveBootstrapPolicy(onboardingResponse) {
|
|
230
|
+
const onboarding = onboardingResponse && isRecord(onboardingResponse) && "onboarding" in onboardingResponse
|
|
231
|
+
? onboardingResponse.onboarding
|
|
232
|
+
: null;
|
|
233
|
+
if (isRecord(onboarding) && isRecord(onboarding.effectiveBootstrapPolicy)) {
|
|
234
|
+
return sanitizeBootstrapPolicy(onboarding.effectiveBootstrapPolicy);
|
|
235
|
+
}
|
|
236
|
+
if (isRecord(onboarding) && isRecord(onboarding.defaultBootstrapPolicy)) {
|
|
237
|
+
return sanitizeBootstrapPolicy(onboarding.defaultBootstrapPolicy);
|
|
238
|
+
}
|
|
239
|
+
return { ...DEFAULT_BOOTSTRAP_POLICY };
|
|
240
|
+
}
|
|
180
241
|
async function loadForgeSessionBootstrapPayload(config) {
|
|
242
|
+
const onboardingResponse = await readForgePayload(config, "/api/v1/agents/onboarding");
|
|
243
|
+
const bootstrapPolicy = resolveBootstrapPolicy(onboardingResponse);
|
|
244
|
+
if (bootstrapPolicy.mode === "disabled") {
|
|
245
|
+
return {
|
|
246
|
+
bootstrapPolicy,
|
|
247
|
+
overview: null,
|
|
248
|
+
goals: [],
|
|
249
|
+
projects: [],
|
|
250
|
+
tasks: [],
|
|
251
|
+
habits: [],
|
|
252
|
+
strategies: [],
|
|
253
|
+
peoplePages: []
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
const goalsPath = bootstrapPolicy.mode === "full"
|
|
257
|
+
? withQuery("/api/v1/goals", { limit: 100 })
|
|
258
|
+
: withQuery("/api/v1/goals", {
|
|
259
|
+
status: bootstrapPolicy.mode === "active_only" ? "active" : undefined,
|
|
260
|
+
limit: bootstrapPolicy.goalsLimit
|
|
261
|
+
});
|
|
262
|
+
const projectsPath = bootstrapPolicy.mode === "full"
|
|
263
|
+
? withQuery("/api/v1/projects", { limit: 100 })
|
|
264
|
+
: withQuery("/api/v1/projects", {
|
|
265
|
+
status: bootstrapPolicy.mode === "active_only" ? "active" : undefined,
|
|
266
|
+
limit: bootstrapPolicy.projectsLimit
|
|
267
|
+
});
|
|
268
|
+
const tasksPath = bootstrapPolicy.mode === "full"
|
|
269
|
+
? withQuery("/api/v1/tasks", { limit: 100 })
|
|
270
|
+
: withQuery("/api/v1/tasks", {
|
|
271
|
+
status: bootstrapPolicy.mode === "active_only" ? "focus" : undefined,
|
|
272
|
+
limit: bootstrapPolicy.tasksLimit
|
|
273
|
+
});
|
|
274
|
+
const habitsPath = bootstrapPolicy.mode === "full"
|
|
275
|
+
? withQuery("/api/v1/habits", { limit: 100 })
|
|
276
|
+
: withQuery("/api/v1/habits", {
|
|
277
|
+
dueToday: bootstrapPolicy.mode === "active_only" ? true : undefined,
|
|
278
|
+
limit: bootstrapPolicy.habitsLimit
|
|
279
|
+
});
|
|
280
|
+
const strategiesPath = bootstrapPolicy.mode === "full"
|
|
281
|
+
? withQuery("/api/v1/strategies", { limit: 100 })
|
|
282
|
+
: withQuery("/api/v1/strategies", {
|
|
283
|
+
status: bootstrapPolicy.mode === "active_only" ? "active" : undefined,
|
|
284
|
+
limit: bootstrapPolicy.strategiesLimit
|
|
285
|
+
});
|
|
286
|
+
const wikiPagesPath = bootstrapPolicy.includePeoplePages && bootstrapPolicy.peoplePageLimit > 0
|
|
287
|
+
? withQuery("/api/v1/wiki/pages", {
|
|
288
|
+
kind: "wiki",
|
|
289
|
+
limit: bootstrapPolicy.mode === "full"
|
|
290
|
+
? 200
|
|
291
|
+
: Math.max(bootstrapPolicy.peoplePageLimit * 4, 25)
|
|
292
|
+
})
|
|
293
|
+
: null;
|
|
181
294
|
const [overviewResponse, goalsResponse, projectsResponse, tasksResponse, habitsResponse, strategiesResponse, wikiPagesResponse] = await Promise.all([
|
|
182
295
|
readForgePayload(config, "/api/v1/operator/overview"),
|
|
183
|
-
readForgePayload(config,
|
|
184
|
-
readForgePayload(config,
|
|
185
|
-
readForgePayload(config,
|
|
186
|
-
readForgePayload(config,
|
|
187
|
-
readForgePayload(config,
|
|
188
|
-
|
|
296
|
+
readForgePayload(config, goalsPath),
|
|
297
|
+
readForgePayload(config, projectsPath),
|
|
298
|
+
readForgePayload(config, tasksPath),
|
|
299
|
+
readForgePayload(config, habitsPath),
|
|
300
|
+
readForgePayload(config, strategiesPath),
|
|
301
|
+
wikiPagesPath
|
|
302
|
+
? readForgePayload(config, wikiPagesPath)
|
|
303
|
+
: Promise.resolve({ pages: [] })
|
|
189
304
|
]);
|
|
190
305
|
const wikiPages = asArray(wikiPagesResponse.pages).filter((page) => isRecord(page) &&
|
|
191
306
|
typeof page.slug === "string" &&
|
|
192
307
|
typeof page.title === "string");
|
|
193
308
|
return {
|
|
309
|
+
bootstrapPolicy,
|
|
194
310
|
overview: overviewResponse && isRecord(overviewResponse) && "overview" in overviewResponse
|
|
195
311
|
? (overviewResponse.overview ?? null)
|
|
196
312
|
: null,
|
|
197
|
-
goals:
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
313
|
+
goals: bootstrapPolicy.mode === "full"
|
|
314
|
+
? asArray(goalsResponse.goals)
|
|
315
|
+
: asArray(goalsResponse.goals).slice(0, bootstrapPolicy.goalsLimit),
|
|
316
|
+
projects: bootstrapPolicy.mode === "full"
|
|
317
|
+
? asArray(projectsResponse.projects)
|
|
318
|
+
: asArray(projectsResponse.projects).slice(0, bootstrapPolicy.projectsLimit),
|
|
319
|
+
tasks: bootstrapPolicy.mode === "full"
|
|
320
|
+
? asArray(tasksResponse.tasks)
|
|
321
|
+
: asArray(tasksResponse.tasks).slice(0, bootstrapPolicy.tasksLimit),
|
|
322
|
+
habits: bootstrapPolicy.mode === "full"
|
|
323
|
+
? asArray(habitsResponse.habits)
|
|
324
|
+
: asArray(habitsResponse.habits).slice(0, bootstrapPolicy.habitsLimit),
|
|
325
|
+
strategies: bootstrapPolicy.mode === "full"
|
|
326
|
+
? asArray(strategiesResponse.strategies)
|
|
327
|
+
: asArray(strategiesResponse.strategies).slice(0, bootstrapPolicy.strategiesLimit),
|
|
328
|
+
peoplePages: bootstrapPolicy.includePeoplePages
|
|
329
|
+
? listPeopleBranchPages(wikiPages).slice(0, bootstrapPolicy.peoplePageLimit)
|
|
330
|
+
: []
|
|
203
331
|
};
|
|
204
332
|
}
|
|
205
333
|
export async function buildLiveForgeSessionBootstrapContext(config) {
|
|
206
|
-
|
|
334
|
+
if (!config.injectBootstrapContext) {
|
|
335
|
+
return "";
|
|
336
|
+
}
|
|
337
|
+
const payload = await loadForgeSessionBootstrapPayload(config);
|
|
338
|
+
if (payload.bootstrapPolicy.mode === "disabled") {
|
|
339
|
+
return "";
|
|
340
|
+
}
|
|
341
|
+
return buildForgeSessionBootstrapContext(payload);
|
|
207
342
|
}
|
|
208
343
|
export function registerForgeSessionBootstrapHook(api, config) {
|
|
209
344
|
if (!api.registerHook) {
|
|
210
345
|
return;
|
|
211
346
|
}
|
|
347
|
+
if (!config.injectBootstrapContext) {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
212
350
|
api.registerHook("agent:bootstrap", async (event) => {
|
|
213
351
|
if (!isAgentBootstrapEvent(event)) {
|
|
214
352
|
return;
|