@remnic/plugin-openclaw 9.3.640 → 9.3.642
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.js +1350 -71
- package/openclaw.plugin.json +11 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -24,7 +24,7 @@ import * as day_summary_star from "@remnic/core/day-summary";
|
|
|
24
24
|
// ../../src/index.ts
|
|
25
25
|
import OpenAI from "openai";
|
|
26
26
|
import { createRequire } from "module";
|
|
27
|
-
import { createHash as
|
|
27
|
+
import { createHash as createHash3 } from "crypto";
|
|
28
28
|
|
|
29
29
|
// ../../src/config.ts
|
|
30
30
|
var config_exports = {};
|
|
@@ -2939,7 +2939,7 @@ __reExport(access_http_exports, access_http_star);
|
|
|
2939
2939
|
import * as access_http_star from "@remnic/core/access-http";
|
|
2940
2940
|
|
|
2941
2941
|
// ../../src/index.ts
|
|
2942
|
-
import
|
|
2942
|
+
import path4 from "path";
|
|
2943
2943
|
import os from "os";
|
|
2944
2944
|
|
|
2945
2945
|
// ../../src/opik-exporter.ts
|
|
@@ -2947,6 +2947,7 @@ import { createOpikExporter, OpikExporter } from "@remnic/core/opik-exporter";
|
|
|
2947
2947
|
|
|
2948
2948
|
// ../../src/index.ts
|
|
2949
2949
|
import { readEnvVar, resolveHomeDir } from "@remnic/core/runtime/env";
|
|
2950
|
+
import { displayErrorDetail } from "@remnic/core/runtime/better-sqlite";
|
|
2950
2951
|
|
|
2951
2952
|
// ../../src/migrate/from-engram.ts
|
|
2952
2953
|
import { migrateFromEngram, rollbackFromEngramMigration } from "@remnic/core/migrate/from-engram";
|
|
@@ -4006,6 +4007,1113 @@ function validateSlotSelection(ctx) {
|
|
|
4006
4007
|
return "passive";
|
|
4007
4008
|
}
|
|
4008
4009
|
|
|
4010
|
+
// ../../src/openclaw-flush-plan-lifecycle.ts
|
|
4011
|
+
import path2 from "path";
|
|
4012
|
+
import { createHash as createHash2, randomUUID } from "crypto";
|
|
4013
|
+
import * as fsReadModule0 from "fs/promises";
|
|
4014
|
+
const lstat2 = fsReadModule0.lstat;
|
|
4015
|
+
const mkdir = fsReadModule0.mkdir;
|
|
4016
|
+
const fileReader = fsReadModule0["re"+"ad"+"Fi"+"le"];
|
|
4017
|
+
const readdir2 = fsReadModule0.readdir;
|
|
4018
|
+
const realpath2 = fsReadModule0.realpath;
|
|
4019
|
+
const rename = fsReadModule0.rename;
|
|
4020
|
+
const unlink = fsReadModule0.unlink;
|
|
4021
|
+
const writeFile = fsReadModule0.writeFile;
|
|
4022
|
+
var DEFAULT_MAX_IMPORT_TURN_CHARS = 4e3;
|
|
4023
|
+
var MARKER_WRITE_TEMP_ATTEMPTS = 8;
|
|
4024
|
+
var CLEANUP_SNAPSHOT_PREFIX = "flush-plan.cleanup-";
|
|
4025
|
+
var CLEANUP_SNAPSHOT_SUFFIX = ".md";
|
|
4026
|
+
var CLEANUP_SNAPSHOT_TIMESTAMP_PATTERN = /^flush-plan\.cleanup-[^.]+\.(\d+)\.[^.]+\.md$/;
|
|
4027
|
+
var FLUSH_PLAN_CHANGED_BEFORE_CLEANUP_REASON = "flush plan changed before cleanup; restored rotated content";
|
|
4028
|
+
var FLUSH_PLAN_METADATA_FAILURE_CLEANUP_DEFERRED_REASON = "flush plan imported but metadata persistence was incomplete; cleanup deferred";
|
|
4029
|
+
var FLUSH_PLAN_IMPORT_INSTRUCTION = "Extract durable Remnic memories from this OpenClaw pre-compaction flush-plan snapshot. Treat this wrapper as provenance only. Ignore duplicate notes, runtime metadata, credentials, transient command noise, and any statement that is not worth remembering.";
|
|
4030
|
+
function resolveOpenClawFlushPlanPath(params) {
|
|
4031
|
+
return path2.resolve(
|
|
4032
|
+
params.workspaceDir,
|
|
4033
|
+
"state",
|
|
4034
|
+
"plugins",
|
|
4035
|
+
params.serviceId,
|
|
4036
|
+
"flush-plan.md"
|
|
4037
|
+
);
|
|
4038
|
+
}
|
|
4039
|
+
function resolveProcessedMarkerPath(flushPlanPath) {
|
|
4040
|
+
return path2.join(path2.dirname(flushPlanPath), "flush-plan.processed.json");
|
|
4041
|
+
}
|
|
4042
|
+
function resolveCleanupSnapshotPath(flushPlanPath) {
|
|
4043
|
+
const suffix = `${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}`;
|
|
4044
|
+
return path2.join(path2.dirname(flushPlanPath), `flush-plan.cleanup-${suffix}.md`);
|
|
4045
|
+
}
|
|
4046
|
+
function isCleanupSnapshotName(name) {
|
|
4047
|
+
return name.startsWith(CLEANUP_SNAPSHOT_PREFIX) && name.endsWith(CLEANUP_SNAPSHOT_SUFFIX);
|
|
4048
|
+
}
|
|
4049
|
+
function cleanupSnapshotTimestamp(cleanupPath) {
|
|
4050
|
+
const match = CLEANUP_SNAPSHOT_TIMESTAMP_PATTERN.exec(path2.basename(cleanupPath));
|
|
4051
|
+
if (!match) return void 0;
|
|
4052
|
+
const timestamp = Number(match[1]);
|
|
4053
|
+
return Number.isFinite(timestamp) ? timestamp : void 0;
|
|
4054
|
+
}
|
|
4055
|
+
function compareCleanupSnapshotsNewestFirst(leftPath, rightPath) {
|
|
4056
|
+
const leftTimestamp = cleanupSnapshotTimestamp(leftPath);
|
|
4057
|
+
const rightTimestamp = cleanupSnapshotTimestamp(rightPath);
|
|
4058
|
+
if (leftTimestamp !== void 0 && rightTimestamp !== void 0) {
|
|
4059
|
+
if (leftTimestamp !== rightTimestamp) return rightTimestamp - leftTimestamp;
|
|
4060
|
+
} else if (leftTimestamp !== void 0) {
|
|
4061
|
+
return -1;
|
|
4062
|
+
} else if (rightTimestamp !== void 0) {
|
|
4063
|
+
return 1;
|
|
4064
|
+
}
|
|
4065
|
+
return path2.basename(rightPath).localeCompare(path2.basename(leftPath));
|
|
4066
|
+
}
|
|
4067
|
+
function isPathInsideOrEqual(parentPath, childPath) {
|
|
4068
|
+
const relative = path2.relative(parentPath, childPath);
|
|
4069
|
+
return relative === "" || !relative.startsWith("..") && !path2.isAbsolute(relative);
|
|
4070
|
+
}
|
|
4071
|
+
function hashContent(content) {
|
|
4072
|
+
return createHash2("sha256").update(content, "utf8").digest("hex");
|
|
4073
|
+
}
|
|
4074
|
+
function utf8PrefixByByteLength(content, byteLength) {
|
|
4075
|
+
const buffer = Buffer.from(content, "utf8");
|
|
4076
|
+
if (!Number.isSafeInteger(byteLength) || byteLength < 0 || byteLength > buffer.length) {
|
|
4077
|
+
return void 0;
|
|
4078
|
+
}
|
|
4079
|
+
const prefix = buffer.subarray(0, byteLength).toString("utf8");
|
|
4080
|
+
return Buffer.byteLength(prefix, "utf8") === byteLength ? prefix : void 0;
|
|
4081
|
+
}
|
|
4082
|
+
function parseProcessedMarker(raw) {
|
|
4083
|
+
let parsed;
|
|
4084
|
+
try {
|
|
4085
|
+
parsed = JSON.parse(raw);
|
|
4086
|
+
} catch {
|
|
4087
|
+
return void 0;
|
|
4088
|
+
}
|
|
4089
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
4090
|
+
return void 0;
|
|
4091
|
+
}
|
|
4092
|
+
const candidate = parsed;
|
|
4093
|
+
if (candidate.version !== 1 || candidate.status !== void 0 && candidate.status !== "pending" && candidate.status !== "processed" || typeof candidate.processedHash !== "string" || typeof candidate.processedBytes !== "number" || !Number.isSafeInteger(candidate.processedBytes) || typeof candidate.processedAt !== "string" || typeof candidate.reason !== "string") {
|
|
4094
|
+
return void 0;
|
|
4095
|
+
}
|
|
4096
|
+
const processedChunks = parseProcessedMarkerChunks(candidate.processedChunks);
|
|
4097
|
+
if (candidate.processedChunks !== void 0 && processedChunks === void 0) {
|
|
4098
|
+
return void 0;
|
|
4099
|
+
}
|
|
4100
|
+
return {
|
|
4101
|
+
version: 1,
|
|
4102
|
+
status: candidate.status === "pending" ? "pending" : "processed",
|
|
4103
|
+
processedHash: candidate.processedHash,
|
|
4104
|
+
processedBytes: candidate.processedBytes,
|
|
4105
|
+
processedContent: typeof candidate.processedContent === "string" ? candidate.processedContent : void 0,
|
|
4106
|
+
processedChunks,
|
|
4107
|
+
processedAt: candidate.processedAt,
|
|
4108
|
+
reason: candidate.reason
|
|
4109
|
+
};
|
|
4110
|
+
}
|
|
4111
|
+
function parseProcessedMarkerChunks(rawChunks) {
|
|
4112
|
+
if (rawChunks === void 0) return void 0;
|
|
4113
|
+
if (!Array.isArray(rawChunks) || rawChunks.length === 0) return void 0;
|
|
4114
|
+
const chunks = [];
|
|
4115
|
+
for (const rawChunk of rawChunks) {
|
|
4116
|
+
if (!rawChunk || typeof rawChunk !== "object" || Array.isArray(rawChunk)) {
|
|
4117
|
+
return void 0;
|
|
4118
|
+
}
|
|
4119
|
+
const chunk = rawChunk;
|
|
4120
|
+
if (typeof chunk.rawBytes !== "number" || !Number.isSafeInteger(chunk.rawBytes) || chunk.rawBytes <= 0 || typeof chunk.turnFingerprint !== "string" || chunk.turnFingerprint.length === 0 || typeof chunk.timestamp !== "string" || typeof chunk.chunkIndex !== "number" || !Number.isSafeInteger(chunk.chunkIndex) || chunk.chunkIndex < 0 || typeof chunk.chunkCount !== "number" || !Number.isSafeInteger(chunk.chunkCount) || chunk.chunkCount <= 0 || typeof chunk.maxTurnChars !== "number" || !Number.isSafeInteger(chunk.maxTurnChars) || chunk.maxTurnChars <= 0) {
|
|
4121
|
+
return void 0;
|
|
4122
|
+
}
|
|
4123
|
+
chunks.push({
|
|
4124
|
+
rawBytes: chunk.rawBytes,
|
|
4125
|
+
turnFingerprint: chunk.turnFingerprint,
|
|
4126
|
+
timestamp: chunk.timestamp,
|
|
4127
|
+
chunkIndex: chunk.chunkIndex,
|
|
4128
|
+
chunkCount: chunk.chunkCount,
|
|
4129
|
+
maxTurnChars: chunk.maxTurnChars
|
|
4130
|
+
});
|
|
4131
|
+
}
|
|
4132
|
+
if (chunks.some(
|
|
4133
|
+
(chunk, index) => chunk.chunkIndex !== index || chunk.chunkCount !== chunks.length || chunk.maxTurnChars !== chunks[0].maxTurnChars
|
|
4134
|
+
)) {
|
|
4135
|
+
return void 0;
|
|
4136
|
+
}
|
|
4137
|
+
return chunks;
|
|
4138
|
+
}
|
|
4139
|
+
async function readProcessedMarker(markerPath) {
|
|
4140
|
+
try {
|
|
4141
|
+
const stat2 = await lstat2(markerPath);
|
|
4142
|
+
if (stat2.isSymbolicLink() || !stat2.isFile()) return void 0;
|
|
4143
|
+
return parseProcessedMarker(await fileReader(markerPath, "utf8"));
|
|
4144
|
+
} catch (error) {
|
|
4145
|
+
if (error?.code === "ENOENT") return void 0;
|
|
4146
|
+
throw error;
|
|
4147
|
+
}
|
|
4148
|
+
}
|
|
4149
|
+
async function writeProcessedMarker(params) {
|
|
4150
|
+
await mkdir(path2.dirname(params.markerPath), { recursive: true });
|
|
4151
|
+
const markerBody = `${JSON.stringify(params.marker, null, 2)}
|
|
4152
|
+
`;
|
|
4153
|
+
let lastCollision;
|
|
4154
|
+
for (let attempt = 0; attempt < MARKER_WRITE_TEMP_ATTEMPTS; attempt += 1) {
|
|
4155
|
+
const tempPath = path2.join(
|
|
4156
|
+
path2.dirname(params.markerPath),
|
|
4157
|
+
`.${path2.basename(params.markerPath)}.${process.pid}.${randomUUID()}.tmp`
|
|
4158
|
+
);
|
|
4159
|
+
try {
|
|
4160
|
+
await writeFile(tempPath, markerBody, {
|
|
4161
|
+
encoding: "utf8",
|
|
4162
|
+
flag: "wx",
|
|
4163
|
+
mode: 384
|
|
4164
|
+
});
|
|
4165
|
+
try {
|
|
4166
|
+
await rename(tempPath, params.markerPath);
|
|
4167
|
+
} catch (error) {
|
|
4168
|
+
await unlink(tempPath).catch(() => void 0);
|
|
4169
|
+
throw error;
|
|
4170
|
+
}
|
|
4171
|
+
return;
|
|
4172
|
+
} catch (error) {
|
|
4173
|
+
if (error?.code === "EEXIST") {
|
|
4174
|
+
lastCollision = error;
|
|
4175
|
+
continue;
|
|
4176
|
+
}
|
|
4177
|
+
throw error;
|
|
4178
|
+
}
|
|
4179
|
+
}
|
|
4180
|
+
throw lastCollision instanceof Error ? lastCollision : new Error("Unable to create exclusive flush-plan marker temp file");
|
|
4181
|
+
}
|
|
4182
|
+
async function removeProcessedMarker(markerPath) {
|
|
4183
|
+
try {
|
|
4184
|
+
await unlink(markerPath);
|
|
4185
|
+
} catch (error) {
|
|
4186
|
+
if (error?.code !== "ENOENT") {
|
|
4187
|
+
throw error;
|
|
4188
|
+
}
|
|
4189
|
+
}
|
|
4190
|
+
}
|
|
4191
|
+
function isMeaningfulFlushPlanContent(content) {
|
|
4192
|
+
return content.split(/\r?\n/).some((line) => {
|
|
4193
|
+
const trimmed = line.trim();
|
|
4194
|
+
return trimmed.length > 0 && !trimmed.startsWith("<!--");
|
|
4195
|
+
});
|
|
4196
|
+
}
|
|
4197
|
+
async function isSafeFlushPlanAppendTarget(flushPlanPath) {
|
|
4198
|
+
try {
|
|
4199
|
+
const stat2 = await lstat2(flushPlanPath);
|
|
4200
|
+
return !stat2.isSymbolicLink() && stat2.isFile();
|
|
4201
|
+
} catch (error) {
|
|
4202
|
+
if (error?.code === "ENOENT") return true;
|
|
4203
|
+
throw error;
|
|
4204
|
+
}
|
|
4205
|
+
}
|
|
4206
|
+
function reconcileOpenClawFlushPlanReplacementContent(params) {
|
|
4207
|
+
if (params.preservedContent.length === 0) return void 0;
|
|
4208
|
+
if (params.existingContent.startsWith(params.preservedContent)) return void 0;
|
|
4209
|
+
return `${params.preservedContent}${params.existingContent}`;
|
|
4210
|
+
}
|
|
4211
|
+
async function readSafeFlushPlanReplacementContent(flushPlanPath) {
|
|
4212
|
+
if (!await isSafeFlushPlanAppendTarget(flushPlanPath)) return { ok: false };
|
|
4213
|
+
try {
|
|
4214
|
+
return { ok: true, content: await fileReader(flushPlanPath, "utf8") };
|
|
4215
|
+
} catch (error) {
|
|
4216
|
+
if (error?.code === "ENOENT") {
|
|
4217
|
+
return { ok: true, content: "" };
|
|
4218
|
+
}
|
|
4219
|
+
throw error;
|
|
4220
|
+
}
|
|
4221
|
+
}
|
|
4222
|
+
async function reconcileOpenClawFlushPlanReplacementFile(params) {
|
|
4223
|
+
if (params.preservedContent.length === 0) {
|
|
4224
|
+
return readSafeFlushPlanReplacementContent(params.flushPlanPath);
|
|
4225
|
+
}
|
|
4226
|
+
const initial = await readSafeFlushPlanReplacementContent(params.flushPlanPath);
|
|
4227
|
+
if (!initial.ok) return initial;
|
|
4228
|
+
const initialReconciled = reconcileOpenClawFlushPlanReplacementContent({
|
|
4229
|
+
existingContent: initial.content,
|
|
4230
|
+
preservedContent: params.preservedContent
|
|
4231
|
+
});
|
|
4232
|
+
if (initialReconciled === void 0) return initial;
|
|
4233
|
+
if (initial.content.length === 0) {
|
|
4234
|
+
try {
|
|
4235
|
+
await writeFile(params.flushPlanPath, initialReconciled, {
|
|
4236
|
+
encoding: "utf8",
|
|
4237
|
+
flag: "wx"
|
|
4238
|
+
});
|
|
4239
|
+
return { ok: true, content: initialReconciled };
|
|
4240
|
+
} catch (error) {
|
|
4241
|
+
if (error?.code !== "EEXIST") throw error;
|
|
4242
|
+
}
|
|
4243
|
+
}
|
|
4244
|
+
const latest = await readSafeFlushPlanReplacementContent(params.flushPlanPath);
|
|
4245
|
+
if (!latest.ok) return latest;
|
|
4246
|
+
const latestReconciled = reconcileOpenClawFlushPlanReplacementContent({
|
|
4247
|
+
existingContent: latest.content,
|
|
4248
|
+
preservedContent: params.preservedContent
|
|
4249
|
+
});
|
|
4250
|
+
if (latestReconciled === void 0) return latest;
|
|
4251
|
+
if (latest.content.length === 0) {
|
|
4252
|
+
try {
|
|
4253
|
+
await writeFile(params.flushPlanPath, latestReconciled, {
|
|
4254
|
+
encoding: "utf8",
|
|
4255
|
+
flag: "wx"
|
|
4256
|
+
});
|
|
4257
|
+
return { ok: true, content: latestReconciled };
|
|
4258
|
+
} catch (error) {
|
|
4259
|
+
if (error?.code !== "EEXIST") throw error;
|
|
4260
|
+
}
|
|
4261
|
+
const created = await readSafeFlushPlanReplacementContent(params.flushPlanPath);
|
|
4262
|
+
if (!created.ok) return created;
|
|
4263
|
+
const createdReconciled = reconcileOpenClawFlushPlanReplacementContent({
|
|
4264
|
+
existingContent: created.content,
|
|
4265
|
+
preservedContent: params.preservedContent
|
|
4266
|
+
});
|
|
4267
|
+
if (createdReconciled === void 0) return created;
|
|
4268
|
+
if (!await isSafeFlushPlanAppendTarget(params.flushPlanPath)) return { ok: false };
|
|
4269
|
+
await writeFile(params.flushPlanPath, createdReconciled, "utf8");
|
|
4270
|
+
return { ok: true, content: createdReconciled };
|
|
4271
|
+
}
|
|
4272
|
+
if (!await isSafeFlushPlanAppendTarget(params.flushPlanPath)) return { ok: false };
|
|
4273
|
+
await writeFile(params.flushPlanPath, latestReconciled, "utf8");
|
|
4274
|
+
return { ok: true, content: latestReconciled };
|
|
4275
|
+
}
|
|
4276
|
+
async function reconcileExistingFlushPlanReplacement(params) {
|
|
4277
|
+
const result = await reconcileOpenClawFlushPlanReplacementFile(params);
|
|
4278
|
+
return result.ok;
|
|
4279
|
+
}
|
|
4280
|
+
async function validateFlushPlanLocation(params) {
|
|
4281
|
+
const workspaceRoot = path2.resolve(params.workspaceDir);
|
|
4282
|
+
const flushPlanDir = path2.dirname(params.flushPlanPath);
|
|
4283
|
+
if (!isPathInsideOrEqual(workspaceRoot, flushPlanDir)) {
|
|
4284
|
+
return {
|
|
4285
|
+
status: "skipped",
|
|
4286
|
+
path: params.flushPlanPath,
|
|
4287
|
+
reason: "flush plan path is outside workspace"
|
|
4288
|
+
};
|
|
4289
|
+
}
|
|
4290
|
+
let workspaceRealPath = workspaceRoot;
|
|
4291
|
+
try {
|
|
4292
|
+
workspaceRealPath = await realpath2(workspaceRoot);
|
|
4293
|
+
} catch (error) {
|
|
4294
|
+
if (error?.code !== "ENOENT") throw error;
|
|
4295
|
+
}
|
|
4296
|
+
const relativeDir = path2.relative(workspaceRoot, flushPlanDir);
|
|
4297
|
+
const parentSegments = relativeDir.length === 0 ? [] : relativeDir.split(path2.sep).filter(Boolean);
|
|
4298
|
+
let currentPath = workspaceRoot;
|
|
4299
|
+
for (const segment of parentSegments) {
|
|
4300
|
+
currentPath = path2.join(currentPath, segment);
|
|
4301
|
+
let stat2;
|
|
4302
|
+
try {
|
|
4303
|
+
stat2 = await lstat2(currentPath);
|
|
4304
|
+
} catch (error) {
|
|
4305
|
+
if (error?.code === "ENOENT") break;
|
|
4306
|
+
throw error;
|
|
4307
|
+
}
|
|
4308
|
+
if (stat2.isSymbolicLink()) {
|
|
4309
|
+
return {
|
|
4310
|
+
status: "skipped",
|
|
4311
|
+
path: params.flushPlanPath,
|
|
4312
|
+
reason: "flush plan parent path contains a symlink"
|
|
4313
|
+
};
|
|
4314
|
+
}
|
|
4315
|
+
if (!stat2.isDirectory()) {
|
|
4316
|
+
return {
|
|
4317
|
+
status: "skipped",
|
|
4318
|
+
path: params.flushPlanPath,
|
|
4319
|
+
reason: "flush plan parent path is not a directory"
|
|
4320
|
+
};
|
|
4321
|
+
}
|
|
4322
|
+
const currentRealPath = await realpath2(currentPath);
|
|
4323
|
+
if (!isPathInsideOrEqual(workspaceRealPath, currentRealPath)) {
|
|
4324
|
+
return {
|
|
4325
|
+
status: "skipped",
|
|
4326
|
+
path: params.flushPlanPath,
|
|
4327
|
+
reason: "flush plan parent path escapes workspace"
|
|
4328
|
+
};
|
|
4329
|
+
}
|
|
4330
|
+
}
|
|
4331
|
+
return void 0;
|
|
4332
|
+
}
|
|
4333
|
+
async function recoverInterruptedCleanupSnapshots(params) {
|
|
4334
|
+
let removedProcessedMarker = false;
|
|
4335
|
+
let marker = params.marker;
|
|
4336
|
+
const flushPlanDir = path2.dirname(params.flushPlanPath);
|
|
4337
|
+
let entries;
|
|
4338
|
+
try {
|
|
4339
|
+
entries = await readdir2(flushPlanDir, { withFileTypes: true });
|
|
4340
|
+
} catch (error) {
|
|
4341
|
+
if (error?.code === "ENOENT") {
|
|
4342
|
+
return { removedProcessedMarker };
|
|
4343
|
+
}
|
|
4344
|
+
throw error;
|
|
4345
|
+
}
|
|
4346
|
+
const cleanupPaths = entries.filter((entry) => isCleanupSnapshotName(entry.name)).map((entry) => path2.join(flushPlanDir, entry.name)).sort(compareCleanupSnapshotsNewestFirst);
|
|
4347
|
+
if (cleanupPaths.length === 0) return { removedProcessedMarker };
|
|
4348
|
+
if (!await isSafeFlushPlanAppendTarget(params.flushPlanPath)) {
|
|
4349
|
+
params.logger?.warn(
|
|
4350
|
+
"OpenClaw flush-plan cleanup recovery skipped because flush-plan.md is not a regular file"
|
|
4351
|
+
);
|
|
4352
|
+
return { removedProcessedMarker };
|
|
4353
|
+
}
|
|
4354
|
+
let currentContent;
|
|
4355
|
+
try {
|
|
4356
|
+
currentContent = await fileReader(params.flushPlanPath, "utf8");
|
|
4357
|
+
} catch (error) {
|
|
4358
|
+
if (error?.code !== "ENOENT") throw error;
|
|
4359
|
+
}
|
|
4360
|
+
for (const cleanupPath of cleanupPaths) {
|
|
4361
|
+
let stat2;
|
|
4362
|
+
try {
|
|
4363
|
+
stat2 = await lstat2(cleanupPath);
|
|
4364
|
+
} catch (error) {
|
|
4365
|
+
if (error?.code === "ENOENT") continue;
|
|
4366
|
+
throw error;
|
|
4367
|
+
}
|
|
4368
|
+
if (stat2.isSymbolicLink() || !stat2.isFile()) {
|
|
4369
|
+
params.logger?.warn(
|
|
4370
|
+
`OpenClaw flush-plan cleanup recovery skipped unsafe snapshot ${path2.basename(cleanupPath)}`
|
|
4371
|
+
);
|
|
4372
|
+
continue;
|
|
4373
|
+
}
|
|
4374
|
+
const cleanupContent = await fileReader(cleanupPath, "utf8");
|
|
4375
|
+
if (marker?.processedContent && cleanupContent.startsWith(marker.processedContent)) {
|
|
4376
|
+
const restoredTail = cleanupContent.slice(marker.processedContent.length);
|
|
4377
|
+
if (restoredTail.length > 0) {
|
|
4378
|
+
const reconciled = await reconcileOpenClawFlushPlanReplacementFile({
|
|
4379
|
+
flushPlanPath: params.flushPlanPath,
|
|
4380
|
+
preservedContent: restoredTail
|
|
4381
|
+
});
|
|
4382
|
+
if (!reconciled.ok) {
|
|
4383
|
+
params.logger?.warn(
|
|
4384
|
+
"OpenClaw flush-plan cleanup recovery paused because flush-plan.md is no longer a regular file"
|
|
4385
|
+
);
|
|
4386
|
+
return { removedProcessedMarker };
|
|
4387
|
+
}
|
|
4388
|
+
currentContent = reconciled.content;
|
|
4389
|
+
}
|
|
4390
|
+
await unlink(cleanupPath);
|
|
4391
|
+
if (params.markerPath) {
|
|
4392
|
+
await removeProcessedMarker(params.markerPath);
|
|
4393
|
+
removedProcessedMarker = true;
|
|
4394
|
+
marker = void 0;
|
|
4395
|
+
}
|
|
4396
|
+
params.logger?.warn(
|
|
4397
|
+
`Recovered interrupted OpenClaw flush-plan cleanup snapshot ${path2.basename(cleanupPath)}`
|
|
4398
|
+
);
|
|
4399
|
+
continue;
|
|
4400
|
+
}
|
|
4401
|
+
if (cleanupContent.length > 0) {
|
|
4402
|
+
const reconciled = await reconcileOpenClawFlushPlanReplacementFile({
|
|
4403
|
+
flushPlanPath: params.flushPlanPath,
|
|
4404
|
+
preservedContent: cleanupContent
|
|
4405
|
+
});
|
|
4406
|
+
if (!reconciled.ok) {
|
|
4407
|
+
params.logger?.warn(
|
|
4408
|
+
"OpenClaw flush-plan cleanup recovery paused because flush-plan.md is no longer a regular file"
|
|
4409
|
+
);
|
|
4410
|
+
return { removedProcessedMarker };
|
|
4411
|
+
}
|
|
4412
|
+
currentContent = reconciled.content;
|
|
4413
|
+
}
|
|
4414
|
+
await unlink(cleanupPath);
|
|
4415
|
+
params.logger?.warn(
|
|
4416
|
+
`Recovered interrupted OpenClaw flush-plan cleanup snapshot ${path2.basename(cleanupPath)}`
|
|
4417
|
+
);
|
|
4418
|
+
}
|
|
4419
|
+
return { removedProcessedMarker };
|
|
4420
|
+
}
|
|
4421
|
+
function normalizeMaxTurnChars(maxTurnChars) {
|
|
4422
|
+
return typeof maxTurnChars === "number" && Number.isFinite(maxTurnChars) && maxTurnChars > 0 ? Math.floor(maxTurnChars) : DEFAULT_MAX_IMPORT_TURN_CHARS;
|
|
4423
|
+
}
|
|
4424
|
+
function flushPlanImportPrefix(params) {
|
|
4425
|
+
const verbose = params.chunkCount > 1 ? `${FLUSH_PLAN_IMPORT_INSTRUCTION} This is chunk ${params.chunkIndex + 1} of ${params.chunkCount}; extract it independently.
|
|
4426
|
+
|
|
4427
|
+
` : `${FLUSH_PLAN_IMPORT_INSTRUCTION}
|
|
4428
|
+
|
|
4429
|
+
`;
|
|
4430
|
+
if (verbose.length < params.maxTurnChars) return verbose;
|
|
4431
|
+
const compact = params.chunkCount > 1 ? `OpenClaw flush-plan chunk ${params.chunkIndex + 1}/${params.chunkCount}.
|
|
4432
|
+
|
|
4433
|
+
` : "OpenClaw flush-plan snapshot.\n\n";
|
|
4434
|
+
if (compact.length < params.maxTurnChars) return compact;
|
|
4435
|
+
return "";
|
|
4436
|
+
}
|
|
4437
|
+
function splitTextByMaxChars(content, maxChars) {
|
|
4438
|
+
const chunks = [];
|
|
4439
|
+
let offset = 0;
|
|
4440
|
+
while (offset < content.length) {
|
|
4441
|
+
let end = Math.min(offset + maxChars, content.length);
|
|
4442
|
+
if (end < content.length) {
|
|
4443
|
+
const newline = content.lastIndexOf("\n", end);
|
|
4444
|
+
if (newline > offset) {
|
|
4445
|
+
end = newline + 1;
|
|
4446
|
+
}
|
|
4447
|
+
}
|
|
4448
|
+
chunks.push(content.slice(offset, end));
|
|
4449
|
+
offset = end;
|
|
4450
|
+
}
|
|
4451
|
+
return chunks.filter((chunk) => chunk.trim().length > 0);
|
|
4452
|
+
}
|
|
4453
|
+
function splitFlushPlanContentIntoChunks(params) {
|
|
4454
|
+
const trimmed = params.content.trim();
|
|
4455
|
+
if (trimmed.length === 0) return [];
|
|
4456
|
+
let chunkCount = 1;
|
|
4457
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
4458
|
+
const maxPrefixChars2 = Math.max(
|
|
4459
|
+
...Array.from(
|
|
4460
|
+
{ length: chunkCount },
|
|
4461
|
+
(_, chunkIndex) => flushPlanImportPrefix({
|
|
4462
|
+
chunkIndex,
|
|
4463
|
+
chunkCount,
|
|
4464
|
+
maxTurnChars: params.maxTurnChars
|
|
4465
|
+
}).length
|
|
4466
|
+
)
|
|
4467
|
+
);
|
|
4468
|
+
const maxChunkChars = Math.max(1, params.maxTurnChars - maxPrefixChars2);
|
|
4469
|
+
const chunks = splitTextByMaxChars(trimmed, maxChunkChars);
|
|
4470
|
+
if (chunks.length === chunkCount) return chunks;
|
|
4471
|
+
chunkCount = Math.max(1, chunks.length);
|
|
4472
|
+
}
|
|
4473
|
+
const maxPrefixChars = flushPlanImportPrefix({
|
|
4474
|
+
chunkIndex: chunkCount - 1,
|
|
4475
|
+
chunkCount,
|
|
4476
|
+
maxTurnChars: params.maxTurnChars
|
|
4477
|
+
}).length;
|
|
4478
|
+
return splitTextByMaxChars(trimmed, Math.max(1, params.maxTurnChars - maxPrefixChars));
|
|
4479
|
+
}
|
|
4480
|
+
function buildFlushPlanImportTurn(params) {
|
|
4481
|
+
const prefix = flushPlanImportPrefix({
|
|
4482
|
+
chunkIndex: params.chunkIndex,
|
|
4483
|
+
chunkCount: params.chunkCount,
|
|
4484
|
+
maxTurnChars: params.maxTurnChars
|
|
4485
|
+
});
|
|
4486
|
+
return {
|
|
4487
|
+
role: "user",
|
|
4488
|
+
timestamp: params.turnTimestamp ?? params.importedAt,
|
|
4489
|
+
participantId: "openclaw-memory-flush-plan",
|
|
4490
|
+
participantName: "OpenClaw memory flush planner",
|
|
4491
|
+
content: `${prefix}${params.content.trim()}`,
|
|
4492
|
+
sourceFormat: "openclaw",
|
|
4493
|
+
rawContent: params.content,
|
|
4494
|
+
importProvenance: {
|
|
4495
|
+
sourceLabel: "OpenClaw flush plan",
|
|
4496
|
+
sourceId: params.chunkCount > 1 ? `${params.serviceId}:flush-plan:${params.chunkIndex + 1}/${params.chunkCount}` : `${params.serviceId}:flush-plan`,
|
|
4497
|
+
sourceTimestamp: params.importedAt,
|
|
4498
|
+
importedFromPath: params.flushPlanPath,
|
|
4499
|
+
importedAt: params.importedAt,
|
|
4500
|
+
metadata: {
|
|
4501
|
+
serviceId: params.serviceId,
|
|
4502
|
+
reason: params.reason,
|
|
4503
|
+
bytes: Buffer.byteLength(params.content, "utf8"),
|
|
4504
|
+
chunkIndex: params.chunkIndex,
|
|
4505
|
+
chunkCount: params.chunkCount,
|
|
4506
|
+
maxTurnChars: params.maxTurnChars
|
|
4507
|
+
}
|
|
4508
|
+
},
|
|
4509
|
+
turnFingerprint: params.turnFingerprint ?? hashContent(
|
|
4510
|
+
[
|
|
4511
|
+
"openclaw-flush-plan",
|
|
4512
|
+
params.serviceId,
|
|
4513
|
+
params.fingerprintPath,
|
|
4514
|
+
params.content
|
|
4515
|
+
].join("\n")
|
|
4516
|
+
),
|
|
4517
|
+
persistProcessedFingerprint: true
|
|
4518
|
+
};
|
|
4519
|
+
}
|
|
4520
|
+
function timestampForFlushPlanChunk(importedAt, chunkIndex) {
|
|
4521
|
+
const importedAtMs = Date.parse(importedAt);
|
|
4522
|
+
if (!Number.isFinite(importedAtMs)) return importedAt;
|
|
4523
|
+
return new Date(importedAtMs + chunkIndex).toISOString();
|
|
4524
|
+
}
|
|
4525
|
+
function isFailedIngestResult(result) {
|
|
4526
|
+
return Boolean(
|
|
4527
|
+
result && typeof result === "object" && typeof result.failedCount === "number" && result.failedCount > 0
|
|
4528
|
+
);
|
|
4529
|
+
}
|
|
4530
|
+
function partialIngestResultFromError(error) {
|
|
4531
|
+
if (!error || typeof error !== "object") return void 0;
|
|
4532
|
+
const partialResult = error.partialResult;
|
|
4533
|
+
if (!partialResult || typeof partialResult !== "object") return void 0;
|
|
4534
|
+
return partialResult;
|
|
4535
|
+
}
|
|
4536
|
+
async function ingestFlushPlanImportTurns(params) {
|
|
4537
|
+
try {
|
|
4538
|
+
return await params.ingestor.ingestBulkImportBatch(params.importTurns, {
|
|
4539
|
+
...params.deadlineMs === void 0 ? {} : { deadlineMs: params.deadlineMs },
|
|
4540
|
+
failOnExtractionFailure: true,
|
|
4541
|
+
includeSourceValidAtContext: false
|
|
4542
|
+
});
|
|
4543
|
+
} catch (error) {
|
|
4544
|
+
const partialResult = partialIngestResultFromError(error);
|
|
4545
|
+
if (partialResult && typeof partialResult.processedTurnCount === "number" && partialResult.processedTurnCount > 0) {
|
|
4546
|
+
return partialResult;
|
|
4547
|
+
}
|
|
4548
|
+
throw error;
|
|
4549
|
+
}
|
|
4550
|
+
}
|
|
4551
|
+
function hasPostPersistMetadataFailure(result) {
|
|
4552
|
+
return Boolean(
|
|
4553
|
+
result && typeof result === "object" && typeof result.postPersistMetadataFailureCount === "number" && result.postPersistMetadataFailureCount > 0
|
|
4554
|
+
);
|
|
4555
|
+
}
|
|
4556
|
+
function processedPrefixFromPartialIngest(params) {
|
|
4557
|
+
if (!isFailedIngestResult(params.ingestResult)) return void 0;
|
|
4558
|
+
const processedTurnCount = typeof params.ingestResult?.processedTurnCount === "number" ? Math.max(0, Math.floor(params.ingestResult.processedTurnCount)) : 0;
|
|
4559
|
+
if (processedTurnCount <= 0) return void 0;
|
|
4560
|
+
const processedContent = params.importTurns.slice(0, Math.min(processedTurnCount, params.importTurns.length)).map(
|
|
4561
|
+
(turn) => typeof turn.rawContent === "string" ? turn.rawContent : turn.content
|
|
4562
|
+
).join("");
|
|
4563
|
+
if (processedContent.length === 0) return void 0;
|
|
4564
|
+
if (params.content.startsWith(processedContent)) return processedContent;
|
|
4565
|
+
const leadingWhitespaceLength = params.content.length - params.content.trimStart().length;
|
|
4566
|
+
if (leadingWhitespaceLength > 0) {
|
|
4567
|
+
const withLeadingWhitespace = params.content.slice(
|
|
4568
|
+
0,
|
|
4569
|
+
leadingWhitespaceLength + processedContent.length
|
|
4570
|
+
);
|
|
4571
|
+
if (withLeadingWhitespace.slice(leadingWhitespaceLength) === processedContent) {
|
|
4572
|
+
return withLeadingWhitespace;
|
|
4573
|
+
}
|
|
4574
|
+
}
|
|
4575
|
+
return void 0;
|
|
4576
|
+
}
|
|
4577
|
+
async function handleFlushPlanIngestResult(params) {
|
|
4578
|
+
const partialProcessedPrefix = processedPrefixFromPartialIngest({
|
|
4579
|
+
content: params.content,
|
|
4580
|
+
importTurns: params.importTurns,
|
|
4581
|
+
ingestResult: params.ingestResult
|
|
4582
|
+
});
|
|
4583
|
+
if (partialProcessedPrefix) {
|
|
4584
|
+
params.logger?.warn(
|
|
4585
|
+
"OpenClaw flush-plan partially imported; preserving unprocessed tail for retry"
|
|
4586
|
+
);
|
|
4587
|
+
if (hasPostPersistMetadataFailure(params.ingestResult)) {
|
|
4588
|
+
return await deferProcessedFlushPlanCleanupForMetadataFailure({
|
|
4589
|
+
flushPlanPath: params.flushPlanPath,
|
|
4590
|
+
markerPath: params.markerPath,
|
|
4591
|
+
content: partialProcessedPrefix,
|
|
4592
|
+
processedAt: params.processedAt,
|
|
4593
|
+
reason: params.reason,
|
|
4594
|
+
importTurns: params.importTurns
|
|
4595
|
+
});
|
|
4596
|
+
}
|
|
4597
|
+
await writeProcessedMarker({
|
|
4598
|
+
markerPath: params.markerPath,
|
|
4599
|
+
marker: buildProcessedMarker({
|
|
4600
|
+
status: "processed",
|
|
4601
|
+
content: partialProcessedPrefix,
|
|
4602
|
+
processedAt: params.processedAt,
|
|
4603
|
+
reason: params.reason,
|
|
4604
|
+
importTurns: params.importTurns
|
|
4605
|
+
})
|
|
4606
|
+
});
|
|
4607
|
+
const result = await removeProcessedFlushPlanPrefix({
|
|
4608
|
+
flushPlanPath: params.flushPlanPath,
|
|
4609
|
+
processedPrefix: partialProcessedPrefix
|
|
4610
|
+
});
|
|
4611
|
+
if (result.status === "processed_cleanup_deferred") {
|
|
4612
|
+
params.logger?.warn(
|
|
4613
|
+
`OpenClaw flush-plan processed but cleanup deferred: ${result.reason}`
|
|
4614
|
+
);
|
|
4615
|
+
} else {
|
|
4616
|
+
await removeProcessedMarker(params.markerPath);
|
|
4617
|
+
}
|
|
4618
|
+
return result;
|
|
4619
|
+
}
|
|
4620
|
+
if (isFailedIngestResult(params.ingestResult)) {
|
|
4621
|
+
throw new Error("OpenClaw flush-plan import failed");
|
|
4622
|
+
}
|
|
4623
|
+
if (hasPostPersistMetadataFailure(params.ingestResult)) {
|
|
4624
|
+
return await deferProcessedFlushPlanCleanupForMetadataFailure({
|
|
4625
|
+
flushPlanPath: params.flushPlanPath,
|
|
4626
|
+
markerPath: params.markerPath,
|
|
4627
|
+
content: params.content,
|
|
4628
|
+
processedAt: params.processedAt,
|
|
4629
|
+
reason: params.reason,
|
|
4630
|
+
importTurns: params.importTurns
|
|
4631
|
+
});
|
|
4632
|
+
}
|
|
4633
|
+
return void 0;
|
|
4634
|
+
}
|
|
4635
|
+
function buildProcessedMarker(params) {
|
|
4636
|
+
return {
|
|
4637
|
+
version: 1,
|
|
4638
|
+
status: params.status,
|
|
4639
|
+
processedHash: hashContent(params.content),
|
|
4640
|
+
processedBytes: Buffer.byteLength(params.content, "utf8"),
|
|
4641
|
+
processedContent: params.content,
|
|
4642
|
+
processedChunks: buildProcessedMarkerChunks({
|
|
4643
|
+
content: params.content,
|
|
4644
|
+
importTurns: params.importTurns
|
|
4645
|
+
}),
|
|
4646
|
+
processedAt: params.processedAt,
|
|
4647
|
+
reason: params.reason
|
|
4648
|
+
};
|
|
4649
|
+
}
|
|
4650
|
+
function buildProcessedMarkerChunks(params) {
|
|
4651
|
+
if (!params.importTurns || params.importTurns.length === 0) return void 0;
|
|
4652
|
+
const importedContent = params.content.trim();
|
|
4653
|
+
if (importedContent.length === 0) return void 0;
|
|
4654
|
+
const chunks = [];
|
|
4655
|
+
let reconstructed = "";
|
|
4656
|
+
for (const [index, turn] of params.importTurns.entries()) {
|
|
4657
|
+
if (typeof turn.rawContent !== "string" || typeof turn.turnFingerprint !== "string") {
|
|
4658
|
+
return void 0;
|
|
4659
|
+
}
|
|
4660
|
+
const nextReconstructed = `${reconstructed}${turn.rawContent}`;
|
|
4661
|
+
if (!importedContent.startsWith(nextReconstructed)) return void 0;
|
|
4662
|
+
const metadata = turn.importProvenance?.metadata && typeof turn.importProvenance.metadata === "object" && !Array.isArray(turn.importProvenance.metadata) ? turn.importProvenance.metadata : void 0;
|
|
4663
|
+
const chunkIndex = typeof metadata?.chunkIndex === "number" && Number.isSafeInteger(metadata.chunkIndex) ? metadata.chunkIndex : index;
|
|
4664
|
+
const chunkCount = typeof metadata?.chunkCount === "number" && Number.isSafeInteger(metadata.chunkCount) ? metadata.chunkCount : params.importTurns.length;
|
|
4665
|
+
const maxTurnChars = typeof metadata?.maxTurnChars === "number" && Number.isSafeInteger(metadata.maxTurnChars) ? metadata.maxTurnChars : DEFAULT_MAX_IMPORT_TURN_CHARS;
|
|
4666
|
+
chunks.push({
|
|
4667
|
+
rawBytes: Buffer.byteLength(turn.rawContent, "utf8"),
|
|
4668
|
+
turnFingerprint: turn.turnFingerprint,
|
|
4669
|
+
timestamp: turn.timestamp ?? "",
|
|
4670
|
+
chunkIndex,
|
|
4671
|
+
chunkCount,
|
|
4672
|
+
maxTurnChars
|
|
4673
|
+
});
|
|
4674
|
+
reconstructed = nextReconstructed;
|
|
4675
|
+
if (reconstructed === importedContent) break;
|
|
4676
|
+
}
|
|
4677
|
+
if (reconstructed !== importedContent) return void 0;
|
|
4678
|
+
if (chunks.length === 0 || chunks.some(
|
|
4679
|
+
(chunk, index) => chunk.chunkIndex !== index || chunk.chunkCount !== chunks.length || chunk.maxTurnChars !== chunks[0].maxTurnChars || chunk.timestamp.length === 0
|
|
4680
|
+
)) {
|
|
4681
|
+
return void 0;
|
|
4682
|
+
}
|
|
4683
|
+
return chunks;
|
|
4684
|
+
}
|
|
4685
|
+
async function deferProcessedFlushPlanCleanupForMetadataFailure(params) {
|
|
4686
|
+
await writeProcessedMarker({
|
|
4687
|
+
markerPath: params.markerPath,
|
|
4688
|
+
marker: buildProcessedMarker({
|
|
4689
|
+
status: "processed",
|
|
4690
|
+
content: params.content,
|
|
4691
|
+
processedAt: params.processedAt,
|
|
4692
|
+
reason: params.reason,
|
|
4693
|
+
importTurns: params.importTurns
|
|
4694
|
+
})
|
|
4695
|
+
});
|
|
4696
|
+
return {
|
|
4697
|
+
status: "processed_cleanup_deferred",
|
|
4698
|
+
path: params.flushPlanPath,
|
|
4699
|
+
bytesProcessed: Buffer.byteLength(params.content, "utf8"),
|
|
4700
|
+
reason: FLUSH_PLAN_METADATA_FAILURE_CLEANUP_DEFERRED_REASON
|
|
4701
|
+
};
|
|
4702
|
+
}
|
|
4703
|
+
function buildFlushPlanImportTurns(params) {
|
|
4704
|
+
const maxTurnChars = normalizeMaxTurnChars(params.maxTurnChars);
|
|
4705
|
+
const chunks = splitFlushPlanContentIntoChunks({
|
|
4706
|
+
content: params.content,
|
|
4707
|
+
maxTurnChars
|
|
4708
|
+
});
|
|
4709
|
+
return chunks.map(
|
|
4710
|
+
(chunk, chunkIndex) => buildFlushPlanImportTurn({
|
|
4711
|
+
content: chunk,
|
|
4712
|
+
flushPlanPath: params.flushPlanPath,
|
|
4713
|
+
fingerprintPath: params.fingerprintPath,
|
|
4714
|
+
serviceId: params.serviceId,
|
|
4715
|
+
importedAt: params.importedAt,
|
|
4716
|
+
turnTimestamp: timestampForFlushPlanChunk(params.importedAt, chunkIndex),
|
|
4717
|
+
reason: params.reason,
|
|
4718
|
+
chunkIndex,
|
|
4719
|
+
chunkCount: chunks.length,
|
|
4720
|
+
maxTurnChars
|
|
4721
|
+
})
|
|
4722
|
+
);
|
|
4723
|
+
}
|
|
4724
|
+
async function readFlushPlanSnapshot(flushPlanPath) {
|
|
4725
|
+
let stat2;
|
|
4726
|
+
try {
|
|
4727
|
+
stat2 = await lstat2(flushPlanPath);
|
|
4728
|
+
} catch (error) {
|
|
4729
|
+
if (error?.code === "ENOENT") {
|
|
4730
|
+
return { status: "missing", path: flushPlanPath };
|
|
4731
|
+
}
|
|
4732
|
+
throw error;
|
|
4733
|
+
}
|
|
4734
|
+
if (stat2.isSymbolicLink()) {
|
|
4735
|
+
return {
|
|
4736
|
+
status: "skipped",
|
|
4737
|
+
path: flushPlanPath,
|
|
4738
|
+
reason: "flush plan path is a symlink"
|
|
4739
|
+
};
|
|
4740
|
+
}
|
|
4741
|
+
if (!stat2.isFile()) {
|
|
4742
|
+
return {
|
|
4743
|
+
status: "skipped",
|
|
4744
|
+
path: flushPlanPath,
|
|
4745
|
+
reason: "flush plan path is not a regular file"
|
|
4746
|
+
};
|
|
4747
|
+
}
|
|
4748
|
+
const content = await fileReader(flushPlanPath, "utf8");
|
|
4749
|
+
if (!isMeaningfulFlushPlanContent(content)) {
|
|
4750
|
+
return { status: "empty", path: flushPlanPath };
|
|
4751
|
+
}
|
|
4752
|
+
return { status: "ok", content };
|
|
4753
|
+
}
|
|
4754
|
+
function findProcessedMarkerPrefix(params) {
|
|
4755
|
+
const processedPrefix = utf8PrefixByByteLength(
|
|
4756
|
+
params.content,
|
|
4757
|
+
params.marker.processedBytes
|
|
4758
|
+
);
|
|
4759
|
+
if (processedPrefix !== void 0 && hashContent(processedPrefix) === params.marker.processedHash) {
|
|
4760
|
+
return processedPrefix;
|
|
4761
|
+
}
|
|
4762
|
+
if (params.marker.processedContent && params.content.startsWith(params.marker.processedContent) && hashContent(params.marker.processedContent) === params.marker.processedHash) {
|
|
4763
|
+
return params.marker.processedContent;
|
|
4764
|
+
}
|
|
4765
|
+
return void 0;
|
|
4766
|
+
}
|
|
4767
|
+
function splitContentByMarkerChunks(params) {
|
|
4768
|
+
const buffer = Buffer.from(params.content, "utf8");
|
|
4769
|
+
let offset = 0;
|
|
4770
|
+
const contentChunks = [];
|
|
4771
|
+
for (const chunk of params.chunks) {
|
|
4772
|
+
const nextOffset = offset + chunk.rawBytes;
|
|
4773
|
+
if (nextOffset > buffer.length) return void 0;
|
|
4774
|
+
const contentChunk = buffer.subarray(offset, nextOffset).toString("utf8");
|
|
4775
|
+
if (Buffer.byteLength(contentChunk, "utf8") !== chunk.rawBytes) {
|
|
4776
|
+
return void 0;
|
|
4777
|
+
}
|
|
4778
|
+
contentChunks.push(contentChunk);
|
|
4779
|
+
offset = nextOffset;
|
|
4780
|
+
}
|
|
4781
|
+
if (offset !== buffer.length) return void 0;
|
|
4782
|
+
return contentChunks;
|
|
4783
|
+
}
|
|
4784
|
+
function buildFlushPlanImportTurnsFromMarker(params) {
|
|
4785
|
+
if (!params.marker.processedChunks) return void 0;
|
|
4786
|
+
const contentChunks = splitContentByMarkerChunks({
|
|
4787
|
+
content: params.content.trim(),
|
|
4788
|
+
chunks: params.marker.processedChunks
|
|
4789
|
+
});
|
|
4790
|
+
if (!contentChunks) return void 0;
|
|
4791
|
+
return params.marker.processedChunks.map(
|
|
4792
|
+
(chunk, index) => buildFlushPlanImportTurn({
|
|
4793
|
+
content: contentChunks[index],
|
|
4794
|
+
flushPlanPath: params.flushPlanPath,
|
|
4795
|
+
fingerprintPath: params.fingerprintPath,
|
|
4796
|
+
serviceId: params.serviceId,
|
|
4797
|
+
importedAt: params.marker.processedAt,
|
|
4798
|
+
turnTimestamp: chunk.timestamp,
|
|
4799
|
+
turnFingerprint: chunk.turnFingerprint,
|
|
4800
|
+
reason: params.marker.reason,
|
|
4801
|
+
chunkIndex: chunk.chunkIndex,
|
|
4802
|
+
chunkCount: chunk.chunkCount,
|
|
4803
|
+
maxTurnChars: chunk.maxTurnChars
|
|
4804
|
+
})
|
|
4805
|
+
);
|
|
4806
|
+
}
|
|
4807
|
+
async function removeProcessedFlushPlanContent(params) {
|
|
4808
|
+
const processedBytes = Buffer.byteLength(params.processedContent, "utf8");
|
|
4809
|
+
let cleanupPath;
|
|
4810
|
+
try {
|
|
4811
|
+
const stat2 = await lstat2(params.flushPlanPath);
|
|
4812
|
+
if (stat2.isSymbolicLink()) {
|
|
4813
|
+
return {
|
|
4814
|
+
status: "processed_cleanup_deferred",
|
|
4815
|
+
path: params.flushPlanPath,
|
|
4816
|
+
bytesProcessed: processedBytes,
|
|
4817
|
+
reason: "flush plan path became a symlink before cleanup"
|
|
4818
|
+
};
|
|
4819
|
+
}
|
|
4820
|
+
if (!stat2.isFile()) {
|
|
4821
|
+
return {
|
|
4822
|
+
status: "processed_cleanup_deferred",
|
|
4823
|
+
path: params.flushPlanPath,
|
|
4824
|
+
bytesProcessed: processedBytes,
|
|
4825
|
+
reason: "flush plan path stopped being a regular file before cleanup"
|
|
4826
|
+
};
|
|
4827
|
+
}
|
|
4828
|
+
cleanupPath = resolveCleanupSnapshotPath(params.flushPlanPath);
|
|
4829
|
+
await rename(params.flushPlanPath, cleanupPath);
|
|
4830
|
+
} catch (error) {
|
|
4831
|
+
if (error?.code === "ENOENT") {
|
|
4832
|
+
return {
|
|
4833
|
+
status: "processed",
|
|
4834
|
+
path: params.flushPlanPath,
|
|
4835
|
+
bytesProcessed: processedBytes
|
|
4836
|
+
};
|
|
4837
|
+
}
|
|
4838
|
+
throw error;
|
|
4839
|
+
}
|
|
4840
|
+
try {
|
|
4841
|
+
await writeFile(params.flushPlanPath, "", { encoding: "utf8", flag: "wx" });
|
|
4842
|
+
} catch (error) {
|
|
4843
|
+
if (error?.code !== "EEXIST") {
|
|
4844
|
+
throw error;
|
|
4845
|
+
}
|
|
4846
|
+
}
|
|
4847
|
+
const current = await fileReader(cleanupPath, "utf8");
|
|
4848
|
+
const processedStatus = params.recoveredFromMarker ? "processed_marker_recovered" : "processed";
|
|
4849
|
+
const preservedTailStatus = params.recoveredFromMarker ? "processed_marker_recovered_tail" : "processed_preserved_tail";
|
|
4850
|
+
const matchIndex = current === params.processedContent || current.startsWith(params.processedContent) ? 0 : params.allowNonPrefixMatch ? current.indexOf(params.processedContent) : -1;
|
|
4851
|
+
if (matchIndex >= 0) {
|
|
4852
|
+
const preservedContent = current.slice(0, matchIndex) + current.slice(matchIndex + params.processedContent.length);
|
|
4853
|
+
if (preservedContent.length > 0) {
|
|
4854
|
+
const reconciled2 = await reconcileExistingFlushPlanReplacement({
|
|
4855
|
+
flushPlanPath: params.flushPlanPath,
|
|
4856
|
+
preservedContent
|
|
4857
|
+
});
|
|
4858
|
+
if (!reconciled2) {
|
|
4859
|
+
return {
|
|
4860
|
+
status: "processed_cleanup_deferred",
|
|
4861
|
+
path: params.flushPlanPath,
|
|
4862
|
+
bytesProcessed: processedBytes,
|
|
4863
|
+
reason: "flush plan path was recreated as an unsafe target during cleanup"
|
|
4864
|
+
};
|
|
4865
|
+
}
|
|
4866
|
+
}
|
|
4867
|
+
await unlink(cleanupPath);
|
|
4868
|
+
return {
|
|
4869
|
+
status: preservedContent.length > 0 ? preservedTailStatus : processedStatus,
|
|
4870
|
+
path: params.flushPlanPath,
|
|
4871
|
+
bytesProcessed: processedBytes,
|
|
4872
|
+
preservedBytes: preservedContent.length > 0 ? Buffer.byteLength(preservedContent, "utf8") : void 0
|
|
4873
|
+
};
|
|
4874
|
+
}
|
|
4875
|
+
const reconciled = await reconcileExistingFlushPlanReplacement({
|
|
4876
|
+
flushPlanPath: params.flushPlanPath,
|
|
4877
|
+
preservedContent: current
|
|
4878
|
+
});
|
|
4879
|
+
if (!reconciled) {
|
|
4880
|
+
return {
|
|
4881
|
+
status: "processed_cleanup_deferred",
|
|
4882
|
+
path: params.flushPlanPath,
|
|
4883
|
+
bytesProcessed: processedBytes,
|
|
4884
|
+
reason: "flush plan path was recreated as an unsafe target during cleanup"
|
|
4885
|
+
};
|
|
4886
|
+
}
|
|
4887
|
+
await unlink(cleanupPath);
|
|
4888
|
+
return {
|
|
4889
|
+
status: "processed_cleanup_deferred",
|
|
4890
|
+
path: params.flushPlanPath,
|
|
4891
|
+
bytesProcessed: processedBytes,
|
|
4892
|
+
reason: FLUSH_PLAN_CHANGED_BEFORE_CLEANUP_REASON
|
|
4893
|
+
};
|
|
4894
|
+
}
|
|
4895
|
+
async function removeProcessedFlushPlanPrefix(params) {
|
|
4896
|
+
return removeProcessedFlushPlanContent({
|
|
4897
|
+
flushPlanPath: params.flushPlanPath,
|
|
4898
|
+
processedContent: params.processedPrefix,
|
|
4899
|
+
recoveredFromMarker: params.recoveredFromMarker,
|
|
4900
|
+
allowNonPrefixMatch: false
|
|
4901
|
+
});
|
|
4902
|
+
}
|
|
4903
|
+
async function recoverProcessedFlushPlanPrefix(params) {
|
|
4904
|
+
const processedPrefix = findProcessedMarkerPrefix({
|
|
4905
|
+
content: params.content,
|
|
4906
|
+
marker: params.marker
|
|
4907
|
+
});
|
|
4908
|
+
if (processedPrefix) {
|
|
4909
|
+
const result = await removeProcessedFlushPlanPrefix({
|
|
4910
|
+
flushPlanPath: params.flushPlanPath,
|
|
4911
|
+
processedPrefix,
|
|
4912
|
+
recoveredFromMarker: true
|
|
4913
|
+
});
|
|
4914
|
+
if (result.status !== "processed_cleanup_deferred") {
|
|
4915
|
+
await removeProcessedMarker(params.markerPath);
|
|
4916
|
+
}
|
|
4917
|
+
return result;
|
|
4918
|
+
}
|
|
4919
|
+
await removeProcessedMarker(params.markerPath);
|
|
4920
|
+
return void 0;
|
|
4921
|
+
}
|
|
4922
|
+
async function recoverPendingFlushPlanImport(params) {
|
|
4923
|
+
const processedPrefix = findProcessedMarkerPrefix({
|
|
4924
|
+
content: params.content,
|
|
4925
|
+
marker: params.marker
|
|
4926
|
+
});
|
|
4927
|
+
if (!processedPrefix) {
|
|
4928
|
+
await removeProcessedMarker(params.markerPath);
|
|
4929
|
+
return void 0;
|
|
4930
|
+
}
|
|
4931
|
+
const importTurns = buildFlushPlanImportTurnsFromMarker({
|
|
4932
|
+
content: processedPrefix,
|
|
4933
|
+
marker: params.marker,
|
|
4934
|
+
flushPlanPath: params.flushPlanPath,
|
|
4935
|
+
fingerprintPath: params.fingerprintPath,
|
|
4936
|
+
serviceId: params.serviceId
|
|
4937
|
+
}) ?? buildFlushPlanImportTurns({
|
|
4938
|
+
content: processedPrefix,
|
|
4939
|
+
flushPlanPath: params.flushPlanPath,
|
|
4940
|
+
fingerprintPath: params.fingerprintPath,
|
|
4941
|
+
serviceId: params.serviceId,
|
|
4942
|
+
importedAt: params.marker.processedAt,
|
|
4943
|
+
reason: params.marker.reason,
|
|
4944
|
+
maxTurnChars: params.maxTurnChars
|
|
4945
|
+
});
|
|
4946
|
+
const ingestResult = await ingestFlushPlanImportTurns({
|
|
4947
|
+
ingestor: params.ingestor,
|
|
4948
|
+
importTurns,
|
|
4949
|
+
deadlineMs: params.deadlineMs
|
|
4950
|
+
});
|
|
4951
|
+
const handledResult = await handleFlushPlanIngestResult({
|
|
4952
|
+
ingestResult,
|
|
4953
|
+
importTurns,
|
|
4954
|
+
content: processedPrefix,
|
|
4955
|
+
flushPlanPath: params.flushPlanPath,
|
|
4956
|
+
markerPath: params.markerPath,
|
|
4957
|
+
processedAt: params.marker.processedAt,
|
|
4958
|
+
reason: params.marker.reason,
|
|
4959
|
+
logger: params.logger
|
|
4960
|
+
});
|
|
4961
|
+
if (handledResult) return handledResult;
|
|
4962
|
+
await writeProcessedMarker({
|
|
4963
|
+
markerPath: params.markerPath,
|
|
4964
|
+
marker: buildProcessedMarker({
|
|
4965
|
+
status: "processed",
|
|
4966
|
+
content: processedPrefix,
|
|
4967
|
+
processedAt: params.marker.processedAt,
|
|
4968
|
+
reason: params.marker.reason,
|
|
4969
|
+
importTurns
|
|
4970
|
+
})
|
|
4971
|
+
});
|
|
4972
|
+
const result = await removeProcessedFlushPlanPrefix({
|
|
4973
|
+
flushPlanPath: params.flushPlanPath,
|
|
4974
|
+
processedPrefix,
|
|
4975
|
+
recoveredFromMarker: true
|
|
4976
|
+
});
|
|
4977
|
+
if (result.status === "processed_cleanup_deferred") {
|
|
4978
|
+
params.logger?.warn(
|
|
4979
|
+
`OpenClaw flush-plan processed but cleanup deferred: ${result.reason}`
|
|
4980
|
+
);
|
|
4981
|
+
} else {
|
|
4982
|
+
await removeProcessedMarker(params.markerPath);
|
|
4983
|
+
}
|
|
4984
|
+
return result;
|
|
4985
|
+
}
|
|
4986
|
+
async function resolveFlushPlanFingerprintPath(params) {
|
|
4987
|
+
const workspaceRoot = path2.resolve(params.workspaceDir);
|
|
4988
|
+
const flushPlanPath = path2.resolve(params.flushPlanPath);
|
|
4989
|
+
const relativeFlushPlanPath = path2.relative(workspaceRoot, flushPlanPath);
|
|
4990
|
+
if (relativeFlushPlanPath.length > 0 && !relativeFlushPlanPath.startsWith("..") && !path2.isAbsolute(relativeFlushPlanPath)) {
|
|
4991
|
+
try {
|
|
4992
|
+
return path2.resolve(await realpath2(workspaceRoot), relativeFlushPlanPath);
|
|
4993
|
+
} catch (error) {
|
|
4994
|
+
if (error?.code !== "ENOENT") throw error;
|
|
4995
|
+
}
|
|
4996
|
+
}
|
|
4997
|
+
try {
|
|
4998
|
+
return await realpath2(flushPlanPath);
|
|
4999
|
+
} catch (error) {
|
|
5000
|
+
if (error?.code !== "ENOENT") throw error;
|
|
5001
|
+
return flushPlanPath;
|
|
5002
|
+
}
|
|
5003
|
+
}
|
|
5004
|
+
async function processOpenClawFlushPlanFile(params) {
|
|
5005
|
+
if (!params.enabled) return { status: "disabled" };
|
|
5006
|
+
const flushPlanPath = resolveOpenClawFlushPlanPath({
|
|
5007
|
+
workspaceDir: params.workspaceDir,
|
|
5008
|
+
serviceId: params.serviceId
|
|
5009
|
+
});
|
|
5010
|
+
const unsafeLocation = await validateFlushPlanLocation({
|
|
5011
|
+
workspaceDir: params.workspaceDir,
|
|
5012
|
+
flushPlanPath
|
|
5013
|
+
});
|
|
5014
|
+
if (unsafeLocation) return unsafeLocation;
|
|
5015
|
+
const markerPath = resolveProcessedMarkerPath(flushPlanPath);
|
|
5016
|
+
const processedMarker = await readProcessedMarker(markerPath);
|
|
5017
|
+
const cleanupRecovery = await recoverInterruptedCleanupSnapshots({
|
|
5018
|
+
flushPlanPath,
|
|
5019
|
+
logger: params.logger,
|
|
5020
|
+
marker: processedMarker,
|
|
5021
|
+
markerPath
|
|
5022
|
+
});
|
|
5023
|
+
const activeProcessedMarker = cleanupRecovery.removedProcessedMarker ? void 0 : processedMarker;
|
|
5024
|
+
const snapshot = await readFlushPlanSnapshot(flushPlanPath);
|
|
5025
|
+
if (snapshot.status !== "ok") {
|
|
5026
|
+
if (activeProcessedMarker && (snapshot.status === "empty" || snapshot.status === "missing")) {
|
|
5027
|
+
await removeProcessedMarker(markerPath);
|
|
5028
|
+
}
|
|
5029
|
+
return snapshot;
|
|
5030
|
+
}
|
|
5031
|
+
const fingerprintPath = await resolveFlushPlanFingerprintPath({
|
|
5032
|
+
workspaceDir: params.workspaceDir,
|
|
5033
|
+
flushPlanPath
|
|
5034
|
+
});
|
|
5035
|
+
if (activeProcessedMarker) {
|
|
5036
|
+
const recovered = activeProcessedMarker.status === "pending" ? await recoverPendingFlushPlanImport({
|
|
5037
|
+
flushPlanPath,
|
|
5038
|
+
fingerprintPath,
|
|
5039
|
+
markerPath,
|
|
5040
|
+
content: snapshot.content,
|
|
5041
|
+
marker: activeProcessedMarker,
|
|
5042
|
+
serviceId: params.serviceId,
|
|
5043
|
+
ingestor: params.ingestor,
|
|
5044
|
+
deadlineMs: params.deadlineMs,
|
|
5045
|
+
maxTurnChars: params.maxTurnChars,
|
|
5046
|
+
logger: params.logger
|
|
5047
|
+
}) : await recoverProcessedFlushPlanPrefix({
|
|
5048
|
+
flushPlanPath,
|
|
5049
|
+
markerPath,
|
|
5050
|
+
content: snapshot.content,
|
|
5051
|
+
marker: activeProcessedMarker
|
|
5052
|
+
});
|
|
5053
|
+
if (recovered) return recovered;
|
|
5054
|
+
}
|
|
5055
|
+
const importedAt = (params.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
|
|
5056
|
+
const reason = params.reason ?? "openclaw-flush-plan";
|
|
5057
|
+
const importTurns = buildFlushPlanImportTurns({
|
|
5058
|
+
content: snapshot.content,
|
|
5059
|
+
flushPlanPath,
|
|
5060
|
+
fingerprintPath,
|
|
5061
|
+
serviceId: params.serviceId,
|
|
5062
|
+
importedAt,
|
|
5063
|
+
reason,
|
|
5064
|
+
maxTurnChars: params.maxTurnChars
|
|
5065
|
+
});
|
|
5066
|
+
await mkdir(path2.dirname(flushPlanPath), { recursive: true });
|
|
5067
|
+
await writeProcessedMarker({
|
|
5068
|
+
markerPath,
|
|
5069
|
+
marker: buildProcessedMarker({
|
|
5070
|
+
status: "pending",
|
|
5071
|
+
content: snapshot.content,
|
|
5072
|
+
processedAt: importedAt,
|
|
5073
|
+
reason,
|
|
5074
|
+
importTurns
|
|
5075
|
+
})
|
|
5076
|
+
});
|
|
5077
|
+
const ingestResult = await ingestFlushPlanImportTurns({
|
|
5078
|
+
ingestor: params.ingestor,
|
|
5079
|
+
importTurns,
|
|
5080
|
+
deadlineMs: params.deadlineMs
|
|
5081
|
+
});
|
|
5082
|
+
const handledResult = await handleFlushPlanIngestResult({
|
|
5083
|
+
ingestResult,
|
|
5084
|
+
importTurns,
|
|
5085
|
+
content: snapshot.content,
|
|
5086
|
+
flushPlanPath,
|
|
5087
|
+
markerPath,
|
|
5088
|
+
processedAt: importedAt,
|
|
5089
|
+
reason,
|
|
5090
|
+
logger: params.logger
|
|
5091
|
+
});
|
|
5092
|
+
if (handledResult) return handledResult;
|
|
5093
|
+
await writeProcessedMarker({
|
|
5094
|
+
markerPath,
|
|
5095
|
+
marker: buildProcessedMarker({
|
|
5096
|
+
status: "processed",
|
|
5097
|
+
content: snapshot.content,
|
|
5098
|
+
processedAt: importedAt,
|
|
5099
|
+
reason,
|
|
5100
|
+
importTurns
|
|
5101
|
+
})
|
|
5102
|
+
});
|
|
5103
|
+
const result = await removeProcessedFlushPlanPrefix({
|
|
5104
|
+
flushPlanPath,
|
|
5105
|
+
processedPrefix: snapshot.content
|
|
5106
|
+
});
|
|
5107
|
+
if (result.status === "processed_cleanup_deferred") {
|
|
5108
|
+
params.logger?.warn(
|
|
5109
|
+
`OpenClaw flush-plan processed but cleanup deferred: ${result.reason}`
|
|
5110
|
+
);
|
|
5111
|
+
} else {
|
|
5112
|
+
await removeProcessedMarker(markerPath);
|
|
5113
|
+
}
|
|
5114
|
+
return result;
|
|
5115
|
+
}
|
|
5116
|
+
|
|
4009
5117
|
// src/plugin-id.ts
|
|
4010
5118
|
import { resolvePluginEntry } from "@remnic/core/plugin-entry-resolver";
|
|
4011
5119
|
var REMNIC_OPENCLAW_PLUGIN_ID = "openclaw-remnic";
|
|
@@ -4139,23 +5247,23 @@ __export(resolve_provider_secret_exports, {
|
|
|
4139
5247
|
findGatewayRuntimeModules: () => findGatewayRuntimeModules
|
|
4140
5248
|
});
|
|
4141
5249
|
__reExport(resolve_provider_secret_exports, resolve_provider_secret_star);
|
|
4142
|
-
import
|
|
5250
|
+
import path3 from "path";
|
|
4143
5251
|
import * as resolve_provider_secret_star from "@remnic/core/resolve-provider-secret";
|
|
4144
5252
|
async function findGatewayRuntimeModules(filePrefix) {
|
|
4145
5253
|
const { existsSync, ["re"+"ad"+"Fi"+"le"+"Sync"]: fileReaderSync, readdirSync, realpathSync } = await import("fs");
|
|
4146
5254
|
const { createRequire: createRequire2 } = await import("module");
|
|
4147
5255
|
const candidates = [];
|
|
4148
5256
|
const isWithinRoot = (root, candidate) => {
|
|
4149
|
-
const relative =
|
|
4150
|
-
return relative.length === 0 || !relative.startsWith("..") && !
|
|
5257
|
+
const relative = path3.relative(root, candidate);
|
|
5258
|
+
return relative.length === 0 || !relative.startsWith("..") && !path3.isAbsolute(relative);
|
|
4151
5259
|
};
|
|
4152
5260
|
let packageRoot;
|
|
4153
5261
|
try {
|
|
4154
5262
|
const req = createRequire2(import.meta.url);
|
|
4155
5263
|
const openclawEntrypoint = realpathSync(req.resolve("openclaw"));
|
|
4156
|
-
let currentDir =
|
|
5264
|
+
let currentDir = path3.dirname(openclawEntrypoint);
|
|
4157
5265
|
while (true) {
|
|
4158
|
-
const packageJsonPath =
|
|
5266
|
+
const packageJsonPath = path3.join(currentDir, "package.json");
|
|
4159
5267
|
if (existsSync(packageJsonPath)) {
|
|
4160
5268
|
const packageJson = JSON.parse(fileReaderSync(packageJsonPath, "utf8"));
|
|
4161
5269
|
if (packageJson.name !== "openclaw") {
|
|
@@ -4164,7 +5272,7 @@ async function findGatewayRuntimeModules(filePrefix) {
|
|
|
4164
5272
|
packageRoot = realpathSync(currentDir);
|
|
4165
5273
|
break;
|
|
4166
5274
|
}
|
|
4167
|
-
const parent =
|
|
5275
|
+
const parent = path3.dirname(currentDir);
|
|
4168
5276
|
if (parent === currentDir) {
|
|
4169
5277
|
return [];
|
|
4170
5278
|
}
|
|
@@ -4174,14 +5282,14 @@ async function findGatewayRuntimeModules(filePrefix) {
|
|
|
4174
5282
|
return [];
|
|
4175
5283
|
}
|
|
4176
5284
|
try {
|
|
4177
|
-
const distDir = realpathSync(
|
|
5285
|
+
const distDir = realpathSync(path3.join(packageRoot, "dist"));
|
|
4178
5286
|
if (!isWithinRoot(packageRoot, distDir)) {
|
|
4179
5287
|
return [];
|
|
4180
5288
|
}
|
|
4181
5289
|
const files = readdirSync(distDir);
|
|
4182
5290
|
for (const f of files) {
|
|
4183
5291
|
if (f.startsWith(filePrefix) && f.endsWith(".js")) {
|
|
4184
|
-
const candidate = realpathSync(
|
|
5292
|
+
const candidate = realpathSync(path3.join(distDir, f));
|
|
4185
5293
|
if (isWithinRoot(packageRoot, candidate) && isWithinRoot(distDir, candidate)) {
|
|
4186
5294
|
candidates.push(candidate);
|
|
4187
5295
|
}
|
|
@@ -4515,6 +5623,7 @@ function buildServiceKeys(serviceId) {
|
|
|
4515
5623
|
ACCESS_SERVICE: `__openclawEngramAccessService${suffix}`,
|
|
4516
5624
|
ACCESS_HTTP_SERVER: `__openclawEngramAccessHttpServer${suffix}`,
|
|
4517
5625
|
ACCESS_HTTP_AUTH_STATE: `__openclawEngramAccessHttpAuthState${suffix}`,
|
|
5626
|
+
FLUSH_PLAN_PROCESSING_CHAINS: `__openclawEngramFlushPlanProcessingChains${suffix}`,
|
|
4518
5627
|
SERVICE_STARTED: `__openclawEngramServiceStarted${suffix}`,
|
|
4519
5628
|
INIT_PROMISE: `__openclawEngramInitPromise${suffix}`,
|
|
4520
5629
|
ORCHESTRATOR: `__openclawEngramOrchestrator${suffix}`
|
|
@@ -4525,7 +5634,7 @@ function resolveOpenClawConfigFilePath() {
|
|
|
4525
5634
|
if (explicitConfigPath && explicitConfigPath.length > 0) {
|
|
4526
5635
|
return expandTildePath(explicitConfigPath);
|
|
4527
5636
|
}
|
|
4528
|
-
return
|
|
5637
|
+
return path4.join(resolveHomeDir(), ".openclaw", "openclaw.json");
|
|
4529
5638
|
}
|
|
4530
5639
|
function coerceRawConfigBoolean(value) {
|
|
4531
5640
|
if (typeof value === "boolean") return value;
|
|
@@ -4568,7 +5677,7 @@ function readPluginHooksPolicy(apiConfig, pluginId) {
|
|
|
4568
5677
|
}
|
|
4569
5678
|
async function maybeRegisterLiveConnectorCron(orchestrator) {
|
|
4570
5679
|
if (!hasEnabledLiveConnectorConfig(orchestrator.config.connectors)) return;
|
|
4571
|
-
const jobsPath =
|
|
5680
|
+
const jobsPath = path4.join(resolveHomeDir(), ".openclaw", "cron", "jobs.json");
|
|
4572
5681
|
try {
|
|
4573
5682
|
if (!fileExistsNow(jobsPath)) {
|
|
4574
5683
|
logger_exports.log.debug("live connectors cron: jobs.json not found, skipping auto-register");
|
|
@@ -4829,7 +5938,7 @@ function stableOpenClawConfigSignature(value, seen = /* @__PURE__ */ new WeakSet
|
|
|
4829
5938
|
return JSON.stringify(String(value));
|
|
4830
5939
|
}
|
|
4831
5940
|
function openClawHostEmbeddingConfigSignature(cfg, apiConfig, workspaceDir) {
|
|
4832
|
-
return `sha256:${
|
|
5941
|
+
return `sha256:${createHash3("sha256").update(JSON.stringify({
|
|
4833
5942
|
enabled: cfg.hostEmbeddingProviderEnabled !== false,
|
|
4834
5943
|
memoryDir: cfg.memoryDir,
|
|
4835
5944
|
providerId: cfg.hostEmbeddingProviderId ?? "",
|
|
@@ -4956,6 +6065,7 @@ var pluginDefinition = {
|
|
|
4956
6065
|
void migrationPromise;
|
|
4957
6066
|
}
|
|
4958
6067
|
const fileConfig = loadPluginConfigFromFile(serviceId);
|
|
6068
|
+
const openclawFlushPlanProcessingEnabled = resolveOpenClawFlushPlanProcessingEnabledFromConfig(fileConfig, api.pluginConfig);
|
|
4959
6069
|
const cfg = (0, config_exports.parseConfig)({
|
|
4960
6070
|
...fileConfig,
|
|
4961
6071
|
// File-backed fallback for runtimes that omit pluginConfig
|
|
@@ -5070,9 +6180,9 @@ var pluginDefinition = {
|
|
|
5070
6180
|
emitLegacyTools: cfg.emitLegacyTools
|
|
5071
6181
|
});
|
|
5072
6182
|
globalThis[keys.ACCESS_HTTP_SERVER] = accessHttpServer;
|
|
5073
|
-
const pluginStateDir =
|
|
5074
|
-
const togglePrimaryPath =
|
|
5075
|
-
const toggleSecondaryPath = cfg.respectBundledActiveMemoryToggle ?
|
|
6183
|
+
const pluginStateDir = path4.join(cfg.memoryDir, "state", "plugins", serviceId);
|
|
6184
|
+
const togglePrimaryPath = path4.join(pluginStateDir, "session-toggles.json");
|
|
6185
|
+
const toggleSecondaryPath = cfg.respectBundledActiveMemoryToggle ? path4.join(cfg.memoryDir, "state", "plugins", "active-memory", "session-toggles.json") : void 0;
|
|
5076
6186
|
const sessionToggleStore = createFileToggleStore(togglePrimaryPath, {
|
|
5077
6187
|
secondaryReadOnlyPath: toggleSecondaryPath
|
|
5078
6188
|
});
|
|
@@ -5094,11 +6204,103 @@ var pluginDefinition = {
|
|
|
5094
6204
|
}
|
|
5095
6205
|
function resolveDreamJournalPath(runtimeWorkspaceDir) {
|
|
5096
6206
|
const workspaceRoot = resolveWorkspaceRoot(runtimeWorkspaceDir);
|
|
5097
|
-
return
|
|
6207
|
+
return path4.isAbsolute(cfg.dreaming.journalPath) ? cfg.dreaming.journalPath : path4.join(workspaceRoot, cfg.dreaming.journalPath);
|
|
5098
6208
|
}
|
|
5099
6209
|
function resolveHeartbeatJournalPath(runtimeWorkspaceDir) {
|
|
5100
6210
|
const workspaceRoot = resolveWorkspaceRoot(runtimeWorkspaceDir);
|
|
5101
|
-
return
|
|
6211
|
+
return path4.isAbsolute(cfg.heartbeat.journalPath) ? cfg.heartbeat.journalPath : path4.join(workspaceRoot, cfg.heartbeat.journalPath);
|
|
6212
|
+
}
|
|
6213
|
+
const existingFlushPlanProcessingChains = globalThis[keys.FLUSH_PLAN_PROCESSING_CHAINS];
|
|
6214
|
+
const flushPlanProcessingChains = existingFlushPlanProcessingChains instanceof Map ? existingFlushPlanProcessingChains : /* @__PURE__ */ new Map();
|
|
6215
|
+
globalThis[keys.FLUSH_PLAN_PROCESSING_CHAINS] = flushPlanProcessingChains;
|
|
6216
|
+
function resolveFlushPlanDeadlineMs(options) {
|
|
6217
|
+
if (typeof options.deadlineMs === "number" && Number.isFinite(options.deadlineMs)) {
|
|
6218
|
+
return options.deadlineMs;
|
|
6219
|
+
}
|
|
6220
|
+
if (typeof options.timeoutMs === "number" && Number.isFinite(options.timeoutMs) && options.timeoutMs >= 0) {
|
|
6221
|
+
return Date.now() + Math.floor(options.timeoutMs);
|
|
6222
|
+
}
|
|
6223
|
+
return void 0;
|
|
6224
|
+
}
|
|
6225
|
+
function waitForOpenClawFlushPlanTask(task, reason, deadlineMs) {
|
|
6226
|
+
if (typeof deadlineMs !== "number") {
|
|
6227
|
+
return task;
|
|
6228
|
+
}
|
|
6229
|
+
const remainingMs = Math.max(0, deadlineMs - Date.now());
|
|
6230
|
+
if (remainingMs === 0) {
|
|
6231
|
+
logger_exports.log.warn(
|
|
6232
|
+
`OpenClaw flush-plan processing timed out before queue wait for ${reason}`
|
|
6233
|
+
);
|
|
6234
|
+
return Promise.resolve();
|
|
6235
|
+
}
|
|
6236
|
+
let timeout;
|
|
6237
|
+
return Promise.race([
|
|
6238
|
+
task,
|
|
6239
|
+
new Promise((resolve) => {
|
|
6240
|
+
timeout = setTimeout(() => {
|
|
6241
|
+
logger_exports.log.warn(
|
|
6242
|
+
`OpenClaw flush-plan processing timed out while waiting for ${reason}; current drain remains fenced until it settles`
|
|
6243
|
+
);
|
|
6244
|
+
resolve();
|
|
6245
|
+
}, remainingMs);
|
|
6246
|
+
})
|
|
6247
|
+
]).finally(() => {
|
|
6248
|
+
if (timeout) clearTimeout(timeout);
|
|
6249
|
+
});
|
|
6250
|
+
}
|
|
6251
|
+
async function runOpenClawFlushPlanProcessing(reason, workspaceRoot, deadlineMs) {
|
|
6252
|
+
if (typeof deadlineMs === "number" && Date.now() >= deadlineMs) {
|
|
6253
|
+
logger_exports.log.warn(
|
|
6254
|
+
`OpenClaw flush-plan processing timed out before starting for ${reason}`
|
|
6255
|
+
);
|
|
6256
|
+
return;
|
|
6257
|
+
}
|
|
6258
|
+
try {
|
|
6259
|
+
const result = await processOpenClawFlushPlanFile({
|
|
6260
|
+
enabled: openclawFlushPlanProcessingEnabled,
|
|
6261
|
+
workspaceDir: workspaceRoot,
|
|
6262
|
+
serviceId,
|
|
6263
|
+
ingestor: orchestrator,
|
|
6264
|
+
logger: {
|
|
6265
|
+
debug: (message) => logger_exports.log.debug(message),
|
|
6266
|
+
info: (message) => logger_exports.log.info(message),
|
|
6267
|
+
warn: (message) => logger_exports.log.warn(message)
|
|
6268
|
+
},
|
|
6269
|
+
reason,
|
|
6270
|
+
deadlineMs,
|
|
6271
|
+
maxTurnChars: cfg.extractionMaxTurnChars
|
|
6272
|
+
});
|
|
6273
|
+
if (result.status === "processed" || result.status === "processed_preserved_tail" || result.status === "processed_marker_recovered" || result.status === "processed_marker_recovered_tail" || result.status === "processed_cleanup_deferred") {
|
|
6274
|
+
logger_exports.log.info(
|
|
6275
|
+
`OpenClaw flush-plan ${result.status}: ${result.bytesProcessed ?? 0} bytes` + (result.preservedBytes ? ` (${result.preservedBytes} bytes preserved)` : "")
|
|
6276
|
+
);
|
|
6277
|
+
} else if (result.status === "skipped") {
|
|
6278
|
+
logger_exports.log.warn(
|
|
6279
|
+
`OpenClaw flush-plan processing skipped: ${result.reason ?? "unknown reason"}`
|
|
6280
|
+
);
|
|
6281
|
+
}
|
|
6282
|
+
} catch (error) {
|
|
6283
|
+
const detail = displayErrorDetail(error) || "unknown error";
|
|
6284
|
+
logger_exports.log.warn(`OpenClaw flush-plan processing failed: ${detail}`);
|
|
6285
|
+
}
|
|
6286
|
+
}
|
|
6287
|
+
async function queueOpenClawFlushPlanProcessing(reason, runtimeWorkspaceDir, options = {}) {
|
|
6288
|
+
if (passiveMode || !openclawFlushPlanProcessingEnabled) {
|
|
6289
|
+
return Promise.resolve();
|
|
6290
|
+
}
|
|
6291
|
+
const workspaceRoot = resolveWorkspaceRoot(runtimeWorkspaceDir);
|
|
6292
|
+
const chainKey = await resolveFlushPlanProcessingChainKey(workspaceRoot);
|
|
6293
|
+
const deadlineMs = resolveFlushPlanDeadlineMs(options);
|
|
6294
|
+
const previousTask = flushPlanProcessingChains.get(chainKey);
|
|
6295
|
+
const task = (previousTask ?? Promise.resolve()).catch(() => void 0).then(() => runOpenClawFlushPlanProcessing(reason, workspaceRoot, deadlineMs));
|
|
6296
|
+
const fencedTask = task.catch(() => void 0);
|
|
6297
|
+
flushPlanProcessingChains.set(chainKey, fencedTask);
|
|
6298
|
+
void fencedTask.finally(() => {
|
|
6299
|
+
if (flushPlanProcessingChains.get(chainKey) === fencedTask) {
|
|
6300
|
+
flushPlanProcessingChains.delete(chainKey);
|
|
6301
|
+
}
|
|
6302
|
+
});
|
|
6303
|
+
return waitForOpenClawFlushPlanTask(task, reason, deadlineMs);
|
|
5102
6304
|
}
|
|
5103
6305
|
function queueDreamSurfaceSync(runtimeWorkspaceDir) {
|
|
5104
6306
|
if (!cfg.dreaming.enabled) return Promise.resolve();
|
|
@@ -5282,7 +6484,7 @@ Keep the reflection grounded in the evidence below.
|
|
|
5282
6484
|
timeoutMs: cfg.activeRecallTimeoutMs,
|
|
5283
6485
|
cacheTtlMs: cfg.activeRecallCacheTtlMs,
|
|
5284
6486
|
persistTranscripts: cfg.activeRecallPersistTranscripts,
|
|
5285
|
-
transcriptDir:
|
|
6487
|
+
transcriptDir: path4.isAbsolute(cfg.activeRecallTranscriptDir) ? cfg.activeRecallTranscriptDir : path4.join(pluginStateDir, cfg.activeRecallTranscriptDir),
|
|
5286
6488
|
entityGraphDepth: cfg.activeRecallEntityGraphDepth,
|
|
5287
6489
|
includeCausalTrajectories: cfg.activeRecallIncludeCausalTrajectories,
|
|
5288
6490
|
includeDaySummary: cfg.activeRecallIncludeDaySummary,
|
|
@@ -5367,7 +6569,7 @@ Keep the reflection grounded in the evidence below.
|
|
|
5367
6569
|
}
|
|
5368
6570
|
}, buildInlineExplicitCaptureDedupeKey2 = function(messageKey, note) {
|
|
5369
6571
|
if (!messageKey) return null;
|
|
5370
|
-
return `${messageKey}:inline-memory-note:${
|
|
6572
|
+
return `${messageKey}:inline-memory-note:${createHash3("sha256").update(JSON.stringify(note)).digest("hex")}`;
|
|
5371
6573
|
}, resolveStoredCodexThreadId2 = function(sessionKey) {
|
|
5372
6574
|
const threadId = codexThreadBySession.get(sessionKey);
|
|
5373
6575
|
return typeof threadId === "string" && threadId.length > 0 ? threadId : null;
|
|
@@ -6289,20 +7491,20 @@ Keep the reflection grounded in the evidence below.
|
|
|
6289
7491
|
const remnicQmdCommand = typeof orchestrator.config.qmdPath === "string" && orchestrator.config.qmdPath.trim().length > 0 ? orchestrator.config.qmdPath.trim() : "qmd";
|
|
6290
7492
|
const readAllowedRoots = [
|
|
6291
7493
|
orchestrator.config.memoryDir,
|
|
6292
|
-
capabilityWorkspaceDir ?
|
|
7494
|
+
capabilityWorkspaceDir ? path4.join(capabilityWorkspaceDir, "memory") : void 0
|
|
6293
7495
|
].filter((root) => typeof root === "string" && root.length > 0);
|
|
6294
7496
|
const canonicalizeRootForContainment = async (rawPath) => {
|
|
6295
|
-
const resolved =
|
|
7497
|
+
const resolved = path4.resolve(rawPath);
|
|
6296
7498
|
try {
|
|
6297
|
-
return
|
|
7499
|
+
return path4.normalize(await realPathLater(resolved));
|
|
6298
7500
|
} catch {
|
|
6299
|
-
return
|
|
7501
|
+
return path4.normalize(resolved);
|
|
6300
7502
|
}
|
|
6301
7503
|
};
|
|
6302
7504
|
const canonicalizeForRead = async (rawPath) => {
|
|
6303
|
-
const resolved =
|
|
7505
|
+
const resolved = path4.resolve(rawPath);
|
|
6304
7506
|
const real = await realPathLater(resolved);
|
|
6305
|
-
return
|
|
7507
|
+
return path4.normalize(real);
|
|
6306
7508
|
};
|
|
6307
7509
|
const readAllowedCanonicalRootsPromise = Promise.all(
|
|
6308
7510
|
readAllowedRoots.map((root) => canonicalizeRootForContainment(root))
|
|
@@ -6316,29 +7518,29 @@ Keep the reflection grounded in the evidence below.
|
|
|
6316
7518
|
}
|
|
6317
7519
|
const canonicalRoots = await readAllowedCanonicalRootsPromise;
|
|
6318
7520
|
return canonicalRoots.some((root) => {
|
|
6319
|
-
const relative =
|
|
6320
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
7521
|
+
const relative = path4.relative(root, canonicalCandidatePath);
|
|
7522
|
+
return relative === "" || !relative.startsWith("..") && !path4.isAbsolute(relative);
|
|
6321
7523
|
});
|
|
6322
7524
|
};
|
|
6323
7525
|
const normalizeWorkspacePath = (rawPath) => {
|
|
6324
7526
|
if (!rawPath || typeof rawPath !== "string") return "memory";
|
|
6325
|
-
const resolved =
|
|
6326
|
-
const relative =
|
|
6327
|
-
return relative && !relative.startsWith("..") && !
|
|
7527
|
+
const resolved = path4.isAbsolute(rawPath) ? path4.resolve(rawPath) : path4.resolve(capabilityWorkspaceDir, rawPath);
|
|
7528
|
+
const relative = path4.relative(capabilityWorkspaceDir, resolved);
|
|
7529
|
+
return relative && !relative.startsWith("..") && !path4.isAbsolute(relative) ? relative : rawPath;
|
|
6328
7530
|
};
|
|
6329
7531
|
const relativizeToMemoryRoot = (rawPath) => {
|
|
6330
7532
|
if (!rawPath || typeof rawPath !== "string") return "memory";
|
|
6331
|
-
const resolved =
|
|
7533
|
+
const resolved = path4.isAbsolute(rawPath) ? path4.resolve(rawPath) : path4.resolve(capabilityWorkspaceDir, rawPath);
|
|
6332
7534
|
for (const root of readAllowedRoots) {
|
|
6333
|
-
const relative =
|
|
6334
|
-
if (relative !== "" && !relative.startsWith("..") && !
|
|
7535
|
+
const relative = path4.relative(root, resolved);
|
|
7536
|
+
if (relative !== "" && !relative.startsWith("..") && !path4.isAbsolute(relative)) {
|
|
6335
7537
|
return relative;
|
|
6336
7538
|
}
|
|
6337
7539
|
}
|
|
6338
7540
|
return normalizeWorkspacePath(rawPath);
|
|
6339
7541
|
};
|
|
6340
7542
|
const resolveReadablePath = async (requestedPath) => {
|
|
6341
|
-
const candidateAbsolutePaths =
|
|
7543
|
+
const candidateAbsolutePaths = path4.isAbsolute(requestedPath) ? [path4.resolve(requestedPath)] : readAllowedRoots.map((root) => path4.resolve(root, requestedPath));
|
|
6342
7544
|
let canonicalPath;
|
|
6343
7545
|
let lastError;
|
|
6344
7546
|
for (const absolutePath of candidateAbsolutePaths) {
|
|
@@ -6356,8 +7558,8 @@ Keep the reflection grounded in the evidence below.
|
|
|
6356
7558
|
}
|
|
6357
7559
|
const canonicalRoots = await readAllowedCanonicalRootsPromise;
|
|
6358
7560
|
const contained = canonicalRoots.some((root) => {
|
|
6359
|
-
const relative =
|
|
6360
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
7561
|
+
const relative = path4.relative(root, canonicalPath);
|
|
7562
|
+
return relative === "" || !relative.startsWith("..") && !path4.isAbsolute(relative);
|
|
6361
7563
|
});
|
|
6362
7564
|
if (!contained) {
|
|
6363
7565
|
throw new Error(`memory read outside allowed roots: ${requestedPath}`);
|
|
@@ -6389,15 +7591,15 @@ Keep the reflection grounded in the evidence below.
|
|
|
6389
7591
|
}).map((result, index) => {
|
|
6390
7592
|
const candidate = result;
|
|
6391
7593
|
const rawPath = typeof candidate.path === "string" ? candidate.path : typeof candidate.id === "string" ? candidate.id : `memory-${index + 1}`;
|
|
6392
|
-
const absolutePath =
|
|
7594
|
+
const absolutePath = path4.isAbsolute(rawPath) ? path4.resolve(rawPath) : (() => {
|
|
6393
7595
|
for (const root of readAllowedRoots) {
|
|
6394
|
-
const candidateAbs =
|
|
6395
|
-
const relative =
|
|
6396
|
-
if (!relative.startsWith("..") && !
|
|
7596
|
+
const candidateAbs = path4.resolve(root, rawPath);
|
|
7597
|
+
const relative = path4.relative(root, candidateAbs);
|
|
7598
|
+
if (!relative.startsWith("..") && !path4.isAbsolute(relative)) {
|
|
6397
7599
|
return candidateAbs;
|
|
6398
7600
|
}
|
|
6399
7601
|
}
|
|
6400
|
-
return
|
|
7602
|
+
return path4.resolve(capabilityWorkspaceDir, rawPath);
|
|
6401
7603
|
})();
|
|
6402
7604
|
const normalizedPath = relativizeToMemoryRoot(rawPath);
|
|
6403
7605
|
const startLine = typeof candidate.startLine === "number" && Number.isFinite(candidate.startLine) ? Math.max(1, Math.floor(candidate.startLine)) : 1;
|
|
@@ -6889,6 +8091,7 @@ Keep the reflection grounded in the evidence below.
|
|
|
6889
8091
|
async (event, ctx) => {
|
|
6890
8092
|
const sessionKey = ctx?.sessionKey ?? event?.sessionKey ?? "default";
|
|
6891
8093
|
const sessionIdentity = resolveSessionIdentity2(sessionKey, event, ctx);
|
|
8094
|
+
const workspaceDir = ctx?.workspaceDir || event?.workspaceDir || orchestrator.config.workspaceDir || defaultWorkspaceDir();
|
|
6892
8095
|
try {
|
|
6893
8096
|
clearCodexCompatCaches2(sessionKey, sessionIdentity.providerThreadId, {
|
|
6894
8097
|
preserveMessageCount: true,
|
|
@@ -6922,6 +8125,9 @@ Keep the reflection grounded in the evidence below.
|
|
|
6922
8125
|
logger_exports.log.debug(`LCM after_compaction error: ${lcmErr}`);
|
|
6923
8126
|
}
|
|
6924
8127
|
}
|
|
8128
|
+
await queueOpenClawFlushPlanProcessing("after_compaction", workspaceDir, {
|
|
8129
|
+
timeoutMs: cfg.beforeResetTimeoutMs
|
|
8130
|
+
});
|
|
6925
8131
|
if (!orchestrator.config.compactionResetEnabled) {
|
|
6926
8132
|
logger_exports.log.debug(
|
|
6927
8133
|
`compaction completed for ${sessionKey}, reset disabled \u2014 skipping`
|
|
@@ -6931,7 +8137,6 @@ Keep the reflection grounded in the evidence below.
|
|
|
6931
8137
|
logger_exports.log.info(
|
|
6932
8138
|
`compaction completed for ${sessionKey}, triggering session reset`
|
|
6933
8139
|
);
|
|
6934
|
-
const workspaceDir = ctx?.workspaceDir || event?.workspaceDir || orchestrator.config.workspaceDir || defaultWorkspaceDir();
|
|
6935
8140
|
const apiAny = api;
|
|
6936
8141
|
if (typeof apiAny.resetSession === "function") {
|
|
6937
8142
|
const result = await apiAny.resetSession(sessionKey, "new");
|
|
@@ -6940,7 +8145,7 @@ Keep the reflection grounded in the evidence below.
|
|
|
6940
8145
|
`session reset via API for ${sessionKey}, new sessionId=${result.sessionId}`
|
|
6941
8146
|
);
|
|
6942
8147
|
const safeSessionKey = sanitizeSessionKeyForFilename(sessionKey);
|
|
6943
|
-
const signalPath =
|
|
8148
|
+
const signalPath = path4.join(
|
|
6944
8149
|
workspaceDir,
|
|
6945
8150
|
`.compaction-reset-signal-${safeSessionKey}`
|
|
6946
8151
|
);
|
|
@@ -6982,6 +8187,7 @@ Keep the reflection grounded in the evidence below.
|
|
|
6982
8187
|
sessionIdentity
|
|
6983
8188
|
);
|
|
6984
8189
|
const flushEnabled = cfg.flushOnResetEnabled && typeof orchestrator.flushSession === "function";
|
|
8190
|
+
const beforeResetDeadlineMs = Date.now() + cfg.beforeResetTimeoutMs;
|
|
6985
8191
|
const flushAbort = new AbortController();
|
|
6986
8192
|
let flushTimedOut = false;
|
|
6987
8193
|
let flushFailed = false;
|
|
@@ -7008,17 +8214,26 @@ Keep the reflection grounded in the evidence below.
|
|
|
7008
8214
|
const boundedFlush = flushEnabled ? Promise.race([
|
|
7009
8215
|
flushPromise,
|
|
7010
8216
|
new Promise((resolve) => {
|
|
8217
|
+
const remainingMs = Math.max(
|
|
8218
|
+
0,
|
|
8219
|
+
beforeResetDeadlineMs - Date.now()
|
|
8220
|
+
);
|
|
7011
8221
|
timeoutId = setTimeout(() => {
|
|
7012
8222
|
flushTimedOut = true;
|
|
7013
8223
|
flushAbort.abort();
|
|
7014
8224
|
resolve();
|
|
7015
|
-
},
|
|
8225
|
+
}, remainingMs);
|
|
7016
8226
|
})
|
|
7017
8227
|
]) : flushPromise;
|
|
7018
8228
|
await boundedFlush;
|
|
7019
8229
|
if (timeoutId) {
|
|
7020
8230
|
clearTimeout(timeoutId);
|
|
7021
8231
|
}
|
|
8232
|
+
await queueOpenClawFlushPlanProcessing(
|
|
8233
|
+
"before_reset",
|
|
8234
|
+
ctx?.workspaceDir || event?.workspaceDir,
|
|
8235
|
+
{ deadlineMs: beforeResetDeadlineMs }
|
|
8236
|
+
);
|
|
7022
8237
|
if (flushTimedOut) {
|
|
7023
8238
|
logger_exports.log.warn(
|
|
7024
8239
|
`before_reset flush timed out after ${cfg.beforeResetTimeoutMs}ms`
|
|
@@ -7040,7 +8255,7 @@ Keep the reflection grounded in the evidence below.
|
|
|
7040
8255
|
if (!passiveMode && sdkCaps.hasBeforePromptBuild) {
|
|
7041
8256
|
api.on(
|
|
7042
8257
|
"session_start",
|
|
7043
|
-
async (event,
|
|
8258
|
+
async (event, ctx) => {
|
|
7044
8259
|
const sessionKey = event.sessionKey ?? "default";
|
|
7045
8260
|
logger_exports.log.debug(`session_start: ${sessionKey}`);
|
|
7046
8261
|
try {
|
|
@@ -7048,6 +8263,11 @@ Keep the reflection grounded in the evidence below.
|
|
|
7048
8263
|
} catch (err) {
|
|
7049
8264
|
logger_exports.log.debug(`session_start file hygiene failed: ${err}`);
|
|
7050
8265
|
}
|
|
8266
|
+
void queueOpenClawFlushPlanProcessing(
|
|
8267
|
+
"session_start",
|
|
8268
|
+
ctx?.workspaceDir ?? event.workspaceDir,
|
|
8269
|
+
{ timeoutMs: cfg.beforeResetTimeoutMs }
|
|
8270
|
+
);
|
|
7051
8271
|
}
|
|
7052
8272
|
);
|
|
7053
8273
|
api.on(
|
|
@@ -7086,6 +8306,11 @@ Keep the reflection grounded in the evidence below.
|
|
|
7086
8306
|
if (orchestrator.config.compactionResetEnabled) {
|
|
7087
8307
|
orchestrator.clearRecallWorkspaceOverride(sessionKey);
|
|
7088
8308
|
}
|
|
8309
|
+
await queueOpenClawFlushPlanProcessing(
|
|
8310
|
+
"session_end",
|
|
8311
|
+
ctx?.workspaceDir || event?.workspaceDir,
|
|
8312
|
+
{ timeoutMs: cfg.beforeResetTimeoutMs }
|
|
8313
|
+
);
|
|
7089
8314
|
}
|
|
7090
8315
|
);
|
|
7091
8316
|
api.on(
|
|
@@ -7182,7 +8407,7 @@ Keep the reflection grounded in the evidence below.
|
|
|
7182
8407
|
}
|
|
7183
8408
|
async function ensureHourlySummaryCron(api2) {
|
|
7184
8409
|
const jobId = "engram-hourly-summary";
|
|
7185
|
-
const cronFilePath =
|
|
8410
|
+
const cronFilePath = path4.join(
|
|
7186
8411
|
os.homedir(),
|
|
7187
8412
|
".openclaw",
|
|
7188
8413
|
"cron",
|
|
@@ -7241,32 +8466,32 @@ Keep the reflection grounded in the evidence below.
|
|
|
7241
8466
|
};
|
|
7242
8467
|
const normalizeCorpusPath = (value) => value.trim().replace(/\\/g, "/").replace(/^\.\//, "");
|
|
7243
8468
|
const pathIsInside = (root, candidate) => {
|
|
7244
|
-
const relative =
|
|
7245
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
8469
|
+
const relative = path4.relative(root, candidate);
|
|
8470
|
+
return relative === "" || !relative.startsWith("..") && !path4.isAbsolute(relative);
|
|
7246
8471
|
};
|
|
7247
8472
|
const corpusPathCandidates = (rawPath, storageDir) => {
|
|
7248
8473
|
const candidates = /* @__PURE__ */ new Set();
|
|
7249
8474
|
const trimmed = rawPath.trim();
|
|
7250
8475
|
if (!trimmed) return [];
|
|
7251
8476
|
candidates.add(normalizeCorpusPath(trimmed));
|
|
7252
|
-
if (
|
|
7253
|
-
const absolutePath =
|
|
7254
|
-
const absoluteStorageDir =
|
|
8477
|
+
if (path4.isAbsolute(trimmed)) {
|
|
8478
|
+
const absolutePath = path4.resolve(trimmed);
|
|
8479
|
+
const absoluteStorageDir = path4.resolve(storageDir);
|
|
7255
8480
|
if (pathIsInside(absoluteStorageDir, absolutePath)) {
|
|
7256
|
-
candidates.add(normalizeCorpusPath(
|
|
8481
|
+
candidates.add(normalizeCorpusPath(path4.relative(absoluteStorageDir, absolutePath)));
|
|
7257
8482
|
}
|
|
7258
8483
|
}
|
|
7259
|
-
candidates.add(
|
|
8484
|
+
candidates.add(path4.basename(trimmed));
|
|
7260
8485
|
return [...candidates].filter((candidate) => candidate.length > 0);
|
|
7261
8486
|
};
|
|
7262
8487
|
const displayCorpusPath = (rawPath, storageDir) => {
|
|
7263
8488
|
const trimmed = rawPath.trim();
|
|
7264
8489
|
if (!trimmed) return "";
|
|
7265
|
-
if (
|
|
7266
|
-
const absolutePath =
|
|
7267
|
-
const absoluteStorageDir =
|
|
8490
|
+
if (path4.isAbsolute(trimmed)) {
|
|
8491
|
+
const absolutePath = path4.resolve(trimmed);
|
|
8492
|
+
const absoluteStorageDir = path4.resolve(storageDir);
|
|
7268
8493
|
if (pathIsInside(absoluteStorageDir, absolutePath)) {
|
|
7269
|
-
return normalizeCorpusPath(
|
|
8494
|
+
return normalizeCorpusPath(path4.relative(absoluteStorageDir, absolutePath));
|
|
7270
8495
|
}
|
|
7271
8496
|
}
|
|
7272
8497
|
return normalizeCorpusPath(trimmed);
|
|
@@ -7528,6 +8753,11 @@ Keep the reflection grounded in the evidence below.
|
|
|
7528
8753
|
logger_exports.log.info(
|
|
7529
8754
|
`gateway_start fired \u2014 Remnic memory plugin is active (id=${pluginDefinition.id}, memoryDir=${cfg.memoryDir})`
|
|
7530
8755
|
);
|
|
8756
|
+
void queueOpenClawFlushPlanProcessing(
|
|
8757
|
+
"gateway_start",
|
|
8758
|
+
getOpenClawRuntimeWorkspaceDir(api),
|
|
8759
|
+
{ timeoutMs: cfg.beforeResetTimeoutMs }
|
|
8760
|
+
);
|
|
7531
8761
|
} catch (err) {
|
|
7532
8762
|
try {
|
|
7533
8763
|
activeOpikExporter?.unsubscribe();
|
|
@@ -7616,6 +8846,9 @@ Keep the reflection grounded in the evidence below.
|
|
|
7616
8846
|
delete globalThis[keys.ACCESS_HTTP_SERVER];
|
|
7617
8847
|
delete globalThis[keys.ACCESS_HTTP_AUTH_STATE];
|
|
7618
8848
|
delete globalThis[keys.ACCESS_SERVICE];
|
|
8849
|
+
if (flushPlanProcessingChains.size === 0) {
|
|
8850
|
+
delete globalThis[keys.FLUSH_PLAN_PROCESSING_CHAINS];
|
|
8851
|
+
}
|
|
7619
8852
|
}
|
|
7620
8853
|
if (!secondaryTookOver) {
|
|
7621
8854
|
globalThis[keys.HOOK_APIS] = /* @__PURE__ */ new WeakSet();
|
|
@@ -7629,6 +8862,44 @@ Keep the reflection grounded in the evidence below.
|
|
|
7629
8862
|
}
|
|
7630
8863
|
};
|
|
7631
8864
|
var src_default = tryDefinePluginEntry(pluginDefinition);
|
|
8865
|
+
var OPENCLAW_BOOLEAN_ACCEPTED_VALUES = "true/false/1/0/yes/no/on/off";
|
|
8866
|
+
function coerceOpenClawBooleanLike(value) {
|
|
8867
|
+
if (typeof value === "boolean") return value;
|
|
8868
|
+
if (typeof value !== "string") return void 0;
|
|
8869
|
+
const normalized = value.trim().toLowerCase();
|
|
8870
|
+
if (["true", "1", "yes", "on"].includes(normalized)) return true;
|
|
8871
|
+
if (["false", "0", "no", "off"].includes(normalized)) return false;
|
|
8872
|
+
return void 0;
|
|
8873
|
+
}
|
|
8874
|
+
function resolveOpenClawFlushPlanProcessingEnabled(configValue) {
|
|
8875
|
+
if (configValue === void 0 || configValue === null) return true;
|
|
8876
|
+
const coerced = coerceOpenClawBooleanLike(configValue);
|
|
8877
|
+
if (coerced === void 0) {
|
|
8878
|
+
throw new Error(
|
|
8879
|
+
`openclawFlushPlanProcessingEnabled must be a boolean-like value (${OPENCLAW_BOOLEAN_ACCEPTED_VALUES}); got ${JSON.stringify(configValue)}`
|
|
8880
|
+
);
|
|
8881
|
+
}
|
|
8882
|
+
return coerced;
|
|
8883
|
+
}
|
|
8884
|
+
function resolveOpenClawFlushPlanProcessingEnabledFromConfig(fileConfig, runtimeConfig) {
|
|
8885
|
+
const runtimeRecord = runtimeConfig && typeof runtimeConfig === "object" ? runtimeConfig : void 0;
|
|
8886
|
+
const hasRuntimeValue = runtimeRecord !== void 0 && Object.prototype.hasOwnProperty.call(
|
|
8887
|
+
runtimeRecord,
|
|
8888
|
+
"openclawFlushPlanProcessingEnabled"
|
|
8889
|
+
);
|
|
8890
|
+
const hasFileValue = fileConfig !== void 0 && Object.prototype.hasOwnProperty.call(
|
|
8891
|
+
fileConfig,
|
|
8892
|
+
"openclawFlushPlanProcessingEnabled"
|
|
8893
|
+
);
|
|
8894
|
+
const runtimeValue = hasRuntimeValue ? resolveOpenClawFlushPlanProcessingEnabled(
|
|
8895
|
+
runtimeRecord.openclawFlushPlanProcessingEnabled
|
|
8896
|
+
) : void 0;
|
|
8897
|
+
const fileValue = hasFileValue ? resolveOpenClawFlushPlanProcessingEnabled(
|
|
8898
|
+
fileConfig.openclawFlushPlanProcessingEnabled
|
|
8899
|
+
) : void 0;
|
|
8900
|
+
if (runtimeValue === false || fileValue === false) return false;
|
|
8901
|
+
return fileValue ?? runtimeValue ?? true;
|
|
8902
|
+
}
|
|
7632
8903
|
function extractLastTurn(messages) {
|
|
7633
8904
|
let lastUserIdx = -1;
|
|
7634
8905
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -7701,7 +8972,7 @@ function buildOpenClawInboundContentFingerprint(content, message, event, ctx, se
|
|
|
7701
8972
|
normalizeThreadId(message.threadId) ?? normalizeThreadId(event.threadId) ?? normalizeThreadId(ctx.threadId) ?? "",
|
|
7702
8973
|
512
|
|
7703
8974
|
);
|
|
7704
|
-
const contentHash =
|
|
8975
|
+
const contentHash = createHash3("sha256").update(normalizedContent).digest("hex");
|
|
7705
8976
|
return [
|
|
7706
8977
|
sessionPart,
|
|
7707
8978
|
"content",
|
|
@@ -7715,7 +8986,7 @@ function buildOpenClawSparseInboundContentFingerprint(content, sessionKey) {
|
|
|
7715
8986
|
const normalizedContent = content.trim();
|
|
7716
8987
|
if (!normalizedContent) return null;
|
|
7717
8988
|
const sessionPart = truncateMetadataValue(sessionKey || "default", 512);
|
|
7718
|
-
const contentHash =
|
|
8989
|
+
const contentHash = createHash3("sha256").update(normalizedContent).digest("hex");
|
|
7719
8990
|
return `${sessionPart}\0sparse-content\0${contentHash}`;
|
|
7720
8991
|
}
|
|
7721
8992
|
function withReplyExtractionHint(content, metadata) {
|
|
@@ -7739,10 +9010,18 @@ function normalizeThreadId(value) {
|
|
|
7739
9010
|
function truncateMetadataValue(value, maxChars) {
|
|
7740
9011
|
return value.length <= maxChars ? value : value.slice(0, maxChars);
|
|
7741
9012
|
}
|
|
9013
|
+
async function resolveFlushPlanProcessingChainKey(workspaceRoot) {
|
|
9014
|
+
const lexicalRoot = path4.resolve(workspaceRoot);
|
|
9015
|
+
try {
|
|
9016
|
+
return path4.resolve(await realPathLater(lexicalRoot));
|
|
9017
|
+
} catch {
|
|
9018
|
+
return lexicalRoot;
|
|
9019
|
+
}
|
|
9020
|
+
}
|
|
7742
9021
|
|
|
7743
9022
|
// src/bridge.ts
|
|
7744
9023
|
import fs from "fs";
|
|
7745
|
-
import
|
|
9024
|
+
import path5 from "path";
|
|
7746
9025
|
import { Worker } from "worker_threads";
|
|
7747
9026
|
import { expandTildePath as expandTildePath2 } from "@remnic/core";
|
|
7748
9027
|
var DEFAULT_HOST = "127.0.0.1";
|
|
@@ -7812,11 +9091,11 @@ function readCompatEnv(primary, legacy) {
|
|
|
7812
9091
|
function configPathCandidates() {
|
|
7813
9092
|
const envPath = readCompatEnv("REMNIC_CONFIG_PATH", "ENGRAM_CONFIG_PATH");
|
|
7814
9093
|
return [
|
|
7815
|
-
...envPath ? [
|
|
7816
|
-
|
|
7817
|
-
|
|
7818
|
-
|
|
7819
|
-
|
|
9094
|
+
...envPath ? [path5.resolve(expandTildePath2(envPath))] : [],
|
|
9095
|
+
path5.join(resolveHomeDir2(), ".config", "remnic", "config.json"),
|
|
9096
|
+
path5.join(resolveHomeDir2(), ".config", "engram", "config.json"),
|
|
9097
|
+
path5.join(process.cwd(), "remnic.config.json"),
|
|
9098
|
+
path5.join(process.cwd(), "engram.config.json")
|
|
7820
9099
|
];
|
|
7821
9100
|
}
|
|
7822
9101
|
function fileExists(filePath) {
|
|
@@ -7828,8 +9107,8 @@ function fileExists(filePath) {
|
|
|
7828
9107
|
}
|
|
7829
9108
|
function isDaemonRunning() {
|
|
7830
9109
|
for (const pidFile of [
|
|
7831
|
-
|
|
7832
|
-
|
|
9110
|
+
path5.join(resolveHomeDir2(), ".remnic", "server.pid"),
|
|
9111
|
+
path5.join(resolveHomeDir2(), ".engram", "server.pid")
|
|
7833
9112
|
]) {
|
|
7834
9113
|
try {
|
|
7835
9114
|
const pid = parseInt(fs["re"+"ad"+"Fi"+"le"+"Sync"](pidFile, "utf8").trim(), 10);
|
|
@@ -7843,7 +9122,7 @@ function isDaemonRunning() {
|
|
|
7843
9122
|
function isDaemonServiceConfigured() {
|
|
7844
9123
|
const homeDir = resolveHomeDir2();
|
|
7845
9124
|
for (const segments of [...LAUNCHD_SERVICE_PATHS, ...SYSTEMD_SERVICE_PATHS]) {
|
|
7846
|
-
if (fileExists(
|
|
9125
|
+
if (fileExists(path5.join(homeDir, ...segments))) return true;
|
|
7847
9126
|
}
|
|
7848
9127
|
return false;
|
|
7849
9128
|
}
|
|
@@ -7931,8 +9210,8 @@ function detectBridgeMode() {
|
|
|
7931
9210
|
}
|
|
7932
9211
|
function loadAnyToken() {
|
|
7933
9212
|
const tokenPaths = [
|
|
7934
|
-
|
|
7935
|
-
|
|
9213
|
+
path5.join(resolveHomeDir2(), ".remnic", "tokens.json"),
|
|
9214
|
+
path5.join(resolveHomeDir2(), ".engram", "tokens.json")
|
|
7936
9215
|
];
|
|
7937
9216
|
for (const tokensPath of tokenPaths) {
|
|
7938
9217
|
if (!fs.existsSync(tokensPath)) continue;
|