@kontextmind/kxm 0.7.77 → 0.7.79
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/.claude-plugin/marketplace.json +1 -1
- package/docs/contracts/synchronization.md +31 -3
- package/docs/operations.md +27 -1
- package/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +63 -3
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime-supervisor.js +443 -47
- package/plugins/kxm/dist/runtime.js +444 -197
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/src/cli/project.ts +57 -3
- package/plugins/kxm/src/cli.ts +4 -0
- package/plugins/kxm/src/database.ts +1 -1
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/runtime-store.ts +121 -10
- package/plugins/kxm/src/runtime-supervisor.ts +323 -42
|
@@ -18441,7 +18441,7 @@ function restoreBackup(manifestPathOrDir, options = {}) {
|
|
|
18441
18441
|
if (store.storeId === "registry" || store.storeId === "binding-store") {
|
|
18442
18442
|
maxSupported = 1;
|
|
18443
18443
|
} else if (store.storeId.startsWith("events:")) {
|
|
18444
|
-
maxSupported =
|
|
18444
|
+
maxSupported = 7;
|
|
18445
18445
|
}
|
|
18446
18446
|
let targetPath = store.sourcePath;
|
|
18447
18447
|
if (options.projectRoot && manifest.projectRoot && targetPath.startsWith(manifest.projectRoot)) {
|
|
@@ -18720,7 +18720,7 @@ function verifyKxmDriveReceipt(receipt, events, foldedStatus, binding) {
|
|
|
18720
18720
|
if (reasons.length === 0) return { verified: true };
|
|
18721
18721
|
return { verified: false, divergence: reasons.join("; ") };
|
|
18722
18722
|
}
|
|
18723
|
-
var KXM_EVENT_STORE_SCHEMA_VERSION =
|
|
18723
|
+
var KXM_EVENT_STORE_SCHEMA_VERSION = 7;
|
|
18724
18724
|
var KXM_DRIVE_RECEIPT_SCHEMA = "kxm.drive-receipt.v1";
|
|
18725
18725
|
var DRIVE_RECEIPT_MAX_BYTES = 8 * 1024;
|
|
18726
18726
|
var EVENT_STORE_TABLES = {
|
|
@@ -18826,7 +18826,17 @@ var EVENT_STORE_TABLES = {
|
|
|
18826
18826
|
"record"
|
|
18827
18827
|
],
|
|
18828
18828
|
project_controls: ["project_id", "paused", "reason", "updated_at", "actor", "schema", "record"],
|
|
18829
|
-
outbox: [
|
|
18829
|
+
outbox: [
|
|
18830
|
+
"seq",
|
|
18831
|
+
"run_id",
|
|
18832
|
+
"sequence",
|
|
18833
|
+
"sync_event",
|
|
18834
|
+
"attempted_at",
|
|
18835
|
+
"attempt_count",
|
|
18836
|
+
"acked_at",
|
|
18837
|
+
"refused_code",
|
|
18838
|
+
"refused_at"
|
|
18839
|
+
]
|
|
18830
18840
|
};
|
|
18831
18841
|
var KXM_EVENT_STORE_TABLE_NAMES = Object.keys(EVENT_STORE_TABLES).sort();
|
|
18832
18842
|
var EVENT_STORE_SCHEMA = `
|
|
@@ -19020,10 +19030,14 @@ CREATE TABLE IF NOT EXISTS outbox (
|
|
|
19020
19030
|
sequence INTEGER NOT NULL,
|
|
19021
19031
|
sync_event TEXT NOT NULL,
|
|
19022
19032
|
attempted_at TEXT,
|
|
19033
|
+
attempt_count INTEGER NOT NULL DEFAULT 0,
|
|
19023
19034
|
acked_at TEXT,
|
|
19035
|
+
refused_code TEXT,
|
|
19036
|
+
refused_at TEXT,
|
|
19024
19037
|
UNIQUE (run_id, sequence)
|
|
19025
19038
|
) STRICT;
|
|
19026
|
-
CREATE INDEX outbox_pending ON outbox(seq) WHERE acked_at IS NULL;
|
|
19039
|
+
CREATE INDEX outbox_pending ON outbox(seq) WHERE acked_at IS NULL AND refused_code IS NULL;
|
|
19040
|
+
CREATE INDEX outbox_refused ON outbox(seq) WHERE refused_code IS NOT NULL;
|
|
19027
19041
|
`;
|
|
19028
19042
|
var KxmRunEventStore = class {
|
|
19029
19043
|
path;
|
|
@@ -19163,37 +19177,108 @@ var KxmRunEventStore = class {
|
|
|
19163
19177
|
const syncEvent = deriveKxmSyncEvent(event, { redactor: this.syncRedactor });
|
|
19164
19178
|
this.database.prepare("INSERT INTO outbox (run_id, sequence, sync_event) VALUES (?, ?, ?)").run(event.runId, event.sequence, kxmSyncEventBytes(syncEvent));
|
|
19165
19179
|
}
|
|
19166
|
-
/**
|
|
19180
|
+
/**
|
|
19181
|
+
* Retryable outbox rows after `afterSeq`, in outbox order, oldest first.
|
|
19182
|
+
* Acked and durably refused rows are never returned: a refused row is not
|
|
19183
|
+
* waiting for a retry, it is waiting for an operator.
|
|
19184
|
+
*/
|
|
19167
19185
|
pendingOutbox(limit = 100, afterSeq = 0) {
|
|
19168
19186
|
const rows = this.database.prepare(`
|
|
19169
|
-
SELECT seq, run_id, sequence, sync_event, attempted_at, acked_at
|
|
19170
|
-
FROM outbox WHERE acked_at IS NULL AND seq > ? ORDER BY seq ASC LIMIT ?
|
|
19187
|
+
SELECT seq, run_id, sequence, sync_event, attempt_count, attempted_at, acked_at, refused_code, refused_at
|
|
19188
|
+
FROM outbox WHERE acked_at IS NULL AND refused_code IS NULL AND seq > ? ORDER BY seq ASC LIMIT ?
|
|
19171
19189
|
`).all(afterSeq, limit);
|
|
19172
19190
|
return rows.map(outboxFromRow);
|
|
19173
19191
|
}
|
|
19174
|
-
/** Every outbox row of one run,
|
|
19192
|
+
/** Every outbox row of one run, in any state, in sequence order. */
|
|
19175
19193
|
outboxForRun(runId) {
|
|
19176
19194
|
const rows = this.database.prepare(`
|
|
19177
|
-
SELECT seq, run_id, sequence, sync_event, attempted_at, acked_at
|
|
19195
|
+
SELECT seq, run_id, sequence, sync_event, attempt_count, attempted_at, acked_at, refused_code, refused_at
|
|
19178
19196
|
FROM outbox WHERE run_id = ? ORDER BY sequence ASC
|
|
19179
19197
|
`).all(runId);
|
|
19180
19198
|
return rows.map(outboxFromRow);
|
|
19181
19199
|
}
|
|
19200
|
+
/**
|
|
19201
|
+
* Count one attempt against each row. Refused rows are never re-counted: they
|
|
19202
|
+
* are out of the queue, and a tick that reaches them again would mean the
|
|
19203
|
+
* refusal was cleared (which resets the count).
|
|
19204
|
+
*/
|
|
19182
19205
|
markOutboxAttempted(seqs, at) {
|
|
19183
|
-
const statement = this.database.prepare(
|
|
19206
|
+
const statement = this.database.prepare(`
|
|
19207
|
+
UPDATE outbox SET attempted_at = ?, attempt_count = attempt_count + 1
|
|
19208
|
+
WHERE seq = ? AND acked_at IS NULL AND refused_code IS NULL
|
|
19209
|
+
`);
|
|
19184
19210
|
this.transaction(() => {
|
|
19185
19211
|
for (const seq of seqs) statement.run(at, seq);
|
|
19186
19212
|
});
|
|
19187
19213
|
}
|
|
19188
19214
|
/** Advance the cursor: the hub holds these rows now. Returns rows newly acked. */
|
|
19189
19215
|
ackOutbox(seqs, at) {
|
|
19190
|
-
const statement = this.database.prepare("UPDATE outbox SET acked_at =
|
|
19216
|
+
const statement = this.database.prepare("UPDATE outbox SET acked_at = ?, refused_code = NULL, refused_at = NULL WHERE seq = ? AND acked_at IS NULL");
|
|
19191
19217
|
return this.transaction(() => {
|
|
19192
19218
|
let changed = 0;
|
|
19193
19219
|
for (const seq of seqs) changed += Number(statement.run(at, seq).changes);
|
|
19194
19220
|
return changed;
|
|
19195
19221
|
});
|
|
19196
19222
|
}
|
|
19223
|
+
/**
|
|
19224
|
+
* Record a durable hub refusal: the answer will not change if the same bytes
|
|
19225
|
+
* are sent again. Returns rows newly moved out of the pending queue.
|
|
19226
|
+
*/
|
|
19227
|
+
refuseOutbox(refusals, at) {
|
|
19228
|
+
const statement = this.database.prepare(`
|
|
19229
|
+
UPDATE outbox SET refused_code = ?, refused_at = ?
|
|
19230
|
+
WHERE seq = ? AND acked_at IS NULL AND refused_code IS NULL
|
|
19231
|
+
`);
|
|
19232
|
+
return this.transaction(() => {
|
|
19233
|
+
let changed = 0;
|
|
19234
|
+
for (const refusal of refusals) changed += Number(statement.run(refusal.code, at, refusal.seq).changes);
|
|
19235
|
+
return changed;
|
|
19236
|
+
});
|
|
19237
|
+
}
|
|
19238
|
+
/**
|
|
19239
|
+
* Operator revive: put refused rows back in the pending queue with a fresh
|
|
19240
|
+
* attempt budget. Used after the hub-side state is corrected — the Runtime
|
|
19241
|
+
* must not decide on its own that a refusal has become retryable.
|
|
19242
|
+
*/
|
|
19243
|
+
retryRefusedOutbox() {
|
|
19244
|
+
return Number(this.database.prepare(`
|
|
19245
|
+
UPDATE outbox SET refused_code = NULL, refused_at = NULL, attempted_at = NULL, attempt_count = 0
|
|
19246
|
+
WHERE acked_at IS NULL AND refused_code IS NOT NULL
|
|
19247
|
+
`).run().changes);
|
|
19248
|
+
}
|
|
19249
|
+
/**
|
|
19250
|
+
* The outbox as one read model: what is still retryable, what the hub holds,
|
|
19251
|
+
* and what the hub refused with which codes. This is what makes a stalled
|
|
19252
|
+
* sync answerable without opening SQLite.
|
|
19253
|
+
*/
|
|
19254
|
+
outboxStatus() {
|
|
19255
|
+
const counts = this.database.prepare(`
|
|
19256
|
+
SELECT
|
|
19257
|
+
COALESCE(SUM(acked_at IS NULL AND refused_code IS NULL), 0) AS pending,
|
|
19258
|
+
COALESCE(SUM(acked_at IS NOT NULL), 0) AS acked,
|
|
19259
|
+
COALESCE(SUM(acked_at IS NULL AND refused_code IS NOT NULL), 0) AS refused,
|
|
19260
|
+
MAX(attempted_at) AS last_attempt_at
|
|
19261
|
+
FROM outbox
|
|
19262
|
+
`).get();
|
|
19263
|
+
const refusals = this.database.prepare(`
|
|
19264
|
+
SELECT refused_code AS code, COUNT(*) AS count
|
|
19265
|
+
FROM outbox WHERE acked_at IS NULL AND refused_code IS NOT NULL
|
|
19266
|
+
GROUP BY refused_code ORDER BY count DESC, code ASC
|
|
19267
|
+
`).all();
|
|
19268
|
+
const oldest = this.database.prepare(`
|
|
19269
|
+
SELECT seq FROM outbox WHERE acked_at IS NULL AND refused_code IS NULL ORDER BY seq ASC LIMIT 1
|
|
19270
|
+
`).get();
|
|
19271
|
+
return {
|
|
19272
|
+
pending: counts.pending,
|
|
19273
|
+
acked: counts.acked,
|
|
19274
|
+
refused: counts.refused,
|
|
19275
|
+
...counts.last_attempt_at !== null ? { lastAttemptAt: counts.last_attempt_at } : {},
|
|
19276
|
+
...oldest ? { oldestPendingSeq: oldest.seq } : {},
|
|
19277
|
+
// Mapped, not spread: node:sqlite rows are null-prototype objects, and this
|
|
19278
|
+
// read model is handed straight to the CLI and the supervisor's JSON answer.
|
|
19279
|
+
refusals: refusals.map((refusal) => ({ code: refusal.code, count: refusal.count }))
|
|
19280
|
+
};
|
|
19281
|
+
}
|
|
19197
19282
|
events(runId, afterSequence = 0, limit = 200) {
|
|
19198
19283
|
const rows = this.database.prepare(`
|
|
19199
19284
|
SELECT project_id, run_id, sequence, event_id, event_type, command_id, occurred_at, recorded_at, monotonic_ns, config_revision, memory_revision, executor_policy_revision, tool_policy_revision, payload, schema, home_runtime_id
|
|
@@ -19715,8 +19800,11 @@ function outboxFromRow(row) {
|
|
|
19715
19800
|
runId: row.run_id,
|
|
19716
19801
|
sequence: row.sequence,
|
|
19717
19802
|
syncEvent: row.sync_event,
|
|
19803
|
+
attemptCount: row.attempt_count,
|
|
19718
19804
|
...row.attempted_at !== null ? { attemptedAt: row.attempted_at } : {},
|
|
19719
|
-
...row.acked_at !== null ? { ackedAt: row.acked_at } : {}
|
|
19805
|
+
...row.acked_at !== null ? { ackedAt: row.acked_at } : {},
|
|
19806
|
+
...row.refused_code !== null ? { refusedCode: row.refused_code } : {},
|
|
19807
|
+
...row.refused_at !== null ? { refusedAt: row.refused_at } : {}
|
|
19720
19808
|
};
|
|
19721
19809
|
}
|
|
19722
19810
|
function runFromRow(row) {
|
|
@@ -23356,9 +23444,9 @@ function assertKxmConfigError(error) {
|
|
|
23356
23444
|
// plugins/kxm/src/runtime-supervisor.ts
|
|
23357
23445
|
import { spawn as spawn3 } from "node:child_process";
|
|
23358
23446
|
import { createHash as createHash13, createHmac, randomBytes as randomBytes3, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
23359
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
23447
|
+
import { chmodSync as chmodSync2, existsSync as existsSync13, lstatSync as lstatSync5, mkdirSync as mkdirSync7, readFileSync as readFileSync10, renameSync as renameSync4, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "node:fs";
|
|
23360
23448
|
import { createServer } from "node:http";
|
|
23361
|
-
import { dirname as
|
|
23449
|
+
import { dirname as dirname9, isAbsolute as isAbsolute6, join as join15 } from "node:path";
|
|
23362
23450
|
|
|
23363
23451
|
// plugins/kxm/src/oneshot-producer.ts
|
|
23364
23452
|
import { join as join12 } from "node:path";
|
|
@@ -27988,13 +28076,162 @@ function resolveClientHubAuthToken(env, project) {
|
|
|
27988
28076
|
return record2?.projectTokens?.[project]?.trim() || record2?.authToken?.trim() || void 0;
|
|
27989
28077
|
}
|
|
27990
28078
|
|
|
28079
|
+
// plugins/kxm/src/logger.ts
|
|
28080
|
+
import { appendFileSync, existsSync as existsSync12, mkdirSync as mkdirSync6, renameSync as renameSync3, statSync as statSync3, unlinkSync as unlinkSync2 } from "node:fs";
|
|
28081
|
+
import { dirname as dirname8 } from "node:path";
|
|
28082
|
+
var LOG_LEVEL_PRIORITY = {
|
|
28083
|
+
debug: 10,
|
|
28084
|
+
info: 20,
|
|
28085
|
+
warn: 30,
|
|
28086
|
+
error: 40
|
|
28087
|
+
};
|
|
28088
|
+
var DEFAULT_LOG_MAX_BYTES = 10 * 1024 * 1024;
|
|
28089
|
+
var DEFAULT_LOG_MAX_FILES = 3;
|
|
28090
|
+
var SENSITIVE_KEY_PATTERN = /(?:^|_)(?:token|secret|password|apiKey|api_key|authorization|bearer)(?:$|_)/i;
|
|
28091
|
+
var ALLOWED_EXACT_KEYS = /* @__PURE__ */ new Set(["auth", "authType", "authMethod", "authArgs", "canUpdate", "status"]);
|
|
28092
|
+
function redactLogValue(val, key) {
|
|
28093
|
+
if (val === null || val === void 0) return val;
|
|
28094
|
+
if (typeof val === "string") {
|
|
28095
|
+
if (key && SENSITIVE_KEY_PATTERN.test(key) && !ALLOWED_EXACT_KEYS.has(key)) {
|
|
28096
|
+
return "[redacted]";
|
|
28097
|
+
}
|
|
28098
|
+
return redactSecrets(val);
|
|
28099
|
+
}
|
|
28100
|
+
if (typeof val === "number" || typeof val === "boolean") {
|
|
28101
|
+
return val;
|
|
28102
|
+
}
|
|
28103
|
+
if (Array.isArray(val)) {
|
|
28104
|
+
return val.map((item) => redactLogValue(item, key));
|
|
28105
|
+
}
|
|
28106
|
+
if (typeof val === "object") {
|
|
28107
|
+
const out = {};
|
|
28108
|
+
for (const [k, v] of Object.entries(val)) {
|
|
28109
|
+
out[k] = redactLogValue(v, k);
|
|
28110
|
+
}
|
|
28111
|
+
return out;
|
|
28112
|
+
}
|
|
28113
|
+
return String(val);
|
|
28114
|
+
}
|
|
28115
|
+
function rotateLogFiles(filePath, maxFiles) {
|
|
28116
|
+
for (let i = maxFiles; i >= 1; i--) {
|
|
28117
|
+
const current = `${filePath}.${i}`;
|
|
28118
|
+
if (existsSync12(current)) {
|
|
28119
|
+
if (i >= maxFiles) {
|
|
28120
|
+
try {
|
|
28121
|
+
unlinkSync2(current);
|
|
28122
|
+
} catch {
|
|
28123
|
+
}
|
|
28124
|
+
} else {
|
|
28125
|
+
try {
|
|
28126
|
+
renameSync3(current, `${filePath}.${i + 1}`);
|
|
28127
|
+
} catch {
|
|
28128
|
+
}
|
|
28129
|
+
}
|
|
28130
|
+
}
|
|
28131
|
+
}
|
|
28132
|
+
if (existsSync12(filePath)) {
|
|
28133
|
+
try {
|
|
28134
|
+
renameSync3(filePath, `${filePath}.1`);
|
|
28135
|
+
} catch {
|
|
28136
|
+
}
|
|
28137
|
+
}
|
|
28138
|
+
}
|
|
28139
|
+
function createLogger(options) {
|
|
28140
|
+
const component = options.component;
|
|
28141
|
+
const filePath = options.path;
|
|
28142
|
+
const maxBytes = Math.max(100, options.maxBytes ?? DEFAULT_LOG_MAX_BYTES);
|
|
28143
|
+
const maxFiles = Math.max(1, options.maxFiles ?? DEFAULT_LOG_MAX_FILES);
|
|
28144
|
+
const configuredLevel = options.level ?? "info";
|
|
28145
|
+
const isDaemon = Boolean(options.daemon ?? (process.env.KXM_DAEMON === "1" || process.env.KXM_DAEMON === "true"));
|
|
28146
|
+
const shouldStdout = options.stdout ?? !isDaemon;
|
|
28147
|
+
const correlationDefaults = options.correlation ?? {};
|
|
28148
|
+
let currentSize = 0;
|
|
28149
|
+
if (filePath && existsSync12(filePath)) {
|
|
28150
|
+
try {
|
|
28151
|
+
currentSize = statSync3(filePath).size;
|
|
28152
|
+
} catch {
|
|
28153
|
+
currentSize = 0;
|
|
28154
|
+
}
|
|
28155
|
+
}
|
|
28156
|
+
function emit(level, entryOrEvent, extra) {
|
|
28157
|
+
const minPriority = LOG_LEVEL_PRIORITY[configuredLevel] ?? LOG_LEVEL_PRIORITY.info;
|
|
28158
|
+
const currentPriority = LOG_LEVEL_PRIORITY[level] ?? LOG_LEVEL_PRIORITY.info;
|
|
28159
|
+
if (currentPriority < minPriority) return;
|
|
28160
|
+
let base;
|
|
28161
|
+
if (typeof entryOrEvent === "string") {
|
|
28162
|
+
base = { event: entryOrEvent, ...extra };
|
|
28163
|
+
} else {
|
|
28164
|
+
base = { ...entryOrEvent, ...extra };
|
|
28165
|
+
}
|
|
28166
|
+
const timestamp = typeof base.timestamp === "string" ? base.timestamp : (/* @__PURE__ */ new Date()).toISOString();
|
|
28167
|
+
delete base.timestamp;
|
|
28168
|
+
delete base.level;
|
|
28169
|
+
delete base.component;
|
|
28170
|
+
const payload = {
|
|
28171
|
+
timestamp,
|
|
28172
|
+
level,
|
|
28173
|
+
component,
|
|
28174
|
+
...correlationDefaults,
|
|
28175
|
+
...base
|
|
28176
|
+
};
|
|
28177
|
+
const sanitized = redactLogValue(payload);
|
|
28178
|
+
const line = `${JSON.stringify(sanitized)}
|
|
28179
|
+
`;
|
|
28180
|
+
if (filePath) {
|
|
28181
|
+
const lineBytes = Buffer.byteLength(line, "utf8");
|
|
28182
|
+
if (currentSize + lineBytes > maxBytes) {
|
|
28183
|
+
rotateLogFiles(filePath, maxFiles);
|
|
28184
|
+
currentSize = 0;
|
|
28185
|
+
}
|
|
28186
|
+
try {
|
|
28187
|
+
mkdirSync6(dirname8(filePath), { recursive: true });
|
|
28188
|
+
appendFileSync(filePath, line, { encoding: "utf8", mode: 384 });
|
|
28189
|
+
currentSize += lineBytes;
|
|
28190
|
+
} catch {
|
|
28191
|
+
}
|
|
28192
|
+
}
|
|
28193
|
+
if (shouldStdout) {
|
|
28194
|
+
process.stdout.write(line);
|
|
28195
|
+
}
|
|
28196
|
+
}
|
|
28197
|
+
const logFn = ((entryOrEvent, extra) => {
|
|
28198
|
+
let lvl = "info";
|
|
28199
|
+
if (typeof entryOrEvent === "object" && entryOrEvent !== null && typeof entryOrEvent.level === "string") {
|
|
28200
|
+
const candidate = entryOrEvent.level.toLowerCase();
|
|
28201
|
+
if (candidate === "debug" || candidate === "info" || candidate === "warn" || candidate === "error") {
|
|
28202
|
+
lvl = candidate;
|
|
28203
|
+
}
|
|
28204
|
+
}
|
|
28205
|
+
emit(lvl, entryOrEvent, extra);
|
|
28206
|
+
});
|
|
28207
|
+
logFn.info = (entryOrEvent, extra) => emit("info", entryOrEvent, extra);
|
|
28208
|
+
logFn.warn = (entryOrEvent, extra) => emit("warn", entryOrEvent, extra);
|
|
28209
|
+
logFn.error = (entryOrEvent, extra) => emit("error", entryOrEvent, extra);
|
|
28210
|
+
logFn.debug = (entryOrEvent, extra) => emit("debug", entryOrEvent, extra);
|
|
28211
|
+
logFn.child = (sub) => {
|
|
28212
|
+
return createLogger({
|
|
28213
|
+
...options,
|
|
28214
|
+
component: sub.component ? `${component}.${sub.component}` : component,
|
|
28215
|
+
correlation: { ...correlationDefaults, ...sub.correlation }
|
|
28216
|
+
});
|
|
28217
|
+
};
|
|
28218
|
+
logFn.close = () => {
|
|
28219
|
+
};
|
|
28220
|
+
Object.defineProperty(logFn, "options", {
|
|
28221
|
+
value: Object.freeze({ ...options }),
|
|
28222
|
+
writable: false,
|
|
28223
|
+
enumerable: true
|
|
28224
|
+
});
|
|
28225
|
+
return logFn;
|
|
28226
|
+
}
|
|
28227
|
+
|
|
27991
28228
|
// plugins/kxm/src/runtime-supervisor.ts
|
|
27992
28229
|
function kxmSupervisorTokenFile(paths) {
|
|
27993
28230
|
return join15(paths.runtimeDir, "supervisor.token");
|
|
27994
28231
|
}
|
|
27995
28232
|
function publishKxmSupervisorToken(paths, token) {
|
|
27996
28233
|
const file = kxmSupervisorTokenFile(paths);
|
|
27997
|
-
|
|
28234
|
+
mkdirSync7(dirname9(file), { recursive: true, mode: 448 });
|
|
27998
28235
|
const temp = `${file}.${process.pid}.tmp`;
|
|
27999
28236
|
writeFileSync6(temp, `${token}
|
|
28000
28237
|
`, { encoding: "utf8", mode: 384 });
|
|
@@ -28002,7 +28239,7 @@ function publishKxmSupervisorToken(paths, token) {
|
|
|
28002
28239
|
chmodSync2(temp, 384);
|
|
28003
28240
|
} catch {
|
|
28004
28241
|
}
|
|
28005
|
-
|
|
28242
|
+
renameSync4(temp, file);
|
|
28006
28243
|
try {
|
|
28007
28244
|
chmodSync2(file, 384);
|
|
28008
28245
|
} catch {
|
|
@@ -28036,11 +28273,11 @@ function supervisorErrorFile(paths) {
|
|
|
28036
28273
|
}
|
|
28037
28274
|
function clearSupervisorError(paths) {
|
|
28038
28275
|
const file = supervisorErrorFile(paths);
|
|
28039
|
-
if (
|
|
28276
|
+
if (existsSync13(file)) rmSync3(file, { force: true });
|
|
28040
28277
|
}
|
|
28041
28278
|
function recordSupervisorError(paths, message) {
|
|
28042
28279
|
try {
|
|
28043
|
-
|
|
28280
|
+
mkdirSync7(paths.runtimeDir, { recursive: true, mode: 448 });
|
|
28044
28281
|
writeFileSync6(supervisorErrorFile(paths), `${message}
|
|
28045
28282
|
`, { encoding: "utf8", mode: 384 });
|
|
28046
28283
|
} catch {
|
|
@@ -28059,7 +28296,7 @@ function readRecentSupervisorError(paths) {
|
|
|
28059
28296
|
}
|
|
28060
28297
|
}
|
|
28061
28298
|
function kxmSupervisorStatus(paths) {
|
|
28062
|
-
if (!
|
|
28299
|
+
if (!existsSync13(paths.registryDb)) return { running: false };
|
|
28063
28300
|
const registry = new KxmRuntimeRegistry(paths.registryDb);
|
|
28064
28301
|
try {
|
|
28065
28302
|
const record2 = registry.supervisor();
|
|
@@ -28240,6 +28477,8 @@ var DEFAULT_RUNTIME_SYNC_INTERVAL_MS = 1e4;
|
|
|
28240
28477
|
var MIN_RUNTIME_SYNC_INTERVAL_MS = 250;
|
|
28241
28478
|
var MAX_RUNTIME_SYNC_INTERVAL_MS = 6e4;
|
|
28242
28479
|
var OUTBOX_PUSH_BATCH = 32;
|
|
28480
|
+
var OUTBOX_PUSH_BATCH_BYTES = 2e5;
|
|
28481
|
+
var RUNTIME_SYNC_MAX_BACKOFF_MS = 5 * 6e4;
|
|
28243
28482
|
function runtimeSyncIntervalMs(env = process.env) {
|
|
28244
28483
|
const raw = env.KXM_RUNTIME_SYNC_INTERVAL_MS?.trim();
|
|
28245
28484
|
if (!raw) return DEFAULT_RUNTIME_SYNC_INTERVAL_MS;
|
|
@@ -28247,16 +28486,72 @@ function runtimeSyncIntervalMs(env = process.env) {
|
|
|
28247
28486
|
if (!Number.isInteger(parsed)) return DEFAULT_RUNTIME_SYNC_INTERVAL_MS;
|
|
28248
28487
|
return Math.min(MAX_RUNTIME_SYNC_INTERVAL_MS, Math.max(MIN_RUNTIME_SYNC_INTERVAL_MS, parsed));
|
|
28249
28488
|
}
|
|
28489
|
+
function isDurableRefusal(outcome) {
|
|
28490
|
+
return outcome === "conflict" || outcome === "rejected";
|
|
28491
|
+
}
|
|
28492
|
+
function isOversizeRefusal(error) {
|
|
28493
|
+
return error instanceof HubHttpError && (error.statusCode === 413 || error.code === "sync_batch_too_large" || error.code === "payload_too_large");
|
|
28494
|
+
}
|
|
28250
28495
|
async function syncKxmOutbox(eventStore, client, options = {}) {
|
|
28251
28496
|
const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
28252
|
-
const batchSize = options.batchSize ?? OUTBOX_PUSH_BATCH;
|
|
28253
|
-
const result = { pushed: 0, acked: 0,
|
|
28497
|
+
const batchSize = Math.max(1, options.batchSize ?? OUTBOX_PUSH_BATCH);
|
|
28498
|
+
const result = { pushed: 0, acked: 0, refused: 0, refusals: [], unconfirmed: 0, blocked: false };
|
|
28499
|
+
const refuse = (row, code) => {
|
|
28500
|
+
if (eventStore.refuseOutbox([{ seq: row.seq, code }], now()) === 0) return;
|
|
28501
|
+
result.refused += 1;
|
|
28502
|
+
result.refusals.push({ runId: row.runId, sequence: row.sequence, code });
|
|
28503
|
+
};
|
|
28504
|
+
const deliver = async (batch) => {
|
|
28505
|
+
const payload = [];
|
|
28506
|
+
const sendable = [];
|
|
28507
|
+
for (const row of batch) {
|
|
28508
|
+
try {
|
|
28509
|
+
payload.push(JSON.parse(row.syncEvent));
|
|
28510
|
+
sendable.push(row);
|
|
28511
|
+
} catch {
|
|
28512
|
+
refuse(row, "sync_row_unreadable");
|
|
28513
|
+
}
|
|
28514
|
+
}
|
|
28515
|
+
if (sendable.length === 0) return true;
|
|
28516
|
+
eventStore.markOutboxAttempted(sendable.map((row) => row.seq), now());
|
|
28517
|
+
let response;
|
|
28518
|
+
try {
|
|
28519
|
+
response = await client.pushSyncEvents(payload);
|
|
28520
|
+
} catch (error) {
|
|
28521
|
+
if (isOversizeRefusal(error)) {
|
|
28522
|
+
if (sendable.length > 1) {
|
|
28523
|
+
for (const row of sendable) {
|
|
28524
|
+
if (!await deliver([row])) return false;
|
|
28525
|
+
}
|
|
28526
|
+
return true;
|
|
28527
|
+
}
|
|
28528
|
+
refuse(sendable[0], "sync_row_too_large");
|
|
28529
|
+
return true;
|
|
28530
|
+
}
|
|
28531
|
+
result.blockedReason = syncFailureText(error);
|
|
28532
|
+
return false;
|
|
28533
|
+
}
|
|
28534
|
+
result.pushed += sendable.length;
|
|
28535
|
+
const acked = [];
|
|
28536
|
+
sendable.forEach((row, index) => {
|
|
28537
|
+
const entry = response.results[index];
|
|
28538
|
+
const describesRow = entry !== void 0 && (entry.runId === void 0 || entry.runId === row.runId) && (entry.sequence === void 0 || entry.sequence === row.sequence);
|
|
28539
|
+
if (!entry || !describesRow) {
|
|
28540
|
+
result.unconfirmed += 1;
|
|
28541
|
+
return;
|
|
28542
|
+
}
|
|
28543
|
+
if (entry.outcome === "accepted" || entry.outcome === "duplicate") acked.push(row.seq);
|
|
28544
|
+
else if (isDurableRefusal(entry.outcome)) refuse(row, entry.code ?? `sync_${entry.outcome}`);
|
|
28545
|
+
else result.unconfirmed += 1;
|
|
28546
|
+
});
|
|
28547
|
+
result.acked += eventStore.ackOutbox(acked, now());
|
|
28548
|
+
return true;
|
|
28549
|
+
};
|
|
28254
28550
|
let afterSeq = 0;
|
|
28255
28551
|
for (; ; ) {
|
|
28256
28552
|
const rows = eventStore.pendingOutbox(batchSize, afterSeq);
|
|
28257
28553
|
if (rows.length === 0) return result;
|
|
28258
|
-
|
|
28259
|
-
let byteBudget = MAX_BATCH_BYTES;
|
|
28554
|
+
let byteBudget = OUTBOX_PUSH_BATCH_BYTES;
|
|
28260
28555
|
let sendCount = 0;
|
|
28261
28556
|
for (const row of rows) {
|
|
28262
28557
|
const rowBytes = Buffer.byteLength(row.syncEvent, "utf8") + 64;
|
|
@@ -28264,21 +28559,12 @@ async function syncKxmOutbox(eventStore, client, options = {}) {
|
|
|
28264
28559
|
byteBudget -= rowBytes;
|
|
28265
28560
|
sendCount += 1;
|
|
28266
28561
|
}
|
|
28267
|
-
const batch = rows.slice(0, sendCount);
|
|
28268
|
-
if (batch.length === 0) batch.push(rows[0]);
|
|
28562
|
+
const batch = rows.slice(0, Math.max(1, sendCount));
|
|
28269
28563
|
afterSeq = batch[batch.length - 1].seq;
|
|
28270
|
-
|
|
28271
|
-
|
|
28272
|
-
|
|
28273
|
-
const outcomes = new Map(response.results.map((entry) => [`${entry.runId}\0${entry.sequence}`, entry.outcome]));
|
|
28274
|
-
const acked = [];
|
|
28275
|
-
for (const row of batch) {
|
|
28276
|
-
const outcome = outcomes.get(`${row.runId}\0${row.sequence}`);
|
|
28277
|
-
if (outcome === "accepted" || outcome === "duplicate") acked.push(row.seq);
|
|
28278
|
-
else if (outcome === "conflict") result.conflicts += 1;
|
|
28279
|
-
else result.rejected += 1;
|
|
28564
|
+
if (!await deliver(batch)) {
|
|
28565
|
+
result.blocked = true;
|
|
28566
|
+
return result;
|
|
28280
28567
|
}
|
|
28281
|
-
result.acked += eventStore.ackOutbox(acked, now());
|
|
28282
28568
|
}
|
|
28283
28569
|
}
|
|
28284
28570
|
function runtimeHubClientFor(context, env) {
|
|
@@ -28293,11 +28579,24 @@ function runtimeHubClientFor(context, env) {
|
|
|
28293
28579
|
...authToken ? { authToken } : {}
|
|
28294
28580
|
});
|
|
28295
28581
|
}
|
|
28582
|
+
function syncBackoffMs(consecutiveStalls, intervalMs) {
|
|
28583
|
+
if (consecutiveStalls <= 0) return 0;
|
|
28584
|
+
return Math.min(RUNTIME_SYNC_MAX_BACKOFF_MS, intervalMs * 2 ** Math.min(5, consecutiveStalls - 1));
|
|
28585
|
+
}
|
|
28586
|
+
function syncFailureText(error) {
|
|
28587
|
+
const code = error instanceof HubHttpError ? `${error.statusCode} ${error.code ?? "error"}` : void 0;
|
|
28588
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
28589
|
+
return `${code ? `${code}: ` : ""}${message}`.replace(/(kxm_[A-Za-z0-9_]+|[A-Za-z0-9._-]{40,})/g, "[redacted]").slice(0, 300);
|
|
28590
|
+
}
|
|
28591
|
+
function runtimeSupervisorLogFile(paths) {
|
|
28592
|
+
return join15(paths.runtimeDir, "logs", "kxm-runtime.jsonl");
|
|
28593
|
+
}
|
|
28296
28594
|
async function startKxmRuntimeSupervisor(options = {}) {
|
|
28297
28595
|
const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
28596
|
+
const env = options.env ?? process.env;
|
|
28298
28597
|
const paths = kxmRuntimePaths(options.stateRoot !== void 0 ? { stateRoot: options.stateRoot } : {});
|
|
28299
28598
|
try {
|
|
28300
|
-
return await startKxmRuntimeSupervisorInner(paths, options.port, now);
|
|
28599
|
+
return await startKxmRuntimeSupervisorInner(paths, options.port, now, env);
|
|
28301
28600
|
} catch (error) {
|
|
28302
28601
|
if (!(error instanceof KxmConfigError && error.issues.some((issue3) => issue3.code === "runtime_supervisor_conflict"))) {
|
|
28303
28602
|
recordSupervisorError(paths, error.message);
|
|
@@ -28305,9 +28604,15 @@ async function startKxmRuntimeSupervisor(options = {}) {
|
|
|
28305
28604
|
throw error;
|
|
28306
28605
|
}
|
|
28307
28606
|
}
|
|
28308
|
-
async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now) {
|
|
28309
|
-
|
|
28310
|
-
|
|
28607
|
+
async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now, env) {
|
|
28608
|
+
mkdirSync7(paths.runtimeDir, { recursive: true, mode: 448 });
|
|
28609
|
+
mkdirSync7(paths.projectsDir, { recursive: true, mode: 448 });
|
|
28610
|
+
const logger = createLogger({
|
|
28611
|
+
component: "runtime",
|
|
28612
|
+
path: runtimeSupervisorLogFile(paths),
|
|
28613
|
+
stdout: false,
|
|
28614
|
+
maxBytes: 2 * 1024 * 1024
|
|
28615
|
+
});
|
|
28311
28616
|
const token = randomBytes3(32).toString("hex");
|
|
28312
28617
|
const tokenHash = hashKxmSupervisorToken(token);
|
|
28313
28618
|
const registry = new KxmRuntimeRegistry(paths.registryDb);
|
|
@@ -28315,12 +28620,13 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now) {
|
|
|
28315
28620
|
const requestedPort = requestedPortOption ?? 0;
|
|
28316
28621
|
let activeRuntimeId = runtimeId;
|
|
28317
28622
|
const contexts = /* @__PURE__ */ new Map();
|
|
28623
|
+
const syncStatuses = /* @__PURE__ */ new Map();
|
|
28318
28624
|
const registerSyncCredentials = (context) => {
|
|
28319
|
-
const hubToken = resolveClientHubAuthToken(
|
|
28625
|
+
const hubToken = resolveClientHubAuthToken(env, context.projectId);
|
|
28320
28626
|
if (hubToken) context.eventStore.syncRedactor.register(hubToken);
|
|
28321
|
-
for (const key of Object.keys(
|
|
28627
|
+
for (const key of Object.keys(env)) {
|
|
28322
28628
|
if (key.startsWith("KXM_") && (key.endsWith("_TOKEN") || key.endsWith("_KEY")) || key.endsWith("_API_KEY") || key.endsWith("_SECRET")) {
|
|
28323
|
-
const value =
|
|
28629
|
+
const value = env[key]?.trim();
|
|
28324
28630
|
if (value) context.eventStore.syncRedactor.register(value);
|
|
28325
28631
|
}
|
|
28326
28632
|
}
|
|
@@ -28370,6 +28676,28 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now) {
|
|
|
28370
28676
|
});
|
|
28371
28677
|
return;
|
|
28372
28678
|
}
|
|
28679
|
+
if (request.method === "GET" && url.pathname === "/v1/sync/status") {
|
|
28680
|
+
const projectRoot = url.searchParams.get("projectRoot");
|
|
28681
|
+
const statuses = [...syncStatuses.values()].filter((status) => projectRoot === null || status.projectRoot === projectRoot).sort((left, right) => left.projectId.localeCompare(right.projectId));
|
|
28682
|
+
sendJson(response, 200, { ok: true, runtimeId: activeRuntimeId, projects: statuses });
|
|
28683
|
+
return;
|
|
28684
|
+
}
|
|
28685
|
+
if (request.method === "POST" && url.pathname === "/v1/sync/retry") {
|
|
28686
|
+
const body = await readJsonBody(request);
|
|
28687
|
+
const projectRoot = typeof body.projectRoot === "string" ? body.projectRoot : "";
|
|
28688
|
+
const context = contextFor(projectRoot);
|
|
28689
|
+
const key = projectRuntimeKey(context.projectRoot);
|
|
28690
|
+
const retried = context.eventStore.retryRefusedOutbox();
|
|
28691
|
+
const status = syncStatuses.get(key);
|
|
28692
|
+
if (status) {
|
|
28693
|
+
const cleared = { ...status, outbox: context.eventStore.outboxStatus(), consecutiveFailures: 0 };
|
|
28694
|
+
delete cleared.nextAttemptAt;
|
|
28695
|
+
syncStatuses.set(key, cleared);
|
|
28696
|
+
}
|
|
28697
|
+
logger({ event: "runtime_sync_retry", projectId: context.projectId, retried });
|
|
28698
|
+
sendJson(response, 200, { ok: true, projectId: context.projectId, retried, outbox: context.eventStore.outboxStatus() });
|
|
28699
|
+
return;
|
|
28700
|
+
}
|
|
28373
28701
|
if (request.method === "POST" && url.pathname === "/v1/runs") {
|
|
28374
28702
|
const body = await readJsonBody(request);
|
|
28375
28703
|
const projectRoot = typeof body.projectRoot === "string" ? body.projectRoot : "";
|
|
@@ -28669,26 +28997,93 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now) {
|
|
|
28669
28997
|
try {
|
|
28670
28998
|
contextFor(reg.projectRoot);
|
|
28671
28999
|
} catch {
|
|
29000
|
+
logger.warn({
|
|
29001
|
+
event: "runtime_sync_context_unavailable",
|
|
29002
|
+
projectId: reg.projectId,
|
|
29003
|
+
message: `cannot reopen ${reg.projectRoot}: its outbox rows stay pending`
|
|
29004
|
+
});
|
|
28672
29005
|
}
|
|
28673
29006
|
}
|
|
29007
|
+
const recordSyncStatus = (context, next) => {
|
|
29008
|
+
const key = projectRuntimeKey(context.projectRoot);
|
|
29009
|
+
const previous = syncStatuses.get(key);
|
|
29010
|
+
syncStatuses.set(key, next);
|
|
29011
|
+
const changed = previous === void 0 || previous.state !== next.state || next.lastError !== void 0 && previous?.lastError !== next.lastError || JSON.stringify(next.outbox.refusals) !== JSON.stringify(previous?.outbox.refusals ?? []);
|
|
29012
|
+
if (!changed) return;
|
|
29013
|
+
const stalledState = next.state === "blocked" || next.state === "refusing";
|
|
29014
|
+
const entry = {
|
|
29015
|
+
event: stalledState ? "runtime_sync_stalled" : "runtime_sync_state",
|
|
29016
|
+
projectId: next.projectId,
|
|
29017
|
+
runtimeId: next.homeRuntimeId,
|
|
29018
|
+
state: next.state,
|
|
29019
|
+
pending: next.outbox.pending,
|
|
29020
|
+
acked: next.outbox.acked,
|
|
29021
|
+
refused: next.outbox.refused,
|
|
29022
|
+
...next.outbox.refusals.length > 0 ? { refusals: next.outbox.refusals } : {},
|
|
29023
|
+
...next.hubUrl ? { hubUrl: next.hubUrl } : {},
|
|
29024
|
+
...next.lastError ? { reason: next.lastError } : {},
|
|
29025
|
+
...next.nextAttemptAt ? { nextAttemptAt: next.nextAttemptAt } : {}
|
|
29026
|
+
};
|
|
29027
|
+
if (stalledState) logger.warn(entry);
|
|
29028
|
+
else logger.info(entry);
|
|
29029
|
+
};
|
|
28674
29030
|
let syncing = false;
|
|
28675
29031
|
const syncTimer = setInterval(() => {
|
|
28676
29032
|
if (syncing || stopping) return;
|
|
28677
29033
|
syncing = true;
|
|
28678
29034
|
void (async () => {
|
|
28679
29035
|
for (const context of [...contexts.values()]) {
|
|
29036
|
+
const key = projectRuntimeKey(context.projectRoot);
|
|
29037
|
+
const prior = syncStatuses.get(key);
|
|
29038
|
+
if (prior?.nextAttemptAt && Date.parse(prior.nextAttemptAt) > Date.parse(now())) continue;
|
|
29039
|
+
const base = {
|
|
29040
|
+
projectId: context.projectId,
|
|
29041
|
+
projectRoot: context.projectRoot,
|
|
29042
|
+
homeRuntimeId: context.homeRuntimeId,
|
|
29043
|
+
outbox: context.eventStore.outboxStatus(),
|
|
29044
|
+
consecutiveFailures: 0
|
|
29045
|
+
};
|
|
28680
29046
|
try {
|
|
28681
|
-
const client = runtimeHubClientFor(context,
|
|
28682
|
-
if (!client)
|
|
29047
|
+
const client = runtimeHubClientFor(context, env);
|
|
29048
|
+
if (!client) {
|
|
29049
|
+
recordSyncStatus(context, { ...base, state: "no_hub" });
|
|
29050
|
+
continue;
|
|
29051
|
+
}
|
|
28683
29052
|
await client.heartbeat();
|
|
28684
|
-
await syncKxmOutbox(context.eventStore, client, { now });
|
|
28685
|
-
|
|
29053
|
+
const pass = await syncKxmOutbox(context.eventStore, client, { now });
|
|
29054
|
+
const outbox = context.eventStore.outboxStatus();
|
|
29055
|
+
const failures = pass.blocked || pass.unconfirmed > 0 ? (prior?.consecutiveFailures ?? 0) + 1 : 0;
|
|
29056
|
+
const delayMs = syncBackoffMs(failures, runtimeSyncIntervalMs(env));
|
|
29057
|
+
recordSyncStatus(context, {
|
|
29058
|
+
...base,
|
|
29059
|
+
hubUrl: client.options.serverUrl,
|
|
29060
|
+
outbox,
|
|
29061
|
+
state: pass.blocked ? "blocked" : outbox.refused > 0 || pass.unconfirmed > 0 ? "refusing" : "ok",
|
|
29062
|
+
lastCompletedAt: now(),
|
|
29063
|
+
lastPushed: pass.pushed,
|
|
29064
|
+
lastAcked: pass.acked,
|
|
29065
|
+
lastRefused: pass.refused,
|
|
29066
|
+
lastUnconfirmed: pass.unconfirmed,
|
|
29067
|
+
consecutiveFailures: failures,
|
|
29068
|
+
...pass.blockedReason !== void 0 ? { lastError: pass.blockedReason } : {},
|
|
29069
|
+
...delayMs > 0 ? { nextAttemptAt: new Date(Date.parse(now()) + delayMs).toISOString() } : {}
|
|
29070
|
+
});
|
|
29071
|
+
} catch (error) {
|
|
29072
|
+
const failures = (prior?.consecutiveFailures ?? 0) + 1;
|
|
29073
|
+
const delayMs = syncBackoffMs(failures, runtimeSyncIntervalMs(env));
|
|
29074
|
+
recordSyncStatus(context, {
|
|
29075
|
+
...base,
|
|
29076
|
+
state: "blocked",
|
|
29077
|
+
consecutiveFailures: failures,
|
|
29078
|
+
lastError: syncFailureText(error),
|
|
29079
|
+
nextAttemptAt: new Date(Date.parse(now()) + delayMs).toISOString()
|
|
29080
|
+
});
|
|
28686
29081
|
}
|
|
28687
29082
|
}
|
|
28688
29083
|
})().finally(() => {
|
|
28689
29084
|
syncing = false;
|
|
28690
29085
|
});
|
|
28691
|
-
}, runtimeSyncIntervalMs());
|
|
29086
|
+
}, runtimeSyncIntervalMs(env));
|
|
28692
29087
|
syncTimer.unref();
|
|
28693
29088
|
let stopping = false;
|
|
28694
29089
|
const stop = async () => {
|
|
@@ -28723,6 +29118,7 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now) {
|
|
|
28723
29118
|
}
|
|
28724
29119
|
for (const context of contexts.values()) closeKxmRuntimeContext(context);
|
|
28725
29120
|
contexts.clear();
|
|
29121
|
+
logger.close();
|
|
28726
29122
|
const closed = new Promise((resolveStop) => server.close(() => resolveStop()));
|
|
28727
29123
|
server.closeIdleConnections?.();
|
|
28728
29124
|
await closed;
|
|
@@ -29261,155 +29657,6 @@ function createKxmPiProducer(options = {}) {
|
|
|
29261
29657
|
return producer;
|
|
29262
29658
|
}
|
|
29263
29659
|
|
|
29264
|
-
// plugins/kxm/src/logger.ts
|
|
29265
|
-
import { appendFileSync, existsSync as existsSync13, mkdirSync as mkdirSync7, renameSync as renameSync4, statSync as statSync3, unlinkSync as unlinkSync2 } from "node:fs";
|
|
29266
|
-
import { dirname as dirname9 } from "node:path";
|
|
29267
|
-
var LOG_LEVEL_PRIORITY = {
|
|
29268
|
-
debug: 10,
|
|
29269
|
-
info: 20,
|
|
29270
|
-
warn: 30,
|
|
29271
|
-
error: 40
|
|
29272
|
-
};
|
|
29273
|
-
var DEFAULT_LOG_MAX_BYTES = 10 * 1024 * 1024;
|
|
29274
|
-
var DEFAULT_LOG_MAX_FILES = 3;
|
|
29275
|
-
var SENSITIVE_KEY_PATTERN = /(?:^|_)(?:token|secret|password|apiKey|api_key|authorization|bearer)(?:$|_)/i;
|
|
29276
|
-
var ALLOWED_EXACT_KEYS = /* @__PURE__ */ new Set(["auth", "authType", "authMethod", "authArgs", "canUpdate", "status"]);
|
|
29277
|
-
function redactLogValue(val, key) {
|
|
29278
|
-
if (val === null || val === void 0) return val;
|
|
29279
|
-
if (typeof val === "string") {
|
|
29280
|
-
if (key && SENSITIVE_KEY_PATTERN.test(key) && !ALLOWED_EXACT_KEYS.has(key)) {
|
|
29281
|
-
return "[redacted]";
|
|
29282
|
-
}
|
|
29283
|
-
return redactSecrets(val);
|
|
29284
|
-
}
|
|
29285
|
-
if (typeof val === "number" || typeof val === "boolean") {
|
|
29286
|
-
return val;
|
|
29287
|
-
}
|
|
29288
|
-
if (Array.isArray(val)) {
|
|
29289
|
-
return val.map((item) => redactLogValue(item, key));
|
|
29290
|
-
}
|
|
29291
|
-
if (typeof val === "object") {
|
|
29292
|
-
const out = {};
|
|
29293
|
-
for (const [k, v] of Object.entries(val)) {
|
|
29294
|
-
out[k] = redactLogValue(v, k);
|
|
29295
|
-
}
|
|
29296
|
-
return out;
|
|
29297
|
-
}
|
|
29298
|
-
return String(val);
|
|
29299
|
-
}
|
|
29300
|
-
function rotateLogFiles(filePath, maxFiles) {
|
|
29301
|
-
for (let i = maxFiles; i >= 1; i--) {
|
|
29302
|
-
const current = `${filePath}.${i}`;
|
|
29303
|
-
if (existsSync13(current)) {
|
|
29304
|
-
if (i >= maxFiles) {
|
|
29305
|
-
try {
|
|
29306
|
-
unlinkSync2(current);
|
|
29307
|
-
} catch {
|
|
29308
|
-
}
|
|
29309
|
-
} else {
|
|
29310
|
-
try {
|
|
29311
|
-
renameSync4(current, `${filePath}.${i + 1}`);
|
|
29312
|
-
} catch {
|
|
29313
|
-
}
|
|
29314
|
-
}
|
|
29315
|
-
}
|
|
29316
|
-
}
|
|
29317
|
-
if (existsSync13(filePath)) {
|
|
29318
|
-
try {
|
|
29319
|
-
renameSync4(filePath, `${filePath}.1`);
|
|
29320
|
-
} catch {
|
|
29321
|
-
}
|
|
29322
|
-
}
|
|
29323
|
-
}
|
|
29324
|
-
function createLogger(options) {
|
|
29325
|
-
const component = options.component;
|
|
29326
|
-
const filePath = options.path;
|
|
29327
|
-
const maxBytes = Math.max(100, options.maxBytes ?? DEFAULT_LOG_MAX_BYTES);
|
|
29328
|
-
const maxFiles = Math.max(1, options.maxFiles ?? DEFAULT_LOG_MAX_FILES);
|
|
29329
|
-
const configuredLevel = options.level ?? "info";
|
|
29330
|
-
const isDaemon = Boolean(options.daemon ?? (process.env.KXM_DAEMON === "1" || process.env.KXM_DAEMON === "true"));
|
|
29331
|
-
const shouldStdout = options.stdout ?? !isDaemon;
|
|
29332
|
-
const correlationDefaults = options.correlation ?? {};
|
|
29333
|
-
let currentSize = 0;
|
|
29334
|
-
if (filePath && existsSync13(filePath)) {
|
|
29335
|
-
try {
|
|
29336
|
-
currentSize = statSync3(filePath).size;
|
|
29337
|
-
} catch {
|
|
29338
|
-
currentSize = 0;
|
|
29339
|
-
}
|
|
29340
|
-
}
|
|
29341
|
-
function emit(level, entryOrEvent, extra) {
|
|
29342
|
-
const minPriority = LOG_LEVEL_PRIORITY[configuredLevel] ?? LOG_LEVEL_PRIORITY.info;
|
|
29343
|
-
const currentPriority = LOG_LEVEL_PRIORITY[level] ?? LOG_LEVEL_PRIORITY.info;
|
|
29344
|
-
if (currentPriority < minPriority) return;
|
|
29345
|
-
let base;
|
|
29346
|
-
if (typeof entryOrEvent === "string") {
|
|
29347
|
-
base = { event: entryOrEvent, ...extra };
|
|
29348
|
-
} else {
|
|
29349
|
-
base = { ...entryOrEvent, ...extra };
|
|
29350
|
-
}
|
|
29351
|
-
const timestamp = typeof base.timestamp === "string" ? base.timestamp : (/* @__PURE__ */ new Date()).toISOString();
|
|
29352
|
-
delete base.timestamp;
|
|
29353
|
-
delete base.level;
|
|
29354
|
-
delete base.component;
|
|
29355
|
-
const payload = {
|
|
29356
|
-
timestamp,
|
|
29357
|
-
level,
|
|
29358
|
-
component,
|
|
29359
|
-
...correlationDefaults,
|
|
29360
|
-
...base
|
|
29361
|
-
};
|
|
29362
|
-
const sanitized = redactLogValue(payload);
|
|
29363
|
-
const line = `${JSON.stringify(sanitized)}
|
|
29364
|
-
`;
|
|
29365
|
-
if (filePath) {
|
|
29366
|
-
const lineBytes = Buffer.byteLength(line, "utf8");
|
|
29367
|
-
if (currentSize + lineBytes > maxBytes) {
|
|
29368
|
-
rotateLogFiles(filePath, maxFiles);
|
|
29369
|
-
currentSize = 0;
|
|
29370
|
-
}
|
|
29371
|
-
try {
|
|
29372
|
-
mkdirSync7(dirname9(filePath), { recursive: true });
|
|
29373
|
-
appendFileSync(filePath, line, { encoding: "utf8", mode: 384 });
|
|
29374
|
-
currentSize += lineBytes;
|
|
29375
|
-
} catch {
|
|
29376
|
-
}
|
|
29377
|
-
}
|
|
29378
|
-
if (shouldStdout) {
|
|
29379
|
-
process.stdout.write(line);
|
|
29380
|
-
}
|
|
29381
|
-
}
|
|
29382
|
-
const logFn = ((entryOrEvent, extra) => {
|
|
29383
|
-
let lvl = "info";
|
|
29384
|
-
if (typeof entryOrEvent === "object" && entryOrEvent !== null && typeof entryOrEvent.level === "string") {
|
|
29385
|
-
const candidate = entryOrEvent.level.toLowerCase();
|
|
29386
|
-
if (candidate === "debug" || candidate === "info" || candidate === "warn" || candidate === "error") {
|
|
29387
|
-
lvl = candidate;
|
|
29388
|
-
}
|
|
29389
|
-
}
|
|
29390
|
-
emit(lvl, entryOrEvent, extra);
|
|
29391
|
-
});
|
|
29392
|
-
logFn.info = (entryOrEvent, extra) => emit("info", entryOrEvent, extra);
|
|
29393
|
-
logFn.warn = (entryOrEvent, extra) => emit("warn", entryOrEvent, extra);
|
|
29394
|
-
logFn.error = (entryOrEvent, extra) => emit("error", entryOrEvent, extra);
|
|
29395
|
-
logFn.debug = (entryOrEvent, extra) => emit("debug", entryOrEvent, extra);
|
|
29396
|
-
logFn.child = (sub) => {
|
|
29397
|
-
return createLogger({
|
|
29398
|
-
...options,
|
|
29399
|
-
component: sub.component ? `${component}.${sub.component}` : component,
|
|
29400
|
-
correlation: { ...correlationDefaults, ...sub.correlation }
|
|
29401
|
-
});
|
|
29402
|
-
};
|
|
29403
|
-
logFn.close = () => {
|
|
29404
|
-
};
|
|
29405
|
-
Object.defineProperty(logFn, "options", {
|
|
29406
|
-
value: Object.freeze({ ...options }),
|
|
29407
|
-
writable: false,
|
|
29408
|
-
enumerable: true
|
|
29409
|
-
});
|
|
29410
|
-
return logFn;
|
|
29411
|
-
}
|
|
29412
|
-
|
|
29413
29660
|
// plugins/kxm/src/improve.ts
|
|
29414
29661
|
import { createHash as createHash14 } from "node:crypto";
|
|
29415
29662
|
import { existsSync as existsSync14, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7 } from "node:fs";
|