@primitive.ai/prim 0.1.0-alpha.92 → 0.1.0-alpha.94
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/README.md +84 -10
- package/SKILL.md +5 -2
- package/dist/{chunk-67VNLLBN.js → chunk-2GP5UFV7.js} +1 -0
- package/dist/{chunk-26P57LAR.js → chunk-AXQQMX2U.js} +203 -9
- package/dist/{chunk-M6RIJVRE.js → chunk-DSWT6BCC.js} +78 -4
- package/dist/{chunk-FBAYCS3X.js → chunk-DXJQR4QI.js} +150 -38
- package/dist/chunk-FXF4JWK2.js +162 -0
- package/dist/{chunk-PCVGBE5B.js → chunk-G2VMA2QO.js} +2 -1
- package/dist/{chunk-NAHHFSLH.js → chunk-ICM2XCXS.js} +2 -2
- package/dist/{chunk-5HRR4AOR.js → chunk-IMK4JKTA.js} +1 -1
- package/dist/{chunk-OAAP2MY6.js → chunk-K7D2EYBS.js} +2 -2
- package/dist/{chunk-ADUTW66W.js → chunk-O7YAW7G6.js} +6 -6
- package/dist/{chunk-5AFRMOPX.js → chunk-OH3EZXYX.js} +1 -1
- package/dist/{chunk-KFXF2V5G.js → chunk-OTHKG76J.js} +1 -1
- package/dist/chunk-OYHYCK4K.js +13 -0
- package/dist/{chunk-FKXFUUWJ.js → chunk-TYAHC25F.js} +1 -1
- package/dist/{chunk-5GYQ4J3D.js → chunk-UKUAAAXE.js} +2 -2
- package/dist/{chunk-LGGMJYY4.js → chunk-WUXFLE7X.js} +720 -369
- package/dist/{chunk-WX52U7Z2.js → chunk-ZU563PQI.js} +58 -2
- package/dist/daemon/server.js +372 -123
- package/dist/hook-runtime/daemon/server.js +372 -123
- package/dist/hook-runtime/hooks/post-commit.js +192 -58
- package/dist/hook-runtime/hooks/post-rewrite.js +58 -40
- package/dist/hook-runtime/hooks/post-tool-use.js +150 -67
- package/dist/hook-runtime/hooks/pre-tool-use.js +87 -15
- package/dist/hook-runtime/hooks/prim-hook.js +197 -107
- package/dist/hook-runtime/hooks/session-end.js +53 -5
- package/dist/hook-runtime/hooks/session-start.js +880 -280
- package/dist/hook-runtime/index.js +4008 -2438
- package/dist/hook-runtime/statusline-main.js +23 -1
- package/dist/hooks/post-commit.js +15 -8
- package/dist/hooks/post-rewrite.js +11 -8
- package/dist/hooks/post-tool-use.js +13 -15
- package/dist/hooks/pre-commit.js +4 -4
- package/dist/hooks/pre-tool-use.js +10 -12
- package/dist/hooks/prim-hook.js +17 -14
- package/dist/hooks/session-end.js +4 -4
- package/dist/hooks/session-start.js +35 -45
- package/dist/index.js +1401 -620
- package/dist/statusline-main.js +23 -1
- package/package.json +1 -1
- package/dist/chunk-BTNNEH2W.js +0 -79
|
@@ -52,9 +52,9 @@ var require_ucs2length = __commonJS({
|
|
|
52
52
|
});
|
|
53
53
|
|
|
54
54
|
// src/daemon/server.ts
|
|
55
|
-
import { chmodSync as
|
|
55
|
+
import { chmodSync as chmodSync7, existsSync as existsSync9, mkdirSync as mkdirSync8, readFileSync as readFileSync9, unlinkSync as unlinkSync3 } from "fs";
|
|
56
56
|
import { createServer } from "net";
|
|
57
|
-
import { join as
|
|
57
|
+
import { join as join14 } from "path";
|
|
58
58
|
import { fileURLToPath } from "url";
|
|
59
59
|
|
|
60
60
|
// src/client.ts
|
|
@@ -1112,8 +1112,8 @@ async function getPinnedClient(options = {}) {
|
|
|
1112
1112
|
}
|
|
1113
1113
|
|
|
1114
1114
|
// src/flusher.ts
|
|
1115
|
-
import { createReadStream, renameSync as renameSync3, unlinkSync } from "fs";
|
|
1116
|
-
import { dirname as
|
|
1115
|
+
import { createReadStream, renameSync as renameSync3, statSync as statSync2, unlinkSync } from "fs";
|
|
1116
|
+
import { dirname as dirname8, join as join9 } from "path";
|
|
1117
1117
|
|
|
1118
1118
|
// src/dead-letter.ts
|
|
1119
1119
|
import { createHash as createHash3, randomBytes as randomBytes2 } from "crypto";
|
|
@@ -1213,6 +1213,95 @@ function quarantineRawLine(flushingPath, rawLine, now = Date.now, options = {})
|
|
|
1213
1213
|
return record;
|
|
1214
1214
|
}
|
|
1215
1215
|
|
|
1216
|
+
// src/drain-progress.ts
|
|
1217
|
+
import {
|
|
1218
|
+
chmodSync as chmodSync3,
|
|
1219
|
+
existsSync as existsSync4,
|
|
1220
|
+
readFileSync as readFileSync4,
|
|
1221
|
+
readdirSync,
|
|
1222
|
+
rmSync as rmSync5,
|
|
1223
|
+
rmdirSync
|
|
1224
|
+
} from "fs";
|
|
1225
|
+
import { basename, dirname as dirname5, join as join6 } from "path";
|
|
1226
|
+
var DIRECTORY_MODE3 = 448;
|
|
1227
|
+
var FILE_MODE3 = 384;
|
|
1228
|
+
var DRAIN_PROGRESS_DIRNAME = "drain-progress";
|
|
1229
|
+
var CHECKPOINT_NAME = /^(.+)\.json(?:\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.tmp)?$/;
|
|
1230
|
+
function rotationIdentity(stat) {
|
|
1231
|
+
return { size: stat.size, ino: stat.ino, dev: stat.dev };
|
|
1232
|
+
}
|
|
1233
|
+
function drainProgressDirectory(flushingPath) {
|
|
1234
|
+
return join6(dirname5(flushingPath), DRAIN_PROGRESS_DIRNAME);
|
|
1235
|
+
}
|
|
1236
|
+
function drainProgressPath(flushingPath) {
|
|
1237
|
+
return join6(drainProgressDirectory(flushingPath), `${basename(flushingPath)}.json`);
|
|
1238
|
+
}
|
|
1239
|
+
function isByteCount(value) {
|
|
1240
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
1241
|
+
}
|
|
1242
|
+
function drainResumeOffset(flushingPath, rotation) {
|
|
1243
|
+
let parsed;
|
|
1244
|
+
try {
|
|
1245
|
+
parsed = JSON.parse(readFileSync4(drainProgressPath(flushingPath), "utf8"));
|
|
1246
|
+
} catch {
|
|
1247
|
+
return 0;
|
|
1248
|
+
}
|
|
1249
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
1250
|
+
return 0;
|
|
1251
|
+
}
|
|
1252
|
+
const { v, offset, size, ino, dev } = parsed;
|
|
1253
|
+
if (v !== 2 || ino !== rotation.ino || dev !== rotation.dev || size !== rotation.size || !isByteCount(offset) || offset > rotation.size) {
|
|
1254
|
+
return 0;
|
|
1255
|
+
}
|
|
1256
|
+
return offset;
|
|
1257
|
+
}
|
|
1258
|
+
function writeDrainCheckpoint(flushingPath, checkpoint) {
|
|
1259
|
+
const directory = drainProgressDirectory(flushingPath);
|
|
1260
|
+
ensureDurableDirectory(directory, DIRECTORY_MODE3);
|
|
1261
|
+
chmodSync3(directory, DIRECTORY_MODE3);
|
|
1262
|
+
atomicWriteFile(drainProgressPath(flushingPath), `${JSON.stringify(checkpoint)}
|
|
1263
|
+
`, {
|
|
1264
|
+
mode: FILE_MODE3
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
function removeEmptyDirectory(directory) {
|
|
1268
|
+
try {
|
|
1269
|
+
rmdirSync(directory);
|
|
1270
|
+
} catch (error) {
|
|
1271
|
+
const code = error.code;
|
|
1272
|
+
if (code !== "ENOENT" && code !== "ENOTEMPTY" && code !== "EEXIST") {
|
|
1273
|
+
throw error;
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
function removeDrainCheckpoint(flushingPath) {
|
|
1278
|
+
rmSync5(drainProgressPath(flushingPath), { force: true });
|
|
1279
|
+
removeEmptyDirectory(drainProgressDirectory(flushingPath));
|
|
1280
|
+
}
|
|
1281
|
+
function sweepOrphanedDrainCheckpoints(bucketDirectory) {
|
|
1282
|
+
const directory = join6(bucketDirectory, DRAIN_PROGRESS_DIRNAME);
|
|
1283
|
+
let names;
|
|
1284
|
+
try {
|
|
1285
|
+
names = readdirSync(directory);
|
|
1286
|
+
} catch {
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
for (const name of names) {
|
|
1290
|
+
const rotation = CHECKPOINT_NAME.exec(name)?.[1];
|
|
1291
|
+
if (rotation === void 0 || existsSync4(join6(bucketDirectory, rotation))) {
|
|
1292
|
+
continue;
|
|
1293
|
+
}
|
|
1294
|
+
try {
|
|
1295
|
+
rmSync5(join6(directory, name), { force: true });
|
|
1296
|
+
} catch {
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
try {
|
|
1300
|
+
removeEmptyDirectory(directory);
|
|
1301
|
+
} catch {
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1216
1305
|
// src/generated/cli-http-v1.validators.js
|
|
1217
1306
|
var import_ucs2length = __toESM(require_ucs2length(), 1);
|
|
1218
1307
|
var schema33 = { "type": "object", "properties": { "authenticated": { "type": "boolean", "const": true }, "organizationBindingVersion": { "type": "number", "const": 1 }, "captureAuthorityKind": { "type": "string", "enum": ["workos", "service_token"] }, "organizationId": { "anyOf": [{ "type": "string", "minLength": 1 }, { "type": "null" }] }, "workosOrganizationId": { "anyOf": [{ "type": "string", "minLength": 1 }, { "type": "null" }] } }, "required": ["authenticated", "organizationBindingVersion", "captureAuthorityKind", "organizationId", "workosOrganizationId"], "additionalProperties": false };
|
|
@@ -19003,15 +19092,15 @@ validate68.evaluated = { "props": true, "dynamicProps": false, "dynamicItems": f
|
|
|
19003
19092
|
|
|
19004
19093
|
// src/lib/activation.ts
|
|
19005
19094
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
19006
|
-
import { existsSync as
|
|
19095
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
|
|
19007
19096
|
import { homedir as homedir2 } from "os";
|
|
19008
|
-
import { join as
|
|
19097
|
+
import { join as join7 } from "path";
|
|
19009
19098
|
|
|
19010
19099
|
// src/lib/git.ts
|
|
19011
19100
|
import { execFileSync } from "child_process";
|
|
19012
19101
|
import { createHash as createHash4 } from "crypto";
|
|
19013
19102
|
import { lstatSync, realpathSync } from "fs";
|
|
19014
|
-
import { basename, dirname as
|
|
19103
|
+
import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute2, relative, resolve as resolve2, sep } from "path";
|
|
19015
19104
|
var GIT_TIMEOUT_MS = 1e3;
|
|
19016
19105
|
var GITHUB_OWNER_PATTERN = /^(?![A-Za-z0-9-]*--)[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/u;
|
|
19017
19106
|
var GITHUB_REPOSITORY_PATTERN = /^[A-Za-z0-9._-]{1,100}$/u;
|
|
@@ -19121,10 +19210,10 @@ function hasProjectPrimInstall(cwd) {
|
|
|
19121
19210
|
const root = gitToplevel(cwd);
|
|
19122
19211
|
if (root === null || root === homedir2()) return false;
|
|
19123
19212
|
for (const rel of PROJECT_INSTALL_FILES) {
|
|
19124
|
-
const path =
|
|
19125
|
-
if (
|
|
19213
|
+
const path = join7(root, rel);
|
|
19214
|
+
if (existsSync5(path)) {
|
|
19126
19215
|
try {
|
|
19127
|
-
const content =
|
|
19216
|
+
const content = readFileSync5(path, "utf-8");
|
|
19128
19217
|
if (PRIM_HOOK_BINS.some((bin) => content.includes(bin))) return true;
|
|
19129
19218
|
} catch {
|
|
19130
19219
|
}
|
|
@@ -19393,6 +19482,13 @@ function parseOrganizationBinding(value) {
|
|
|
19393
19482
|
}
|
|
19394
19483
|
};
|
|
19395
19484
|
}
|
|
19485
|
+
function summarizeRetainedBuckets(retained) {
|
|
19486
|
+
const counts = /* @__PURE__ */ new Map();
|
|
19487
|
+
for (const item of retained) {
|
|
19488
|
+
counts.set(item.reason, (counts.get(item.reason) ?? 0) + 1);
|
|
19489
|
+
}
|
|
19490
|
+
return [...counts].sort(([left], [right]) => left.localeCompare(right)).map(([reason, count]) => `${reason}:${String(count)}`).join(", ");
|
|
19491
|
+
}
|
|
19396
19492
|
function classifyJournalBuckets(buckets, binding) {
|
|
19397
19493
|
const deliverableBuckets = /* @__PURE__ */ new Set();
|
|
19398
19494
|
const retainedBuckets = [];
|
|
@@ -19467,17 +19563,17 @@ async function inspectJournalDelivery(buckets, options = {}) {
|
|
|
19467
19563
|
import {
|
|
19468
19564
|
appendFileSync,
|
|
19469
19565
|
closeSync as closeSync3,
|
|
19470
|
-
existsSync as
|
|
19566
|
+
existsSync as existsSync6,
|
|
19471
19567
|
fstatSync,
|
|
19472
19568
|
mkdirSync as mkdirSync5,
|
|
19473
19569
|
openSync as openSync3,
|
|
19474
19570
|
opendirSync,
|
|
19475
|
-
readFileSync as
|
|
19571
|
+
readFileSync as readFileSync6,
|
|
19476
19572
|
readSync,
|
|
19477
|
-
readdirSync
|
|
19573
|
+
readdirSync as readdirSync2
|
|
19478
19574
|
} from "fs";
|
|
19479
|
-
import { dirname as
|
|
19480
|
-
var JOURNAL_DIR =
|
|
19575
|
+
import { dirname as dirname7, join as join8 } from "path";
|
|
19576
|
+
var JOURNAL_DIR = join8(primConfigDirectory(), "moves");
|
|
19481
19577
|
var JOURNAL_BASENAME = "journal.ndjson";
|
|
19482
19578
|
var JOURNAL_STATS_SAMPLE_BYTES = 64 * 1024;
|
|
19483
19579
|
var PENDING_STATS_MAX_FILES = 128;
|
|
@@ -19489,7 +19585,7 @@ function envSlug(apiUrl) {
|
|
|
19489
19585
|
return slug;
|
|
19490
19586
|
}
|
|
19491
19587
|
function currentEnvDir() {
|
|
19492
|
-
return
|
|
19588
|
+
return join8(JOURNAL_DIR, envSlug(getSiteUrl()));
|
|
19493
19589
|
}
|
|
19494
19590
|
function parseMoves(content) {
|
|
19495
19591
|
const moves = [];
|
|
@@ -19507,15 +19603,15 @@ function parseMoves(content) {
|
|
|
19507
19603
|
function listBuckets() {
|
|
19508
19604
|
const out = [];
|
|
19509
19605
|
const envDir = currentEnvDir();
|
|
19510
|
-
if (!
|
|
19606
|
+
if (!existsSync6(envDir)) {
|
|
19511
19607
|
return out;
|
|
19512
19608
|
}
|
|
19513
|
-
for (const entry of
|
|
19609
|
+
for (const entry of readdirSync2(envDir, { withFileTypes: true })) {
|
|
19514
19610
|
if (!entry.isDirectory()) {
|
|
19515
19611
|
continue;
|
|
19516
19612
|
}
|
|
19517
|
-
const path =
|
|
19518
|
-
if (
|
|
19613
|
+
const path = join8(envDir, entry.name, JOURNAL_BASENAME);
|
|
19614
|
+
if (existsSync6(path)) {
|
|
19519
19615
|
out.push({ bucket: entry.name, path });
|
|
19520
19616
|
}
|
|
19521
19617
|
}
|
|
@@ -19532,22 +19628,23 @@ function oldestCapturedAt(moves) {
|
|
|
19532
19628
|
}
|
|
19533
19629
|
return oldest;
|
|
19534
19630
|
}
|
|
19535
|
-
function sampleJournalFile(path, maxBytes = JOURNAL_STATS_SAMPLE_BYTES) {
|
|
19631
|
+
function sampleJournalFile(path, maxBytes = JOURNAL_STATS_SAMPLE_BYTES, options = {}) {
|
|
19536
19632
|
const fd = openSync3(path, "r");
|
|
19537
19633
|
try {
|
|
19538
19634
|
const initial = fstatSync(fd);
|
|
19539
|
-
const
|
|
19635
|
+
const start = options.fromDrainCheckpoint ? drainResumeOffset(path, rotationIdentity(initial)) : 0;
|
|
19636
|
+
const target = Math.min(initial.size - start, Math.max(0, maxBytes));
|
|
19540
19637
|
const buffer = Buffer.allocUnsafe(target);
|
|
19541
19638
|
let sampledBytes = 0;
|
|
19542
19639
|
while (sampledBytes < target) {
|
|
19543
|
-
const count = readSync(fd, buffer, sampledBytes, target - sampledBytes, sampledBytes);
|
|
19640
|
+
const count = readSync(fd, buffer, sampledBytes, target - sampledBytes, start + sampledBytes);
|
|
19544
19641
|
if (count === 0) {
|
|
19545
19642
|
break;
|
|
19546
19643
|
}
|
|
19547
19644
|
sampledBytes += count;
|
|
19548
19645
|
}
|
|
19549
19646
|
const stat = fstatSync(fd);
|
|
19550
|
-
const sampled = stat.size > sampledBytes;
|
|
19647
|
+
const sampled = stat.size - start > sampledBytes;
|
|
19551
19648
|
let content = buffer.subarray(0, sampledBytes).toString("utf-8");
|
|
19552
19649
|
if (sampled) {
|
|
19553
19650
|
const lastNewline = content.lastIndexOf("\n");
|
|
@@ -19557,6 +19654,7 @@ function sampleJournalFile(path, maxBytes = JOURNAL_STATS_SAMPLE_BYTES) {
|
|
|
19557
19654
|
const moves = parseMoves(content);
|
|
19558
19655
|
return {
|
|
19559
19656
|
sizeBytes: stat.size,
|
|
19657
|
+
pendingBytes: Math.max(0, stat.size - start),
|
|
19560
19658
|
mtimeMs: stat.mtimeMs,
|
|
19561
19659
|
lineCount: sampled && stat.size > 0 ? Math.max(1, lines.length) : lines.length,
|
|
19562
19660
|
oldestCapturedAt: oldestCapturedAt(moves),
|
|
@@ -19575,7 +19673,7 @@ function parseFlushingPid(name) {
|
|
|
19575
19673
|
return Number.isSafeInteger(pid) && pid > 0 ? pid : void 0;
|
|
19576
19674
|
}
|
|
19577
19675
|
function listFlushingInDir(dir, bucket, options = {}) {
|
|
19578
|
-
if (!
|
|
19676
|
+
if (!existsSync6(dir)) {
|
|
19579
19677
|
return [];
|
|
19580
19678
|
}
|
|
19581
19679
|
const out = [];
|
|
@@ -19589,10 +19687,10 @@ function listFlushingInDir(dir, bucket, options = {}) {
|
|
|
19589
19687
|
entry = entries.readSync();
|
|
19590
19688
|
continue;
|
|
19591
19689
|
}
|
|
19592
|
-
const path =
|
|
19690
|
+
const path = join8(dir, entry.name);
|
|
19593
19691
|
let sample;
|
|
19594
19692
|
try {
|
|
19595
|
-
sample = sampleJournalFile(path, remainingBytes);
|
|
19693
|
+
sample = sampleJournalFile(path, remainingBytes, { fromDrainCheckpoint: true });
|
|
19596
19694
|
} catch (err) {
|
|
19597
19695
|
if (err.code === "ENOENT") {
|
|
19598
19696
|
entry = entries.readSync();
|
|
@@ -19616,18 +19714,18 @@ function listFlushingInDir(dir, bucket, options = {}) {
|
|
|
19616
19714
|
}
|
|
19617
19715
|
function listFlushing(options = {}) {
|
|
19618
19716
|
const envDir = currentEnvDir();
|
|
19619
|
-
if (!
|
|
19717
|
+
if (!existsSync6(envDir)) {
|
|
19620
19718
|
return [];
|
|
19621
19719
|
}
|
|
19622
19720
|
const out = [];
|
|
19623
19721
|
let remainingBytes = options.sampleBytes ?? JOURNAL_STATS_SAMPLE_BYTES;
|
|
19624
19722
|
const maxFiles = options.maxFiles ?? Number.POSITIVE_INFINITY;
|
|
19625
|
-
for (const entry of
|
|
19723
|
+
for (const entry of readdirSync2(envDir, { withFileTypes: true })) {
|
|
19626
19724
|
if (out.length >= maxFiles) {
|
|
19627
19725
|
break;
|
|
19628
19726
|
}
|
|
19629
19727
|
if (entry.isDirectory()) {
|
|
19630
|
-
const found = listFlushingInDir(
|
|
19728
|
+
const found = listFlushingInDir(join8(envDir, entry.name), entry.name, {
|
|
19631
19729
|
sampleBytes: remainingBytes,
|
|
19632
19730
|
maxFiles: maxFiles - out.length
|
|
19633
19731
|
});
|
|
@@ -19640,6 +19738,17 @@ function listFlushing(options = {}) {
|
|
|
19640
19738
|
}
|
|
19641
19739
|
return out;
|
|
19642
19740
|
}
|
|
19741
|
+
function sweepOrphanedDrainProgress() {
|
|
19742
|
+
const envDir = currentEnvDir();
|
|
19743
|
+
if (!existsSync6(envDir)) {
|
|
19744
|
+
return;
|
|
19745
|
+
}
|
|
19746
|
+
for (const entry of readdirSync2(envDir, { withFileTypes: true })) {
|
|
19747
|
+
if (entry.isDirectory()) {
|
|
19748
|
+
sweepOrphanedDrainCheckpoints(join8(envDir, entry.name));
|
|
19749
|
+
}
|
|
19750
|
+
}
|
|
19751
|
+
}
|
|
19643
19752
|
function bucketStats(options = {}) {
|
|
19644
19753
|
const out = [];
|
|
19645
19754
|
let remainingBytes = options.sampleBytes ?? JOURNAL_STATS_SAMPLE_BYTES;
|
|
@@ -19686,8 +19795,12 @@ function pendingJournalStats() {
|
|
|
19686
19795
|
|
|
19687
19796
|
// src/flusher.ts
|
|
19688
19797
|
var BATCH_SIZE = 500;
|
|
19798
|
+
var BATCH_MAX_BYTES = 1048576;
|
|
19689
19799
|
var HTTP_TIMEOUT_MS = 1e4;
|
|
19690
19800
|
var ORPHAN_QUARANTINE_MS = 6e4;
|
|
19801
|
+
function exceedsBatchBytes(count, bytes, nextBytes, maxBytes) {
|
|
19802
|
+
return count > 0 && bytes + nextBytes > maxBytes;
|
|
19803
|
+
}
|
|
19691
19804
|
function addDrainCounts(left, right) {
|
|
19692
19805
|
return {
|
|
19693
19806
|
flushed: left.flushed + right.flushed,
|
|
@@ -19737,23 +19850,25 @@ function mayBeBatchSizeFailure(error) {
|
|
|
19737
19850
|
}
|
|
19738
19851
|
return error instanceof Error && error.name === "TimeoutError";
|
|
19739
19852
|
}
|
|
19740
|
-
async function* rawJournalLines(input) {
|
|
19853
|
+
async function* rawJournalLines(input, offset) {
|
|
19741
19854
|
let pending = [];
|
|
19742
19855
|
let pendingBytes = 0;
|
|
19856
|
+
let end = offset;
|
|
19743
19857
|
for await (const value of input) {
|
|
19744
19858
|
const chunk = Buffer.isBuffer(value) ? value : Buffer.from(value);
|
|
19745
19859
|
let start = 0;
|
|
19746
19860
|
let newline = chunk.indexOf(10, start);
|
|
19747
19861
|
while (newline !== -1) {
|
|
19748
19862
|
const tail = chunk.subarray(start, newline + 1);
|
|
19749
|
-
|
|
19750
|
-
|
|
19751
|
-
} else {
|
|
19863
|
+
let line = tail;
|
|
19864
|
+
if (pending.length > 0) {
|
|
19752
19865
|
pending.push(tail);
|
|
19753
|
-
|
|
19866
|
+
line = Buffer.concat(pending, pendingBytes + tail.length);
|
|
19754
19867
|
pending = [];
|
|
19755
19868
|
pendingBytes = 0;
|
|
19756
19869
|
}
|
|
19870
|
+
end += line.length;
|
|
19871
|
+
yield { bytes: line, end };
|
|
19757
19872
|
start = newline + 1;
|
|
19758
19873
|
newline = chunk.indexOf(10, start);
|
|
19759
19874
|
}
|
|
@@ -19764,18 +19879,43 @@ async function* rawJournalLines(input) {
|
|
|
19764
19879
|
}
|
|
19765
19880
|
}
|
|
19766
19881
|
if (pendingBytes > 0) {
|
|
19767
|
-
|
|
19882
|
+
const bytes = pending.length === 1 ? pending[0] : Buffer.concat(pending, pendingBytes);
|
|
19883
|
+
yield { bytes, end: end + pendingBytes };
|
|
19768
19884
|
}
|
|
19769
19885
|
}
|
|
19886
|
+
var drainProgressWarned = false;
|
|
19770
19887
|
async function drainFlushingPath(flushingPath, client2, binding, options = {}) {
|
|
19888
|
+
const rotation = rotationIdentity(statSync2(flushingPath));
|
|
19889
|
+
const resumeAt = drainResumeOffset(flushingPath, rotation);
|
|
19890
|
+
const recordProgress = (end) => {
|
|
19891
|
+
if (end === void 0 || end >= rotation.size) {
|
|
19892
|
+
return;
|
|
19893
|
+
}
|
|
19894
|
+
try {
|
|
19895
|
+
writeDrainCheckpoint(flushingPath, { v: 2, offset: end, ...rotation });
|
|
19896
|
+
drainProgressWarned = false;
|
|
19897
|
+
} catch (error) {
|
|
19898
|
+
if (!drainProgressWarned) {
|
|
19899
|
+
drainProgressWarned = true;
|
|
19900
|
+
process.stderr.write(
|
|
19901
|
+
`[prim] could not record drain progress: ${error instanceof Error ? error.message : String(error)}
|
|
19902
|
+
`
|
|
19903
|
+
);
|
|
19904
|
+
}
|
|
19905
|
+
}
|
|
19906
|
+
};
|
|
19907
|
+
const reportProgress = (delta) => {
|
|
19908
|
+
options.onProgress?.(delta);
|
|
19909
|
+
return delta;
|
|
19910
|
+
};
|
|
19771
19911
|
const postBatch = async (batch2) => {
|
|
19912
|
+
const moves = batch2.map((entry) => entry.move);
|
|
19772
19913
|
try {
|
|
19773
|
-
const request2 = buildOrganizationBoundMoveRequest(
|
|
19914
|
+
const request2 = buildOrganizationBoundMoveRequest(moves, binding);
|
|
19774
19915
|
const response = await client2.post("/api/cli/moves/ingest", request2, {
|
|
19775
19916
|
signal: AbortSignal.timeout(HTTP_TIMEOUT_MS)
|
|
19776
19917
|
});
|
|
19777
|
-
requireDurableIngestAcknowledgement(response,
|
|
19778
|
-
return { flushed: batch2.length, quarantined: 0 };
|
|
19918
|
+
requireDurableIngestAcknowledgement(response, moves.length, collectionScopeRoots(moves));
|
|
19779
19919
|
} catch (error) {
|
|
19780
19920
|
const reason = deadLetterReason(error);
|
|
19781
19921
|
if (!(reason || mayBeBatchSizeFailure(error))) {
|
|
@@ -19790,33 +19930,43 @@ async function drainFlushingPath(flushingPath, client2, binding, options = {}) {
|
|
|
19790
19930
|
if (!reason) {
|
|
19791
19931
|
throw error;
|
|
19792
19932
|
}
|
|
19793
|
-
const [
|
|
19933
|
+
const [entry] = batch2;
|
|
19794
19934
|
const quarantined = quarantineMove(
|
|
19795
19935
|
flushingPath,
|
|
19796
|
-
move,
|
|
19936
|
+
entry.move,
|
|
19797
19937
|
reason,
|
|
19798
19938
|
Date.now,
|
|
19799
19939
|
options.deadLetterPersistence
|
|
19800
19940
|
);
|
|
19941
|
+
recordProgress(entry.end);
|
|
19801
19942
|
process.stderr.write(
|
|
19802
19943
|
`[prim] quarantined rejected move ${quarantined.quarantineId.slice(0, 12)} (${reason})
|
|
19803
19944
|
`
|
|
19804
19945
|
);
|
|
19805
|
-
return { flushed: 0, quarantined: 1 };
|
|
19946
|
+
return reportProgress({ flushed: 0, quarantined: 1 });
|
|
19806
19947
|
}
|
|
19948
|
+
recordProgress(batch2.at(-1)?.end);
|
|
19949
|
+
return reportProgress({ flushed: batch2.length, quarantined: 0 });
|
|
19807
19950
|
};
|
|
19808
|
-
const input = createReadStream(flushingPath);
|
|
19951
|
+
const input = createReadStream(flushingPath, { start: resumeAt });
|
|
19809
19952
|
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
19810
19953
|
let batch = [];
|
|
19954
|
+
let batchBytes = 0;
|
|
19811
19955
|
let counts = { flushed: 0, quarantined: 0 };
|
|
19956
|
+
const postPending = async () => {
|
|
19957
|
+
counts = addDrainCounts(counts, await postBatch(batch));
|
|
19958
|
+
batch = [];
|
|
19959
|
+
batchBytes = 0;
|
|
19960
|
+
};
|
|
19812
19961
|
try {
|
|
19813
|
-
for await (const rawLine of rawJournalLines(input)) {
|
|
19962
|
+
for await (const { bytes: rawLine, end } of rawJournalLines(input, resumeAt)) {
|
|
19814
19963
|
if (rawLine.length === 1 && rawLine[0] === 10 || rawLine.length === 2 && rawLine[0] === 13 && rawLine[1] === 10) {
|
|
19815
19964
|
continue;
|
|
19816
19965
|
}
|
|
19966
|
+
let move;
|
|
19817
19967
|
try {
|
|
19818
19968
|
const line = decoder.decode(rawLine);
|
|
19819
|
-
|
|
19969
|
+
move = JSON.parse(line);
|
|
19820
19970
|
} catch {
|
|
19821
19971
|
const quarantined = quarantineRawLine(
|
|
19822
19972
|
flushingPath,
|
|
@@ -19828,24 +19978,35 @@ async function drainFlushingPath(flushingPath, client2, binding, options = {}) {
|
|
|
19828
19978
|
`[prim] quarantined invalid journal line ${quarantined.quarantineId.slice(0, 12)} (invalid_move)
|
|
19829
19979
|
`
|
|
19830
19980
|
);
|
|
19831
|
-
counts
|
|
19981
|
+
counts = addDrainCounts(counts, reportProgress({ flushed: 0, quarantined: 1 }));
|
|
19982
|
+
if (batch.length === 0) {
|
|
19983
|
+
recordProgress(end);
|
|
19984
|
+
}
|
|
19832
19985
|
continue;
|
|
19833
19986
|
}
|
|
19834
|
-
if (batch.length
|
|
19835
|
-
|
|
19836
|
-
|
|
19987
|
+
if (exceedsBatchBytes(batch.length, batchBytes, rawLine.length, BATCH_MAX_BYTES)) {
|
|
19988
|
+
await postPending();
|
|
19989
|
+
}
|
|
19990
|
+
batch.push({ move, end });
|
|
19991
|
+
batchBytes += rawLine.length;
|
|
19992
|
+
if (batch.length >= BATCH_SIZE) {
|
|
19993
|
+
await postPending();
|
|
19837
19994
|
}
|
|
19838
19995
|
}
|
|
19839
19996
|
if (batch.length > 0) {
|
|
19840
|
-
|
|
19997
|
+
await postPending();
|
|
19841
19998
|
}
|
|
19842
19999
|
} finally {
|
|
19843
20000
|
input.destroy();
|
|
19844
20001
|
}
|
|
19845
20002
|
unlinkSync(flushingPath);
|
|
20003
|
+
try {
|
|
20004
|
+
removeDrainCheckpoint(flushingPath);
|
|
20005
|
+
} catch {
|
|
20006
|
+
}
|
|
19846
20007
|
return counts;
|
|
19847
20008
|
}
|
|
19848
|
-
async function drainPath(path, client2, binding) {
|
|
20009
|
+
async function drainPath(path, client2, binding, onProgress) {
|
|
19849
20010
|
const tmpPath = `${path}.flushing.${String(Date.now())}.${String(process.pid)}`;
|
|
19850
20011
|
try {
|
|
19851
20012
|
renameSync3(path, tmpPath);
|
|
@@ -19855,7 +20016,19 @@ async function drainPath(path, client2, binding) {
|
|
|
19855
20016
|
}
|
|
19856
20017
|
throw err;
|
|
19857
20018
|
}
|
|
19858
|
-
return drainFlushingPath(tmpPath, client2, binding);
|
|
20019
|
+
return drainFlushingPath(tmpPath, client2, binding, { onProgress });
|
|
20020
|
+
}
|
|
20021
|
+
async function settleDrain(drain) {
|
|
20022
|
+
let progress = { flushed: 0, quarantined: 0 };
|
|
20023
|
+
try {
|
|
20024
|
+
return {
|
|
20025
|
+
counts: await drain((delta) => {
|
|
20026
|
+
progress = addDrainCounts(progress, delta);
|
|
20027
|
+
})
|
|
20028
|
+
};
|
|
20029
|
+
} catch (error) {
|
|
20030
|
+
return { counts: progress, failure: { error } };
|
|
20031
|
+
}
|
|
19859
20032
|
}
|
|
19860
20033
|
function selectRecoverable(files, now, opts = {}) {
|
|
19861
20034
|
const quarantineMs = opts.quarantineMs ?? ORPHAN_QUARANTINE_MS;
|
|
@@ -19872,7 +20045,8 @@ async function recoverOrphans(candidates, options) {
|
|
|
19872
20045
|
flushed: 0,
|
|
19873
20046
|
quarantined: 0,
|
|
19874
20047
|
errors: [],
|
|
19875
|
-
failedBuckets: /* @__PURE__ */ new Set()
|
|
20048
|
+
failedBuckets: /* @__PURE__ */ new Set(),
|
|
20049
|
+
failedDrainFlushed: 0
|
|
19876
20050
|
};
|
|
19877
20051
|
const recoverable = selectRecoverable(candidates, options.now ?? Date.now(), {
|
|
19878
20052
|
ownerPid: options.ownerPid ?? process.pid,
|
|
@@ -19883,28 +20057,41 @@ async function recoverOrphans(candidates, options) {
|
|
|
19883
20057
|
if (summary.failedBuckets.has(file.bucket)) {
|
|
19884
20058
|
continue;
|
|
19885
20059
|
}
|
|
19886
|
-
|
|
19887
|
-
|
|
19888
|
-
|
|
19889
|
-
|
|
19890
|
-
|
|
19891
|
-
summary.errors.push(
|
|
20060
|
+
const { counts, failure } = await settleDrain((onProgress) => drain(file.path, onProgress));
|
|
20061
|
+
summary.flushed += counts.flushed;
|
|
20062
|
+
summary.quarantined += counts.quarantined;
|
|
20063
|
+
if (failure) {
|
|
20064
|
+
summary.failedDrainFlushed += counts.flushed;
|
|
20065
|
+
summary.errors.push(failure.error);
|
|
19892
20066
|
summary.failedBuckets.add(file.bucket);
|
|
19893
20067
|
}
|
|
19894
20068
|
}
|
|
19895
20069
|
return summary;
|
|
19896
20070
|
}
|
|
19897
20071
|
var FlushError = class extends Error {
|
|
20072
|
+
/** Moves the whole sweep acknowledged, across every drain it ran. */
|
|
19898
20073
|
flushed;
|
|
19899
20074
|
quarantined;
|
|
19900
|
-
|
|
20075
|
+
/** Buckets the failed sweep held back without sending, as on success. */
|
|
20076
|
+
retained;
|
|
20077
|
+
/**
|
|
20078
|
+
* Of `flushed`, the Moves acknowledged by the drains that failed, before
|
|
20079
|
+
* they failed. Only this shows the failure itself is advancing: `flushed`
|
|
20080
|
+
* also counts other buckets' drains, so a bucket whose drain fails every
|
|
20081
|
+
* time would read as progress while a sibling bucket kept delivering.
|
|
20082
|
+
*/
|
|
20083
|
+
failedDrainFlushed;
|
|
20084
|
+
constructor(cause, flushed, quarantined, retained = [], failedDrainFlushed = 0) {
|
|
19901
20085
|
super(cause instanceof Error ? cause.message : String(cause), { cause });
|
|
19902
20086
|
this.name = "FlushError";
|
|
19903
20087
|
this.flushed = flushed;
|
|
19904
20088
|
this.quarantined = quarantined;
|
|
20089
|
+
this.retained = retained;
|
|
20090
|
+
this.failedDrainFlushed = failedDrainFlushed;
|
|
19905
20091
|
}
|
|
19906
20092
|
};
|
|
19907
20093
|
async function flushOnce() {
|
|
20094
|
+
sweepOrphanedDrainProgress();
|
|
19908
20095
|
const orphanCandidates = listFlushing({ sampleBytes: 0 });
|
|
19909
20096
|
const liveBuckets = listBuckets();
|
|
19910
20097
|
if (orphanCandidates.length === 0 && liveBuckets.length === 0) {
|
|
@@ -19921,10 +20108,11 @@ async function flushOnce() {
|
|
|
19921
20108
|
const binding = inspection.binding;
|
|
19922
20109
|
const recovered = await recoverOrphans(
|
|
19923
20110
|
orphanCandidates.filter((file) => inspection.deliverableBuckets.has(file.bucket)),
|
|
19924
|
-
{ drain: (path) => drainFlushingPath(path, client2, binding) }
|
|
20111
|
+
{ drain: (path, onProgress) => drainFlushingPath(path, client2, binding, { onProgress }) }
|
|
19925
20112
|
);
|
|
19926
20113
|
let total = recovered.flushed;
|
|
19927
20114
|
let quarantined = recovered.quarantined;
|
|
20115
|
+
let failedDrainFlushed = recovered.failedDrainFlushed;
|
|
19928
20116
|
const errors = recovered.errors;
|
|
19929
20117
|
for (const { bucket, path } of liveBuckets) {
|
|
19930
20118
|
if (!inspection.deliverableBuckets.has(bucket)) {
|
|
@@ -19933,21 +20121,29 @@ async function flushOnce() {
|
|
|
19933
20121
|
if (recovered.failedBuckets.has(bucket)) {
|
|
19934
20122
|
continue;
|
|
19935
20123
|
}
|
|
19936
|
-
|
|
19937
|
-
|
|
19938
|
-
|
|
19939
|
-
|
|
19940
|
-
|
|
19941
|
-
|
|
20124
|
+
const { counts, failure } = await settleDrain(
|
|
20125
|
+
(onProgress) => drainPath(path, client2, binding, onProgress)
|
|
20126
|
+
);
|
|
20127
|
+
total += counts.flushed;
|
|
20128
|
+
quarantined += counts.quarantined;
|
|
20129
|
+
if (failure) {
|
|
20130
|
+
failedDrainFlushed += counts.flushed;
|
|
20131
|
+
errors.push(failure.error);
|
|
19942
20132
|
}
|
|
19943
20133
|
}
|
|
19944
20134
|
if (errors.length > 0) {
|
|
19945
|
-
throw new FlushError(
|
|
20135
|
+
throw new FlushError(
|
|
20136
|
+
errors[0],
|
|
20137
|
+
total,
|
|
20138
|
+
quarantined,
|
|
20139
|
+
inspection.retainedBuckets,
|
|
20140
|
+
failedDrainFlushed
|
|
20141
|
+
);
|
|
19946
20142
|
}
|
|
19947
20143
|
return inspection.retainedBuckets.length > 0 ? { flushed: total, quarantined, retained: inspection.retainedBuckets } : { flushed: total, quarantined };
|
|
19948
20144
|
}
|
|
19949
20145
|
var flushInFlight;
|
|
19950
|
-
var FLUSH_LOCK_PATH =
|
|
20146
|
+
var FLUSH_LOCK_PATH = join9(dirname8(JOURNAL_DIR), ".flush.lock");
|
|
19951
20147
|
var FLUSH_LOCK_TIMEOUT_MS = 250;
|
|
19952
20148
|
function isFlushLockContended(error) {
|
|
19953
20149
|
return error instanceof Error && error.message.startsWith("timed out waiting for file lock");
|
|
@@ -19971,6 +20167,30 @@ function flush() {
|
|
|
19971
20167
|
flushInFlight = attempt;
|
|
19972
20168
|
return attempt;
|
|
19973
20169
|
}
|
|
20170
|
+
function hasPendingDrainWork(stats) {
|
|
20171
|
+
return stats.pendingCount > 0 || stats.sampled || stats.strandedFileCount > 0;
|
|
20172
|
+
}
|
|
20173
|
+
|
|
20174
|
+
// src/lib/delivery-backlog.ts
|
|
20175
|
+
var MS_PER_SECOND = 1e3;
|
|
20176
|
+
var MS_PER_MINUTE = 60 * MS_PER_SECOND;
|
|
20177
|
+
var MS_PER_HOUR = 60 * MS_PER_MINUTE;
|
|
20178
|
+
var MS_PER_DAY = 24 * MS_PER_HOUR;
|
|
20179
|
+
function isNonNegativeInteger(value) {
|
|
20180
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
20181
|
+
}
|
|
20182
|
+
function isPositiveInteger(value) {
|
|
20183
|
+
return isNonNegativeInteger(value) && value > 0;
|
|
20184
|
+
}
|
|
20185
|
+
function deliveryBacklogState(ingestion) {
|
|
20186
|
+
if (ingestion?.healthy !== false) return void 0;
|
|
20187
|
+
const failures = ingestion.consecutiveFailures;
|
|
20188
|
+
const failedWithoutProgress = failures !== 0 && !(isPositiveInteger(failures) && isPositiveInteger(ingestion.lastFailedDrainAcknowledgedCount));
|
|
20189
|
+
if (failedWithoutProgress) return "failing";
|
|
20190
|
+
const retained = ingestion.lastRetainedBucketCount;
|
|
20191
|
+
if (!isNonNegativeInteger(retained)) return "failing";
|
|
20192
|
+
return retained > 0 ? "retained" : "draining";
|
|
20193
|
+
}
|
|
19974
20194
|
|
|
19975
20195
|
// src/lib/presence.ts
|
|
19976
20196
|
var DECISION_ORIGIN = "https://app.getprimitive.ai";
|
|
@@ -20046,7 +20266,8 @@ function formatStatusline(version, snapshot, resolveIngestionStatus, options = {
|
|
|
20046
20266
|
if (snapshot.ingestion?.healthy === false) {
|
|
20047
20267
|
const pending = snapshot.ingestion.pendingCount;
|
|
20048
20268
|
const qualifier = snapshot.ingestion.pendingSampled ? "at least " : "";
|
|
20049
|
-
|
|
20269
|
+
const delivery = deliveryBacklogState(snapshot.ingestion) === "draining" && snapshot.heartbeat?.healthy === true ? "draining" : "stalled";
|
|
20270
|
+
return `primitive ${version} (daemon: degraded \xB7 delivery: ${delivery}${typeof pending === "number" ? ` \xB7 ${qualifier}${String(pending)} pending` : ""}${repositorySuffix}${ingestionSuffix(ingestionStatus)})`;
|
|
20050
20271
|
}
|
|
20051
20272
|
if (snapshot.heartbeat?.healthy === false) {
|
|
20052
20273
|
return `primitive ${version} (daemon: degraded \xB7 presence: unavailable${repositorySuffix}${ingestionSuffix(ingestionStatus)})`;
|
|
@@ -20124,8 +20345,8 @@ var StatuslineIngestionCache = class {
|
|
|
20124
20345
|
|
|
20125
20346
|
// src/daemon/client-instance-id.ts
|
|
20126
20347
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
20127
|
-
import { chmodSync as
|
|
20128
|
-
import { join as
|
|
20348
|
+
import { chmodSync as chmodSync4, lstatSync as lstatSync2, readFileSync as readFileSync7 } from "fs";
|
|
20349
|
+
import { join as join10 } from "path";
|
|
20129
20350
|
var CONFIG_DIRECTORY_MODE = 448;
|
|
20130
20351
|
var INSTANCE_FILE_MODE = 384;
|
|
20131
20352
|
var INSTANCE_ENTROPY_BYTES = 32;
|
|
@@ -20142,7 +20363,7 @@ function ensurePrivateConfigDirectory(configDir) {
|
|
|
20142
20363
|
if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
|
|
20143
20364
|
throw new Error("prim config path must be a private directory");
|
|
20144
20365
|
}
|
|
20145
|
-
|
|
20366
|
+
chmodSync4(configDir, CONFIG_DIRECTORY_MODE);
|
|
20146
20367
|
syncDirectory(configDir);
|
|
20147
20368
|
}
|
|
20148
20369
|
function readExisting(path) {
|
|
@@ -20158,11 +20379,11 @@ function readExisting(path) {
|
|
|
20158
20379
|
if (!metadata.isFile() || metadata.isSymbolicLink()) {
|
|
20159
20380
|
throw new Error("stored client instance identity is invalid");
|
|
20160
20381
|
}
|
|
20161
|
-
|
|
20382
|
+
chmodSync4(path, INSTANCE_FILE_MODE);
|
|
20162
20383
|
if (metadata.size > MAX_STORED_BYTES) {
|
|
20163
20384
|
throw new Error("stored client instance identity is invalid");
|
|
20164
20385
|
}
|
|
20165
|
-
const raw =
|
|
20386
|
+
const raw = readFileSync7(path, "utf8");
|
|
20166
20387
|
const value = raw.endsWith("\n") ? raw.slice(0, -1) : raw;
|
|
20167
20388
|
if (raw !== value && raw !== `${value}
|
|
20168
20389
|
` || !isClientInstanceId(value)) {
|
|
@@ -20176,8 +20397,8 @@ function atomicWrite2(path, value) {
|
|
|
20176
20397
|
}
|
|
20177
20398
|
async function getOrCreateClientInstanceId(options) {
|
|
20178
20399
|
const configDir = options.configDir;
|
|
20179
|
-
const path =
|
|
20180
|
-
const lockPath =
|
|
20400
|
+
const path = join10(configDir, "client_instance_id");
|
|
20401
|
+
const lockPath = join10(configDir, "client-instance.lock");
|
|
20181
20402
|
ensurePrivateConfigDirectory(configDir);
|
|
20182
20403
|
return withFileLock(
|
|
20183
20404
|
lockPath,
|
|
@@ -20201,7 +20422,7 @@ async function getOrCreateClientInstanceId(options) {
|
|
|
20201
20422
|
|
|
20202
20423
|
// src/daemon/client.ts
|
|
20203
20424
|
import { createConnection } from "net";
|
|
20204
|
-
import { join as
|
|
20425
|
+
import { join as join11 } from "path";
|
|
20205
20426
|
|
|
20206
20427
|
// src/daemon/principal.ts
|
|
20207
20428
|
import { createHash as createHash5 } from "crypto";
|
|
@@ -20278,7 +20499,7 @@ function parseDaemonResponseEnvelope(value) {
|
|
|
20278
20499
|
}
|
|
20279
20500
|
|
|
20280
20501
|
// src/daemon/client.ts
|
|
20281
|
-
var SOCK_PATH =
|
|
20502
|
+
var SOCK_PATH = join11(primConfigDirectory(), "sock");
|
|
20282
20503
|
var DEFAULT_TIMEOUT_MS2 = 250;
|
|
20283
20504
|
var nextRequestId = 1;
|
|
20284
20505
|
function daemonRequest(method, params = {}, opts = {}) {
|
|
@@ -20547,8 +20768,8 @@ function assertCallerEnvMatches(callerEnv, boundEnv) {
|
|
|
20547
20768
|
}
|
|
20548
20769
|
|
|
20549
20770
|
// src/daemon/health.ts
|
|
20550
|
-
import { chmodSync as
|
|
20551
|
-
import { dirname as
|
|
20771
|
+
import { chmodSync as chmodSync5, existsSync as existsSync7, mkdirSync as mkdirSync6, renameSync as renameSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
20772
|
+
import { dirname as dirname9, join as join12 } from "path";
|
|
20552
20773
|
var CONFIG_DIR_MODE2 = 448;
|
|
20553
20774
|
var HEALTH_FILE_MODE = 384;
|
|
20554
20775
|
var INGESTION_SLA_MS = 3e4;
|
|
@@ -20556,12 +20777,13 @@ var HEARTBEAT_FRESH_MS = 9e4;
|
|
|
20556
20777
|
var HEARTBEAT_RETRY_CAP_MS = 3e4;
|
|
20557
20778
|
var INGESTION_RETRY_BASE_MS = 5e3;
|
|
20558
20779
|
var INGESTION_RETRY_CAP_MS = 15e3;
|
|
20559
|
-
var DAEMON_HEALTH_PATH =
|
|
20560
|
-
function createDaemonHealthState(version, pid, startedAt2) {
|
|
20780
|
+
var DAEMON_HEALTH_PATH = join12(primConfigDirectory(), "daemon-health.json");
|
|
20781
|
+
function createDaemonHealthState(version, pid, startedAt2, siteUrl) {
|
|
20561
20782
|
return {
|
|
20562
20783
|
schemaVersion: 1,
|
|
20563
20784
|
version,
|
|
20564
20785
|
pid,
|
|
20786
|
+
siteUrl,
|
|
20565
20787
|
startedAt: startedAt2,
|
|
20566
20788
|
updatedAt: startedAt2,
|
|
20567
20789
|
healthy: false,
|
|
@@ -20572,7 +20794,9 @@ function createDaemonHealthState(version, pid, startedAt2) {
|
|
|
20572
20794
|
pendingCount: 0,
|
|
20573
20795
|
pendingSampled: false,
|
|
20574
20796
|
strandedCount: 0,
|
|
20575
|
-
lastAcknowledgedCount: 0
|
|
20797
|
+
lastAcknowledgedCount: 0,
|
|
20798
|
+
lastFailedDrainAcknowledgedCount: 0,
|
|
20799
|
+
lastRetainedBucketCount: 0
|
|
20576
20800
|
}
|
|
20577
20801
|
};
|
|
20578
20802
|
}
|
|
@@ -20594,37 +20818,37 @@ function ingestionRetryDelayMs(consecutiveFailures, random = Math.random) {
|
|
|
20594
20818
|
return retryDelayMs(consecutiveFailures, INGESTION_RETRY_CAP_MS, random);
|
|
20595
20819
|
}
|
|
20596
20820
|
function writeDaemonHealthState(state, path = DAEMON_HEALTH_PATH) {
|
|
20597
|
-
const dir =
|
|
20598
|
-
if (!
|
|
20821
|
+
const dir = dirname9(path);
|
|
20822
|
+
if (!existsSync7(dir)) {
|
|
20599
20823
|
mkdirSync6(dir, { recursive: true, mode: CONFIG_DIR_MODE2 });
|
|
20600
20824
|
}
|
|
20601
|
-
const tmp =
|
|
20825
|
+
const tmp = join12(dir, `.${process.pid}.daemon-health.tmp`);
|
|
20602
20826
|
writeFileSync4(tmp, `${JSON.stringify(state, null, 2)}
|
|
20603
20827
|
`, { mode: HEALTH_FILE_MODE });
|
|
20604
|
-
|
|
20828
|
+
chmodSync5(tmp, HEALTH_FILE_MODE);
|
|
20605
20829
|
renameSync4(tmp, path);
|
|
20606
20830
|
}
|
|
20607
20831
|
|
|
20608
20832
|
// src/daemon/instance-lock.ts
|
|
20609
20833
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
20610
20834
|
import {
|
|
20611
|
-
chmodSync as
|
|
20612
|
-
existsSync as
|
|
20835
|
+
chmodSync as chmodSync6,
|
|
20836
|
+
existsSync as existsSync8,
|
|
20613
20837
|
mkdirSync as mkdirSync7,
|
|
20614
|
-
readFileSync as
|
|
20838
|
+
readFileSync as readFileSync8,
|
|
20615
20839
|
renameSync as renameSync5,
|
|
20616
|
-
rmSync as
|
|
20617
|
-
statSync as
|
|
20840
|
+
rmSync as rmSync6,
|
|
20841
|
+
statSync as statSync3,
|
|
20618
20842
|
unlinkSync as unlinkSync2,
|
|
20619
20843
|
writeFileSync as writeFileSync5
|
|
20620
20844
|
} from "fs";
|
|
20621
|
-
import { join as
|
|
20845
|
+
import { join as join13 } from "path";
|
|
20622
20846
|
var DIR_MODE = 448;
|
|
20623
|
-
var
|
|
20847
|
+
var FILE_MODE4 = 384;
|
|
20624
20848
|
var DAEMON_STARTUP_GRACE_MS = 5e3;
|
|
20625
20849
|
function readLockRecord(path) {
|
|
20626
20850
|
try {
|
|
20627
|
-
const value = JSON.parse(
|
|
20851
|
+
const value = JSON.parse(readFileSync8(path, "utf-8"));
|
|
20628
20852
|
if (Number.isSafeInteger(value.pid) && value.pid > 0 && typeof value.instanceId === "string" && typeof value.startedAt === "number") {
|
|
20629
20853
|
return value;
|
|
20630
20854
|
}
|
|
@@ -20634,23 +20858,23 @@ function readLockRecord(path) {
|
|
|
20634
20858
|
}
|
|
20635
20859
|
function numericPid(path) {
|
|
20636
20860
|
try {
|
|
20637
|
-
const pid = Number(
|
|
20861
|
+
const pid = Number(readFileSync8(path, "utf-8").trim());
|
|
20638
20862
|
return Number.isSafeInteger(pid) && pid > 0 ? pid : void 0;
|
|
20639
20863
|
} catch {
|
|
20640
20864
|
return;
|
|
20641
20865
|
}
|
|
20642
20866
|
}
|
|
20643
20867
|
function readDaemonOwner(configDir) {
|
|
20644
|
-
return readLockRecord(
|
|
20868
|
+
return readLockRecord(join13(configDir, "daemon.lock", "owner.json"));
|
|
20645
20869
|
}
|
|
20646
20870
|
function readDaemonPid(configDir) {
|
|
20647
|
-
const path =
|
|
20871
|
+
const path = join13(configDir, "daemon.pid");
|
|
20648
20872
|
const pid = numericPid(path);
|
|
20649
20873
|
if (pid === void 0) {
|
|
20650
20874
|
return;
|
|
20651
20875
|
}
|
|
20652
20876
|
try {
|
|
20653
|
-
return { pid, mtimeMs:
|
|
20877
|
+
return { pid, mtimeMs: statSync3(path).mtimeMs };
|
|
20654
20878
|
} catch {
|
|
20655
20879
|
return;
|
|
20656
20880
|
}
|
|
@@ -20659,11 +20883,11 @@ function acquireDaemonOwnership(configDir, opts = {}) {
|
|
|
20659
20883
|
const pid = opts.pid ?? process.pid;
|
|
20660
20884
|
const now = opts.now ?? Date.now();
|
|
20661
20885
|
const isAlive = opts.isAlive ?? processIsAlive;
|
|
20662
|
-
const lockDir =
|
|
20663
|
-
const ownerPath2 =
|
|
20664
|
-
const pidPath =
|
|
20886
|
+
const lockDir = join13(configDir, "daemon.lock");
|
|
20887
|
+
const ownerPath2 = join13(lockDir, "owner.json");
|
|
20888
|
+
const pidPath = join13(configDir, "daemon.pid");
|
|
20665
20889
|
const instanceId = randomUUID2();
|
|
20666
|
-
if (!
|
|
20890
|
+
if (!existsSync8(configDir)) {
|
|
20667
20891
|
mkdirSync7(configDir, { recursive: true, mode: DIR_MODE });
|
|
20668
20892
|
}
|
|
20669
20893
|
for (; ; ) {
|
|
@@ -20681,7 +20905,7 @@ function acquireDaemonOwnership(configDir, opts = {}) {
|
|
|
20681
20905
|
if (!existing) {
|
|
20682
20906
|
let mtimeMs;
|
|
20683
20907
|
try {
|
|
20684
|
-
mtimeMs =
|
|
20908
|
+
mtimeMs = statSync3(lockDir).mtimeMs;
|
|
20685
20909
|
} catch (statErr) {
|
|
20686
20910
|
if (statErr.code === "ENOENT") {
|
|
20687
20911
|
continue;
|
|
@@ -20695,7 +20919,7 @@ function acquireDaemonOwnership(configDir, opts = {}) {
|
|
|
20695
20919
|
const stale = `${lockDir}.stale.${String(pid)}.${instanceId}`;
|
|
20696
20920
|
try {
|
|
20697
20921
|
renameSync5(lockDir, stale);
|
|
20698
|
-
|
|
20922
|
+
rmSync6(stale, { recursive: true, force: true });
|
|
20699
20923
|
} catch (renameErr) {
|
|
20700
20924
|
if (renameErr.code !== "ENOENT") {
|
|
20701
20925
|
throw renameErr;
|
|
@@ -20713,15 +20937,15 @@ function acquireDaemonOwnership(configDir, opts = {}) {
|
|
|
20713
20937
|
};
|
|
20714
20938
|
const record = { pid, instanceId, startedAt: now };
|
|
20715
20939
|
writeFileSync5(ownerPath2, `${JSON.stringify(record)}
|
|
20716
|
-
`, { mode:
|
|
20717
|
-
|
|
20940
|
+
`, { mode: FILE_MODE4 });
|
|
20941
|
+
chmodSync6(ownerPath2, FILE_MODE4);
|
|
20718
20942
|
const legacyPid = numericPid(pidPath);
|
|
20719
20943
|
if (legacyPid !== void 0 && legacyPid !== pid && isAlive(legacyPid)) {
|
|
20720
|
-
|
|
20944
|
+
rmSync6(lockDir, { recursive: true, force: true });
|
|
20721
20945
|
throw new Error(`daemon already running (pid=${String(legacyPid)})`);
|
|
20722
20946
|
}
|
|
20723
|
-
writeFileSync5(pidPath, String(pid), { mode:
|
|
20724
|
-
|
|
20947
|
+
writeFileSync5(pidPath, String(pid), { mode: FILE_MODE4 });
|
|
20948
|
+
chmodSync6(pidPath, FILE_MODE4);
|
|
20725
20949
|
return ownership2;
|
|
20726
20950
|
}
|
|
20727
20951
|
function ownsDaemonInstance(ownership2) {
|
|
@@ -20751,7 +20975,7 @@ function releaseDaemonOwnership(ownership2, socketPath) {
|
|
|
20751
20975
|
}
|
|
20752
20976
|
}
|
|
20753
20977
|
}
|
|
20754
|
-
|
|
20978
|
+
rmSync6(ownership2.lockDir, { recursive: true, force: true });
|
|
20755
20979
|
if (cleanupError) {
|
|
20756
20980
|
throw cleanupError;
|
|
20757
20981
|
}
|
|
@@ -20846,7 +21070,7 @@ function parseStatuslineRequest(buffer) {
|
|
|
20846
21070
|
|
|
20847
21071
|
// src/daemon/server.ts
|
|
20848
21072
|
var CONFIG_DIR = primConfigDirectory();
|
|
20849
|
-
var SOCK_PATH2 =
|
|
21073
|
+
var SOCK_PATH2 = join14(CONFIG_DIR, "sock");
|
|
20850
21074
|
var CONFIG_DIR_MODE3 = 448;
|
|
20851
21075
|
var SOCKET_MODE = 384;
|
|
20852
21076
|
var MAX_SOCKET_CONNECTIONS = 64;
|
|
@@ -20863,7 +21087,7 @@ var EXIT_CRASH = 1;
|
|
|
20863
21087
|
var startedAt = Date.now();
|
|
20864
21088
|
var client = getClient();
|
|
20865
21089
|
var runtimeVersion = resolveRuntimeVersion();
|
|
20866
|
-
var daemonHealth = createDaemonHealthState(runtimeVersion, process.pid, startedAt);
|
|
21090
|
+
var daemonHealth = createDaemonHealthState(runtimeVersion, process.pid, startedAt, getSiteUrl());
|
|
20867
21091
|
var statuslineIngestionCache = new StatuslineIngestionCache(decisionIngestionStatus, {
|
|
20868
21092
|
resolveRepositoryBindingState: repositoryBindingState
|
|
20869
21093
|
});
|
|
@@ -20915,7 +21139,7 @@ function resolveRuntimeVersion() {
|
|
|
20915
21139
|
}
|
|
20916
21140
|
try {
|
|
20917
21141
|
const here = fileURLToPath(new URL(".", import.meta.url));
|
|
20918
|
-
const pkg = JSON.parse(
|
|
21142
|
+
const pkg = JSON.parse(readFileSync9(join14(here, "..", "..", "package.json"), "utf-8"));
|
|
20919
21143
|
return pkg.version ?? "unknown";
|
|
20920
21144
|
} catch {
|
|
20921
21145
|
return "unknown";
|
|
@@ -20933,6 +21157,7 @@ function purgePrincipalScopedState() {
|
|
|
20933
21157
|
lastOnlineNames = void 0;
|
|
20934
21158
|
lastOnlineTeammates = void 0;
|
|
20935
21159
|
lastOkAtLocal = void 0;
|
|
21160
|
+
clearSweepRecord();
|
|
20936
21161
|
}
|
|
20937
21162
|
function synchronizeDaemonCredential() {
|
|
20938
21163
|
const token = resolveAuthCredential()?.token;
|
|
@@ -20940,6 +21165,7 @@ function synchronizeDaemonCredential() {
|
|
|
20940
21165
|
if (credentialKey !== activeCredentialKey) {
|
|
20941
21166
|
activeCredentialKey = credentialKey;
|
|
20942
21167
|
purgePrincipalScopedState();
|
|
21168
|
+
persistHealth();
|
|
20943
21169
|
}
|
|
20944
21170
|
return resolveDaemonPrincipal(token);
|
|
20945
21171
|
}
|
|
@@ -20953,7 +21179,7 @@ function assertCallerPrincipalMatches(caller) {
|
|
|
20953
21179
|
}
|
|
20954
21180
|
function ensureDaemonDirectoryPermissions() {
|
|
20955
21181
|
mkdirSync8(CONFIG_DIR, { recursive: true, mode: CONFIG_DIR_MODE3 });
|
|
20956
|
-
|
|
21182
|
+
chmodSync7(CONFIG_DIR, CONFIG_DIR_MODE3);
|
|
20957
21183
|
}
|
|
20958
21184
|
function persistHealth() {
|
|
20959
21185
|
refreshDaemonHealth(daemonHealth, Date.now());
|
|
@@ -20971,6 +21197,15 @@ function updatePendingHealth() {
|
|
|
20971
21197
|
daemonHealth.ingestion.pendingSampled = pending.sampled;
|
|
20972
21198
|
daemonHealth.ingestion.oldestPendingAt = pending.oldestPendingAt;
|
|
20973
21199
|
daemonHealth.ingestion.strandedCount = pending.strandedCount;
|
|
21200
|
+
return pending;
|
|
21201
|
+
}
|
|
21202
|
+
function recordRetainedBuckets(retained) {
|
|
21203
|
+
daemonHealth.ingestion.lastRetainedBucketCount = retained.length;
|
|
21204
|
+
daemonHealth.ingestion.lastRetainedReasons = retained.length > 0 ? summarizeRetainedBuckets(retained) : void 0;
|
|
21205
|
+
}
|
|
21206
|
+
function clearSweepRecord() {
|
|
21207
|
+
daemonHealth.ingestion.lastFailedDrainAcknowledgedCount = 0;
|
|
21208
|
+
recordRetainedBuckets([]);
|
|
20974
21209
|
}
|
|
20975
21210
|
function enterReauthHold() {
|
|
20976
21211
|
if (reauthHold) {
|
|
@@ -21006,6 +21241,8 @@ function exitReauthHold() {
|
|
|
21006
21241
|
daemonHealth.ingestion.consecutiveFailures = 0;
|
|
21007
21242
|
daemonHealth.heartbeat.lastError = void 0;
|
|
21008
21243
|
daemonHealth.ingestion.lastError = void 0;
|
|
21244
|
+
clearSweepRecord();
|
|
21245
|
+
daemonHealth.heartbeat.lastSuccessAt = void 0;
|
|
21009
21246
|
persistHealth();
|
|
21010
21247
|
process.stderr.write(
|
|
21011
21248
|
"[prim-daemon] re-authentication detected \u2014 resuming heartbeat + ingestion + Decision cache\n"
|
|
@@ -21014,6 +21251,11 @@ function exitReauthHold() {
|
|
|
21014
21251
|
void runIngestionLoop();
|
|
21015
21252
|
void runDecisionDigestLoop();
|
|
21016
21253
|
}
|
|
21254
|
+
function resumeIfReauthenticated() {
|
|
21255
|
+
if (reauthHold && !isSessionEnded() && resolveAuthCredential()) {
|
|
21256
|
+
exitReauthHold();
|
|
21257
|
+
}
|
|
21258
|
+
}
|
|
21017
21259
|
async function takeOwnership() {
|
|
21018
21260
|
const now = Date.now();
|
|
21019
21261
|
const recordedOwner = readDaemonOwner(CONFIG_DIR);
|
|
@@ -21023,7 +21265,7 @@ async function takeOwnership() {
|
|
|
21023
21265
|
(pid) => pid !== void 0 && processIsAlive(pid)
|
|
21024
21266
|
)
|
|
21025
21267
|
);
|
|
21026
|
-
if (
|
|
21268
|
+
if (existsSync9(SOCK_PATH2) || candidatePids.size > 0) {
|
|
21027
21269
|
const snapshot = await daemonRequest(
|
|
21028
21270
|
"status_snapshot",
|
|
21029
21271
|
{},
|
|
@@ -21178,22 +21420,26 @@ function scheduleIngestion(delayMs) {
|
|
|
21178
21420
|
}
|
|
21179
21421
|
async function runIngestionLoop() {
|
|
21180
21422
|
synchronizeDaemonCredential();
|
|
21423
|
+
let pending;
|
|
21181
21424
|
try {
|
|
21182
|
-
updatePendingHealth();
|
|
21425
|
+
pending = updatePendingHealth();
|
|
21183
21426
|
} catch (err) {
|
|
21184
21427
|
const failures = daemonHealth.ingestion.consecutiveFailures + 1;
|
|
21185
21428
|
const delay = ingestionRetryDelayMs(failures);
|
|
21186
21429
|
daemonHealth.ingestion.consecutiveFailures = failures;
|
|
21187
21430
|
daemonHealth.ingestion.lastError = `journal scan failed: ${errorMessage2(err)}`;
|
|
21188
21431
|
daemonHealth.ingestion.nextRetryAt = Date.now() + delay;
|
|
21432
|
+
daemonHealth.ingestion.lastAcknowledgedCount = 0;
|
|
21433
|
+
daemonHealth.ingestion.lastFailedDrainAcknowledgedCount = 0;
|
|
21189
21434
|
persistHealth();
|
|
21190
21435
|
scheduleIngestion(delay);
|
|
21191
21436
|
return;
|
|
21192
21437
|
}
|
|
21193
|
-
if (
|
|
21438
|
+
if (!hasPendingDrainWork(pending)) {
|
|
21194
21439
|
daemonHealth.ingestion.consecutiveFailures = 0;
|
|
21195
21440
|
daemonHealth.ingestion.lastError = void 0;
|
|
21196
21441
|
daemonHealth.ingestion.nextRetryAt = void 0;
|
|
21442
|
+
clearSweepRecord();
|
|
21197
21443
|
persistHealth();
|
|
21198
21444
|
scheduleIngestion(INGESTION_POLL_INTERVAL_MS);
|
|
21199
21445
|
return;
|
|
@@ -21209,8 +21455,10 @@ async function runIngestionLoop() {
|
|
|
21209
21455
|
}
|
|
21210
21456
|
daemonHealth.ingestion.lastSuccessAt = Date.now();
|
|
21211
21457
|
daemonHealth.ingestion.lastAcknowledgedCount = result.flushed;
|
|
21458
|
+
daemonHealth.ingestion.lastFailedDrainAcknowledgedCount = 0;
|
|
21212
21459
|
daemonHealth.ingestion.consecutiveFailures = 0;
|
|
21213
21460
|
daemonHealth.ingestion.lastError = void 0;
|
|
21461
|
+
recordRetainedBuckets(result.retained ?? []);
|
|
21214
21462
|
updatePendingHealth();
|
|
21215
21463
|
persistHealth();
|
|
21216
21464
|
scheduleIngestion(INGESTION_POLL_INTERVAL_MS);
|
|
@@ -21218,6 +21466,8 @@ async function runIngestionLoop() {
|
|
|
21218
21466
|
const failures = daemonHealth.ingestion.consecutiveFailures + 1;
|
|
21219
21467
|
const delay = ingestionRetryDelayMs(failures);
|
|
21220
21468
|
daemonHealth.ingestion.lastAcknowledgedCount = err instanceof FlushError ? err.flushed : 0;
|
|
21469
|
+
daemonHealth.ingestion.lastFailedDrainAcknowledgedCount = err instanceof FlushError ? err.failedDrainFlushed : 0;
|
|
21470
|
+
if (err instanceof FlushError) recordRetainedBuckets(err.retained);
|
|
21221
21471
|
daemonHealth.ingestion.consecutiveFailures = failures;
|
|
21222
21472
|
daemonHealth.ingestion.lastError = errorMessage2(err);
|
|
21223
21473
|
daemonHealth.ingestion.nextRetryAt = Date.now() + delay;
|
|
@@ -21276,6 +21526,7 @@ async function proxyGet(params, allowedPrefix, caller) {
|
|
|
21276
21526
|
return result;
|
|
21277
21527
|
}
|
|
21278
21528
|
function handleStatusSnapshot(params, caller, enforcePrincipal = false) {
|
|
21529
|
+
resumeIfReauthenticated();
|
|
21279
21530
|
const daemonPrincipal = synchronizeDaemonCredential();
|
|
21280
21531
|
const wasHealthy = daemonHealth.healthy;
|
|
21281
21532
|
const heartbeatWasHealthy = daemonHealth.heartbeat.healthy;
|
|
@@ -21552,7 +21803,7 @@ function startSocketServer() {
|
|
|
21552
21803
|
});
|
|
21553
21804
|
socketServer.listen(SOCK_PATH2, () => {
|
|
21554
21805
|
try {
|
|
21555
|
-
|
|
21806
|
+
chmodSync7(SOCK_PATH2, SOCKET_MODE);
|
|
21556
21807
|
} catch (err) {
|
|
21557
21808
|
shutdown(EXIT_CRASH, `[prim-daemon] failed to secure socket: ${errorMessage2(err)}`);
|
|
21558
21809
|
return;
|
|
@@ -21579,9 +21830,7 @@ function startTimers() {
|
|
|
21579
21830
|
}
|
|
21580
21831
|
async function runTokenCheckLoop() {
|
|
21581
21832
|
if (reauthHold) {
|
|
21582
|
-
|
|
21583
|
-
exitReauthHold();
|
|
21584
|
-
}
|
|
21833
|
+
resumeIfReauthenticated();
|
|
21585
21834
|
} else {
|
|
21586
21835
|
await ensureTokenFresh();
|
|
21587
21836
|
synchronizeDaemonCredential();
|