@primitive.ai/prim 0.1.0-alpha.91 → 0.1.0-alpha.93
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 +34 -2
- package/dist/{chunk-67VNLLBN.js → chunk-2GP5UFV7.js} +1 -0
- package/dist/{chunk-VW3FDYRT.js → chunk-36TWVNBE.js} +6 -6
- package/dist/{chunk-HU4PW6I6.js → chunk-C3VAA5CG.js} +1 -1
- package/dist/{chunk-FOLKHXLJ.js → chunk-DCJN6LZH.js} +37 -5
- package/dist/{chunk-M6RIJVRE.js → chunk-DSWT6BCC.js} +78 -4
- package/dist/{chunk-FBAYCS3X.js → chunk-DXJQR4QI.js} +150 -38
- package/dist/{chunk-PCVGBE5B.js → chunk-G2VMA2QO.js} +2 -1
- package/dist/{chunk-NAHHFSLH.js → chunk-ICM2XCXS.js} +2 -2
- package/dist/{chunk-OAAP2MY6.js → chunk-K7D2EYBS.js} +2 -2
- 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-EWZMDD44.js → chunk-X64HTHUF.js} +22 -3
- package/dist/{chunk-WX52U7Z2.js → chunk-ZU563PQI.js} +58 -2
- package/dist/daemon/server.js +382 -124
- package/dist/hook-runtime/daemon/server.js +382 -124
- package/dist/hook-runtime/hooks/post-commit.js +57 -39
- package/dist/hook-runtime/hooks/post-rewrite.js +58 -40
- package/dist/hook-runtime/hooks/post-tool-use.js +111 -78
- package/dist/hook-runtime/hooks/pre-tool-use.js +48 -26
- package/dist/hook-runtime/hooks/prim-hook.js +157 -117
- package/dist/hook-runtime/hooks/session-end.js +1 -1
- package/dist/hook-runtime/hooks/session-start.js +38 -4
- package/dist/hook-runtime/index.js +1293 -526
- package/dist/hook-runtime/statusline-main.js +23 -1
- package/dist/hooks/post-commit.js +11 -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 +12 -36
- package/dist/index.js +867 -291
- package/dist/statusline-main.js +23 -1
- package/package.json +1 -1
- package/dist/chunk-BTNNEH2W.js +0 -79
package/README.md
CHANGED
|
@@ -238,13 +238,39 @@ installation.
|
|
|
238
238
|
|
|
239
239
|
A supervised long-lived companion process that continuously drains captured
|
|
240
240
|
Moves, accelerates decision-graph reads, and powers the "team: N online"
|
|
241
|
-
presence count. `prim setup` requires it to
|
|
242
|
-
`--no-daemon` opt-out is
|
|
241
|
+
presence count. `prim setup` requires it to be live on the current version with
|
|
242
|
+
an authenticated, healthy heartbeat unless the explicit `--no-daemon` opt-out is
|
|
243
|
+
supplied. Hooks still fail soft if it later degrades.
|
|
244
|
+
|
|
245
|
+
`prim daemon start` (and `restart`/`ensure`) does not gate on delivery. While
|
|
246
|
+
Moves are past the 30s delivery SLA it reports them as draining in the
|
|
247
|
+
background when the daemon's last sweep recorded no failure, or when the drains
|
|
248
|
+
that failed acknowledged Moves before failing; another bucket delivering in the
|
|
249
|
+
same sweep does not count. Journal buckets the daemon holds back (unbound,
|
|
250
|
+
another organization, identity unavailable) read as delivery held back, and a
|
|
251
|
+
failure with no progress reads as delivery failing; both are a ⚠ warning,
|
|
252
|
+
still exit 0. Its JSON adds `draining`, `deliveryRetained`, `deliveryFailing`,
|
|
253
|
+
and `ingestion` in those states. Start's JSON `healthy` means the start
|
|
254
|
+
succeeded, so it can differ from `daemon status --json`, which stays degraded
|
|
255
|
+
until the backlog drains. Setup's own health check reports a backlog the
|
|
256
|
+
daemon is draining as a warning and fails on held-back buckets and on failures
|
|
257
|
+
recorded without progress; standalone `prim doctor` still fails a missed SLA.
|
|
243
258
|
|
|
244
259
|
```bash
|
|
245
260
|
prim daemon start # start (stop / restart / status)
|
|
246
261
|
```
|
|
247
262
|
|
|
263
|
+
Ordinary commands may upgrade an older daemon in the background. When an
|
|
264
|
+
installed CLI runs an attended, allowlisted command (for example `auth`,
|
|
265
|
+
`decisions`, `moves`, `skill`, or an agent's `install`/`status`) on macOS and
|
|
266
|
+
finds the launchd daemon on an older version, or on its own version but unable
|
|
267
|
+
to run (say, after a Node upgrade deleted the binary it pinned), it starts a
|
|
268
|
+
detached `prim daemon ensure`, at most once an hour whatever version it
|
|
269
|
+
targets. It never downgrades, never repoints the daemon at another deployment
|
|
270
|
+
or config root, and never runs from hooks or their background drains, `daemon`,
|
|
271
|
+
`doctor`, `setup` or its steps, `auth clear`, removal commands, a development
|
|
272
|
+
checkout, or as root. Set `PRIM_DAEMON_DRIFT_HEAL=0` to turn it off.
|
|
273
|
+
|
|
248
274
|
### Decisions
|
|
249
275
|
|
|
250
276
|
Read and respond to the decision graph.
|
|
@@ -338,6 +364,12 @@ prim moves tail # Pretty-print recent journal entries
|
|
|
338
364
|
prim moves flush # Drain the local journals to the server (also runs from hooks)
|
|
339
365
|
```
|
|
340
366
|
|
|
367
|
+
Any other command except `uninstall` and a `setup` that starts the daemon (or
|
|
368
|
+
one of its steps) that finds Moves waiting over a minute (or cannot tell how
|
|
369
|
+
long) hands them to a detached background `prim moves flush` and never waits for
|
|
370
|
+
it. It starts none while a live daemon on the same version and deployment, with
|
|
371
|
+
a healthy heartbeat and no recorded ingestion failure, already owns the drain.
|
|
372
|
+
|
|
341
373
|
### Skill
|
|
342
374
|
|
|
343
375
|
```bash
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
formatStatusline
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-ZU563PQI.js";
|
|
4
4
|
import {
|
|
5
5
|
decisionIngestionStatus,
|
|
6
6
|
repositoryBindingState
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-DSWT6BCC.js";
|
|
8
8
|
import {
|
|
9
9
|
getSiteUrl,
|
|
10
10
|
isSessionEnded,
|
|
11
11
|
terminalSafeText
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-G2VMA2QO.js";
|
|
13
13
|
import {
|
|
14
14
|
packageVersion
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-X64HTHUF.js";
|
|
16
16
|
import {
|
|
17
17
|
gitToplevel
|
|
18
18
|
} from "./chunk-H4UNYFUC.js";
|
|
@@ -20,11 +20,11 @@ import {
|
|
|
20
20
|
daemonPrincipalsMatch,
|
|
21
21
|
daemonRequest,
|
|
22
22
|
resolveDaemonPrincipal
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-OTHKG76J.js";
|
|
24
24
|
import {
|
|
25
25
|
primConfigDirectory,
|
|
26
26
|
withFileLock
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-2GP5UFV7.js";
|
|
28
28
|
|
|
29
29
|
// src/hooks/codex-context.ts
|
|
30
30
|
import { createHash, randomBytes as randomBytes2 } from "crypto";
|
|
@@ -1,18 +1,21 @@
|
|
|
1
|
+
import {
|
|
2
|
+
unattendedEnv
|
|
3
|
+
} from "./chunk-OYHYCK4K.js";
|
|
1
4
|
import {
|
|
2
5
|
isValidRepoSyncId,
|
|
3
6
|
setRepoSyncId,
|
|
4
7
|
setRepositoryBindingState
|
|
5
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-DSWT6BCC.js";
|
|
6
9
|
import {
|
|
7
10
|
HttpError,
|
|
8
11
|
getClient
|
|
9
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-G2VMA2QO.js";
|
|
10
13
|
import {
|
|
11
14
|
binFile,
|
|
12
15
|
compareSemver,
|
|
13
16
|
cursorConfigDirectory,
|
|
14
17
|
pinnedNpxCommand
|
|
15
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-X64HTHUF.js";
|
|
16
19
|
import {
|
|
17
20
|
gitToplevel,
|
|
18
21
|
githubRepositoryFullName
|
|
@@ -20,7 +23,7 @@ import {
|
|
|
20
23
|
import {
|
|
21
24
|
atomicWriteFile,
|
|
22
25
|
withFileLock
|
|
23
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-2GP5UFV7.js";
|
|
24
27
|
|
|
25
28
|
// src/lib/bin-cache.ts
|
|
26
29
|
import { chmodSync, mkdirSync } from "fs";
|
|
@@ -1504,6 +1507,34 @@ async function bindRepository(root, options) {
|
|
|
1504
1507
|
return bindRepositoryWithClient(root, getClient(), options);
|
|
1505
1508
|
}
|
|
1506
1509
|
|
|
1510
|
+
// src/daemon/self-heal.ts
|
|
1511
|
+
import { spawn } from "child_process";
|
|
1512
|
+
function kickDaemonEnsure(options = {}) {
|
|
1513
|
+
const primEntry = options.primEntry === void 0 ? binFile("prim") : options.primEntry;
|
|
1514
|
+
if (!primEntry) {
|
|
1515
|
+
return false;
|
|
1516
|
+
}
|
|
1517
|
+
try {
|
|
1518
|
+
const args = [primEntry, "daemon", "ensure"];
|
|
1519
|
+
if (options.latestBootstrap ?? (options.platform ?? process.platform) === "darwin") {
|
|
1520
|
+
args.push("--latest-bootstrap");
|
|
1521
|
+
}
|
|
1522
|
+
const child = (options.spawnProcess ?? spawn)(
|
|
1523
|
+
options.nodeEntry ?? process.execPath,
|
|
1524
|
+
args,
|
|
1525
|
+
// Unattended: neither SessionStart's repair nor a drift heal is a person
|
|
1526
|
+
// running prim, so the child (and its descendants) never heal in turn.
|
|
1527
|
+
{ detached: true, stdio: "ignore", env: unattendedEnv() }
|
|
1528
|
+
);
|
|
1529
|
+
child.once("error", () => {
|
|
1530
|
+
});
|
|
1531
|
+
child.unref();
|
|
1532
|
+
return true;
|
|
1533
|
+
} catch {
|
|
1534
|
+
return false;
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1507
1538
|
export {
|
|
1508
1539
|
warmBinCache,
|
|
1509
1540
|
postCommitHookBlock,
|
|
@@ -1525,5 +1556,6 @@ export {
|
|
|
1525
1556
|
refreshClaudePlugins,
|
|
1526
1557
|
hasUsableCodexGuidance,
|
|
1527
1558
|
hasUsableCursorSkill,
|
|
1528
|
-
registerSkillCommands
|
|
1559
|
+
registerSkillCommands,
|
|
1560
|
+
kickDaemonEnsure
|
|
1529
1561
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
getClient
|
|
3
|
-
|
|
2
|
+
getClient,
|
|
3
|
+
terminalSafeLine
|
|
4
|
+
} from "./chunk-G2VMA2QO.js";
|
|
4
5
|
import {
|
|
5
6
|
gitToplevel,
|
|
6
7
|
githubRepositoryFullName
|
|
@@ -8,7 +9,7 @@ import {
|
|
|
8
9
|
import {
|
|
9
10
|
__commonJS,
|
|
10
11
|
__toESM
|
|
11
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-2GP5UFV7.js";
|
|
12
13
|
|
|
13
14
|
// node_modules/.pnpm/ajv@8.20.0/node_modules/ajv/dist/runtime/ucs2length.js
|
|
14
15
|
var require_ucs2length = __commonJS({
|
|
@@ -18331,6 +18332,73 @@ function scheduleCollectScopeRefresh(cwd, collectScopeVersion, dependencies = de
|
|
|
18331
18332
|
scheduledRefreshes.set(cwd, refresh);
|
|
18332
18333
|
}
|
|
18333
18334
|
|
|
18335
|
+
// src/lib/ansi.ts
|
|
18336
|
+
var ANSI_CODES = {
|
|
18337
|
+
yellow: "\x1B[33m",
|
|
18338
|
+
magenta: "\x1B[35m",
|
|
18339
|
+
blue: "\x1B[34m",
|
|
18340
|
+
cyan: "\x1B[36m",
|
|
18341
|
+
green: "\x1B[32m",
|
|
18342
|
+
red: "\x1B[31m",
|
|
18343
|
+
// 256-color approximation of the orange trigger marker (xterm 208).
|
|
18344
|
+
orange: "\x1B[38;5;208m",
|
|
18345
|
+
gray: "\x1B[90m"
|
|
18346
|
+
};
|
|
18347
|
+
var ANSI_RESET = "\x1B[0m";
|
|
18348
|
+
var ANSI_DIM = "\x1B[2m";
|
|
18349
|
+
var ANSI_BOLD = "\x1B[1m";
|
|
18350
|
+
function supportsColor() {
|
|
18351
|
+
if (process.env.NO_COLOR !== void 0 && process.env.NO_COLOR !== "") {
|
|
18352
|
+
return false;
|
|
18353
|
+
}
|
|
18354
|
+
return process.stderr.isTTY === true;
|
|
18355
|
+
}
|
|
18356
|
+
function color(text, c) {
|
|
18357
|
+
if (!supportsColor()) {
|
|
18358
|
+
return text;
|
|
18359
|
+
}
|
|
18360
|
+
return `${ANSI_CODES[c]}${text}${ANSI_RESET}`;
|
|
18361
|
+
}
|
|
18362
|
+
function dim(text) {
|
|
18363
|
+
if (!supportsColor()) {
|
|
18364
|
+
return text;
|
|
18365
|
+
}
|
|
18366
|
+
return `${ANSI_DIM}${text}${ANSI_RESET}`;
|
|
18367
|
+
}
|
|
18368
|
+
function bold(text) {
|
|
18369
|
+
if (!supportsColor()) {
|
|
18370
|
+
return text;
|
|
18371
|
+
}
|
|
18372
|
+
return `${ANSI_BOLD}${text}${ANSI_RESET}`;
|
|
18373
|
+
}
|
|
18374
|
+
var AREA_COLORS = {
|
|
18375
|
+
auth: "yellow",
|
|
18376
|
+
data: "magenta",
|
|
18377
|
+
mobile: "blue",
|
|
18378
|
+
infra: "cyan",
|
|
18379
|
+
ui: "green",
|
|
18380
|
+
billing: "orange",
|
|
18381
|
+
api: "blue",
|
|
18382
|
+
docs: "gray",
|
|
18383
|
+
testing: "gray"
|
|
18384
|
+
};
|
|
18385
|
+
function colorForArea(area) {
|
|
18386
|
+
if (!area) {
|
|
18387
|
+
return "gray";
|
|
18388
|
+
}
|
|
18389
|
+
return AREA_COLORS[area] ?? "gray";
|
|
18390
|
+
}
|
|
18391
|
+
var MAX_HEALTH_ERROR_LENGTH = 240;
|
|
18392
|
+
function boundedHealthError(value) {
|
|
18393
|
+
if (!value) return void 0;
|
|
18394
|
+
const clean = terminalSafeLine(value);
|
|
18395
|
+
if (!clean) return void 0;
|
|
18396
|
+
return clean.length <= MAX_HEALTH_ERROR_LENGTH ? clean : `${clean.slice(0, MAX_HEALTH_ERROR_LENGTH - 1)}\u2026`;
|
|
18397
|
+
}
|
|
18398
|
+
function stripAnsi(text) {
|
|
18399
|
+
return text.replace(/\u001b\[[0-9;]*m/g, "");
|
|
18400
|
+
}
|
|
18401
|
+
|
|
18334
18402
|
export {
|
|
18335
18403
|
repoActiveFlag,
|
|
18336
18404
|
isRepoActive,
|
|
@@ -18354,5 +18422,11 @@ export {
|
|
|
18354
18422
|
isGitHubInstallIntentStatusResponse,
|
|
18355
18423
|
cachedCollectScopeAdmits,
|
|
18356
18424
|
fetchAndCacheCollectScope,
|
|
18357
|
-
scheduleCollectScopeRefresh
|
|
18425
|
+
scheduleCollectScopeRefresh,
|
|
18426
|
+
color,
|
|
18427
|
+
dim,
|
|
18428
|
+
bold,
|
|
18429
|
+
colorForArea,
|
|
18430
|
+
boundedHealthError,
|
|
18431
|
+
stripAnsi
|
|
18358
18432
|
};
|
|
@@ -1,27 +1,120 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getSiteUrl
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-G2VMA2QO.js";
|
|
4
4
|
import {
|
|
5
|
+
atomicWriteFile,
|
|
6
|
+
ensureDurableDirectory,
|
|
5
7
|
jwtOrganizationId,
|
|
6
8
|
primConfigDirectory,
|
|
7
9
|
resolveAuthCredential
|
|
8
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-2GP5UFV7.js";
|
|
9
11
|
|
|
10
12
|
// src/journal.ts
|
|
11
13
|
import {
|
|
12
14
|
appendFileSync,
|
|
13
15
|
closeSync,
|
|
14
|
-
existsSync,
|
|
16
|
+
existsSync as existsSync2,
|
|
15
17
|
fstatSync,
|
|
16
18
|
mkdirSync,
|
|
17
19
|
openSync,
|
|
18
20
|
opendirSync,
|
|
19
|
-
readFileSync,
|
|
21
|
+
readFileSync as readFileSync2,
|
|
20
22
|
readSync,
|
|
21
|
-
readdirSync
|
|
23
|
+
readdirSync as readdirSync2
|
|
22
24
|
} from "fs";
|
|
23
|
-
import { dirname, join } from "path";
|
|
24
|
-
|
|
25
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
26
|
+
|
|
27
|
+
// src/drain-progress.ts
|
|
28
|
+
import {
|
|
29
|
+
chmodSync,
|
|
30
|
+
existsSync,
|
|
31
|
+
readFileSync,
|
|
32
|
+
readdirSync,
|
|
33
|
+
rmSync,
|
|
34
|
+
rmdirSync
|
|
35
|
+
} from "fs";
|
|
36
|
+
import { basename, dirname, join } from "path";
|
|
37
|
+
var DIRECTORY_MODE = 448;
|
|
38
|
+
var FILE_MODE = 384;
|
|
39
|
+
var DRAIN_PROGRESS_DIRNAME = "drain-progress";
|
|
40
|
+
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)?$/;
|
|
41
|
+
function rotationIdentity(stat) {
|
|
42
|
+
return { size: stat.size, ino: stat.ino, dev: stat.dev };
|
|
43
|
+
}
|
|
44
|
+
function drainProgressDirectory(flushingPath) {
|
|
45
|
+
return join(dirname(flushingPath), DRAIN_PROGRESS_DIRNAME);
|
|
46
|
+
}
|
|
47
|
+
function drainProgressPath(flushingPath) {
|
|
48
|
+
return join(drainProgressDirectory(flushingPath), `${basename(flushingPath)}.json`);
|
|
49
|
+
}
|
|
50
|
+
function isByteCount(value) {
|
|
51
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
52
|
+
}
|
|
53
|
+
function drainResumeOffset(flushingPath, rotation) {
|
|
54
|
+
let parsed;
|
|
55
|
+
try {
|
|
56
|
+
parsed = JSON.parse(readFileSync(drainProgressPath(flushingPath), "utf8"));
|
|
57
|
+
} catch {
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
60
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
61
|
+
return 0;
|
|
62
|
+
}
|
|
63
|
+
const { v, offset, size, ino, dev } = parsed;
|
|
64
|
+
if (v !== 2 || ino !== rotation.ino || dev !== rotation.dev || size !== rotation.size || !isByteCount(offset) || offset > rotation.size) {
|
|
65
|
+
return 0;
|
|
66
|
+
}
|
|
67
|
+
return offset;
|
|
68
|
+
}
|
|
69
|
+
function writeDrainCheckpoint(flushingPath, checkpoint) {
|
|
70
|
+
const directory = drainProgressDirectory(flushingPath);
|
|
71
|
+
ensureDurableDirectory(directory, DIRECTORY_MODE);
|
|
72
|
+
chmodSync(directory, DIRECTORY_MODE);
|
|
73
|
+
atomicWriteFile(drainProgressPath(flushingPath), `${JSON.stringify(checkpoint)}
|
|
74
|
+
`, {
|
|
75
|
+
mode: FILE_MODE
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function removeEmptyDirectory(directory) {
|
|
79
|
+
try {
|
|
80
|
+
rmdirSync(directory);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
const code = error.code;
|
|
83
|
+
if (code !== "ENOENT" && code !== "ENOTEMPTY" && code !== "EEXIST") {
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function removeDrainCheckpoint(flushingPath) {
|
|
89
|
+
rmSync(drainProgressPath(flushingPath), { force: true });
|
|
90
|
+
removeEmptyDirectory(drainProgressDirectory(flushingPath));
|
|
91
|
+
}
|
|
92
|
+
function sweepOrphanedDrainCheckpoints(bucketDirectory) {
|
|
93
|
+
const directory = join(bucketDirectory, DRAIN_PROGRESS_DIRNAME);
|
|
94
|
+
let names;
|
|
95
|
+
try {
|
|
96
|
+
names = readdirSync(directory);
|
|
97
|
+
} catch {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
for (const name of names) {
|
|
101
|
+
const rotation = CHECKPOINT_NAME.exec(name)?.[1];
|
|
102
|
+
if (rotation === void 0 || existsSync(join(bucketDirectory, rotation))) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
rmSync(join(directory, name), { force: true });
|
|
107
|
+
} catch {
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
removeEmptyDirectory(directory);
|
|
112
|
+
} catch {
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/journal.ts
|
|
117
|
+
var JOURNAL_DIR = join2(primConfigDirectory(), "moves");
|
|
25
118
|
var UNBOUND_BUCKET = "_unbound";
|
|
26
119
|
var JOURNAL_BASENAME = "journal.ndjson";
|
|
27
120
|
var JOURNAL_STATS_SAMPLE_BYTES = 64 * 1024;
|
|
@@ -34,26 +127,26 @@ function envSlug(apiUrl) {
|
|
|
34
127
|
return slug;
|
|
35
128
|
}
|
|
36
129
|
function currentEnvDir() {
|
|
37
|
-
return
|
|
130
|
+
return join2(JOURNAL_DIR, envSlug(getSiteUrl()));
|
|
38
131
|
}
|
|
39
132
|
var DIR_MODE = 448;
|
|
40
|
-
var
|
|
133
|
+
var FILE_MODE2 = 384;
|
|
41
134
|
var SAFE_BUCKET = /^[A-Za-z0-9_-]+$/;
|
|
42
135
|
var RESERVED_BUCKETS = /* @__PURE__ */ new Set([UNBOUND_BUCKET, "_legacy"]);
|
|
43
136
|
function appendMoveToPath(path, move) {
|
|
44
|
-
const dir =
|
|
45
|
-
if (!
|
|
137
|
+
const dir = dirname2(path);
|
|
138
|
+
if (!existsSync2(dir)) {
|
|
46
139
|
mkdirSync(dir, { recursive: true, mode: DIR_MODE });
|
|
47
140
|
}
|
|
48
141
|
appendFileSync(path, `${JSON.stringify(move)}
|
|
49
|
-
`, { mode:
|
|
142
|
+
`, { mode: FILE_MODE2 });
|
|
50
143
|
}
|
|
51
144
|
function bucketDir(orgId) {
|
|
52
145
|
const safe = orgId !== void 0 && SAFE_BUCKET.test(orgId) && !RESERVED_BUCKETS.has(orgId);
|
|
53
|
-
return
|
|
146
|
+
return join2(currentEnvDir(), safe ? orgId : UNBOUND_BUCKET);
|
|
54
147
|
}
|
|
55
148
|
function journalPath(orgId) {
|
|
56
|
-
return
|
|
149
|
+
return join2(bucketDir(orgId), JOURNAL_BASENAME);
|
|
57
150
|
}
|
|
58
151
|
function appendMove(move, orgId) {
|
|
59
152
|
appendMoveToPath(journalPath(orgId), move);
|
|
@@ -72,23 +165,23 @@ function parseMoves(content) {
|
|
|
72
165
|
return moves;
|
|
73
166
|
}
|
|
74
167
|
function readMovesFromPath(path) {
|
|
75
|
-
if (!
|
|
168
|
+
if (!existsSync2(path)) {
|
|
76
169
|
return [];
|
|
77
170
|
}
|
|
78
|
-
return parseMoves(
|
|
171
|
+
return parseMoves(readFileSync2(path, "utf-8"));
|
|
79
172
|
}
|
|
80
173
|
function listBuckets() {
|
|
81
174
|
const out = [];
|
|
82
175
|
const envDir = currentEnvDir();
|
|
83
|
-
if (!
|
|
176
|
+
if (!existsSync2(envDir)) {
|
|
84
177
|
return out;
|
|
85
178
|
}
|
|
86
|
-
for (const entry of
|
|
179
|
+
for (const entry of readdirSync2(envDir, { withFileTypes: true })) {
|
|
87
180
|
if (!entry.isDirectory()) {
|
|
88
181
|
continue;
|
|
89
182
|
}
|
|
90
|
-
const path =
|
|
91
|
-
if (
|
|
183
|
+
const path = join2(envDir, entry.name, JOURNAL_BASENAME);
|
|
184
|
+
if (existsSync2(path)) {
|
|
92
185
|
out.push({ bucket: entry.name, path });
|
|
93
186
|
}
|
|
94
187
|
}
|
|
@@ -105,22 +198,23 @@ function oldestCapturedAt(moves) {
|
|
|
105
198
|
}
|
|
106
199
|
return oldest;
|
|
107
200
|
}
|
|
108
|
-
function sampleJournalFile(path, maxBytes = JOURNAL_STATS_SAMPLE_BYTES) {
|
|
201
|
+
function sampleJournalFile(path, maxBytes = JOURNAL_STATS_SAMPLE_BYTES, options = {}) {
|
|
109
202
|
const fd = openSync(path, "r");
|
|
110
203
|
try {
|
|
111
204
|
const initial = fstatSync(fd);
|
|
112
|
-
const
|
|
205
|
+
const start = options.fromDrainCheckpoint ? drainResumeOffset(path, rotationIdentity(initial)) : 0;
|
|
206
|
+
const target = Math.min(initial.size - start, Math.max(0, maxBytes));
|
|
113
207
|
const buffer = Buffer.allocUnsafe(target);
|
|
114
208
|
let sampledBytes = 0;
|
|
115
209
|
while (sampledBytes < target) {
|
|
116
|
-
const count = readSync(fd, buffer, sampledBytes, target - sampledBytes, sampledBytes);
|
|
210
|
+
const count = readSync(fd, buffer, sampledBytes, target - sampledBytes, start + sampledBytes);
|
|
117
211
|
if (count === 0) {
|
|
118
212
|
break;
|
|
119
213
|
}
|
|
120
214
|
sampledBytes += count;
|
|
121
215
|
}
|
|
122
216
|
const stat = fstatSync(fd);
|
|
123
|
-
const sampled = stat.size > sampledBytes;
|
|
217
|
+
const sampled = stat.size - start > sampledBytes;
|
|
124
218
|
let content = buffer.subarray(0, sampledBytes).toString("utf-8");
|
|
125
219
|
if (sampled) {
|
|
126
220
|
const lastNewline = content.lastIndexOf("\n");
|
|
@@ -130,6 +224,7 @@ function sampleJournalFile(path, maxBytes = JOURNAL_STATS_SAMPLE_BYTES) {
|
|
|
130
224
|
const moves = parseMoves(content);
|
|
131
225
|
return {
|
|
132
226
|
sizeBytes: stat.size,
|
|
227
|
+
pendingBytes: Math.max(0, stat.size - start),
|
|
133
228
|
mtimeMs: stat.mtimeMs,
|
|
134
229
|
lineCount: sampled && stat.size > 0 ? Math.max(1, lines.length) : lines.length,
|
|
135
230
|
oldestCapturedAt: oldestCapturedAt(moves),
|
|
@@ -148,7 +243,7 @@ function parseFlushingPid(name) {
|
|
|
148
243
|
return Number.isSafeInteger(pid) && pid > 0 ? pid : void 0;
|
|
149
244
|
}
|
|
150
245
|
function listFlushingInDir(dir, bucket, options = {}) {
|
|
151
|
-
if (!
|
|
246
|
+
if (!existsSync2(dir)) {
|
|
152
247
|
return [];
|
|
153
248
|
}
|
|
154
249
|
const out = [];
|
|
@@ -162,10 +257,10 @@ function listFlushingInDir(dir, bucket, options = {}) {
|
|
|
162
257
|
entry = entries.readSync();
|
|
163
258
|
continue;
|
|
164
259
|
}
|
|
165
|
-
const path =
|
|
260
|
+
const path = join2(dir, entry.name);
|
|
166
261
|
let sample;
|
|
167
262
|
try {
|
|
168
|
-
sample = sampleJournalFile(path, remainingBytes);
|
|
263
|
+
sample = sampleJournalFile(path, remainingBytes, { fromDrainCheckpoint: true });
|
|
169
264
|
} catch (err) {
|
|
170
265
|
if (err.code === "ENOENT") {
|
|
171
266
|
entry = entries.readSync();
|
|
@@ -189,18 +284,18 @@ function listFlushingInDir(dir, bucket, options = {}) {
|
|
|
189
284
|
}
|
|
190
285
|
function listFlushing(options = {}) {
|
|
191
286
|
const envDir = currentEnvDir();
|
|
192
|
-
if (!
|
|
287
|
+
if (!existsSync2(envDir)) {
|
|
193
288
|
return [];
|
|
194
289
|
}
|
|
195
290
|
const out = [];
|
|
196
291
|
let remainingBytes = options.sampleBytes ?? JOURNAL_STATS_SAMPLE_BYTES;
|
|
197
292
|
const maxFiles = options.maxFiles ?? Number.POSITIVE_INFINITY;
|
|
198
|
-
for (const entry of
|
|
293
|
+
for (const entry of readdirSync2(envDir, { withFileTypes: true })) {
|
|
199
294
|
if (out.length >= maxFiles) {
|
|
200
295
|
break;
|
|
201
296
|
}
|
|
202
297
|
if (entry.isDirectory()) {
|
|
203
|
-
const found = listFlushingInDir(
|
|
298
|
+
const found = listFlushingInDir(join2(envDir, entry.name), entry.name, {
|
|
204
299
|
sampleBytes: remainingBytes,
|
|
205
300
|
maxFiles: maxFiles - out.length
|
|
206
301
|
});
|
|
@@ -213,6 +308,17 @@ function listFlushing(options = {}) {
|
|
|
213
308
|
}
|
|
214
309
|
return out;
|
|
215
310
|
}
|
|
311
|
+
function sweepOrphanedDrainProgress() {
|
|
312
|
+
const envDir = currentEnvDir();
|
|
313
|
+
if (!existsSync2(envDir)) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
for (const entry of readdirSync2(envDir, { withFileTypes: true })) {
|
|
317
|
+
if (entry.isDirectory()) {
|
|
318
|
+
sweepOrphanedDrainCheckpoints(join2(envDir, entry.name));
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
216
322
|
function bucketStats(options = {}) {
|
|
217
323
|
const out = [];
|
|
218
324
|
let remainingBytes = options.sampleBytes ?? JOURNAL_STATS_SAMPLE_BYTES;
|
|
@@ -258,17 +364,17 @@ function pendingJournalStats() {
|
|
|
258
364
|
}
|
|
259
365
|
|
|
260
366
|
// src/binding.ts
|
|
261
|
-
import { existsSync as
|
|
262
|
-
import { dirname as
|
|
367
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
368
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
263
369
|
var PRIM_CONFIG_DIR = primConfigDirectory();
|
|
264
|
-
var SESSIONS_DIR =
|
|
370
|
+
var SESSIONS_DIR = join3(PRIM_CONFIG_DIR, "sessions");
|
|
265
371
|
var WORKSPACE_FILE = ".prim/workspace.json";
|
|
266
372
|
function readJsonSafe(path) {
|
|
267
|
-
if (!
|
|
373
|
+
if (!existsSync3(path)) {
|
|
268
374
|
return;
|
|
269
375
|
}
|
|
270
376
|
try {
|
|
271
|
-
return JSON.parse(
|
|
377
|
+
return JSON.parse(readFileSync3(path, "utf-8"));
|
|
272
378
|
} catch {
|
|
273
379
|
return;
|
|
274
380
|
}
|
|
@@ -277,17 +383,17 @@ function fromSessionMarker(sessionId) {
|
|
|
277
383
|
if (!sessionId) {
|
|
278
384
|
return;
|
|
279
385
|
}
|
|
280
|
-
const marker = readJsonSafe(
|
|
386
|
+
const marker = readJsonSafe(join3(SESSIONS_DIR, `${sessionId}.json`));
|
|
281
387
|
return marker?.orgId;
|
|
282
388
|
}
|
|
283
389
|
function fromWorkspaceFile(startDir) {
|
|
284
390
|
let dir = startDir;
|
|
285
391
|
while (true) {
|
|
286
|
-
const config = readJsonSafe(
|
|
392
|
+
const config = readJsonSafe(join3(dir, WORKSPACE_FILE));
|
|
287
393
|
if (config?.orgId) {
|
|
288
394
|
return config.orgId;
|
|
289
395
|
}
|
|
290
|
-
const parent =
|
|
396
|
+
const parent = dirname3(dir);
|
|
291
397
|
if (parent === dir) {
|
|
292
398
|
return;
|
|
293
399
|
}
|
|
@@ -315,11 +421,17 @@ function resolveOrg(args) {
|
|
|
315
421
|
}
|
|
316
422
|
|
|
317
423
|
export {
|
|
424
|
+
rotationIdentity,
|
|
425
|
+
drainResumeOffset,
|
|
426
|
+
writeDrainCheckpoint,
|
|
427
|
+
removeDrainCheckpoint,
|
|
318
428
|
JOURNAL_DIR,
|
|
429
|
+
envSlug,
|
|
319
430
|
appendMove,
|
|
320
431
|
readMovesFromPath,
|
|
321
432
|
listBuckets,
|
|
322
433
|
listFlushing,
|
|
434
|
+
sweepOrphanedDrainProgress,
|
|
323
435
|
bucketStats,
|
|
324
436
|
pendingJournalStats,
|
|
325
437
|
SESSIONS_DIR,
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
resolveAuthCredential,
|
|
20
20
|
withFileLock,
|
|
21
21
|
workosConnectTokenEndpoint
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-2GP5UFV7.js";
|
|
23
23
|
|
|
24
24
|
// src/lib/terminal-safe.ts
|
|
25
25
|
function replaceIsolatedSurrogates(value) {
|
|
@@ -645,6 +645,7 @@ export {
|
|
|
645
645
|
isTerminalSafeText,
|
|
646
646
|
stripControlChars,
|
|
647
647
|
getSiteUrl,
|
|
648
|
+
getSiteUrlForEnvironment,
|
|
648
649
|
getTokenExpiresAt,
|
|
649
650
|
isSessionEnded,
|
|
650
651
|
commitCredentials,
|
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
} from "./chunk-EZXEWFDG.js";
|
|
4
4
|
import {
|
|
5
5
|
scrubEnvironmentPaths
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-K7D2EYBS.js";
|
|
7
7
|
import {
|
|
8
8
|
isValidRepoSyncId
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-DSWT6BCC.js";
|
|
10
10
|
|
|
11
11
|
// src/hooks/prim-hook-core.ts
|
|
12
12
|
import { createHash, randomUUID } from "crypto";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
boundedHealthError
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-DSWT6BCC.js";
|
|
4
4
|
import {
|
|
5
5
|
terminalSafeLine
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-G2VMA2QO.js";
|
|
7
7
|
|
|
8
8
|
// src/hooks/redact.ts
|
|
9
9
|
import { existsSync, readFileSync, statSync } from "fs";
|