@remnic/plugin-openclaw 9.54.1 → 9.54.3
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 +288 -103
- package/openclaw.plugin.json +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -5533,7 +5533,7 @@ function parseEnvironmentFile(body) {
|
|
|
5533
5533
|
}
|
|
5534
5534
|
return parsed;
|
|
5535
5535
|
}
|
|
5536
|
-
function readUnitEnvironment(unit, scope,
|
|
5536
|
+
function readUnitEnvironment(unit, scope, readFile3, listDir) {
|
|
5537
5537
|
const directives = readEffectiveDirectives(unit);
|
|
5538
5538
|
const merged = new Map(directives.env);
|
|
5539
5539
|
for (const candidate of directives.envFiles) {
|
|
@@ -5542,7 +5542,7 @@ function readUnitEnvironment(unit, scope, readFile4, listDir) {
|
|
|
5542
5542
|
const resolved = settleUnitValue(expandedFile);
|
|
5543
5543
|
if (!path3.isAbsolute(resolved)) continue;
|
|
5544
5544
|
for (const match of expandEnvironmentFilePattern(resolved, listDir)) {
|
|
5545
|
-
const body =
|
|
5545
|
+
const body = readFile3(match);
|
|
5546
5546
|
if (body === void 0) continue;
|
|
5547
5547
|
for (const [key, value] of parseEnvironmentFile(body)) merged.set(key, value);
|
|
5548
5548
|
}
|
|
@@ -5582,8 +5582,8 @@ function defaultUnitFileReader(candidate) {
|
|
|
5582
5582
|
return void 0;
|
|
5583
5583
|
}
|
|
5584
5584
|
}
|
|
5585
|
-
function readUnitEnv(unit, name, scope,
|
|
5586
|
-
const systemdValue = readUnitEnvironment(unit, scope,
|
|
5585
|
+
function readUnitEnv(unit, name, scope, readFile3 = defaultUnitFileReader, listDir = defaultUnitDirLister) {
|
|
5586
|
+
const systemdValue = readUnitEnvironment(unit, scope, readFile3, listDir).get(name);
|
|
5587
5587
|
const systemd = systemdValue === void 0 ? null : [void 0, systemdValue];
|
|
5588
5588
|
const launchdRaw = new RegExp(`<key>${name}</key>\\s*<string>([^<]*)</string>`).exec(unit);
|
|
5589
5589
|
const launchd = launchdRaw === null ? null : [void 0, decodePlistString(launchdRaw[1] ?? "")];
|
|
@@ -5641,9 +5641,9 @@ function readUnitWorkingDirectory(unit) {
|
|
|
5641
5641
|
if (raw === void 0 || raw === "") return void 0;
|
|
5642
5642
|
return /^(["']).*\1$/.test(raw) ? raw.slice(1, -1) : raw;
|
|
5643
5643
|
}
|
|
5644
|
-
function readUnitCliOverrides(unit, scope,
|
|
5644
|
+
function readUnitCliOverrides(unit, scope, readFile3, listDir) {
|
|
5645
5645
|
const tokens = [];
|
|
5646
|
-
const environment = readUnitEnvironment(unit, scope,
|
|
5646
|
+
const environment = readUnitEnvironment(unit, scope, readFile3, listDir);
|
|
5647
5647
|
const substituteBraced = (token) => token.replace(
|
|
5648
5648
|
/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g,
|
|
5649
5649
|
(match, name) => environment.get(name) ?? match
|
|
@@ -5706,12 +5706,12 @@ function readUnitCliOverrides(unit, scope, readFile4, listDir) {
|
|
|
5706
5706
|
...authToken === void 0 ? {} : { authToken }
|
|
5707
5707
|
};
|
|
5708
5708
|
}
|
|
5709
|
-
function resolveUnitEndpoint(unit, scope,
|
|
5710
|
-
const cli = readUnitCliOverrides(unit, scope,
|
|
5709
|
+
function resolveUnitEndpoint(unit, scope, readFile3 = defaultUnitFileReader, listDir = defaultUnitDirLister) {
|
|
5710
|
+
const cli = readUnitCliOverrides(unit, scope, readFile3, listDir);
|
|
5711
5711
|
const envOverride = (primary, legacy) => {
|
|
5712
|
-
const value = readUnitEnv(unit, primary, scope,
|
|
5712
|
+
const value = readUnitEnv(unit, primary, scope, readFile3, listDir);
|
|
5713
5713
|
if (value !== void 0) return value === "" ? void 0 : value;
|
|
5714
|
-
const legacyValue = readUnitEnv(unit, legacy, scope,
|
|
5714
|
+
const legacyValue = readUnitEnv(unit, legacy, scope, readFile3, listDir);
|
|
5715
5715
|
return legacyValue === "" ? void 0 : legacyValue;
|
|
5716
5716
|
};
|
|
5717
5717
|
const host = cli.host ?? envOverride("REMNIC_HOST", "ENGRAM_HOST");
|
|
@@ -5719,17 +5719,17 @@ function resolveUnitEndpoint(unit, scope, readFile4 = defaultUnitFileReader, lis
|
|
|
5719
5719
|
const authToken = cli.authToken ?? envOverride("REMNIC_AUTH_TOKEN", "ENGRAM_AUTH_TOKEN");
|
|
5720
5720
|
const configFromCli = cli.configPath === void 0 ? {} : { configPath: cli.configPath };
|
|
5721
5721
|
return {
|
|
5722
|
-
...resolveUnitConfigPathInner(unit, scope,
|
|
5722
|
+
...resolveUnitConfigPathInner(unit, scope, readFile3, listDir),
|
|
5723
5723
|
...configFromCli,
|
|
5724
5724
|
...host === void 0 ? {} : { host },
|
|
5725
5725
|
...port === void 0 ? {} : { port },
|
|
5726
5726
|
...authToken === void 0 ? {} : { authToken }
|
|
5727
5727
|
};
|
|
5728
5728
|
}
|
|
5729
|
-
function resolveUnitConfigPathInner(unit, scope,
|
|
5729
|
+
function resolveUnitConfigPathInner(unit, scope, readFile3, listDir) {
|
|
5730
5730
|
const workingDirectory = readUnitWorkingDirectory(unit);
|
|
5731
5731
|
for (const name of ["REMNIC_CONFIG_PATH", "ENGRAM_CONFIG_PATH"]) {
|
|
5732
|
-
const raw = readUnitEnv(unit, name, scope,
|
|
5732
|
+
const raw = readUnitEnv(unit, name, scope, readFile3, listDir);
|
|
5733
5733
|
if (raw === "") return {};
|
|
5734
5734
|
if (raw === void 0) continue;
|
|
5735
5735
|
const resolved = resolveAgainstWorkingDirectory(raw, workingDirectory, scope);
|
|
@@ -5739,7 +5739,7 @@ function resolveUnitConfigPathInner(unit, scope, readFile4, listDir) {
|
|
|
5739
5739
|
if (workingDirectory !== void 0) {
|
|
5740
5740
|
for (const name of ["remnic.config.json", "engram.config.json"]) {
|
|
5741
5741
|
const candidate = resolveAgainstWorkingDirectory(name, workingDirectory, scope);
|
|
5742
|
-
if (candidate !== void 0 &&
|
|
5742
|
+
if (candidate !== void 0 && readFile3(candidate) !== void 0) {
|
|
5743
5743
|
return { configPath: candidate };
|
|
5744
5744
|
}
|
|
5745
5745
|
}
|
|
@@ -6783,9 +6783,11 @@ function daemonTargetFor(bridge) {
|
|
|
6783
6783
|
}
|
|
6784
6784
|
|
|
6785
6785
|
// src/delegate-flush-plan-ingest.ts
|
|
6786
|
-
import {
|
|
6786
|
+
import { constants } from "fs";
|
|
6787
|
+
import { lstat as lstat3, open, rename as rename2, unlink as unlink2, writeFile as writeFile2 } from "fs/promises";
|
|
6787
6788
|
import path7 from "path";
|
|
6788
6789
|
import { log as log4 } from "@remnic/core/logger";
|
|
6790
|
+
import { withHeldFileLock } from "@remnic/core/utils/serialize-mutations";
|
|
6789
6791
|
|
|
6790
6792
|
// src/memory-flush-plan.ts
|
|
6791
6793
|
var DEFAULT_MAX_TURN_CHARS = 8e3;
|
|
@@ -6806,71 +6808,300 @@ function buildMemoryFlushPlan(options) {
|
|
|
6806
6808
|
};
|
|
6807
6809
|
}
|
|
6808
6810
|
|
|
6811
|
+
// src/delegate-http.ts
|
|
6812
|
+
async function readJsonObject(res) {
|
|
6813
|
+
const parsed = await res.json().catch(() => null);
|
|
6814
|
+
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
6815
|
+
}
|
|
6816
|
+
async function noteRefusal(res, serviceId, pathname, source) {
|
|
6817
|
+
await res.body?.cancel();
|
|
6818
|
+
if (res.status === 401 || res.status === 403) {
|
|
6819
|
+
reportDaemonAuthorizationFailure(serviceId, pathname, res.status, source);
|
|
6820
|
+
}
|
|
6821
|
+
return { status: res.status, body: null };
|
|
6822
|
+
}
|
|
6823
|
+
async function postJsonWithStatus(target, serviceId, pathname, body, timeoutMs) {
|
|
6824
|
+
const headers = { "Content-Type": "application/json" };
|
|
6825
|
+
const auth = target.resolveAuthToken();
|
|
6826
|
+
if (auth.token) headers.Authorization = `Bearer ${auth.token}`;
|
|
6827
|
+
const res = await fetch(daemonUrl(target, pathname), {
|
|
6828
|
+
method: "POST",
|
|
6829
|
+
headers,
|
|
6830
|
+
body: JSON.stringify(body),
|
|
6831
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
6832
|
+
});
|
|
6833
|
+
if (!res.ok) return noteRefusal(res, serviceId, pathname, auth.source);
|
|
6834
|
+
return { status: res.status, body: await readJsonObject(res) };
|
|
6835
|
+
}
|
|
6836
|
+
async function postJson(target, serviceId, pathname, body, timeoutMs) {
|
|
6837
|
+
const response = await postJsonWithStatus(target, serviceId, pathname, body, timeoutMs);
|
|
6838
|
+
if (response.status === 401 || response.status === 403) return null;
|
|
6839
|
+
if (response.status < 200 || response.status > 299) {
|
|
6840
|
+
throw new Error(`daemon ${pathname} responded ${response.status}`);
|
|
6841
|
+
}
|
|
6842
|
+
return response.body;
|
|
6843
|
+
}
|
|
6844
|
+
async function getJson(target, serviceId, pathname, timeoutMs) {
|
|
6845
|
+
const headers = {};
|
|
6846
|
+
const auth = target.resolveAuthToken();
|
|
6847
|
+
if (auth.token) headers.Authorization = `Bearer ${auth.token}`;
|
|
6848
|
+
const res = await fetch(daemonUrl(target, pathname), {
|
|
6849
|
+
headers,
|
|
6850
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
6851
|
+
});
|
|
6852
|
+
if (!res.ok) return noteRefusal(res, serviceId, pathname, auth.source);
|
|
6853
|
+
return { status: res.status, body: await readJsonObject(res) };
|
|
6854
|
+
}
|
|
6855
|
+
|
|
6809
6856
|
// src/delegate-flush-plan-ingest.ts
|
|
6810
6857
|
var MAX_OBSERVE_CHUNK_BYTES = 96 * 1024;
|
|
6811
6858
|
var MIN_OBSERVE_CHUNK_BYTES = 4 * 1024;
|
|
6859
|
+
var LOCK_MAX_WAIT_MS = 5e3;
|
|
6860
|
+
var LOCK_STALE_MS = 6e4;
|
|
6861
|
+
var MAX_RECLAIM_PASSES = 4;
|
|
6812
6862
|
async function ingestFlushPlanNotes(options) {
|
|
6813
6863
|
if (options.workspaceDir === void 0) return;
|
|
6864
|
+
const workspaceDir = options.workspaceDir;
|
|
6814
6865
|
const planPath = path7.join(
|
|
6815
|
-
|
|
6866
|
+
workspaceDir,
|
|
6816
6867
|
...buildMemoryFlushPlan({ serviceId: options.serviceId }).relativePath.split("/")
|
|
6817
6868
|
);
|
|
6818
|
-
|
|
6869
|
+
const paths = {
|
|
6870
|
+
plan: planPath,
|
|
6871
|
+
inflight: `${planPath}.inflight`,
|
|
6872
|
+
rotating: `${planPath}.rotating`,
|
|
6873
|
+
oversized: `${planPath}.oversized`,
|
|
6874
|
+
lock: `${planPath}.lock`
|
|
6875
|
+
};
|
|
6876
|
+
for (const candidate of [paths.plan, paths.inflight, paths.rotating, paths.oversized]) {
|
|
6877
|
+
if (await isLinkFreeUnder(workspaceDir, candidate)) continue;
|
|
6819
6878
|
log4.warn(
|
|
6820
|
-
`[${options.serviceId}] flush-plan ingestion skipped: ${
|
|
6879
|
+
`[${options.serviceId}] flush-plan ingestion skipped: ${candidate}, a parent, or the workspace root is a symlink`
|
|
6821
6880
|
);
|
|
6822
6881
|
return;
|
|
6823
6882
|
}
|
|
6824
|
-
|
|
6825
|
-
|
|
6883
|
+
const lockWaitMs = Math.max(1, Math.min(LOCK_MAX_WAIT_MS, Math.floor(options.remainingTimeoutMs() / 2)));
|
|
6884
|
+
await withHeldFileLock(
|
|
6885
|
+
paths.lock,
|
|
6886
|
+
{ staleMs: LOCK_STALE_MS, maxWaitMs: lockWaitMs },
|
|
6887
|
+
async (acquired, lock) => {
|
|
6888
|
+
if (!acquired) {
|
|
6889
|
+
log4.warn(
|
|
6890
|
+
`[${options.serviceId}] flush-plan ingestion skipped: another flush holds the lock; the notes drain on the next flush`
|
|
6891
|
+
);
|
|
6892
|
+
return;
|
|
6893
|
+
}
|
|
6894
|
+
for (const candidate of [paths.plan, paths.inflight, paths.rotating, paths.oversized]) {
|
|
6895
|
+
if (await isLinkFreeUnder(workspaceDir, candidate)) continue;
|
|
6896
|
+
log4.warn(
|
|
6897
|
+
`[${options.serviceId}] flush-plan ingestion skipped: ${candidate}, a parent, or the workspace root became a symlink`
|
|
6898
|
+
);
|
|
6899
|
+
return;
|
|
6900
|
+
}
|
|
6901
|
+
await ingestUnderLock(options, paths, lock);
|
|
6902
|
+
}
|
|
6903
|
+
);
|
|
6904
|
+
}
|
|
6905
|
+
async function ingestUnderLock(options, paths, lock) {
|
|
6826
6906
|
let chunkBytes = MAX_OBSERVE_CHUNK_BYTES;
|
|
6827
|
-
|
|
6828
|
-
|
|
6829
|
-
|
|
6907
|
+
for (let pass = 0; pass < MAX_RECLAIM_PASSES; pass++) {
|
|
6908
|
+
if (options.remainingTimeoutMs() <= 0) return;
|
|
6909
|
+
const claimed = await claimPendingNotes(paths, options.serviceId, lock);
|
|
6910
|
+
if (claimed === void 0) {
|
|
6830
6911
|
log4.warn(
|
|
6831
|
-
`[${options.serviceId}] flush-plan
|
|
6912
|
+
`[${options.serviceId}] flush-plan lock was lost during recovery; its new owner drains the snapshot`
|
|
6832
6913
|
);
|
|
6833
6914
|
return;
|
|
6834
6915
|
}
|
|
6835
|
-
|
|
6836
|
-
|
|
6837
|
-
|
|
6838
|
-
|
|
6839
|
-
|
|
6840
|
-
|
|
6841
|
-
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
|
|
6845
|
-
|
|
6846
|
-
|
|
6847
|
-
|
|
6848
|
-
|
|
6849
|
-
|
|
6850
|
-
|
|
6851
|
-
|
|
6916
|
+
if (claimed.trim().length === 0) {
|
|
6917
|
+
if (await lock.refresh()) await discard(paths.inflight);
|
|
6918
|
+
return;
|
|
6919
|
+
}
|
|
6920
|
+
let pending = claimed;
|
|
6921
|
+
let stop = false;
|
|
6922
|
+
let lockLost = false;
|
|
6923
|
+
try {
|
|
6924
|
+
while (pending.trim().length > 0) {
|
|
6925
|
+
const timeoutMs = options.remainingTimeoutMs();
|
|
6926
|
+
if (timeoutMs <= 0) {
|
|
6927
|
+
log4.warn(
|
|
6928
|
+
`[${options.serviceId}] flush-plan ingestion stopped: the caller's deadline is spent; the remainder drains on the next flush`
|
|
6929
|
+
);
|
|
6930
|
+
stop = true;
|
|
6931
|
+
return;
|
|
6932
|
+
}
|
|
6933
|
+
const [chunk] = chunkOnLineBoundaries(pending, chunkBytes);
|
|
6934
|
+
if (chunk === void 0) {
|
|
6935
|
+
stop = true;
|
|
6936
|
+
return;
|
|
6937
|
+
}
|
|
6938
|
+
const response = await postJsonWithStatus(
|
|
6939
|
+
options.target,
|
|
6940
|
+
options.serviceId,
|
|
6941
|
+
"/engram/v1/observe",
|
|
6942
|
+
{
|
|
6943
|
+
sessionKey: options.sessionKey,
|
|
6944
|
+
messages: [{ role: "user", content: chunk }],
|
|
6945
|
+
...options.namespace === void 0 ? {} : { namespace: options.namespace }
|
|
6946
|
+
},
|
|
6947
|
+
timeoutMs
|
|
6948
|
+
);
|
|
6949
|
+
if (isRefusal(response.status)) {
|
|
6950
|
+
log4.warn(
|
|
6951
|
+
`[${options.serviceId}] flush-plan notes were refused by the daemon (${response.status}); keeping them for the next flush`
|
|
6952
|
+
);
|
|
6953
|
+
stop = true;
|
|
6954
|
+
return;
|
|
6955
|
+
}
|
|
6956
|
+
if (response.status === 413) {
|
|
6957
|
+
if (countLines(chunk) > 1) {
|
|
6958
|
+
chunkBytes = Math.max(1, Math.floor(chunkBytes / 2));
|
|
6959
|
+
continue;
|
|
6960
|
+
}
|
|
6961
|
+
pending = await quarantineOversizedLine(paths, pending, options.serviceId);
|
|
6962
|
+
chunkBytes = MAX_OBSERVE_CHUNK_BYTES;
|
|
6963
|
+
if (!await lock.refresh()) {
|
|
6964
|
+
log4.warn(
|
|
6965
|
+
`[${options.serviceId}] flush-plan lock was lost mid-flush; the snapshot is left to its new owner`
|
|
6966
|
+
);
|
|
6967
|
+
lockLost = true;
|
|
6968
|
+
stop = true;
|
|
6969
|
+
return;
|
|
6970
|
+
}
|
|
6971
|
+
await atomicWrite(paths.inflight, pending);
|
|
6972
|
+
continue;
|
|
6973
|
+
}
|
|
6974
|
+
if (response.status < 200 || response.status > 299) {
|
|
6975
|
+
stop = true;
|
|
6976
|
+
throw new Error(`daemon /engram/v1/observe responded ${response.status}`);
|
|
6977
|
+
}
|
|
6978
|
+
pending = pending.slice(chunk.length);
|
|
6979
|
+
if (!await lock.refresh()) {
|
|
6980
|
+
log4.warn(
|
|
6981
|
+
`[${options.serviceId}] flush-plan lock was lost mid-flush; the snapshot is left to its new owner`
|
|
6982
|
+
);
|
|
6983
|
+
lockLost = true;
|
|
6984
|
+
stop = true;
|
|
6985
|
+
return;
|
|
6986
|
+
}
|
|
6987
|
+
await atomicWrite(paths.inflight, pending);
|
|
6852
6988
|
}
|
|
6853
|
-
|
|
6854
|
-
|
|
6855
|
-
|
|
6989
|
+
} finally {
|
|
6990
|
+
if (!lockLost && await lock.refresh()) {
|
|
6991
|
+
await releasePendingNotes(paths, pending, options.serviceId);
|
|
6992
|
+
} else if (!lockLost) {
|
|
6993
|
+
log4.warn(
|
|
6994
|
+
`[${options.serviceId}] flush-plan lock was lost before the snapshot could be updated; its new owner drains the remainder`
|
|
6995
|
+
);
|
|
6996
|
+
}
|
|
6997
|
+
}
|
|
6998
|
+
if (stop) return;
|
|
6999
|
+
}
|
|
7000
|
+
}
|
|
7001
|
+
function countLines(chunk) {
|
|
7002
|
+
const body = chunk.endsWith("\n") ? chunk.slice(0, -1) : chunk;
|
|
7003
|
+
if (body.length === 0) return chunk.length > 0 ? 1 : 0;
|
|
7004
|
+
return body.split("\n").length;
|
|
7005
|
+
}
|
|
7006
|
+
function isRefusal(status) {
|
|
7007
|
+
return status === 401 || status === 403 || status === 431;
|
|
7008
|
+
}
|
|
7009
|
+
async function claimPendingNotes(paths, serviceId, lock) {
|
|
7010
|
+
const recovery = await mergeRotatedIntoInflight(paths, lock);
|
|
7011
|
+
if (recovery === "lock-lost") return void 0;
|
|
7012
|
+
if (recovery === "recovered") {
|
|
7013
|
+
log4.warn(`[${serviceId}] recovered flush-plan notes left by an interrupted ingestion`);
|
|
7014
|
+
}
|
|
7015
|
+
if (!await lock.refresh()) return void 0;
|
|
7016
|
+
try {
|
|
7017
|
+
await rename2(paths.plan, paths.rotating);
|
|
7018
|
+
} catch (err) {
|
|
7019
|
+
if (err?.code !== "ENOENT") throw err;
|
|
7020
|
+
return await readIfPresent(paths.inflight) ?? "";
|
|
7021
|
+
}
|
|
7022
|
+
if (await mergeRotatedIntoInflight(paths, lock) === "lock-lost") return void 0;
|
|
7023
|
+
return await readIfPresent(paths.inflight) ?? "";
|
|
7024
|
+
}
|
|
7025
|
+
async function mergeRotatedIntoInflight(paths, lock) {
|
|
7026
|
+
const rotated = await readIfPresent(paths.rotating);
|
|
7027
|
+
if (rotated === void 0) return "nothing";
|
|
7028
|
+
const existing = await readIfPresent(paths.inflight) ?? "";
|
|
7029
|
+
let merged = `${existing}${rotated}`;
|
|
7030
|
+
if (merged.length > 0) {
|
|
7031
|
+
if (!await lock.refresh()) return "lock-lost";
|
|
7032
|
+
await atomicWrite(paths.inflight, merged);
|
|
7033
|
+
}
|
|
7034
|
+
const late = await readIfPresent(paths.rotating);
|
|
7035
|
+
if (late !== void 0 && late.length > rotated.length) {
|
|
7036
|
+
if (!await lock.refresh()) return "lock-lost";
|
|
7037
|
+
merged = `${existing}${late}`;
|
|
7038
|
+
await atomicWrite(paths.inflight, merged);
|
|
7039
|
+
}
|
|
7040
|
+
if (!await lock.refresh()) return "lock-lost";
|
|
7041
|
+
await discard(paths.rotating);
|
|
7042
|
+
return merged.length > 0 ? "recovered" : "nothing";
|
|
7043
|
+
}
|
|
7044
|
+
async function releasePendingNotes(paths, pending, serviceId) {
|
|
7045
|
+
try {
|
|
7046
|
+
if (pending.length === 0) {
|
|
7047
|
+
await discard(paths.inflight);
|
|
6856
7048
|
return;
|
|
6857
7049
|
}
|
|
6858
|
-
|
|
7050
|
+
await atomicWrite(paths.inflight, pending);
|
|
7051
|
+
} catch (err) {
|
|
7052
|
+
log4.warn(
|
|
7053
|
+
`[${serviceId}] could not persist unsent flush-plan notes; the next ingestion recovers them: ${String(err)}`
|
|
7054
|
+
);
|
|
6859
7055
|
}
|
|
6860
7056
|
}
|
|
6861
|
-
async function
|
|
7057
|
+
async function quarantineOversizedLine(paths, pending, serviceId) {
|
|
7058
|
+
const breakAt = pending.indexOf("\n");
|
|
7059
|
+
const line = breakAt === -1 ? pending : pending.slice(0, breakAt + 1);
|
|
7060
|
+
const rest = breakAt === -1 ? "" : pending.slice(breakAt + 1);
|
|
7061
|
+
const handle = await open(
|
|
7062
|
+
paths.oversized,
|
|
7063
|
+
constants.O_WRONLY | constants.O_CREAT | constants.O_APPEND | constants.O_NOFOLLOW,
|
|
7064
|
+
384
|
|
7065
|
+
);
|
|
6862
7066
|
try {
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
|
|
7067
|
+
await handle.writeFile(line.endsWith("\n") ? line : `${line}
|
|
7068
|
+
`, "utf8");
|
|
7069
|
+
} finally {
|
|
7070
|
+
await handle.close();
|
|
7071
|
+
}
|
|
7072
|
+
log4.warn(
|
|
7073
|
+
`[${serviceId}] a flush-plan note exceeds the daemon's body limit; moved it to ${paths.oversized} so the remaining notes can drain`
|
|
7074
|
+
);
|
|
7075
|
+
return rest;
|
|
7076
|
+
}
|
|
7077
|
+
async function readIfPresent(filePath) {
|
|
7078
|
+
let handle;
|
|
7079
|
+
try {
|
|
7080
|
+
handle = await open(filePath, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
7081
|
+
return await handle.readFile("utf8");
|
|
7082
|
+
} catch (err) {
|
|
7083
|
+
if (err?.code === "ENOENT") return void 0;
|
|
7084
|
+
throw err;
|
|
7085
|
+
} finally {
|
|
7086
|
+
await handle?.close();
|
|
7087
|
+
}
|
|
7088
|
+
}
|
|
7089
|
+
async function atomicWrite(filePath, content) {
|
|
7090
|
+
const tempPath = `${filePath}.tmp-${process.pid}-${Math.random().toString(36).slice(2, 10)}`;
|
|
7091
|
+
try {
|
|
7092
|
+
await writeFile2(tempPath, content, { encoding: "utf8", flag: "wx", mode: 384 });
|
|
7093
|
+
await rename2(tempPath, filePath);
|
|
7094
|
+
} catch (err) {
|
|
7095
|
+
await discard(tempPath);
|
|
7096
|
+
throw err;
|
|
6866
7097
|
}
|
|
6867
7098
|
}
|
|
6868
|
-
async function
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
|
|
6872
|
-
|
|
6873
|
-
|
|
7099
|
+
async function discard(filePath) {
|
|
7100
|
+
try {
|
|
7101
|
+
await unlink2(filePath);
|
|
7102
|
+
} catch (err) {
|
|
7103
|
+
if (err?.code !== "ENOENT") throw err;
|
|
7104
|
+
}
|
|
6874
7105
|
}
|
|
6875
7106
|
function chunkOnLineBoundaries(text, limit) {
|
|
6876
7107
|
if (Buffer.byteLength(text, "utf8") <= limit) return [text];
|
|
@@ -6931,7 +7162,7 @@ function extractTextContent(msg) {
|
|
|
6931
7162
|
}
|
|
6932
7163
|
|
|
6933
7164
|
// src/delegate-capability.ts
|
|
6934
|
-
import { readFile as
|
|
7165
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
6935
7166
|
import { log as log5 } from "@remnic/core/logger";
|
|
6936
7167
|
var HEALTH_CACHE_TTL_MS = 3e4;
|
|
6937
7168
|
var SEARCH_CANDIDATE_CAP = 25e3;
|
|
@@ -7266,7 +7497,7 @@ function createDelegateMemoryCapability(options) {
|
|
|
7266
7497
|
await requireLocalReadAuthorized("readFile", ["memory_get"]);
|
|
7267
7498
|
const requestedPath = sharedScope().normalizeWorkspacePath(params.relPath);
|
|
7268
7499
|
const absolutePath = await sharedScope().resolveReadablePath(params.relPath);
|
|
7269
|
-
const allLines = (await
|
|
7500
|
+
const allLines = (await readFile2(absolutePath, "utf8")).split(/\r?\n/);
|
|
7270
7501
|
if (params.from !== void 0 && (!Number.isInteger(params.from) || params.from < 1)) {
|
|
7271
7502
|
throw new Error(
|
|
7272
7503
|
`memory read rejected (from must be a positive integer): ${String(params.from)}`
|
|
@@ -7410,52 +7641,6 @@ function registerDelegateMemoryCapability(api, options) {
|
|
|
7410
7641
|
|
|
7411
7642
|
// src/delegate-runtime.ts
|
|
7412
7643
|
var DELEGATE_BATCH_FLUSH_CACHE_TTL_MS = 3e4;
|
|
7413
|
-
async function postJson(target, serviceId, pathname, body, timeoutMs) {
|
|
7414
|
-
const headers = { "Content-Type": "application/json" };
|
|
7415
|
-
const auth = target.resolveAuthToken();
|
|
7416
|
-
if (auth.token) headers.Authorization = `Bearer ${auth.token}`;
|
|
7417
|
-
const res = await fetch(daemonUrl(target, pathname), {
|
|
7418
|
-
method: "POST",
|
|
7419
|
-
headers,
|
|
7420
|
-
body: JSON.stringify(body),
|
|
7421
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
7422
|
-
});
|
|
7423
|
-
if (!res.ok) {
|
|
7424
|
-
if (res.status === 401 || res.status === 403) {
|
|
7425
|
-
const status = res.status === 401 ? 401 : 403;
|
|
7426
|
-
await res.body?.cancel();
|
|
7427
|
-
reportDaemonAuthorizationFailure(serviceId, pathname, status, auth.source);
|
|
7428
|
-
return null;
|
|
7429
|
-
}
|
|
7430
|
-
throw new Error(`daemon ${pathname} responded ${res.status}`);
|
|
7431
|
-
}
|
|
7432
|
-
const parsed = await res.json().catch(() => null);
|
|
7433
|
-
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
7434
|
-
}
|
|
7435
|
-
async function getJson(target, serviceId, pathname, timeoutMs) {
|
|
7436
|
-
const headers = {};
|
|
7437
|
-
const auth = target.resolveAuthToken();
|
|
7438
|
-
if (auth.token) headers.Authorization = `Bearer ${auth.token}`;
|
|
7439
|
-
const res = await fetch(daemonUrl(target, pathname), {
|
|
7440
|
-
headers,
|
|
7441
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
7442
|
-
});
|
|
7443
|
-
if (!res.ok) {
|
|
7444
|
-
if (res.status === 401 || res.status === 403) {
|
|
7445
|
-
const status = res.status === 401 ? 401 : 403;
|
|
7446
|
-
await res.body?.cancel();
|
|
7447
|
-
reportDaemonAuthorizationFailure(serviceId, pathname, status, auth.source);
|
|
7448
|
-
return { status: res.status, body: null };
|
|
7449
|
-
}
|
|
7450
|
-
await res.body?.cancel();
|
|
7451
|
-
return { status: res.status, body: null };
|
|
7452
|
-
}
|
|
7453
|
-
const parsed = await res.json().catch(() => null);
|
|
7454
|
-
return {
|
|
7455
|
-
status: res.status,
|
|
7456
|
-
body: typeof parsed === "object" && parsed !== null ? parsed : null
|
|
7457
|
-
};
|
|
7458
|
-
}
|
|
7459
7644
|
function sessionKeyFrom(event, ctx) {
|
|
7460
7645
|
const fromCtx = ctx?.sessionKey;
|
|
7461
7646
|
if (typeof fromCtx === "string" && fromCtx.length > 0) return fromCtx;
|