forge-openclaw-plugin 0.2.49 → 0.2.51
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 +3 -3
- package/dist/assets/{index-BAmEvOXb.js → index-DX8RiahO.js} +46 -46
- package/dist/assets/index-DX8RiahO.js.map +1 -0
- package/dist/assets/index-gthTrgvO.css +1 -0
- package/dist/index.html +2 -2
- package/dist/openclaw/parity.js +14 -0
- package/dist/openclaw/routes.js +42 -0
- package/dist/openclaw/tools.js +3 -3
- package/dist/server/server/migrations/019_wiki_memory.sql +1 -1
- package/dist/server/server/migrations/054_sqlite_backed_wiki_memory.sql +8 -0
- package/dist/server/server/src/app.js +24 -13
- package/dist/server/server/src/db.js +11 -2
- package/dist/server/server/src/openapi.js +40 -4
- package/dist/server/server/src/repositories/notes.js +5 -2
- package/dist/server/server/src/repositories/wiki-memory.js +17 -191
- package/dist/server/server/src/services/data-management.js +2 -9
- package/dist/server/server/src/services/legacy-wiki-markdown-import.js +328 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/server/migrations/019_wiki_memory.sql +1 -1
- package/server/migrations/054_sqlite_backed_wiki_memory.sql +8 -0
- package/skills/forge-openclaw/SKILL.md +6 -6
- package/skills/forge-openclaw/entity_conversation_playbooks.md +30 -0
- package/skills/forge-openclaw/psyche_entity_playbooks.md +21 -0
- package/dist/assets/index-2_tuemtU.css +0 -1
- package/dist/assets/index-BAmEvOXb.js.map +0 -1
package/dist/index.html
CHANGED
|
@@ -13,14 +13,14 @@
|
|
|
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-DX8RiahO.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">
|
|
20
20
|
<link rel="modulepreload" crossorigin href="/forge/assets/motion-CU5aNClV.js">
|
|
21
21
|
<link rel="modulepreload" crossorigin href="/forge/assets/table-CK0KcPYW.js">
|
|
22
22
|
<link rel="stylesheet" crossorigin href="/forge/assets/vendor-DT3pnAKJ.css">
|
|
23
|
-
<link rel="stylesheet" crossorigin href="/forge/assets/index-
|
|
23
|
+
<link rel="stylesheet" crossorigin href="/forge/assets/index-gthTrgvO.css">
|
|
24
24
|
</head>
|
|
25
25
|
<body class="bg-canvas text-ink antialiased">
|
|
26
26
|
<div id="root"></div>
|
package/dist/openclaw/parity.js
CHANGED
|
@@ -34,11 +34,13 @@ export const FORGE_SUPPORTED_PLUGIN_API_ROUTES = [
|
|
|
34
34
|
{ method: "GET", path: "/api/v1/movement/all-time", purpose: "health" },
|
|
35
35
|
{ method: "GET", path: "/api/v1/movement/timeline", purpose: "health" },
|
|
36
36
|
{ method: "GET", path: "/api/v1/movement/places", purpose: "health" },
|
|
37
|
+
{ method: "GET", path: "/api/v1/movement/boxes/:id", purpose: "health" },
|
|
37
38
|
{ method: "POST", path: "/api/v1/movement/places", purpose: "health" },
|
|
38
39
|
{ method: "PATCH", path: "/api/v1/movement/places/:id", purpose: "health" },
|
|
39
40
|
{ method: "GET", path: "/api/v1/movement/trips/:id", purpose: "health" },
|
|
40
41
|
{ method: "POST", path: "/api/v1/movement/selection", purpose: "health" },
|
|
41
42
|
{ method: "GET", path: "/api/v1/movement/settings", purpose: "health" },
|
|
43
|
+
{ method: "PATCH", path: "/api/v1/movement/settings", purpose: "health" },
|
|
42
44
|
{ method: "POST", path: "/api/v1/movement/user-boxes", purpose: "health" },
|
|
43
45
|
{
|
|
44
46
|
method: "POST",
|
|
@@ -50,18 +52,30 @@ export const FORGE_SUPPORTED_PLUGIN_API_ROUTES = [
|
|
|
50
52
|
path: "/api/v1/movement/user-boxes/:id",
|
|
51
53
|
purpose: "health"
|
|
52
54
|
},
|
|
55
|
+
{
|
|
56
|
+
method: "DELETE",
|
|
57
|
+
path: "/api/v1/movement/user-boxes/:id",
|
|
58
|
+
purpose: "health"
|
|
59
|
+
},
|
|
53
60
|
{
|
|
54
61
|
method: "POST",
|
|
55
62
|
path: "/api/v1/movement/automatic-boxes/:id/invalidate",
|
|
56
63
|
purpose: "health"
|
|
57
64
|
},
|
|
58
65
|
{ method: "PATCH", path: "/api/v1/movement/stays/:id", purpose: "health" },
|
|
66
|
+
{ method: "DELETE", path: "/api/v1/movement/stays/:id", purpose: "health" },
|
|
59
67
|
{ method: "PATCH", path: "/api/v1/movement/trips/:id", purpose: "health" },
|
|
68
|
+
{ method: "DELETE", path: "/api/v1/movement/trips/:id", purpose: "health" },
|
|
60
69
|
{
|
|
61
70
|
method: "PATCH",
|
|
62
71
|
path: "/api/v1/movement/trips/:id/points/:pointId",
|
|
63
72
|
purpose: "health"
|
|
64
73
|
},
|
|
74
|
+
{
|
|
75
|
+
method: "DELETE",
|
|
76
|
+
path: "/api/v1/movement/trips/:id/points/:pointId",
|
|
77
|
+
purpose: "health"
|
|
78
|
+
},
|
|
65
79
|
{ method: "GET", path: "/api/v1/life-force", purpose: "health" },
|
|
66
80
|
{ method: "PATCH", path: "/api/v1/life-force/profile", purpose: "health" },
|
|
67
81
|
{
|
package/dist/openclaw/routes.js
CHANGED
|
@@ -198,6 +198,12 @@ export const FORGE_PLUGIN_ROUTE_GROUPS = [
|
|
|
198
198
|
upstreamPath: "/api/v1/movement/places",
|
|
199
199
|
target: (_match, url) => passthroughSearch("/api/v1/movement/places", url)
|
|
200
200
|
},
|
|
201
|
+
{
|
|
202
|
+
method: "GET",
|
|
203
|
+
pattern: /^\/forge\/v1\/movement\/boxes\/([^/]+)$/,
|
|
204
|
+
upstreamPath: "/api/v1/movement/boxes/:id",
|
|
205
|
+
target: (match, url) => passthroughSearch(`/api/v1/movement/boxes/${match[1]}`, url)
|
|
206
|
+
},
|
|
201
207
|
{
|
|
202
208
|
method: "POST",
|
|
203
209
|
pattern: /^\/forge\/v1\/movement\/places$/,
|
|
@@ -233,6 +239,14 @@ export const FORGE_PLUGIN_ROUTE_GROUPS = [
|
|
|
233
239
|
upstreamPath: "/api/v1/movement/settings",
|
|
234
240
|
target: (_match, url) => passthroughSearch("/api/v1/movement/settings", url)
|
|
235
241
|
},
|
|
242
|
+
{
|
|
243
|
+
method: "PATCH",
|
|
244
|
+
pattern: /^\/forge\/v1\/movement\/settings$/,
|
|
245
|
+
upstreamPath: "/api/v1/movement/settings",
|
|
246
|
+
requestBody: "json",
|
|
247
|
+
requiresToken: true,
|
|
248
|
+
target: (_match, url) => passthroughSearch("/api/v1/movement/settings", url)
|
|
249
|
+
},
|
|
236
250
|
{
|
|
237
251
|
method: "POST",
|
|
238
252
|
pattern: /^\/forge\/v1\/movement\/user-boxes$/,
|
|
@@ -257,6 +271,13 @@ export const FORGE_PLUGIN_ROUTE_GROUPS = [
|
|
|
257
271
|
requiresToken: true,
|
|
258
272
|
target: (match, url) => passthroughSearch(`/api/v1/movement/user-boxes/${match[1]}`, url)
|
|
259
273
|
},
|
|
274
|
+
{
|
|
275
|
+
method: "DELETE",
|
|
276
|
+
pattern: /^\/forge\/v1\/movement\/user-boxes\/([^/]+)$/,
|
|
277
|
+
upstreamPath: "/api/v1/movement/user-boxes/:id",
|
|
278
|
+
requiresToken: true,
|
|
279
|
+
target: (match, url) => passthroughSearch(`/api/v1/movement/user-boxes/${match[1]}`, url)
|
|
280
|
+
},
|
|
260
281
|
{
|
|
261
282
|
method: "POST",
|
|
262
283
|
pattern: /^\/forge\/v1\/movement\/automatic-boxes\/([^/]+)\/invalidate$/,
|
|
@@ -273,6 +294,13 @@ export const FORGE_PLUGIN_ROUTE_GROUPS = [
|
|
|
273
294
|
requiresToken: true,
|
|
274
295
|
target: (match, url) => passthroughSearch(`/api/v1/movement/stays/${match[1]}`, url)
|
|
275
296
|
},
|
|
297
|
+
{
|
|
298
|
+
method: "DELETE",
|
|
299
|
+
pattern: /^\/forge\/v1\/movement\/stays\/([^/]+)$/,
|
|
300
|
+
upstreamPath: "/api/v1/movement/stays/:id",
|
|
301
|
+
requiresToken: true,
|
|
302
|
+
target: (match, url) => passthroughSearch(`/api/v1/movement/stays/${match[1]}`, url)
|
|
303
|
+
},
|
|
276
304
|
{
|
|
277
305
|
method: "PATCH",
|
|
278
306
|
pattern: /^\/forge\/v1\/movement\/trips\/([^/]+)$/,
|
|
@@ -281,6 +309,13 @@ export const FORGE_PLUGIN_ROUTE_GROUPS = [
|
|
|
281
309
|
requiresToken: true,
|
|
282
310
|
target: (match, url) => passthroughSearch(`/api/v1/movement/trips/${match[1]}`, url)
|
|
283
311
|
},
|
|
312
|
+
{
|
|
313
|
+
method: "DELETE",
|
|
314
|
+
pattern: /^\/forge\/v1\/movement\/trips\/([^/]+)$/,
|
|
315
|
+
upstreamPath: "/api/v1/movement/trips/:id",
|
|
316
|
+
requiresToken: true,
|
|
317
|
+
target: (match, url) => passthroughSearch(`/api/v1/movement/trips/${match[1]}`, url)
|
|
318
|
+
},
|
|
284
319
|
{
|
|
285
320
|
method: "PATCH",
|
|
286
321
|
pattern: /^\/forge\/v1\/movement\/trips\/([^/]+)\/points\/([^/]+)$/,
|
|
@@ -288,6 +323,13 @@ export const FORGE_PLUGIN_ROUTE_GROUPS = [
|
|
|
288
323
|
requestBody: "json",
|
|
289
324
|
requiresToken: true,
|
|
290
325
|
target: (match, url) => passthroughSearch(`/api/v1/movement/trips/${match[1]}/points/${match[2]}`, url)
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
method: "DELETE",
|
|
329
|
+
pattern: /^\/forge\/v1\/movement\/trips\/([^/]+)\/points\/([^/]+)$/,
|
|
330
|
+
upstreamPath: "/api/v1/movement/trips/:id/points/:pointId",
|
|
331
|
+
requiresToken: true,
|
|
332
|
+
target: (match, url) => passthroughSearch(`/api/v1/movement/trips/${match[1]}/points/${match[2]}`, url)
|
|
291
333
|
}
|
|
292
334
|
]
|
|
293
335
|
},
|
package/dist/openclaw/tools.js
CHANGED
|
@@ -344,7 +344,7 @@ export function registerForgePluginTools(api, config) {
|
|
|
344
344
|
api.registerTool({
|
|
345
345
|
name: "forge_upsert_wiki_page",
|
|
346
346
|
label: "Forge Upsert Wiki Page",
|
|
347
|
-
description: "Create a new wiki page or update an existing one through the
|
|
347
|
+
description: "Create a new wiki page or update an existing one through the SQLite-backed wiki surface.",
|
|
348
348
|
parameters: wikiPageMutationSchema(),
|
|
349
349
|
async execute(_toolCallId, params) {
|
|
350
350
|
const typed = (params ?? {});
|
|
@@ -375,8 +375,8 @@ export function registerForgePluginTools(api, config) {
|
|
|
375
375
|
});
|
|
376
376
|
registerWriteTool(api, config, {
|
|
377
377
|
name: "forge_sync_wiki_vault",
|
|
378
|
-
label: "Forge
|
|
379
|
-
description: "
|
|
378
|
+
label: "Forge Refresh Wiki Indexes",
|
|
379
|
+
description: "Rebuild SQLite wiki search, link, and metadata indexes.",
|
|
380
380
|
parameters: Type.Object({
|
|
381
381
|
spaceId: optionalString()
|
|
382
382
|
}),
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
UPDATE wiki_spaces
|
|
2
|
+
SET description = 'Shared wiki space for SQLite-backed Forge knowledge.'
|
|
3
|
+
WHERE id = 'wiki_space_shared'
|
|
4
|
+
AND description != 'Shared wiki space for SQLite-backed Forge knowledge.';
|
|
5
|
+
|
|
6
|
+
UPDATE notes
|
|
7
|
+
SET source_path = ''
|
|
8
|
+
WHERE source_path != '';
|
|
@@ -2643,7 +2643,7 @@ const AGENT_ONBOARDING_ENTITY_CATALOG = [
|
|
|
2643
2643
|
}),
|
|
2644
2644
|
enrichOnboardingEntityGuide({
|
|
2645
2645
|
entityType: "wiki_page",
|
|
2646
|
-
purpose: "A
|
|
2646
|
+
purpose: "A SQLite-backed Forge wiki page or evidence page.",
|
|
2647
2647
|
minimumCreateFields: ["title", "contentMarkdown"],
|
|
2648
2648
|
relationshipRules: [
|
|
2649
2649
|
"Wiki pages live on the wiki surface and use specialized page upsert routes rather than batch CRUD.",
|
|
@@ -2772,6 +2772,8 @@ const AGENT_ONBOARDING_CONVERSATION_RULES = [
|
|
|
2772
2772
|
"When useful, help the user name, define, and connect the record in that order: offer a working label, clarify what belongs inside it, then ask about links only after the record itself feels steady.",
|
|
2773
2773
|
"When the meaning is clearer than the wording, offer a tentative title or formulation yourself and invite correction instead of forcing the user to wordsmith alone.",
|
|
2774
2774
|
"For direct update or review requests, the next question should usually narrow the saved object, timeframe, or route family instead of reopening the whole meaning-making arc.",
|
|
2775
|
+
"For updates, start with the smallest thing that now feels wrong, newly true, or newly visible rather than restarting the whole story.",
|
|
2776
|
+
"For review requests, ask what practical question the user wants the read to answer before you ask for more scope.",
|
|
2775
2777
|
"The opening question should help the user understand what they are actually trying to save, decide, review, or change, not make them perform the schema out loud.",
|
|
2776
2778
|
"If the user already named the exact correction in usable language, confirm only the missing scope, timing, or route-selecting detail that still matters, then act.",
|
|
2777
2779
|
"Once a specialized-surface lane is clear, speak in route-relevant nouns such as timeline, overlay, weekday template, published output, run detail, or node result instead of generic record language.",
|
|
@@ -3550,7 +3552,7 @@ const AGENT_ONBOARDING_TOOL_INPUT_CATALOG = [
|
|
|
3550
3552
|
requiredFields: [],
|
|
3551
3553
|
notes: [
|
|
3552
3554
|
"Semantic search is optional and profile-driven.",
|
|
3553
|
-
"The wiki is
|
|
3555
|
+
"The wiki is SQLite-backed, so pages and evidence live in Forge's database."
|
|
3554
3556
|
],
|
|
3555
3557
|
example: "{}"
|
|
3556
3558
|
},
|
|
@@ -3592,13 +3594,13 @@ const AGENT_ONBOARDING_TOOL_INPUT_CATALOG = [
|
|
|
3592
3594
|
},
|
|
3593
3595
|
{
|
|
3594
3596
|
toolName: "forge_upsert_wiki_page",
|
|
3595
|
-
summary: "Create a new wiki page or update an existing one through the
|
|
3597
|
+
summary: "Create a new wiki page or update an existing one through the SQLite-backed wiki surface.",
|
|
3596
3598
|
whenToUse: "Use when the user explicitly wants wiki memory persisted or reorganized.",
|
|
3597
3599
|
inputShape: '{ pageId?: string, kind?: "wiki"|"evidence", title: string, slug?: string, summary?: string, aliases?: string[], contentMarkdown: string, author?: string|null, tags?: string[], spaceId?: string, frontmatter?: object, links?: Array<{ entityType, entityId, anchorKey? }> }',
|
|
3598
3600
|
requiredFields: ["title", "contentMarkdown"],
|
|
3599
3601
|
notes: [
|
|
3600
3602
|
"When pageId is omitted, Forge creates a new page.",
|
|
3601
|
-
"When pageId is present, Forge patches the existing
|
|
3603
|
+
"When pageId is present, Forge patches the existing SQLite note record."
|
|
3602
3604
|
],
|
|
3603
3605
|
example: '{"title":"Taste map","contentMarkdown":"# Taste map\\n\\n[[forge:goal:goal_123|Core goal]] influences this page.","spaceId":"wiki_space_shared"}'
|
|
3604
3606
|
},
|
|
@@ -3609,19 +3611,19 @@ const AGENT_ONBOARDING_TOOL_INPUT_CATALOG = [
|
|
|
3609
3611
|
inputShape: "{ spaceId?: string }",
|
|
3610
3612
|
requiredFields: [],
|
|
3611
3613
|
notes: [
|
|
3612
|
-
"This is the explicit health surface for the
|
|
3614
|
+
"This is the explicit health surface for the SQLite-backed wiki memory layer.",
|
|
3613
3615
|
"Use it before proposing cleanup work or auto-maintenance."
|
|
3614
3616
|
],
|
|
3615
3617
|
example: '{"spaceId":"wiki_space_shared"}'
|
|
3616
3618
|
},
|
|
3617
3619
|
{
|
|
3618
3620
|
toolName: "forge_sync_wiki_vault",
|
|
3619
|
-
summary: "
|
|
3620
|
-
whenToUse: "Use after
|
|
3621
|
+
summary: "Rebuild SQLite wiki search, link, and metadata indexes.",
|
|
3622
|
+
whenToUse: "Use after large SQLite wiki changes or maintenance work that should refresh derived metadata.",
|
|
3621
3623
|
inputShape: "{ spaceId?: string }",
|
|
3622
3624
|
requiredFields: [],
|
|
3623
3625
|
notes: [
|
|
3624
|
-
"Forge treats
|
|
3626
|
+
"Forge treats SQLite as the canonical wiki store; this route refreshes derived indexes."
|
|
3625
3627
|
],
|
|
3626
3628
|
example: '{"spaceId":"wiki_space_shared"}'
|
|
3627
3629
|
},
|
|
@@ -3640,11 +3642,11 @@ const AGENT_ONBOARDING_TOOL_INPUT_CATALOG = [
|
|
|
3640
3642
|
{
|
|
3641
3643
|
toolName: "forge_ingest_wiki_source",
|
|
3642
3644
|
summary: "Ingest raw text, local files, or URLs into the wiki, preserving a raw source artifact and returning page plus proposal outputs.",
|
|
3643
|
-
whenToUse: "Use when the operator wants source material compiled into
|
|
3645
|
+
whenToUse: "Use when the operator wants source material compiled into SQLite-backed wiki memory and optional Forge-entity proposals.",
|
|
3644
3646
|
inputShape: '{ spaceId?: string, titleHint?: string, sourceKind: "raw_text"|"local_path"|"url", sourceText?: string, sourcePath?: string, sourceUrl?: string, mimeType?: string, llmProfileId?: string, parseStrategy?: "auto"|"text_only"|"multimodal", entityProposalMode?: "none"|"suggest", createAsKind?: "wiki"|"evidence", linkedEntityHints?: Array<{ entityType, entityId, anchorKey? }> }',
|
|
3645
3647
|
requiredFields: ["sourceKind", "sourceText/sourcePath/sourceUrl"],
|
|
3646
3648
|
notes: [
|
|
3647
|
-
"Forge preserves a raw artifact
|
|
3649
|
+
"Forge preserves a raw ingest artifact separately from SQLite page content.",
|
|
3648
3650
|
"Entity proposals are suggestions only; they are not auto-applied."
|
|
3649
3651
|
],
|
|
3650
3652
|
example: '{"sourceKind":"url","sourceUrl":"https://example.com/article","titleHint":"Research import","parseStrategy":"auto","entityProposalMode":"suggest"}'
|
|
@@ -4017,7 +4019,7 @@ function buildAgentOnboardingPayload(request) {
|
|
|
4017
4019
|
task: "A concrete actionable work item. Task status is board state, not proof of live work.",
|
|
4018
4020
|
taskRun: "A live work session attached to a task. Start, heartbeat, focus, complete, and release runs instead of faking work with status alone.",
|
|
4019
4021
|
note: "A Markdown work note that can link to one or many entities. Use notes for progress evidence, context, and close-out summaries.",
|
|
4020
|
-
wiki: "Forge Wiki is the
|
|
4022
|
+
wiki: "Forge Wiki is the SQLite-backed memory layer: Markdown content in notes rows plus media, backlinks, optional embeddings, explicit spaces, and structured links back to Forge entities.",
|
|
4021
4023
|
sleepSession: "A sleep session is a first-class health record with timing, sleep and bed duration, stage breakdown, recovery metrics, annotations, and Forge links back to planning or Psyche context.",
|
|
4022
4024
|
workoutSession: "A workout session is a first-class sports record imported from HealthKit or generated from a habit. It holds workout type, timing, energy or distance when available, subjective effort, narrative context, and Forge links.",
|
|
4023
4025
|
preferences: "Forge Preferences is the explicit taste-modeling domain. It has workspaces, contexts, concept libraries, direct items, pairwise judgments, direct signals, and inferred scores.",
|
|
@@ -4379,17 +4381,26 @@ function buildAgentOnboardingPayload(request) {
|
|
|
4379
4381
|
movementTimeline: "/api/v1/movement/timeline",
|
|
4380
4382
|
movementAllTime: "/api/v1/movement/all-time",
|
|
4381
4383
|
movementPlaces: "/api/v1/movement/places",
|
|
4384
|
+
movementBoxDetail: "/api/v1/movement/boxes/:id",
|
|
4385
|
+
movementSettings: "/api/v1/movement/settings",
|
|
4386
|
+
movementSettingsUpdate: "/api/v1/movement/settings",
|
|
4382
4387
|
movementTripDetail: "/api/v1/movement/trips/:id",
|
|
4383
4388
|
movementSelection: "/api/v1/movement/selection",
|
|
4384
4389
|
movementUserBoxPreflight: "/api/v1/movement/user-boxes/preflight",
|
|
4385
4390
|
movementUserBoxUpdate: "/api/v1/movement/user-boxes/:id",
|
|
4391
|
+
movementUserBoxDelete: "/api/v1/movement/user-boxes/:id",
|
|
4386
4392
|
movementAutomaticBoxInvalidate: "/api/v1/movement/automatic-boxes/:id/invalidate",
|
|
4387
4393
|
movementStayUpdate: "/api/v1/movement/stays/:id",
|
|
4394
|
+
movementStayDelete: "/api/v1/movement/stays/:id",
|
|
4388
4395
|
movementTripUpdate: "/api/v1/movement/trips/:id",
|
|
4396
|
+
movementTripDelete: "/api/v1/movement/trips/:id",
|
|
4389
4397
|
movementTripPointUpdate: "/api/v1/movement/trips/:id/points/:pointId",
|
|
4398
|
+
movementTripPointDelete: "/api/v1/movement/trips/:id/points/:pointId",
|
|
4399
|
+
workbenchBoxCatalog: "/api/v1/workbench/catalog/boxes",
|
|
4390
4400
|
workbenchFlows: "/api/v1/workbench/flows",
|
|
4391
4401
|
workbenchFlowBySlug: "/api/v1/workbench/flows/by-slug/:slug",
|
|
4392
4402
|
workbenchPublishedOutput: "/api/v1/workbench/flows/:id/output",
|
|
4403
|
+
workbenchRuns: "/api/v1/workbench/flows/:id/runs",
|
|
4393
4404
|
workbenchRunDetail: "/api/v1/workbench/flows/:id/runs/:runId",
|
|
4394
4405
|
workbenchNodeResult: "/api/v1/workbench/flows/:id/runs/:runId/nodes/:nodeId",
|
|
4395
4406
|
workbenchLatestNodeOutput: "/api/v1/workbench/flows/:id/nodes/:nodeId/output",
|
|
@@ -4465,9 +4476,9 @@ function buildAgentOnboardingPayload(request) {
|
|
|
4465
4476
|
saveSuggestionPlacement: "end_of_message",
|
|
4466
4477
|
saveSuggestionTone: "gentle_optional",
|
|
4467
4478
|
maxQuestionsPerTurn: 1,
|
|
4468
|
-
psycheExplorationRule: "When a Psyche entity needs understanding first, begin with one exploratory question before any working formulation, replacement belief, suggested title, or save pitch. Keep the opening reflection to one or two short sentences, stay in plain prose instead of bullets or numbered lists, keep that first reply short, do not mention Forge search or save structure yet, avoid colons or list-shaped phrasing, prefer what/when/how over why until the experience is grounded, wait for the user's answer before offering a fuller formulation, ask permission before moving from charged exploration into naming or challenge when needed, make the next question help the user feel more able to name the experience rather than more examined, do not widen into adjacent entities until the current one has a working sentence the user recognizes, and once the lived experience is coherent stop deepening and help the user name it cleanly. When the user is updating a Psyche record because of one fresh episode, anchor in that episode before renaming the durable formulation. If the user accepts the wording, move toward the save instead of reopening deeper exploration.",
|
|
4479
|
+
psycheExplorationRule: "When a Psyche entity needs understanding first, begin with one exploratory question before any working formulation, replacement belief, suggested title, or save pitch. Keep the opening reflection to one or two short sentences, stay in plain prose instead of bullets or numbered lists, keep that first reply short, do not mention Forge search or save structure yet, avoid colons or list-shaped phrasing, prefer what/when/how over why until the experience is grounded, wait for the user's answer before offering a fuller formulation, ask permission before moving from charged exploration into naming or challenge when needed, make the next question help the user feel more able to name the experience rather than more examined, do not widen into adjacent entities until the current one has a working sentence the user recognizes, and once the lived experience is coherent stop deepening and help the user name it cleanly. When the user is updating a Psyche record because of one fresh episode, anchor in that episode before renaming the durable formulation, begin with the smallest part of the old wording that no longer fits, and do not reopen the full origin story unless the new understanding is truly structural. If the user accepts the wording, move toward the save instead of reopening deeper exploration.",
|
|
4469
4480
|
specializedSurfaceRule: "For Movement, Life Force, and Workbench, clarify the lane first, then name the dedicated route family in plain language and do not guess at a generic CRUD path. Use specializedDomainSurfaces.routeSelectionQuestions when they are present so the next follow-up stays route-selective instead of generic. Once the lane is clear, talk in route-relevant nouns such as timeline, overlay, weekday template, published output, run detail, or node result rather than generic record language. If the truth of the current state is still uncertain, read the relevant specialized view before you mutate it. When the user already named a precise correction or review target, confirm only the route-selecting detail that is still missing. After a concrete specialized-surface correction, read the relevant specialized view back when the user is trying to understand the result rather than just store it. The canonical runtime routes stay under /api/v1/*, and the OpenClaw HTTP mirror exposes the same families under /forge/v1/movement, /forge/v1/life-force, and /forge/v1/workbench.",
|
|
4470
|
-
reviewShortcutRule: "When the user is reviewing or correcting an existing record, narrow the saved object, timeframe, or route family first. Do not reopen the whole intake unless the user is actually redefining the record.",
|
|
4481
|
+
reviewShortcutRule: "When the user is reviewing or correcting an existing record, ask what practical question they want the read or correction to answer, then narrow the saved object, timeframe, or route family first. Do not reopen the whole intake unless the user is actually redefining the record.",
|
|
4471
4482
|
readModelWriteRule: "Self-observation is note-backed and should be written through observed notes with frontmatter.observedAt. Sleep and workout sessions stay on batch CRUD by default; use the reflective review helpers only when enriching one already-known record after review.",
|
|
4472
4483
|
psycheOpeningQuestionRule: "Prefer a concrete opening question tied to the entity: ask when the value mattered, what happened the last time the pattern appeared, what cue or body signal came first before the behavior, what the belief starts saying about self or outcome, what feels most at risk inside the mode, what the part is trying to get the user to do or stop doing, or where the shift began in the incident. Reflect briefly before the question, choose one follow-up lane at a time, say what is becoming clearer before the next deeper question, and if several Psyche entities are visible hold the adjacent ones lightly until the main container is clear.",
|
|
4473
4484
|
duplicateCheckRoute: "/api/v1/entities/search",
|
|
@@ -34,14 +34,12 @@ function resolveLegacyDatabasePath(root = dataRoot) {
|
|
|
34
34
|
function hasCanonicalRuntimeLayout(root = dataRoot) {
|
|
35
35
|
const canonicalRoot = resolveCanonicalDataDir(root);
|
|
36
36
|
return (existsSync(resolveCanonicalDatabasePath(root)) ||
|
|
37
|
-
existsSync(path.join(canonicalRoot, "wiki")) ||
|
|
38
37
|
existsSync(path.join(canonicalRoot, "wiki-ingest")) ||
|
|
39
38
|
existsSync(path.join(canonicalRoot, ".forge-secrets.key")));
|
|
40
39
|
}
|
|
41
40
|
function hasLegacyRuntimeLayout(root = dataRoot) {
|
|
42
41
|
const legacyRoot = resolveLegacyDataDir(root);
|
|
43
42
|
return (existsSync(resolveLegacyDatabasePath(root)) ||
|
|
44
|
-
existsSync(path.join(legacyRoot, "wiki")) ||
|
|
45
43
|
existsSync(path.join(legacyRoot, "wiki-ingest")) ||
|
|
46
44
|
existsSync(path.join(legacyRoot, ".forge-secrets.key")));
|
|
47
45
|
}
|
|
@@ -81,6 +79,7 @@ export function resolveDefaultDataRoot(currentWorkingDir = process.cwd()) {
|
|
|
81
79
|
}
|
|
82
80
|
let dataRoot = resolveDefaultDataRoot();
|
|
83
81
|
let seedDemoDataEnabled = false;
|
|
82
|
+
let legacyWikiAutoImportEnabled = true;
|
|
84
83
|
let db = null;
|
|
85
84
|
let transactionDepth = 0;
|
|
86
85
|
let savepointCounter = 0;
|
|
@@ -163,6 +162,9 @@ export function configureDatabase(options = {}) {
|
|
|
163
162
|
seedDemoDataEnabled = options.seedDemoData;
|
|
164
163
|
}
|
|
165
164
|
}
|
|
165
|
+
export function configureLegacyWikiAutoImport(enabled) {
|
|
166
|
+
legacyWikiAutoImportEnabled = enabled;
|
|
167
|
+
}
|
|
166
168
|
async function listMigrationFiles() {
|
|
167
169
|
const files = await readdir(migrationsDir);
|
|
168
170
|
return files.filter((file) => file.endsWith(".sql")).sort();
|
|
@@ -391,6 +393,13 @@ export async function initializeDatabase() {
|
|
|
391
393
|
seedData();
|
|
392
394
|
}
|
|
393
395
|
ensureQuestionnaireSeeds();
|
|
396
|
+
if (legacyWikiAutoImportEnabled) {
|
|
397
|
+
const { importLegacyWikiMarkdownOnStartup } = await import("./services/legacy-wiki-markdown-import.js");
|
|
398
|
+
const legacyWikiImport = await importLegacyWikiMarkdownOnStartup(getDataDir());
|
|
399
|
+
if (legacyWikiImport.scanned > 0) {
|
|
400
|
+
logForgeDebug(`[forge-db] imported legacy wiki markdown scanned=${legacyWikiImport.scanned} inserted=${legacyWikiImport.inserted} updated=${legacyWikiImport.updated} backed_up=${legacyWikiImport.backedUp} backup_path=${legacyWikiImport.backupPath}`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
394
403
|
}
|
|
395
404
|
export function configureDatabaseSeeding(enabled) {
|
|
396
405
|
seedDemoDataEnabled = enabled;
|
|
@@ -115,7 +115,7 @@ const API_TAGS = [
|
|
|
115
115
|
},
|
|
116
116
|
{
|
|
117
117
|
name: "Wiki",
|
|
118
|
-
description: "
|
|
118
|
+
description: "SQLite-backed wiki settings, pages, ingest, sync, health, and search."
|
|
119
119
|
},
|
|
120
120
|
{
|
|
121
121
|
name: "Preferences",
|
|
@@ -3253,17 +3253,26 @@ export function buildOpenApiDocument() {
|
|
|
3253
3253
|
"movementTimeline",
|
|
3254
3254
|
"movementAllTime",
|
|
3255
3255
|
"movementPlaces",
|
|
3256
|
+
"movementBoxDetail",
|
|
3257
|
+
"movementSettings",
|
|
3258
|
+
"movementSettingsUpdate",
|
|
3256
3259
|
"movementTripDetail",
|
|
3257
3260
|
"movementSelection",
|
|
3258
3261
|
"movementUserBoxPreflight",
|
|
3259
3262
|
"movementUserBoxUpdate",
|
|
3263
|
+
"movementUserBoxDelete",
|
|
3260
3264
|
"movementAutomaticBoxInvalidate",
|
|
3261
3265
|
"movementStayUpdate",
|
|
3266
|
+
"movementStayDelete",
|
|
3262
3267
|
"movementTripUpdate",
|
|
3268
|
+
"movementTripDelete",
|
|
3263
3269
|
"movementTripPointUpdate",
|
|
3270
|
+
"movementTripPointDelete",
|
|
3271
|
+
"workbenchBoxCatalog",
|
|
3264
3272
|
"workbenchFlows",
|
|
3265
3273
|
"workbenchFlowBySlug",
|
|
3266
3274
|
"workbenchPublishedOutput",
|
|
3275
|
+
"workbenchRuns",
|
|
3267
3276
|
"workbenchRunDetail",
|
|
3268
3277
|
"workbenchNodeResult",
|
|
3269
3278
|
"workbenchLatestNodeOutput",
|
|
@@ -3292,17 +3301,26 @@ export function buildOpenApiDocument() {
|
|
|
3292
3301
|
movementTimeline: { type: "string" },
|
|
3293
3302
|
movementAllTime: { type: "string" },
|
|
3294
3303
|
movementPlaces: { type: "string" },
|
|
3304
|
+
movementBoxDetail: { type: "string" },
|
|
3305
|
+
movementSettings: { type: "string" },
|
|
3306
|
+
movementSettingsUpdate: { type: "string" },
|
|
3295
3307
|
movementTripDetail: { type: "string" },
|
|
3296
3308
|
movementSelection: { type: "string" },
|
|
3297
3309
|
movementUserBoxPreflight: { type: "string" },
|
|
3298
3310
|
movementUserBoxUpdate: { type: "string" },
|
|
3311
|
+
movementUserBoxDelete: { type: "string" },
|
|
3299
3312
|
movementAutomaticBoxInvalidate: { type: "string" },
|
|
3300
3313
|
movementStayUpdate: { type: "string" },
|
|
3314
|
+
movementStayDelete: { type: "string" },
|
|
3301
3315
|
movementTripUpdate: { type: "string" },
|
|
3316
|
+
movementTripDelete: { type: "string" },
|
|
3302
3317
|
movementTripPointUpdate: { type: "string" },
|
|
3318
|
+
movementTripPointDelete: { type: "string" },
|
|
3319
|
+
workbenchBoxCatalog: { type: "string" },
|
|
3303
3320
|
workbenchFlows: { type: "string" },
|
|
3304
3321
|
workbenchFlowBySlug: { type: "string" },
|
|
3305
3322
|
workbenchPublishedOutput: { type: "string" },
|
|
3323
|
+
workbenchRuns: { type: "string" },
|
|
3306
3324
|
workbenchRunDetail: { type: "string" },
|
|
3307
3325
|
workbenchNodeResult: { type: "string" },
|
|
3308
3326
|
workbenchLatestNodeOutput: { type: "string" },
|
|
@@ -4830,6 +4848,24 @@ export function buildOpenApiDocument() {
|
|
|
4830
4848
|
}
|
|
4831
4849
|
}
|
|
4832
4850
|
},
|
|
4851
|
+
"/api/v1/movement/boxes/{id}": {
|
|
4852
|
+
get: {
|
|
4853
|
+
summary: "Read one movement box with projected detail, provenance, and raw linked evidence",
|
|
4854
|
+
responses: {
|
|
4855
|
+
"200": jsonResponse({
|
|
4856
|
+
type: "object",
|
|
4857
|
+
required: ["movement"],
|
|
4858
|
+
properties: {
|
|
4859
|
+
movement: {
|
|
4860
|
+
type: "object",
|
|
4861
|
+
additionalProperties: true
|
|
4862
|
+
}
|
|
4863
|
+
}
|
|
4864
|
+
}, "Movement box detail"),
|
|
4865
|
+
"404": { $ref: "#/components/responses/Error" }
|
|
4866
|
+
}
|
|
4867
|
+
}
|
|
4868
|
+
},
|
|
4833
4869
|
"/api/v1/movement/trips/{id}": {
|
|
4834
4870
|
get: {
|
|
4835
4871
|
summary: "Read one movement trip with its full detail",
|
|
@@ -5260,7 +5296,7 @@ export function buildOpenApiDocument() {
|
|
|
5260
5296
|
}
|
|
5261
5297
|
},
|
|
5262
5298
|
post: {
|
|
5263
|
-
summary: "Create a wiki page through the
|
|
5299
|
+
summary: "Create a wiki page through the SQLite-backed wiki surface",
|
|
5264
5300
|
responses: {
|
|
5265
5301
|
"200": jsonResponse({
|
|
5266
5302
|
type: "object",
|
|
@@ -5288,7 +5324,7 @@ export function buildOpenApiDocument() {
|
|
|
5288
5324
|
}
|
|
5289
5325
|
},
|
|
5290
5326
|
patch: {
|
|
5291
|
-
summary: "Update an existing wiki page through the
|
|
5327
|
+
summary: "Update an existing wiki page through the SQLite-backed surface",
|
|
5292
5328
|
responses: {
|
|
5293
5329
|
"200": jsonResponse({
|
|
5294
5330
|
type: "object",
|
|
@@ -5334,7 +5370,7 @@ export function buildOpenApiDocument() {
|
|
|
5334
5370
|
},
|
|
5335
5371
|
"/api/v1/wiki/sync": {
|
|
5336
5372
|
post: {
|
|
5337
|
-
summary: "
|
|
5373
|
+
summary: "Rebuild SQLite wiki search, link, and metadata indexes",
|
|
5338
5374
|
responses: {
|
|
5339
5375
|
"200": jsonResponse({
|
|
5340
5376
|
type: "object",
|
|
@@ -40,6 +40,9 @@ function normalizeTags(tags) {
|
|
|
40
40
|
return true;
|
|
41
41
|
});
|
|
42
42
|
}
|
|
43
|
+
function canonicalNoteSourcePath() {
|
|
44
|
+
return "";
|
|
45
|
+
}
|
|
43
46
|
function parseTagsJson(raw) {
|
|
44
47
|
try {
|
|
45
48
|
const parsed = JSON.parse(raw);
|
|
@@ -438,7 +441,7 @@ export function createNote(input, context) {
|
|
|
438
441
|
source_path, frontmatter_json, revision_hash, last_synced_at, created_at, updated_at
|
|
439
442
|
)
|
|
440
443
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
441
|
-
.run(id, wikiFields.kind, wikiFields.title, wikiFields.slug, wikiFields.spaceId, wikiFields.parentSlug, wikiFields.indexOrder, wikiFields.showInIndex ? 1 : 0, JSON.stringify(wikiFields.aliases), wikiFields.summary, parsed.contentMarkdown, contentPlain, parsed.author ?? context.actor ?? null, context.source, JSON.stringify(parsed.tags), parsed.destroyAt,
|
|
444
|
+
.run(id, wikiFields.kind, wikiFields.title, wikiFields.slug, wikiFields.spaceId, wikiFields.parentSlug, wikiFields.indexOrder, wikiFields.showInIndex ? 1 : 0, JSON.stringify(wikiFields.aliases), wikiFields.summary, parsed.contentMarkdown, contentPlain, parsed.author ?? context.actor ?? null, context.source, JSON.stringify(parsed.tags), parsed.destroyAt, canonicalNoteSourcePath(), JSON.stringify(parsed.frontmatter), parsed.revisionHash, parsed.lastSyncedAt ?? null, now, now);
|
|
442
445
|
insertLinks(id, parsed.links, now);
|
|
443
446
|
setEntityOwner("note", id, parsed.userId, parsed.author ?? context.actor ?? null);
|
|
444
447
|
clearDeletedEntityRecord("note", id);
|
|
@@ -504,7 +507,7 @@ export function updateNote(noteId, input, context) {
|
|
|
504
507
|
existing
|
|
505
508
|
});
|
|
506
509
|
const nextFrontmatter = patch.frontmatter === undefined ? existing.frontmatter : patch.frontmatter;
|
|
507
|
-
const nextSourcePath =
|
|
510
|
+
const nextSourcePath = canonicalNoteSourcePath();
|
|
508
511
|
const nextRevisionHash = patch.revisionHash === undefined
|
|
509
512
|
? existing.revisionHash
|
|
510
513
|
: patch.revisionHash;
|