forge-openclaw-plugin 0.2.115 → 0.2.116

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.
@@ -20,8 +20,11 @@ const MODEL_CONTEXT_WINDOWS = {
20
20
  };
21
21
  const DEFAULT_CONTEXT_WINDOW = 400_000;
22
22
  const RESERVED_RESPONSE_TOKENS = 140_000;
23
+ const CODEX_WIKI_COMPILE_CONTEXT_WINDOW = 120_000;
24
+ const CODEX_WIKI_COMPILE_RESERVED_RESPONSE_TOKENS = 60_000;
23
25
  const APPROX_CHARS_PER_TOKEN = 4;
24
26
  const REQUEST_TIMEOUT_MS = 90_000;
27
+ const CODEX_FOREGROUND_COMPILE_TIMEOUT_MS = 10 * 60_000;
25
28
  const BACKGROUND_POLL_INTERVAL_MS = 2_000;
26
29
  const DEFAULT_CODEX_BASE_URL = "https://chatgpt.com/backend-api";
27
30
  const CODEX_JWT_CLAIM_PATH = "https://api.openai.com/auth";
@@ -358,8 +361,14 @@ function estimateTokens(text) {
358
361
  return Math.ceil(text.length / APPROX_CHARS_PER_TOKEN);
359
362
  }
360
363
  function computeSourceExcerpt(profile, sourceText) {
361
- const contextWindow = MODEL_CONTEXT_WINDOWS[profile.model] ?? DEFAULT_CONTEXT_WINDOW;
362
- const inputBudget = Math.max(16_000, contextWindow - RESERVED_RESPONSE_TOKENS);
364
+ const configuredContextWindow = MODEL_CONTEXT_WINDOWS[profile.model] ?? DEFAULT_CONTEXT_WINDOW;
365
+ const contextWindow = isCodexProfile(profile)
366
+ ? Math.min(configuredContextWindow, CODEX_WIKI_COMPILE_CONTEXT_WINDOW)
367
+ : configuredContextWindow;
368
+ const reservedResponseTokens = isCodexProfile(profile)
369
+ ? CODEX_WIKI_COMPILE_RESERVED_RESPONSE_TOKENS
370
+ : RESERVED_RESPONSE_TOKENS;
371
+ const inputBudget = Math.max(16_000, contextWindow - reservedResponseTokens);
363
372
  const estimatedTokens = estimateTokens(sourceText);
364
373
  if (estimatedTokens <= inputBudget) {
365
374
  return {
@@ -617,6 +626,10 @@ export class OpenAiResponsesProvider {
617
626
  "- For chats and transcripts, extract the durable parts: people, relationships, ongoing projects, commitments, habits, values, decisions, questions, sources, and evidence.",
618
627
  "- Merge repetitive back-and-forth into concise summaries.",
619
628
  "- Use short quotes only when the exact phrase matters.",
629
+ "Sensitive information rules:",
630
+ "- Never store or reproduce secrets, passwords, passphrases, recovery codes, API keys, access tokens, refresh tokens, session cookies, private keys, seed phrases, one-time codes, full payment card numbers, or equivalent credentials.",
631
+ "- If the source contains a secret or credential, replace the value with [REDACTED SECRET] and keep only the minimum non-secret context needed to explain why it appeared.",
632
+ "- Do not create wiki pages, notes, entity proposals, aliases, tags, titles, or page update suggestions that preserve secret values.",
620
633
  "How to split pages:",
621
634
  "- Keep markdown as the overview page for this source.",
622
635
  "- If one topic deserves its own page, put it in articleCandidates with title, slug, summary, rationale, markdown, tags, aliases, and parentSlug.",
@@ -820,7 +833,9 @@ export class OpenAiResponsesProvider {
820
833
  }
821
834
  })
822
835
  }),
823
- signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
836
+ signal: AbortSignal.timeout(isCodexProfile(profile)
837
+ ? CODEX_FOREGROUND_COMPILE_TIMEOUT_MS
838
+ : REQUEST_TIMEOUT_MS)
824
839
  });
825
840
  }
826
841
  catch (error) {
@@ -10,6 +10,8 @@ import { createNoteLinkSchema, crudEntityTypeSchema, noteKindSchema, noteSchema
10
10
  import { deleteEncryptedSecret, readEncryptedSecret, storeEncryptedSecret } from "./calendar.js";
11
11
  import { isEntityDeleted } from "./deleted-entities.js";
12
12
  import { recordDiagnosticLog } from "./diagnostic-logs.js";
13
+ const MAX_WIKI_INGEST_TEXT_CHUNK_CHARS = 220_000;
14
+ const WIKI_INGEST_TEXT_CHUNK_OVERLAP_CHARS = 2_000;
13
15
  const wikiSpaceSchema = z.object({
14
16
  id: z.string(),
15
17
  slug: z.string(),
@@ -2017,6 +2019,115 @@ export async function reindexWikiEmbeddings(input, secrets) {
2017
2019
  chunkCount
2018
2020
  };
2019
2021
  }
2022
+ function isChunkableWikiIngestTextAsset(asset) {
2023
+ const mimeType = asset.mime_type.toLowerCase();
2024
+ const fileName = asset.file_name.toLowerCase();
2025
+ if (!existsSync(asset.file_path)) {
2026
+ return false;
2027
+ }
2028
+ if (asset.size_bytes <= MAX_WIKI_INGEST_TEXT_CHUNK_CHARS) {
2029
+ return false;
2030
+ }
2031
+ const metadata = parseJsonRecord(asset.metadata_json);
2032
+ if (metadata?.chunkParentAssetId || metadata?.textChunked) {
2033
+ return false;
2034
+ }
2035
+ return (mimeType.startsWith("text/") ||
2036
+ fileName.endsWith(".txt") ||
2037
+ fileName.endsWith(".md") ||
2038
+ fileName.endsWith(".markdown") ||
2039
+ fileName.endsWith(".csv") ||
2040
+ fileName.endsWith(".json"));
2041
+ }
2042
+ function splitWikiIngestTextIntoChunks(sourceText, maxChars) {
2043
+ const text = sourceText.trim();
2044
+ if (text.length <= maxChars) {
2045
+ return [text];
2046
+ }
2047
+ const chunks = [];
2048
+ let start = 0;
2049
+ while (start < text.length) {
2050
+ const hardEnd = Math.min(text.length, start + maxChars);
2051
+ let end = hardEnd;
2052
+ if (hardEnd < text.length) {
2053
+ const newline = text.lastIndexOf("\n", hardEnd);
2054
+ if (newline > start + Math.floor(maxChars * 0.65)) {
2055
+ end = newline + 1;
2056
+ }
2057
+ }
2058
+ const chunk = text.slice(start, end).trim();
2059
+ if (chunk.length > 0) {
2060
+ chunks.push(chunk);
2061
+ }
2062
+ if (end >= text.length) {
2063
+ break;
2064
+ }
2065
+ start = Math.max(0, end - WIKI_INGEST_TEXT_CHUNK_OVERLAP_CHARS);
2066
+ }
2067
+ return chunks;
2068
+ }
2069
+ async function splitLargeWikiIngestTextAsset(options) {
2070
+ if (!isChunkableWikiIngestTextAsset(options.asset)) {
2071
+ return 0;
2072
+ }
2073
+ const sourceText = await readFile(options.asset.file_path, "utf8");
2074
+ const chunks = splitWikiIngestTextIntoChunks(sourceText, MAX_WIKI_INGEST_TEXT_CHUNK_CHARS);
2075
+ if (chunks.length <= 1) {
2076
+ return 0;
2077
+ }
2078
+ const extension = path.extname(options.asset.file_name) || ".txt";
2079
+ const baseName = path.basename(options.asset.file_name, extension) ||
2080
+ options.asset.file_name ||
2081
+ "source";
2082
+ const width = String(chunks.length).length;
2083
+ for (const [index, chunk] of chunks.entries()) {
2084
+ const chunkNumber = index + 1;
2085
+ const chunkFileName = `${baseName}-part-${String(chunkNumber).padStart(width, "0")}-of-${String(chunks.length).padStart(width, "0")}${extension}`;
2086
+ const chunkHeader = [
2087
+ `Source file: ${options.asset.file_name}`,
2088
+ `Source locator: ${options.asset.source_locator || options.asset.file_name}`,
2089
+ `Chunk: ${chunkNumber}/${chunks.length}`,
2090
+ `Parent checksum: ${options.asset.checksum}`,
2091
+ "",
2092
+ chunk
2093
+ ].join("\n");
2094
+ const persisted = await persistIngestUpload({
2095
+ jobId: options.jobId,
2096
+ fileName: chunkFileName,
2097
+ mimeType: options.asset.mime_type || "text/plain",
2098
+ payload: Buffer.from(chunkHeader, "utf8")
2099
+ });
2100
+ createWikiIngestAssetRecord({
2101
+ jobId: options.jobId,
2102
+ sourceKind: "upload",
2103
+ sourceLocator: `${options.asset.source_locator || options.asset.file_name}#chunk-${chunkNumber}`,
2104
+ fileName: chunkFileName,
2105
+ mimeType: options.asset.mime_type || "text/plain",
2106
+ filePath: persisted.filePath,
2107
+ sizeBytes: persisted.sizeBytes,
2108
+ checksum: persisted.checksum,
2109
+ metadata: {
2110
+ chunkParentAssetId: options.asset.id,
2111
+ chunkParentChecksum: options.asset.checksum,
2112
+ chunkIndex: chunkNumber,
2113
+ chunkCount: chunks.length,
2114
+ chunkOverlapChars: WIKI_INGEST_TEXT_CHUNK_OVERLAP_CHARS,
2115
+ chunkMaxChars: MAX_WIKI_INGEST_TEXT_CHUNK_CHARS
2116
+ }
2117
+ });
2118
+ }
2119
+ updateWikiIngestAsset(options.asset.id, {
2120
+ status: "completed",
2121
+ metadata: {
2122
+ ...parseJsonRecord(options.asset.metadata_json),
2123
+ textChunked: true,
2124
+ textChunkCount: chunks.length,
2125
+ textChunkMaxChars: MAX_WIKI_INGEST_TEXT_CHUNK_CHARS,
2126
+ textChunkOverlapChars: WIKI_INGEST_TEXT_CHUNK_OVERLAP_CHARS
2127
+ }
2128
+ });
2129
+ return chunks.length;
2130
+ }
2020
2131
  function getWikiIngestJobDir(jobId) {
2021
2132
  return path.join(resolveDataDir(), "wiki-ingest", jobId);
2022
2133
  }
@@ -2615,7 +2726,10 @@ export async function processWikiIngestJob(jobId, options) {
2615
2726
  const initialAssets = listWikiIngestJobAssetsInternal(jobId);
2616
2727
  let processedFiles = initialAssets.filter((asset) => asset.status === "completed").length;
2617
2728
  let totalFiles = Math.max(job.total_files, initialAssets.length);
2618
- let hadSuccess = false;
2729
+ let hadSuccess = (() => {
2730
+ const counts = refreshCounts();
2731
+ return counts.pageCount + counts.entityCount > 0;
2732
+ })();
2619
2733
  while (assetQueue().length > 0) {
2620
2734
  const nextAsset = assetQueue().find((asset) => ["processing", "queued"].includes(asset.status));
2621
2735
  if (!nextAsset) {
@@ -2677,6 +2791,27 @@ export async function processWikiIngestJob(jobId, options) {
2677
2791
  }
2678
2792
  continue;
2679
2793
  }
2794
+ const derivedChunkCount = await splitLargeWikiIngestTextAsset({
2795
+ jobId,
2796
+ asset: nextAsset
2797
+ });
2798
+ if (derivedChunkCount > 0) {
2799
+ totalFiles = Math.max(derivedChunkCount, totalFiles - 1 + derivedChunkCount);
2800
+ updateWikiIngestJob(jobId, {
2801
+ totalFiles,
2802
+ latestMessage: `Split ${nextAsset.file_name || "large text source"} into ${derivedChunkCount} chunks.`
2803
+ });
2804
+ createWikiIngestLog(jobId, `Split ${nextAsset.file_name || "large text source"} into ${derivedChunkCount} chunks.`, "info", {
2805
+ sourceAssetId: nextAsset.id,
2806
+ fileName: nextAsset.file_name,
2807
+ sourceLocator: nextAsset.source_locator,
2808
+ checksum: nextAsset.checksum,
2809
+ chunkCount: derivedChunkCount,
2810
+ chunkMaxChars: MAX_WIKI_INGEST_TEXT_CHUNK_CHARS,
2811
+ chunkOverlapChars: WIKI_INGEST_TEXT_CHUNK_OVERLAP_CHARS
2812
+ });
2813
+ continue;
2814
+ }
2680
2815
  updateWikiIngestAsset(nextAsset.id, { status: "processing" });
2681
2816
  currentAssetContext = {
2682
2817
  assetId: nextAsset.id,
@@ -301,6 +301,10 @@ function candidateIrohBinaries() {
301
301
  return candidateIrohAssetRoots().flatMap((root) => [
302
302
  path.join(root, "companion-iroh", "target", "release", binaryName),
303
303
  path.join(root, "companion-iroh", "target", "debug", binaryName),
304
+ path.join(root, "companion-iroh-src", "target", "release", binaryName),
305
+ path.join(root, "companion-iroh-src", "target", "debug", binaryName),
306
+ path.join(root, "dist", "companion-iroh-src", "target", "release", binaryName),
307
+ path.join(root, "dist", "companion-iroh-src", "target", "debug", binaryName),
304
308
  path.join(root, "openclaw-plugin", "dist", "companion-iroh", platformKey, binaryName),
305
309
  path.join(root, "companion-iroh", platformKey, binaryName),
306
310
  path.join(root, "companion-iroh", binaryName)
@@ -309,7 +313,8 @@ function candidateIrohBinaries() {
309
313
  function resolveCompanionIrohManifestPath() {
310
314
  const candidates = candidateIrohAssetRoots().flatMap((root) => [
311
315
  path.join(root, "companion-iroh", "Cargo.toml"),
312
- path.join(root, "companion-iroh-src", "Cargo.toml")
316
+ path.join(root, "companion-iroh-src", "Cargo.toml"),
317
+ path.join(root, "dist", "companion-iroh-src", "Cargo.toml")
313
318
  ]);
314
319
  return candidates.find((candidate) => existsSync(candidate)) ?? null;
315
320
  }
@@ -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.115",
5
+ "version": "0.2.116",
6
6
  "activation": {
7
7
  "onStartup": true,
8
8
  "onCapabilities": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forge-openclaw-plugin",
3
- "version": "0.2.115",
3
+ "version": "0.2.116",
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": "Apache-2.0",