@kontextmind/kxm 0.7.78 → 0.7.80
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 +77 -12
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime-supervisor.js +443 -47
- package/plugins/kxm/dist/runtime.js +460 -206
- 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 +32 -17
- 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
|
@@ -18326,20 +18326,30 @@ function restoreDatabaseFile(backupPath, targetPath, storeId, expectedSchemaVers
|
|
|
18326
18326
|
integrity: "ok"
|
|
18327
18327
|
};
|
|
18328
18328
|
}
|
|
18329
|
+
var KXM_BACKUP_CEILINGS = {
|
|
18330
|
+
"hub-store": 5,
|
|
18331
|
+
registry: 1,
|
|
18332
|
+
"binding-store": 1,
|
|
18333
|
+
events: 7
|
|
18334
|
+
};
|
|
18335
|
+
function kxmBackupCeiling(storeId) {
|
|
18336
|
+
if (storeId.startsWith("events:")) return KXM_BACKUP_CEILINGS.events;
|
|
18337
|
+
return KXM_BACKUP_CEILINGS[storeId] ?? KXM_BACKUP_CEILINGS["hub-store"];
|
|
18338
|
+
}
|
|
18329
18339
|
function discoverProjectStores(projectRoot, options = {}) {
|
|
18330
18340
|
const root = resolve3(projectRoot);
|
|
18331
18341
|
const stores = [];
|
|
18332
18342
|
const hubPath = options.hubDataPath ? resolve3(options.hubDataPath) : join4(root, ".kxm", "state", "kxm.db");
|
|
18333
18343
|
if (existsSync4(hubPath)) {
|
|
18334
|
-
stores.push({ storeId: "hub-store", sourcePath: hubPath, maxSupportedVersion:
|
|
18344
|
+
stores.push({ storeId: "hub-store", sourcePath: hubPath, maxSupportedVersion: kxmBackupCeiling("hub-store") });
|
|
18335
18345
|
}
|
|
18336
18346
|
const registryPath = join4(root, ".kxm", "runtime", "registry.db");
|
|
18337
18347
|
if (existsSync4(registryPath)) {
|
|
18338
|
-
stores.push({ storeId: "registry", sourcePath: registryPath, maxSupportedVersion:
|
|
18348
|
+
stores.push({ storeId: "registry", sourcePath: registryPath, maxSupportedVersion: kxmBackupCeiling("registry") });
|
|
18339
18349
|
}
|
|
18340
18350
|
const bindingsPath = join4(root, ".kxm", "runtime", "bindings.db");
|
|
18341
18351
|
if (existsSync4(bindingsPath)) {
|
|
18342
|
-
stores.push({ storeId: "binding-store", sourcePath: bindingsPath, maxSupportedVersion:
|
|
18352
|
+
stores.push({ storeId: "binding-store", sourcePath: bindingsPath, maxSupportedVersion: kxmBackupCeiling("binding-store") });
|
|
18343
18353
|
}
|
|
18344
18354
|
const eventsDir = join4(root, ".kxm", "runtime", "events");
|
|
18345
18355
|
if (existsSync4(eventsDir)) {
|
|
@@ -18350,7 +18360,7 @@ function discoverProjectStores(projectRoot, options = {}) {
|
|
|
18350
18360
|
stores.push({
|
|
18351
18361
|
storeId: `events:${key}`,
|
|
18352
18362
|
sourcePath: join4(eventsDir, entry.name),
|
|
18353
|
-
maxSupportedVersion:
|
|
18363
|
+
maxSupportedVersion: kxmBackupCeiling(`events:${key}`)
|
|
18354
18364
|
});
|
|
18355
18365
|
}
|
|
18356
18366
|
}
|
|
@@ -18437,12 +18447,7 @@ function restoreBackup(manifestPathOrDir, options = {}) {
|
|
|
18437
18447
|
`backup file ${store.backupFile} sha256 ${actualSha256} does not match manifest hash ${store.sha256}`
|
|
18438
18448
|
);
|
|
18439
18449
|
}
|
|
18440
|
-
|
|
18441
|
-
if (store.storeId === "registry" || store.storeId === "binding-store") {
|
|
18442
|
-
maxSupported = 1;
|
|
18443
|
-
} else if (store.storeId.startsWith("events:")) {
|
|
18444
|
-
maxSupported = 6;
|
|
18445
|
-
}
|
|
18450
|
+
const maxSupported = kxmBackupCeiling(store.storeId);
|
|
18446
18451
|
let targetPath = store.sourcePath;
|
|
18447
18452
|
if (options.projectRoot && manifest.projectRoot && targetPath.startsWith(manifest.projectRoot)) {
|
|
18448
18453
|
const rel = targetPath.slice(manifest.projectRoot.length).replace(/^[\\/]+/, "");
|
|
@@ -18720,7 +18725,7 @@ function verifyKxmDriveReceipt(receipt, events, foldedStatus, binding) {
|
|
|
18720
18725
|
if (reasons.length === 0) return { verified: true };
|
|
18721
18726
|
return { verified: false, divergence: reasons.join("; ") };
|
|
18722
18727
|
}
|
|
18723
|
-
var KXM_EVENT_STORE_SCHEMA_VERSION =
|
|
18728
|
+
var KXM_EVENT_STORE_SCHEMA_VERSION = 7;
|
|
18724
18729
|
var KXM_DRIVE_RECEIPT_SCHEMA = "kxm.drive-receipt.v1";
|
|
18725
18730
|
var DRIVE_RECEIPT_MAX_BYTES = 8 * 1024;
|
|
18726
18731
|
var EVENT_STORE_TABLES = {
|
|
@@ -18826,7 +18831,17 @@ var EVENT_STORE_TABLES = {
|
|
|
18826
18831
|
"record"
|
|
18827
18832
|
],
|
|
18828
18833
|
project_controls: ["project_id", "paused", "reason", "updated_at", "actor", "schema", "record"],
|
|
18829
|
-
outbox: [
|
|
18834
|
+
outbox: [
|
|
18835
|
+
"seq",
|
|
18836
|
+
"run_id",
|
|
18837
|
+
"sequence",
|
|
18838
|
+
"sync_event",
|
|
18839
|
+
"attempted_at",
|
|
18840
|
+
"attempt_count",
|
|
18841
|
+
"acked_at",
|
|
18842
|
+
"refused_code",
|
|
18843
|
+
"refused_at"
|
|
18844
|
+
]
|
|
18830
18845
|
};
|
|
18831
18846
|
var KXM_EVENT_STORE_TABLE_NAMES = Object.keys(EVENT_STORE_TABLES).sort();
|
|
18832
18847
|
var EVENT_STORE_SCHEMA = `
|
|
@@ -19020,10 +19035,14 @@ CREATE TABLE IF NOT EXISTS outbox (
|
|
|
19020
19035
|
sequence INTEGER NOT NULL,
|
|
19021
19036
|
sync_event TEXT NOT NULL,
|
|
19022
19037
|
attempted_at TEXT,
|
|
19038
|
+
attempt_count INTEGER NOT NULL DEFAULT 0,
|
|
19023
19039
|
acked_at TEXT,
|
|
19040
|
+
refused_code TEXT,
|
|
19041
|
+
refused_at TEXT,
|
|
19024
19042
|
UNIQUE (run_id, sequence)
|
|
19025
19043
|
) STRICT;
|
|
19026
|
-
CREATE INDEX outbox_pending ON outbox(seq) WHERE acked_at IS NULL;
|
|
19044
|
+
CREATE INDEX outbox_pending ON outbox(seq) WHERE acked_at IS NULL AND refused_code IS NULL;
|
|
19045
|
+
CREATE INDEX outbox_refused ON outbox(seq) WHERE refused_code IS NOT NULL;
|
|
19027
19046
|
`;
|
|
19028
19047
|
var KxmRunEventStore = class {
|
|
19029
19048
|
path;
|
|
@@ -19163,37 +19182,108 @@ var KxmRunEventStore = class {
|
|
|
19163
19182
|
const syncEvent = deriveKxmSyncEvent(event, { redactor: this.syncRedactor });
|
|
19164
19183
|
this.database.prepare("INSERT INTO outbox (run_id, sequence, sync_event) VALUES (?, ?, ?)").run(event.runId, event.sequence, kxmSyncEventBytes(syncEvent));
|
|
19165
19184
|
}
|
|
19166
|
-
/**
|
|
19185
|
+
/**
|
|
19186
|
+
* Retryable outbox rows after `afterSeq`, in outbox order, oldest first.
|
|
19187
|
+
* Acked and durably refused rows are never returned: a refused row is not
|
|
19188
|
+
* waiting for a retry, it is waiting for an operator.
|
|
19189
|
+
*/
|
|
19167
19190
|
pendingOutbox(limit = 100, afterSeq = 0) {
|
|
19168
19191
|
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 ?
|
|
19192
|
+
SELECT seq, run_id, sequence, sync_event, attempt_count, attempted_at, acked_at, refused_code, refused_at
|
|
19193
|
+
FROM outbox WHERE acked_at IS NULL AND refused_code IS NULL AND seq > ? ORDER BY seq ASC LIMIT ?
|
|
19171
19194
|
`).all(afterSeq, limit);
|
|
19172
19195
|
return rows.map(outboxFromRow);
|
|
19173
19196
|
}
|
|
19174
|
-
/** Every outbox row of one run,
|
|
19197
|
+
/** Every outbox row of one run, in any state, in sequence order. */
|
|
19175
19198
|
outboxForRun(runId) {
|
|
19176
19199
|
const rows = this.database.prepare(`
|
|
19177
|
-
SELECT seq, run_id, sequence, sync_event, attempted_at, acked_at
|
|
19200
|
+
SELECT seq, run_id, sequence, sync_event, attempt_count, attempted_at, acked_at, refused_code, refused_at
|
|
19178
19201
|
FROM outbox WHERE run_id = ? ORDER BY sequence ASC
|
|
19179
19202
|
`).all(runId);
|
|
19180
19203
|
return rows.map(outboxFromRow);
|
|
19181
19204
|
}
|
|
19205
|
+
/**
|
|
19206
|
+
* Count one attempt against each row. Refused rows are never re-counted: they
|
|
19207
|
+
* are out of the queue, and a tick that reaches them again would mean the
|
|
19208
|
+
* refusal was cleared (which resets the count).
|
|
19209
|
+
*/
|
|
19182
19210
|
markOutboxAttempted(seqs, at) {
|
|
19183
|
-
const statement = this.database.prepare(
|
|
19211
|
+
const statement = this.database.prepare(`
|
|
19212
|
+
UPDATE outbox SET attempted_at = ?, attempt_count = attempt_count + 1
|
|
19213
|
+
WHERE seq = ? AND acked_at IS NULL AND refused_code IS NULL
|
|
19214
|
+
`);
|
|
19184
19215
|
this.transaction(() => {
|
|
19185
19216
|
for (const seq of seqs) statement.run(at, seq);
|
|
19186
19217
|
});
|
|
19187
19218
|
}
|
|
19188
19219
|
/** Advance the cursor: the hub holds these rows now. Returns rows newly acked. */
|
|
19189
19220
|
ackOutbox(seqs, at) {
|
|
19190
|
-
const statement = this.database.prepare("UPDATE outbox SET acked_at =
|
|
19221
|
+
const statement = this.database.prepare("UPDATE outbox SET acked_at = ?, refused_code = NULL, refused_at = NULL WHERE seq = ? AND acked_at IS NULL");
|
|
19191
19222
|
return this.transaction(() => {
|
|
19192
19223
|
let changed = 0;
|
|
19193
19224
|
for (const seq of seqs) changed += Number(statement.run(at, seq).changes);
|
|
19194
19225
|
return changed;
|
|
19195
19226
|
});
|
|
19196
19227
|
}
|
|
19228
|
+
/**
|
|
19229
|
+
* Record a durable hub refusal: the answer will not change if the same bytes
|
|
19230
|
+
* are sent again. Returns rows newly moved out of the pending queue.
|
|
19231
|
+
*/
|
|
19232
|
+
refuseOutbox(refusals, at) {
|
|
19233
|
+
const statement = this.database.prepare(`
|
|
19234
|
+
UPDATE outbox SET refused_code = ?, refused_at = ?
|
|
19235
|
+
WHERE seq = ? AND acked_at IS NULL AND refused_code IS NULL
|
|
19236
|
+
`);
|
|
19237
|
+
return this.transaction(() => {
|
|
19238
|
+
let changed = 0;
|
|
19239
|
+
for (const refusal of refusals) changed += Number(statement.run(refusal.code, at, refusal.seq).changes);
|
|
19240
|
+
return changed;
|
|
19241
|
+
});
|
|
19242
|
+
}
|
|
19243
|
+
/**
|
|
19244
|
+
* Operator revive: put refused rows back in the pending queue with a fresh
|
|
19245
|
+
* attempt budget. Used after the hub-side state is corrected — the Runtime
|
|
19246
|
+
* must not decide on its own that a refusal has become retryable.
|
|
19247
|
+
*/
|
|
19248
|
+
retryRefusedOutbox() {
|
|
19249
|
+
return Number(this.database.prepare(`
|
|
19250
|
+
UPDATE outbox SET refused_code = NULL, refused_at = NULL, attempted_at = NULL, attempt_count = 0
|
|
19251
|
+
WHERE acked_at IS NULL AND refused_code IS NOT NULL
|
|
19252
|
+
`).run().changes);
|
|
19253
|
+
}
|
|
19254
|
+
/**
|
|
19255
|
+
* The outbox as one read model: what is still retryable, what the hub holds,
|
|
19256
|
+
* and what the hub refused with which codes. This is what makes a stalled
|
|
19257
|
+
* sync answerable without opening SQLite.
|
|
19258
|
+
*/
|
|
19259
|
+
outboxStatus() {
|
|
19260
|
+
const counts = this.database.prepare(`
|
|
19261
|
+
SELECT
|
|
19262
|
+
COALESCE(SUM(acked_at IS NULL AND refused_code IS NULL), 0) AS pending,
|
|
19263
|
+
COALESCE(SUM(acked_at IS NOT NULL), 0) AS acked,
|
|
19264
|
+
COALESCE(SUM(acked_at IS NULL AND refused_code IS NOT NULL), 0) AS refused,
|
|
19265
|
+
MAX(attempted_at) AS last_attempt_at
|
|
19266
|
+
FROM outbox
|
|
19267
|
+
`).get();
|
|
19268
|
+
const refusals = this.database.prepare(`
|
|
19269
|
+
SELECT refused_code AS code, COUNT(*) AS count
|
|
19270
|
+
FROM outbox WHERE acked_at IS NULL AND refused_code IS NOT NULL
|
|
19271
|
+
GROUP BY refused_code ORDER BY count DESC, code ASC
|
|
19272
|
+
`).all();
|
|
19273
|
+
const oldest = this.database.prepare(`
|
|
19274
|
+
SELECT seq FROM outbox WHERE acked_at IS NULL AND refused_code IS NULL ORDER BY seq ASC LIMIT 1
|
|
19275
|
+
`).get();
|
|
19276
|
+
return {
|
|
19277
|
+
pending: counts.pending,
|
|
19278
|
+
acked: counts.acked,
|
|
19279
|
+
refused: counts.refused,
|
|
19280
|
+
...counts.last_attempt_at !== null ? { lastAttemptAt: counts.last_attempt_at } : {},
|
|
19281
|
+
...oldest ? { oldestPendingSeq: oldest.seq } : {},
|
|
19282
|
+
// Mapped, not spread: node:sqlite rows are null-prototype objects, and this
|
|
19283
|
+
// read model is handed straight to the CLI and the supervisor's JSON answer.
|
|
19284
|
+
refusals: refusals.map((refusal) => ({ code: refusal.code, count: refusal.count }))
|
|
19285
|
+
};
|
|
19286
|
+
}
|
|
19197
19287
|
events(runId, afterSequence = 0, limit = 200) {
|
|
19198
19288
|
const rows = this.database.prepare(`
|
|
19199
19289
|
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 +19805,11 @@ function outboxFromRow(row) {
|
|
|
19715
19805
|
runId: row.run_id,
|
|
19716
19806
|
sequence: row.sequence,
|
|
19717
19807
|
syncEvent: row.sync_event,
|
|
19808
|
+
attemptCount: row.attempt_count,
|
|
19718
19809
|
...row.attempted_at !== null ? { attemptedAt: row.attempted_at } : {},
|
|
19719
|
-
...row.acked_at !== null ? { ackedAt: row.acked_at } : {}
|
|
19810
|
+
...row.acked_at !== null ? { ackedAt: row.acked_at } : {},
|
|
19811
|
+
...row.refused_code !== null ? { refusedCode: row.refused_code } : {},
|
|
19812
|
+
...row.refused_at !== null ? { refusedAt: row.refused_at } : {}
|
|
19720
19813
|
};
|
|
19721
19814
|
}
|
|
19722
19815
|
function runFromRow(row) {
|
|
@@ -23356,9 +23449,9 @@ function assertKxmConfigError(error) {
|
|
|
23356
23449
|
// plugins/kxm/src/runtime-supervisor.ts
|
|
23357
23450
|
import { spawn as spawn3 } from "node:child_process";
|
|
23358
23451
|
import { createHash as createHash13, createHmac, randomBytes as randomBytes3, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
23359
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
23452
|
+
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
23453
|
import { createServer } from "node:http";
|
|
23361
|
-
import { dirname as
|
|
23454
|
+
import { dirname as dirname9, isAbsolute as isAbsolute6, join as join15 } from "node:path";
|
|
23362
23455
|
|
|
23363
23456
|
// plugins/kxm/src/oneshot-producer.ts
|
|
23364
23457
|
import { join as join12 } from "node:path";
|
|
@@ -27988,13 +28081,162 @@ function resolveClientHubAuthToken(env, project) {
|
|
|
27988
28081
|
return record2?.projectTokens?.[project]?.trim() || record2?.authToken?.trim() || void 0;
|
|
27989
28082
|
}
|
|
27990
28083
|
|
|
28084
|
+
// plugins/kxm/src/logger.ts
|
|
28085
|
+
import { appendFileSync, existsSync as existsSync12, mkdirSync as mkdirSync6, renameSync as renameSync3, statSync as statSync3, unlinkSync as unlinkSync2 } from "node:fs";
|
|
28086
|
+
import { dirname as dirname8 } from "node:path";
|
|
28087
|
+
var LOG_LEVEL_PRIORITY = {
|
|
28088
|
+
debug: 10,
|
|
28089
|
+
info: 20,
|
|
28090
|
+
warn: 30,
|
|
28091
|
+
error: 40
|
|
28092
|
+
};
|
|
28093
|
+
var DEFAULT_LOG_MAX_BYTES = 10 * 1024 * 1024;
|
|
28094
|
+
var DEFAULT_LOG_MAX_FILES = 3;
|
|
28095
|
+
var SENSITIVE_KEY_PATTERN = /(?:^|_)(?:token|secret|password|apiKey|api_key|authorization|bearer)(?:$|_)/i;
|
|
28096
|
+
var ALLOWED_EXACT_KEYS = /* @__PURE__ */ new Set(["auth", "authType", "authMethod", "authArgs", "canUpdate", "status"]);
|
|
28097
|
+
function redactLogValue(val, key) {
|
|
28098
|
+
if (val === null || val === void 0) return val;
|
|
28099
|
+
if (typeof val === "string") {
|
|
28100
|
+
if (key && SENSITIVE_KEY_PATTERN.test(key) && !ALLOWED_EXACT_KEYS.has(key)) {
|
|
28101
|
+
return "[redacted]";
|
|
28102
|
+
}
|
|
28103
|
+
return redactSecrets(val);
|
|
28104
|
+
}
|
|
28105
|
+
if (typeof val === "number" || typeof val === "boolean") {
|
|
28106
|
+
return val;
|
|
28107
|
+
}
|
|
28108
|
+
if (Array.isArray(val)) {
|
|
28109
|
+
return val.map((item) => redactLogValue(item, key));
|
|
28110
|
+
}
|
|
28111
|
+
if (typeof val === "object") {
|
|
28112
|
+
const out = {};
|
|
28113
|
+
for (const [k, v] of Object.entries(val)) {
|
|
28114
|
+
out[k] = redactLogValue(v, k);
|
|
28115
|
+
}
|
|
28116
|
+
return out;
|
|
28117
|
+
}
|
|
28118
|
+
return String(val);
|
|
28119
|
+
}
|
|
28120
|
+
function rotateLogFiles(filePath, maxFiles) {
|
|
28121
|
+
for (let i = maxFiles; i >= 1; i--) {
|
|
28122
|
+
const current = `${filePath}.${i}`;
|
|
28123
|
+
if (existsSync12(current)) {
|
|
28124
|
+
if (i >= maxFiles) {
|
|
28125
|
+
try {
|
|
28126
|
+
unlinkSync2(current);
|
|
28127
|
+
} catch {
|
|
28128
|
+
}
|
|
28129
|
+
} else {
|
|
28130
|
+
try {
|
|
28131
|
+
renameSync3(current, `${filePath}.${i + 1}`);
|
|
28132
|
+
} catch {
|
|
28133
|
+
}
|
|
28134
|
+
}
|
|
28135
|
+
}
|
|
28136
|
+
}
|
|
28137
|
+
if (existsSync12(filePath)) {
|
|
28138
|
+
try {
|
|
28139
|
+
renameSync3(filePath, `${filePath}.1`);
|
|
28140
|
+
} catch {
|
|
28141
|
+
}
|
|
28142
|
+
}
|
|
28143
|
+
}
|
|
28144
|
+
function createLogger(options) {
|
|
28145
|
+
const component = options.component;
|
|
28146
|
+
const filePath = options.path;
|
|
28147
|
+
const maxBytes = Math.max(100, options.maxBytes ?? DEFAULT_LOG_MAX_BYTES);
|
|
28148
|
+
const maxFiles = Math.max(1, options.maxFiles ?? DEFAULT_LOG_MAX_FILES);
|
|
28149
|
+
const configuredLevel = options.level ?? "info";
|
|
28150
|
+
const isDaemon = Boolean(options.daemon ?? (process.env.KXM_DAEMON === "1" || process.env.KXM_DAEMON === "true"));
|
|
28151
|
+
const shouldStdout = options.stdout ?? !isDaemon;
|
|
28152
|
+
const correlationDefaults = options.correlation ?? {};
|
|
28153
|
+
let currentSize = 0;
|
|
28154
|
+
if (filePath && existsSync12(filePath)) {
|
|
28155
|
+
try {
|
|
28156
|
+
currentSize = statSync3(filePath).size;
|
|
28157
|
+
} catch {
|
|
28158
|
+
currentSize = 0;
|
|
28159
|
+
}
|
|
28160
|
+
}
|
|
28161
|
+
function emit(level, entryOrEvent, extra) {
|
|
28162
|
+
const minPriority = LOG_LEVEL_PRIORITY[configuredLevel] ?? LOG_LEVEL_PRIORITY.info;
|
|
28163
|
+
const currentPriority = LOG_LEVEL_PRIORITY[level] ?? LOG_LEVEL_PRIORITY.info;
|
|
28164
|
+
if (currentPriority < minPriority) return;
|
|
28165
|
+
let base;
|
|
28166
|
+
if (typeof entryOrEvent === "string") {
|
|
28167
|
+
base = { event: entryOrEvent, ...extra };
|
|
28168
|
+
} else {
|
|
28169
|
+
base = { ...entryOrEvent, ...extra };
|
|
28170
|
+
}
|
|
28171
|
+
const timestamp = typeof base.timestamp === "string" ? base.timestamp : (/* @__PURE__ */ new Date()).toISOString();
|
|
28172
|
+
delete base.timestamp;
|
|
28173
|
+
delete base.level;
|
|
28174
|
+
delete base.component;
|
|
28175
|
+
const payload = {
|
|
28176
|
+
timestamp,
|
|
28177
|
+
level,
|
|
28178
|
+
component,
|
|
28179
|
+
...correlationDefaults,
|
|
28180
|
+
...base
|
|
28181
|
+
};
|
|
28182
|
+
const sanitized = redactLogValue(payload);
|
|
28183
|
+
const line = `${JSON.stringify(sanitized)}
|
|
28184
|
+
`;
|
|
28185
|
+
if (filePath) {
|
|
28186
|
+
const lineBytes = Buffer.byteLength(line, "utf8");
|
|
28187
|
+
if (currentSize + lineBytes > maxBytes) {
|
|
28188
|
+
rotateLogFiles(filePath, maxFiles);
|
|
28189
|
+
currentSize = 0;
|
|
28190
|
+
}
|
|
28191
|
+
try {
|
|
28192
|
+
mkdirSync6(dirname8(filePath), { recursive: true });
|
|
28193
|
+
appendFileSync(filePath, line, { encoding: "utf8", mode: 384 });
|
|
28194
|
+
currentSize += lineBytes;
|
|
28195
|
+
} catch {
|
|
28196
|
+
}
|
|
28197
|
+
}
|
|
28198
|
+
if (shouldStdout) {
|
|
28199
|
+
process.stdout.write(line);
|
|
28200
|
+
}
|
|
28201
|
+
}
|
|
28202
|
+
const logFn = ((entryOrEvent, extra) => {
|
|
28203
|
+
let lvl = "info";
|
|
28204
|
+
if (typeof entryOrEvent === "object" && entryOrEvent !== null && typeof entryOrEvent.level === "string") {
|
|
28205
|
+
const candidate = entryOrEvent.level.toLowerCase();
|
|
28206
|
+
if (candidate === "debug" || candidate === "info" || candidate === "warn" || candidate === "error") {
|
|
28207
|
+
lvl = candidate;
|
|
28208
|
+
}
|
|
28209
|
+
}
|
|
28210
|
+
emit(lvl, entryOrEvent, extra);
|
|
28211
|
+
});
|
|
28212
|
+
logFn.info = (entryOrEvent, extra) => emit("info", entryOrEvent, extra);
|
|
28213
|
+
logFn.warn = (entryOrEvent, extra) => emit("warn", entryOrEvent, extra);
|
|
28214
|
+
logFn.error = (entryOrEvent, extra) => emit("error", entryOrEvent, extra);
|
|
28215
|
+
logFn.debug = (entryOrEvent, extra) => emit("debug", entryOrEvent, extra);
|
|
28216
|
+
logFn.child = (sub) => {
|
|
28217
|
+
return createLogger({
|
|
28218
|
+
...options,
|
|
28219
|
+
component: sub.component ? `${component}.${sub.component}` : component,
|
|
28220
|
+
correlation: { ...correlationDefaults, ...sub.correlation }
|
|
28221
|
+
});
|
|
28222
|
+
};
|
|
28223
|
+
logFn.close = () => {
|
|
28224
|
+
};
|
|
28225
|
+
Object.defineProperty(logFn, "options", {
|
|
28226
|
+
value: Object.freeze({ ...options }),
|
|
28227
|
+
writable: false,
|
|
28228
|
+
enumerable: true
|
|
28229
|
+
});
|
|
28230
|
+
return logFn;
|
|
28231
|
+
}
|
|
28232
|
+
|
|
27991
28233
|
// plugins/kxm/src/runtime-supervisor.ts
|
|
27992
28234
|
function kxmSupervisorTokenFile(paths) {
|
|
27993
28235
|
return join15(paths.runtimeDir, "supervisor.token");
|
|
27994
28236
|
}
|
|
27995
28237
|
function publishKxmSupervisorToken(paths, token) {
|
|
27996
28238
|
const file = kxmSupervisorTokenFile(paths);
|
|
27997
|
-
|
|
28239
|
+
mkdirSync7(dirname9(file), { recursive: true, mode: 448 });
|
|
27998
28240
|
const temp = `${file}.${process.pid}.tmp`;
|
|
27999
28241
|
writeFileSync6(temp, `${token}
|
|
28000
28242
|
`, { encoding: "utf8", mode: 384 });
|
|
@@ -28002,7 +28244,7 @@ function publishKxmSupervisorToken(paths, token) {
|
|
|
28002
28244
|
chmodSync2(temp, 384);
|
|
28003
28245
|
} catch {
|
|
28004
28246
|
}
|
|
28005
|
-
|
|
28247
|
+
renameSync4(temp, file);
|
|
28006
28248
|
try {
|
|
28007
28249
|
chmodSync2(file, 384);
|
|
28008
28250
|
} catch {
|
|
@@ -28036,11 +28278,11 @@ function supervisorErrorFile(paths) {
|
|
|
28036
28278
|
}
|
|
28037
28279
|
function clearSupervisorError(paths) {
|
|
28038
28280
|
const file = supervisorErrorFile(paths);
|
|
28039
|
-
if (
|
|
28281
|
+
if (existsSync13(file)) rmSync3(file, { force: true });
|
|
28040
28282
|
}
|
|
28041
28283
|
function recordSupervisorError(paths, message) {
|
|
28042
28284
|
try {
|
|
28043
|
-
|
|
28285
|
+
mkdirSync7(paths.runtimeDir, { recursive: true, mode: 448 });
|
|
28044
28286
|
writeFileSync6(supervisorErrorFile(paths), `${message}
|
|
28045
28287
|
`, { encoding: "utf8", mode: 384 });
|
|
28046
28288
|
} catch {
|
|
@@ -28059,7 +28301,7 @@ function readRecentSupervisorError(paths) {
|
|
|
28059
28301
|
}
|
|
28060
28302
|
}
|
|
28061
28303
|
function kxmSupervisorStatus(paths) {
|
|
28062
|
-
if (!
|
|
28304
|
+
if (!existsSync13(paths.registryDb)) return { running: false };
|
|
28063
28305
|
const registry = new KxmRuntimeRegistry(paths.registryDb);
|
|
28064
28306
|
try {
|
|
28065
28307
|
const record2 = registry.supervisor();
|
|
@@ -28240,6 +28482,8 @@ var DEFAULT_RUNTIME_SYNC_INTERVAL_MS = 1e4;
|
|
|
28240
28482
|
var MIN_RUNTIME_SYNC_INTERVAL_MS = 250;
|
|
28241
28483
|
var MAX_RUNTIME_SYNC_INTERVAL_MS = 6e4;
|
|
28242
28484
|
var OUTBOX_PUSH_BATCH = 32;
|
|
28485
|
+
var OUTBOX_PUSH_BATCH_BYTES = 2e5;
|
|
28486
|
+
var RUNTIME_SYNC_MAX_BACKOFF_MS = 5 * 6e4;
|
|
28243
28487
|
function runtimeSyncIntervalMs(env = process.env) {
|
|
28244
28488
|
const raw = env.KXM_RUNTIME_SYNC_INTERVAL_MS?.trim();
|
|
28245
28489
|
if (!raw) return DEFAULT_RUNTIME_SYNC_INTERVAL_MS;
|
|
@@ -28247,16 +28491,72 @@ function runtimeSyncIntervalMs(env = process.env) {
|
|
|
28247
28491
|
if (!Number.isInteger(parsed)) return DEFAULT_RUNTIME_SYNC_INTERVAL_MS;
|
|
28248
28492
|
return Math.min(MAX_RUNTIME_SYNC_INTERVAL_MS, Math.max(MIN_RUNTIME_SYNC_INTERVAL_MS, parsed));
|
|
28249
28493
|
}
|
|
28494
|
+
function isDurableRefusal(outcome) {
|
|
28495
|
+
return outcome === "conflict" || outcome === "rejected";
|
|
28496
|
+
}
|
|
28497
|
+
function isOversizeRefusal(error) {
|
|
28498
|
+
return error instanceof HubHttpError && (error.statusCode === 413 || error.code === "sync_batch_too_large" || error.code === "payload_too_large");
|
|
28499
|
+
}
|
|
28250
28500
|
async function syncKxmOutbox(eventStore, client, options = {}) {
|
|
28251
28501
|
const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
28252
|
-
const batchSize = options.batchSize ?? OUTBOX_PUSH_BATCH;
|
|
28253
|
-
const result = { pushed: 0, acked: 0,
|
|
28502
|
+
const batchSize = Math.max(1, options.batchSize ?? OUTBOX_PUSH_BATCH);
|
|
28503
|
+
const result = { pushed: 0, acked: 0, refused: 0, refusals: [], unconfirmed: 0, blocked: false };
|
|
28504
|
+
const refuse = (row, code) => {
|
|
28505
|
+
if (eventStore.refuseOutbox([{ seq: row.seq, code }], now()) === 0) return;
|
|
28506
|
+
result.refused += 1;
|
|
28507
|
+
result.refusals.push({ runId: row.runId, sequence: row.sequence, code });
|
|
28508
|
+
};
|
|
28509
|
+
const deliver = async (batch) => {
|
|
28510
|
+
const payload = [];
|
|
28511
|
+
const sendable = [];
|
|
28512
|
+
for (const row of batch) {
|
|
28513
|
+
try {
|
|
28514
|
+
payload.push(JSON.parse(row.syncEvent));
|
|
28515
|
+
sendable.push(row);
|
|
28516
|
+
} catch {
|
|
28517
|
+
refuse(row, "sync_row_unreadable");
|
|
28518
|
+
}
|
|
28519
|
+
}
|
|
28520
|
+
if (sendable.length === 0) return true;
|
|
28521
|
+
eventStore.markOutboxAttempted(sendable.map((row) => row.seq), now());
|
|
28522
|
+
let response;
|
|
28523
|
+
try {
|
|
28524
|
+
response = await client.pushSyncEvents(payload);
|
|
28525
|
+
} catch (error) {
|
|
28526
|
+
if (isOversizeRefusal(error)) {
|
|
28527
|
+
if (sendable.length > 1) {
|
|
28528
|
+
for (const row of sendable) {
|
|
28529
|
+
if (!await deliver([row])) return false;
|
|
28530
|
+
}
|
|
28531
|
+
return true;
|
|
28532
|
+
}
|
|
28533
|
+
refuse(sendable[0], "sync_row_too_large");
|
|
28534
|
+
return true;
|
|
28535
|
+
}
|
|
28536
|
+
result.blockedReason = syncFailureText(error);
|
|
28537
|
+
return false;
|
|
28538
|
+
}
|
|
28539
|
+
result.pushed += sendable.length;
|
|
28540
|
+
const acked = [];
|
|
28541
|
+
sendable.forEach((row, index) => {
|
|
28542
|
+
const entry = response.results[index];
|
|
28543
|
+
const describesRow = entry !== void 0 && (entry.runId === void 0 || entry.runId === row.runId) && (entry.sequence === void 0 || entry.sequence === row.sequence);
|
|
28544
|
+
if (!entry || !describesRow) {
|
|
28545
|
+
result.unconfirmed += 1;
|
|
28546
|
+
return;
|
|
28547
|
+
}
|
|
28548
|
+
if (entry.outcome === "accepted" || entry.outcome === "duplicate") acked.push(row.seq);
|
|
28549
|
+
else if (isDurableRefusal(entry.outcome)) refuse(row, entry.code ?? `sync_${entry.outcome}`);
|
|
28550
|
+
else result.unconfirmed += 1;
|
|
28551
|
+
});
|
|
28552
|
+
result.acked += eventStore.ackOutbox(acked, now());
|
|
28553
|
+
return true;
|
|
28554
|
+
};
|
|
28254
28555
|
let afterSeq = 0;
|
|
28255
28556
|
for (; ; ) {
|
|
28256
28557
|
const rows = eventStore.pendingOutbox(batchSize, afterSeq);
|
|
28257
28558
|
if (rows.length === 0) return result;
|
|
28258
|
-
|
|
28259
|
-
let byteBudget = MAX_BATCH_BYTES;
|
|
28559
|
+
let byteBudget = OUTBOX_PUSH_BATCH_BYTES;
|
|
28260
28560
|
let sendCount = 0;
|
|
28261
28561
|
for (const row of rows) {
|
|
28262
28562
|
const rowBytes = Buffer.byteLength(row.syncEvent, "utf8") + 64;
|
|
@@ -28264,21 +28564,12 @@ async function syncKxmOutbox(eventStore, client, options = {}) {
|
|
|
28264
28564
|
byteBudget -= rowBytes;
|
|
28265
28565
|
sendCount += 1;
|
|
28266
28566
|
}
|
|
28267
|
-
const batch = rows.slice(0, sendCount);
|
|
28268
|
-
if (batch.length === 0) batch.push(rows[0]);
|
|
28567
|
+
const batch = rows.slice(0, Math.max(1, sendCount));
|
|
28269
28568
|
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;
|
|
28569
|
+
if (!await deliver(batch)) {
|
|
28570
|
+
result.blocked = true;
|
|
28571
|
+
return result;
|
|
28280
28572
|
}
|
|
28281
|
-
result.acked += eventStore.ackOutbox(acked, now());
|
|
28282
28573
|
}
|
|
28283
28574
|
}
|
|
28284
28575
|
function runtimeHubClientFor(context, env) {
|
|
@@ -28293,11 +28584,24 @@ function runtimeHubClientFor(context, env) {
|
|
|
28293
28584
|
...authToken ? { authToken } : {}
|
|
28294
28585
|
});
|
|
28295
28586
|
}
|
|
28587
|
+
function syncBackoffMs(consecutiveStalls, intervalMs) {
|
|
28588
|
+
if (consecutiveStalls <= 0) return 0;
|
|
28589
|
+
return Math.min(RUNTIME_SYNC_MAX_BACKOFF_MS, intervalMs * 2 ** Math.min(5, consecutiveStalls - 1));
|
|
28590
|
+
}
|
|
28591
|
+
function syncFailureText(error) {
|
|
28592
|
+
const code = error instanceof HubHttpError ? `${error.statusCode} ${error.code ?? "error"}` : void 0;
|
|
28593
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
28594
|
+
return `${code ? `${code}: ` : ""}${message}`.replace(/(kxm_[A-Za-z0-9_]+|[A-Za-z0-9._-]{40,})/g, "[redacted]").slice(0, 300);
|
|
28595
|
+
}
|
|
28596
|
+
function runtimeSupervisorLogFile(paths) {
|
|
28597
|
+
return join15(paths.runtimeDir, "logs", "kxm-runtime.jsonl");
|
|
28598
|
+
}
|
|
28296
28599
|
async function startKxmRuntimeSupervisor(options = {}) {
|
|
28297
28600
|
const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
28601
|
+
const env = options.env ?? process.env;
|
|
28298
28602
|
const paths = kxmRuntimePaths(options.stateRoot !== void 0 ? { stateRoot: options.stateRoot } : {});
|
|
28299
28603
|
try {
|
|
28300
|
-
return await startKxmRuntimeSupervisorInner(paths, options.port, now);
|
|
28604
|
+
return await startKxmRuntimeSupervisorInner(paths, options.port, now, env);
|
|
28301
28605
|
} catch (error) {
|
|
28302
28606
|
if (!(error instanceof KxmConfigError && error.issues.some((issue3) => issue3.code === "runtime_supervisor_conflict"))) {
|
|
28303
28607
|
recordSupervisorError(paths, error.message);
|
|
@@ -28305,9 +28609,15 @@ async function startKxmRuntimeSupervisor(options = {}) {
|
|
|
28305
28609
|
throw error;
|
|
28306
28610
|
}
|
|
28307
28611
|
}
|
|
28308
|
-
async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now) {
|
|
28309
|
-
|
|
28310
|
-
|
|
28612
|
+
async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now, env) {
|
|
28613
|
+
mkdirSync7(paths.runtimeDir, { recursive: true, mode: 448 });
|
|
28614
|
+
mkdirSync7(paths.projectsDir, { recursive: true, mode: 448 });
|
|
28615
|
+
const logger = createLogger({
|
|
28616
|
+
component: "runtime",
|
|
28617
|
+
path: runtimeSupervisorLogFile(paths),
|
|
28618
|
+
stdout: false,
|
|
28619
|
+
maxBytes: 2 * 1024 * 1024
|
|
28620
|
+
});
|
|
28311
28621
|
const token = randomBytes3(32).toString("hex");
|
|
28312
28622
|
const tokenHash = hashKxmSupervisorToken(token);
|
|
28313
28623
|
const registry = new KxmRuntimeRegistry(paths.registryDb);
|
|
@@ -28315,12 +28625,13 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now) {
|
|
|
28315
28625
|
const requestedPort = requestedPortOption ?? 0;
|
|
28316
28626
|
let activeRuntimeId = runtimeId;
|
|
28317
28627
|
const contexts = /* @__PURE__ */ new Map();
|
|
28628
|
+
const syncStatuses = /* @__PURE__ */ new Map();
|
|
28318
28629
|
const registerSyncCredentials = (context) => {
|
|
28319
|
-
const hubToken = resolveClientHubAuthToken(
|
|
28630
|
+
const hubToken = resolveClientHubAuthToken(env, context.projectId);
|
|
28320
28631
|
if (hubToken) context.eventStore.syncRedactor.register(hubToken);
|
|
28321
|
-
for (const key of Object.keys(
|
|
28632
|
+
for (const key of Object.keys(env)) {
|
|
28322
28633
|
if (key.startsWith("KXM_") && (key.endsWith("_TOKEN") || key.endsWith("_KEY")) || key.endsWith("_API_KEY") || key.endsWith("_SECRET")) {
|
|
28323
|
-
const value =
|
|
28634
|
+
const value = env[key]?.trim();
|
|
28324
28635
|
if (value) context.eventStore.syncRedactor.register(value);
|
|
28325
28636
|
}
|
|
28326
28637
|
}
|
|
@@ -28370,6 +28681,28 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now) {
|
|
|
28370
28681
|
});
|
|
28371
28682
|
return;
|
|
28372
28683
|
}
|
|
28684
|
+
if (request.method === "GET" && url.pathname === "/v1/sync/status") {
|
|
28685
|
+
const projectRoot = url.searchParams.get("projectRoot");
|
|
28686
|
+
const statuses = [...syncStatuses.values()].filter((status) => projectRoot === null || status.projectRoot === projectRoot).sort((left, right) => left.projectId.localeCompare(right.projectId));
|
|
28687
|
+
sendJson(response, 200, { ok: true, runtimeId: activeRuntimeId, projects: statuses });
|
|
28688
|
+
return;
|
|
28689
|
+
}
|
|
28690
|
+
if (request.method === "POST" && url.pathname === "/v1/sync/retry") {
|
|
28691
|
+
const body = await readJsonBody(request);
|
|
28692
|
+
const projectRoot = typeof body.projectRoot === "string" ? body.projectRoot : "";
|
|
28693
|
+
const context = contextFor(projectRoot);
|
|
28694
|
+
const key = projectRuntimeKey(context.projectRoot);
|
|
28695
|
+
const retried = context.eventStore.retryRefusedOutbox();
|
|
28696
|
+
const status = syncStatuses.get(key);
|
|
28697
|
+
if (status) {
|
|
28698
|
+
const cleared = { ...status, outbox: context.eventStore.outboxStatus(), consecutiveFailures: 0 };
|
|
28699
|
+
delete cleared.nextAttemptAt;
|
|
28700
|
+
syncStatuses.set(key, cleared);
|
|
28701
|
+
}
|
|
28702
|
+
logger({ event: "runtime_sync_retry", projectId: context.projectId, retried });
|
|
28703
|
+
sendJson(response, 200, { ok: true, projectId: context.projectId, retried, outbox: context.eventStore.outboxStatus() });
|
|
28704
|
+
return;
|
|
28705
|
+
}
|
|
28373
28706
|
if (request.method === "POST" && url.pathname === "/v1/runs") {
|
|
28374
28707
|
const body = await readJsonBody(request);
|
|
28375
28708
|
const projectRoot = typeof body.projectRoot === "string" ? body.projectRoot : "";
|
|
@@ -28669,26 +29002,93 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now) {
|
|
|
28669
29002
|
try {
|
|
28670
29003
|
contextFor(reg.projectRoot);
|
|
28671
29004
|
} catch {
|
|
29005
|
+
logger.warn({
|
|
29006
|
+
event: "runtime_sync_context_unavailable",
|
|
29007
|
+
projectId: reg.projectId,
|
|
29008
|
+
message: `cannot reopen ${reg.projectRoot}: its outbox rows stay pending`
|
|
29009
|
+
});
|
|
28672
29010
|
}
|
|
28673
29011
|
}
|
|
29012
|
+
const recordSyncStatus = (context, next) => {
|
|
29013
|
+
const key = projectRuntimeKey(context.projectRoot);
|
|
29014
|
+
const previous = syncStatuses.get(key);
|
|
29015
|
+
syncStatuses.set(key, next);
|
|
29016
|
+
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 ?? []);
|
|
29017
|
+
if (!changed) return;
|
|
29018
|
+
const stalledState = next.state === "blocked" || next.state === "refusing";
|
|
29019
|
+
const entry = {
|
|
29020
|
+
event: stalledState ? "runtime_sync_stalled" : "runtime_sync_state",
|
|
29021
|
+
projectId: next.projectId,
|
|
29022
|
+
runtimeId: next.homeRuntimeId,
|
|
29023
|
+
state: next.state,
|
|
29024
|
+
pending: next.outbox.pending,
|
|
29025
|
+
acked: next.outbox.acked,
|
|
29026
|
+
refused: next.outbox.refused,
|
|
29027
|
+
...next.outbox.refusals.length > 0 ? { refusals: next.outbox.refusals } : {},
|
|
29028
|
+
...next.hubUrl ? { hubUrl: next.hubUrl } : {},
|
|
29029
|
+
...next.lastError ? { reason: next.lastError } : {},
|
|
29030
|
+
...next.nextAttemptAt ? { nextAttemptAt: next.nextAttemptAt } : {}
|
|
29031
|
+
};
|
|
29032
|
+
if (stalledState) logger.warn(entry);
|
|
29033
|
+
else logger.info(entry);
|
|
29034
|
+
};
|
|
28674
29035
|
let syncing = false;
|
|
28675
29036
|
const syncTimer = setInterval(() => {
|
|
28676
29037
|
if (syncing || stopping) return;
|
|
28677
29038
|
syncing = true;
|
|
28678
29039
|
void (async () => {
|
|
28679
29040
|
for (const context of [...contexts.values()]) {
|
|
29041
|
+
const key = projectRuntimeKey(context.projectRoot);
|
|
29042
|
+
const prior = syncStatuses.get(key);
|
|
29043
|
+
if (prior?.nextAttemptAt && Date.parse(prior.nextAttemptAt) > Date.parse(now())) continue;
|
|
29044
|
+
const base = {
|
|
29045
|
+
projectId: context.projectId,
|
|
29046
|
+
projectRoot: context.projectRoot,
|
|
29047
|
+
homeRuntimeId: context.homeRuntimeId,
|
|
29048
|
+
outbox: context.eventStore.outboxStatus(),
|
|
29049
|
+
consecutiveFailures: 0
|
|
29050
|
+
};
|
|
28680
29051
|
try {
|
|
28681
|
-
const client = runtimeHubClientFor(context,
|
|
28682
|
-
if (!client)
|
|
29052
|
+
const client = runtimeHubClientFor(context, env);
|
|
29053
|
+
if (!client) {
|
|
29054
|
+
recordSyncStatus(context, { ...base, state: "no_hub" });
|
|
29055
|
+
continue;
|
|
29056
|
+
}
|
|
28683
29057
|
await client.heartbeat();
|
|
28684
|
-
await syncKxmOutbox(context.eventStore, client, { now });
|
|
28685
|
-
|
|
29058
|
+
const pass = await syncKxmOutbox(context.eventStore, client, { now });
|
|
29059
|
+
const outbox = context.eventStore.outboxStatus();
|
|
29060
|
+
const failures = pass.blocked || pass.unconfirmed > 0 ? (prior?.consecutiveFailures ?? 0) + 1 : 0;
|
|
29061
|
+
const delayMs = syncBackoffMs(failures, runtimeSyncIntervalMs(env));
|
|
29062
|
+
recordSyncStatus(context, {
|
|
29063
|
+
...base,
|
|
29064
|
+
hubUrl: client.options.serverUrl,
|
|
29065
|
+
outbox,
|
|
29066
|
+
state: pass.blocked ? "blocked" : outbox.refused > 0 || pass.unconfirmed > 0 ? "refusing" : "ok",
|
|
29067
|
+
lastCompletedAt: now(),
|
|
29068
|
+
lastPushed: pass.pushed,
|
|
29069
|
+
lastAcked: pass.acked,
|
|
29070
|
+
lastRefused: pass.refused,
|
|
29071
|
+
lastUnconfirmed: pass.unconfirmed,
|
|
29072
|
+
consecutiveFailures: failures,
|
|
29073
|
+
...pass.blockedReason !== void 0 ? { lastError: pass.blockedReason } : {},
|
|
29074
|
+
...delayMs > 0 ? { nextAttemptAt: new Date(Date.parse(now()) + delayMs).toISOString() } : {}
|
|
29075
|
+
});
|
|
29076
|
+
} catch (error) {
|
|
29077
|
+
const failures = (prior?.consecutiveFailures ?? 0) + 1;
|
|
29078
|
+
const delayMs = syncBackoffMs(failures, runtimeSyncIntervalMs(env));
|
|
29079
|
+
recordSyncStatus(context, {
|
|
29080
|
+
...base,
|
|
29081
|
+
state: "blocked",
|
|
29082
|
+
consecutiveFailures: failures,
|
|
29083
|
+
lastError: syncFailureText(error),
|
|
29084
|
+
nextAttemptAt: new Date(Date.parse(now()) + delayMs).toISOString()
|
|
29085
|
+
});
|
|
28686
29086
|
}
|
|
28687
29087
|
}
|
|
28688
29088
|
})().finally(() => {
|
|
28689
29089
|
syncing = false;
|
|
28690
29090
|
});
|
|
28691
|
-
}, runtimeSyncIntervalMs());
|
|
29091
|
+
}, runtimeSyncIntervalMs(env));
|
|
28692
29092
|
syncTimer.unref();
|
|
28693
29093
|
let stopping = false;
|
|
28694
29094
|
const stop = async () => {
|
|
@@ -28723,6 +29123,7 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now) {
|
|
|
28723
29123
|
}
|
|
28724
29124
|
for (const context of contexts.values()) closeKxmRuntimeContext(context);
|
|
28725
29125
|
contexts.clear();
|
|
29126
|
+
logger.close();
|
|
28726
29127
|
const closed = new Promise((resolveStop) => server.close(() => resolveStop()));
|
|
28727
29128
|
server.closeIdleConnections?.();
|
|
28728
29129
|
await closed;
|
|
@@ -29261,155 +29662,6 @@ function createKxmPiProducer(options = {}) {
|
|
|
29261
29662
|
return producer;
|
|
29262
29663
|
}
|
|
29263
29664
|
|
|
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
29665
|
// plugins/kxm/src/improve.ts
|
|
29414
29666
|
import { createHash as createHash14 } from "node:crypto";
|
|
29415
29667
|
import { existsSync as existsSync14, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7 } from "node:fs";
|
|
@@ -31041,6 +31293,7 @@ export {
|
|
|
31041
31293
|
IMPROVEMENT_REPORT_SCHEMA,
|
|
31042
31294
|
IMPROVEMENT_REPORT_V1_SCHEMA,
|
|
31043
31295
|
KXM_ABSENT_MEMORY_REVISION,
|
|
31296
|
+
KXM_BACKUP_CEILINGS,
|
|
31044
31297
|
KXM_DRIVE_RECEIPT_SCHEMA,
|
|
31045
31298
|
KXM_EVENT_STORE_SCHEMA_VERSION,
|
|
31046
31299
|
KXM_EVENT_STORE_TABLE_NAMES,
|
|
@@ -31119,6 +31372,7 @@ export {
|
|
|
31119
31372
|
isKxmRuntimeContextClosed,
|
|
31120
31373
|
isTransactionContention,
|
|
31121
31374
|
isWindowsHarnessShim,
|
|
31375
|
+
kxmBackupCeiling,
|
|
31122
31376
|
kxmDeclaredExecutorIds,
|
|
31123
31377
|
kxmDeclaredRepositoryIds,
|
|
31124
31378
|
kxmEventBase,
|