forge-openclaw-plugin 0.2.21 → 0.2.23

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/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-B4A6TooJ.js"></script>
16
+ <script type="module" crossorigin src="/forge/assets/index-Ch_xeZ2u.js"></script>
17
17
  <link rel="modulepreload" crossorigin href="/forge/assets/viz-C6hfyqzu.js">
18
18
  <link rel="modulepreload" crossorigin href="/forge/assets/vendor-De38P6YR.js">
19
19
  <link rel="modulepreload" crossorigin href="/forge/assets/ui-BzK4azQb.js">
@@ -21,7 +21,7 @@
21
21
  <link rel="modulepreload" crossorigin href="/forge/assets/table-BWzTaky1.js">
22
22
  <link rel="modulepreload" crossorigin href="/forge/assets/board-_C6oMy5w.js">
23
23
  <link rel="stylesheet" crossorigin href="/forge/assets/vendor-DT3pnAKJ.css">
24
- <link rel="stylesheet" crossorigin href="/forge/assets/index-D6Xs_2mo.css">
24
+ <link rel="stylesheet" crossorigin href="/forge/assets/index-DvVM7K6j.css">
25
25
  </head>
26
26
  <body class="bg-canvas text-ink antialiased">
27
27
  <div id="root"></div>
@@ -4688,6 +4688,33 @@ export async function buildServer(options = {}) {
4688
4688
  }
4689
4689
  return getWikiPageDetail(note.id);
4690
4690
  });
4691
+ app.delete("/api/v1/wiki/pages/:id", async (request, reply) => {
4692
+ const { id } = request.params;
4693
+ const current = getNoteById(id);
4694
+ if (!current || (current.kind !== "wiki" && current.kind !== "evidence")) {
4695
+ reply.code(404);
4696
+ return { error: "Wiki page not found" };
4697
+ }
4698
+ if (current.slug === "index") {
4699
+ reply.code(400);
4700
+ return { error: "The wiki home page cannot be deleted." };
4701
+ }
4702
+ const linkedEntityType = current.links[0]?.entityType ?? null;
4703
+ const auth = requireNoteAccess(request.headers, linkedEntityType, {
4704
+ route: "/api/v1/wiki/pages/:id",
4705
+ entityType: linkedEntityType
4706
+ });
4707
+ const deleted = deleteEntity("note", id, entityDeleteQuerySchema.parse(request.query ?? {}), toActivityContext(auth));
4708
+ if (!deleted) {
4709
+ reply.code(404);
4710
+ return { error: "Wiki page not found" };
4711
+ }
4712
+ return {
4713
+ deleted: {
4714
+ id: deleted.id
4715
+ }
4716
+ };
4717
+ });
4691
4718
  app.post("/api/v1/wiki/search", async (request) => {
4692
4719
  requireScopedAccess(request.headers, ["read", "write"], { route: "/api/v1/wiki/search" });
4693
4720
  return searchWikiPages(wikiSearchQuerySchema.parse(request.body ?? {}), managers.secrets);
package/dist/server/db.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { existsSync } from "node:fs";
1
2
  import { mkdir, readdir, readFile } from "node:fs/promises";
2
3
  import path from "node:path";
3
4
  import { fileURLToPath } from "node:url";
@@ -12,7 +13,21 @@ function dateOffsetIso(days) {
12
13
  }
13
14
  const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
14
15
  const migrationsDir = path.join(projectRoot, "server", "migrations");
15
- let dataRoot = process.cwd();
16
+ const monorepoForgeDataRoot = path.resolve(projectRoot, "..", "..", "data", "forge");
17
+ export function resolveDefaultDataRoot(currentWorkingDir = process.cwd()) {
18
+ const configured = process.env.FORGE_DATA_ROOT?.trim();
19
+ if (configured) {
20
+ return path.resolve(configured);
21
+ }
22
+ // Inside the private monorepo, prefer the tracked shared Forge data root so
23
+ // the local app, Hermes, OpenClaw, and repo-managed data all point at the
24
+ // same state by default.
25
+ if (existsSync(path.join(monorepoForgeDataRoot, "data"))) {
26
+ return monorepoForgeDataRoot;
27
+ }
28
+ return path.resolve(currentWorkingDir);
29
+ }
30
+ let dataRoot = resolveDefaultDataRoot();
16
31
  let seedDemoDataEnabled = false;
17
32
  let db = null;
18
33
  let transactionDepth = 0;
@@ -8,6 +8,7 @@ import { resolveDataDir, getDatabase } from "../db.js";
8
8
  import { decorateOwnedEntity } from "./entity-ownership.js";
9
9
  import { createNoteLinkSchema, crudEntityTypeSchema, noteKindSchema, noteSchema as persistedNoteSchema, wikiSearchModeSchema, wikiSpaceVisibilitySchema } from "../types.js";
10
10
  import { deleteEncryptedSecret, readEncryptedSecret, storeEncryptedSecret } from "./calendar.js";
11
+ import { isEntityDeleted } from "./deleted-entities.js";
11
12
  import { recordDiagnosticLog } from "./diagnostic-logs.js";
12
13
  const wikiSpaceSchema = z.object({
13
14
  id: z.string(),
@@ -528,6 +529,103 @@ function getNoteBySlugRaw(spaceId, slug, exceptNoteId) {
528
529
  .get(...(exceptNoteId ? [spaceId, slug, exceptNoteId] : [spaceId, slug]));
529
530
  return row;
530
531
  }
532
+ function getNoteByTitleRaw(spaceId, title, exceptNoteId) {
533
+ return getDatabase()
534
+ .prepare(`SELECT id, kind, title, slug, space_id, aliases_json, summary, content_markdown, content_plain, author, source,
535
+ tags_json, destroy_at, source_path, frontmatter_json, revision_hash, last_synced_at, parent_slug, index_order, show_in_index, created_at, updated_at
536
+ FROM notes
537
+ WHERE space_id = ?
538
+ AND lower(title) = lower(?)
539
+ ${exceptNoteId ? "AND id != ?" : ""}
540
+ LIMIT 1`)
541
+ .get(...(exceptNoteId ? [spaceId, title, exceptNoteId] : [spaceId, title]));
542
+ }
543
+ function listNotesByTitleRaw(spaceId, title, exceptNoteId) {
544
+ return getDatabase()
545
+ .prepare(`SELECT id, kind, title, slug, space_id, aliases_json, summary, content_markdown, content_plain, author, source,
546
+ tags_json, destroy_at, source_path, frontmatter_json, revision_hash, last_synced_at, parent_slug, index_order, show_in_index, created_at, updated_at
547
+ FROM notes
548
+ WHERE space_id = ?
549
+ AND lower(title) = lower(?)
550
+ ${exceptNoteId ? "AND id != ?" : ""}
551
+ ORDER BY updated_at DESC`)
552
+ .all(...(exceptNoteId ? [spaceId, title, exceptNoteId] : [spaceId, title]));
553
+ }
554
+ function getActiveNoteByIdRaw(noteId) {
555
+ const row = getNoteByIdRaw(noteId);
556
+ if (!row || isEntityDeleted("note", row.id)) {
557
+ return null;
558
+ }
559
+ return row;
560
+ }
561
+ function getActiveNoteBySlugRaw(spaceId, slug, exceptNoteId) {
562
+ const row = getNoteBySlugRaw(spaceId, slug, exceptNoteId);
563
+ if (!row || isEntityDeleted("note", row.id)) {
564
+ return null;
565
+ }
566
+ return row;
567
+ }
568
+ function scoreReferenceMatch(reference, row) {
569
+ const referenceSlug = slugify(reference);
570
+ const titleSlug = slugify(row.title);
571
+ if (referenceSlug && row.slug === referenceSlug) {
572
+ return 0;
573
+ }
574
+ if (titleSlug && row.slug === titleSlug) {
575
+ return 1;
576
+ }
577
+ const parseSuffix = (base) => {
578
+ if (!base || !row.slug.startsWith(`${base}-`)) {
579
+ return null;
580
+ }
581
+ const suffix = Number(row.slug.slice(base.length + 1));
582
+ return Number.isFinite(suffix) ? suffix : null;
583
+ };
584
+ const referenceSuffix = parseSuffix(referenceSlug);
585
+ if (referenceSuffix !== null) {
586
+ return 100 + referenceSuffix;
587
+ }
588
+ const titleSuffix = parseSuffix(titleSlug);
589
+ if (titleSuffix !== null) {
590
+ return 200 + titleSuffix;
591
+ }
592
+ return 10_000;
593
+ }
594
+ function chooseBestActiveReferenceMatch(reference, rows) {
595
+ const activeRows = rows.filter((row) => !isEntityDeleted("note", row.id));
596
+ if (activeRows.length === 0) {
597
+ return null;
598
+ }
599
+ return [...activeRows].sort((left, right) => {
600
+ const leftScore = scoreReferenceMatch(reference, left);
601
+ const rightScore = scoreReferenceMatch(reference, right);
602
+ if (leftScore !== rightScore) {
603
+ return leftScore - rightScore;
604
+ }
605
+ if (left.updated_at !== right.updated_at) {
606
+ return right.updated_at.localeCompare(left.updated_at);
607
+ }
608
+ return left.id.localeCompare(right.id);
609
+ })[0];
610
+ }
611
+ function getActiveNoteByReferenceRaw(spaceId, reference, exceptNoteId) {
612
+ const normalized = reference.trim();
613
+ if (!normalized) {
614
+ return null;
615
+ }
616
+ const slugMatch = getActiveNoteBySlugRaw(spaceId, normalized, exceptNoteId);
617
+ if (slugMatch) {
618
+ return slugMatch;
619
+ }
620
+ const titleMatch = chooseBestActiveReferenceMatch(normalized, listNotesByTitleRaw(spaceId, normalized, exceptNoteId));
621
+ if (titleMatch) {
622
+ return titleMatch;
623
+ }
624
+ const lowered = normalized.toLowerCase();
625
+ const aliasMatch = chooseBestActiveReferenceMatch(normalized, getNoteRows("WHERE space_id = ?", [spaceId]).filter((row) => row.id !== exceptNoteId &&
626
+ parseJsonStringArray(row.aliases_json).some((alias) => alias.trim().toLowerCase() === lowered)));
627
+ return aliasMatch ?? null;
628
+ }
531
629
  function buildContentPlain(markdown) {
532
630
  return markdown
533
631
  .replace(/^---[\s\S]*?---\s*/m, "")
@@ -1428,7 +1526,7 @@ function rebuildWikiLinkEdges(note) {
1428
1526
  continue;
1429
1527
  }
1430
1528
  }
1431
- const targetNote = getNoteBySlugRaw(note.spaceId, left.trim(), note.id);
1529
+ const targetNote = getActiveNoteByReferenceRaw(note.spaceId, left.trim(), note.id);
1432
1530
  if (targetNote) {
1433
1531
  insert.run(note.id, "page", targetNote.id, null, null, label, left.trim(), isEmbed ? 1 : 0, now, now);
1434
1532
  continue;
@@ -1465,7 +1563,9 @@ function listAllNotes() {
1465
1563
  current.push(link);
1466
1564
  linksByNoteId.set(link.note_id, current);
1467
1565
  }
1468
- return rows.map((row) => mapNoteRow(row, linksByNoteId.get(row.id) ?? []));
1566
+ return rows
1567
+ .filter((row) => !isEntityDeleted("note", row.id))
1568
+ .map((row) => mapNoteRow(row, linksByNoteId.get(row.id) ?? []));
1469
1569
  }
1470
1570
  export function listWikiSpaces() {
1471
1571
  ensureSharedWikiSpace();
@@ -1530,7 +1630,7 @@ export function listWikiPageTree(query) {
1530
1630
  export function getWikiHomePageDetail(input = {}) {
1531
1631
  const spaceId = resolveSpaceId(input.spaceId, null);
1532
1632
  ensureWikiSpaceSeedPages(spaceId);
1533
- const home = getNoteBySlugRaw(spaceId, "index");
1633
+ const home = getActiveNoteBySlugRaw(spaceId, "index");
1534
1634
  if (!home) {
1535
1635
  return null;
1536
1636
  }
@@ -1539,14 +1639,14 @@ export function getWikiHomePageDetail(input = {}) {
1539
1639
  export function getWikiPageDetailBySlug(input) {
1540
1640
  const spaceId = resolveSpaceId(input.spaceId, null);
1541
1641
  ensureWikiSpaceSeedPages(spaceId);
1542
- const row = getNoteBySlugRaw(spaceId, input.slug.trim());
1642
+ const row = getActiveNoteByReferenceRaw(spaceId, input.slug.trim());
1543
1643
  if (!row) {
1544
1644
  return null;
1545
1645
  }
1546
1646
  return getWikiPageDetail(row.id);
1547
1647
  }
1548
1648
  export function getWikiPageDetail(noteId) {
1549
- const row = getNoteByIdRaw(noteId);
1649
+ const row = getActiveNoteByIdRaw(noteId);
1550
1650
  if (!row) {
1551
1651
  return null;
1552
1652
  }
@@ -3125,6 +3225,9 @@ export async function reviewWikiIngestJob(jobId, input, options) {
3125
3225
  if (!candidate) {
3126
3226
  continue;
3127
3227
  }
3228
+ if (candidate.status === "applied") {
3229
+ continue;
3230
+ }
3128
3231
  const action = decision.action ??
3129
3232
  (decision.keep === false ? "discard" : "keep");
3130
3233
  if (action === "discard") {
@@ -2,7 +2,7 @@
2
2
  "id": "forge-openclaw-plugin",
3
3
  "name": "Forge",
4
4
  "description": "Curated OpenClaw adapter for the Forge collaboration API, UI entrypoint, and localhost auto-start runtime.",
5
- "version": "0.2.21",
5
+ "version": "0.2.23",
6
6
  "skills": [
7
7
  "./skills"
8
8
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forge-openclaw-plugin",
3
- "version": "0.2.21",
3
+ "version": "0.2.23",
4
4
  "description": "Curated OpenClaw adapter for the Forge collaboration API, UI entrypoint, and localhost auto-start runtime.",
5
5
  "type": "module",
6
6
  "license": "MIT",