@primitive.ai/prim 0.1.0-alpha.66 → 0.1.0-alpha.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-I5BHJQH2.js +329 -0
- package/dist/chunk-K3AYW6VZ.js +300 -0
- package/dist/{chunk-CPX3BUOC.js → chunk-RTLFE6EZ.js} +6 -2
- package/dist/{chunk-KHZNYEUV.js → chunk-U6L2RU67.js} +1 -1
- package/dist/{chunk-HIMWM526.js → chunk-UN5O5SYG.js} +1 -1
- package/dist/chunk-XGADQPCI.js +26 -0
- package/dist/{chunk-DR66XM5L.js → chunk-XPVNH3J6.js} +1 -1
- package/dist/{chunk-WS3MCNFD.js → chunk-XUAP572Z.js} +1 -1
- package/dist/daemon/server.js +121 -26
- package/dist/hooks/post-commit.js +2 -2
- package/dist/hooks/post-rewrite.js +2 -2
- package/dist/hooks/post-tool-use.js +51 -21
- package/dist/hooks/pre-commit.js +2 -2
- package/dist/hooks/pre-tool-use.js +64 -25
- package/dist/hooks/prim-hook.js +67 -5
- package/dist/hooks/session-end.js +3 -3
- package/dist/hooks/session-start.js +28 -20
- package/dist/index.js +617 -107
- package/dist/statusline-main.js +16 -12
- package/package.json +1 -1
- package/dist/chunk-CZ24W5QU.js +0 -123
- package/dist/chunk-JQFN6LBO.js +0 -110
package/dist/daemon/server.js
CHANGED
|
@@ -394,10 +394,12 @@ function refreshToken(options = {}) {
|
|
|
394
394
|
}
|
|
395
395
|
var HttpError = class extends Error {
|
|
396
396
|
status;
|
|
397
|
-
|
|
397
|
+
body;
|
|
398
|
+
constructor(status, message, body) {
|
|
398
399
|
super(message);
|
|
399
400
|
this.name = "HttpError";
|
|
400
401
|
this.status = status;
|
|
402
|
+
this.body = body;
|
|
401
403
|
}
|
|
402
404
|
};
|
|
403
405
|
var _cachedCredential;
|
|
@@ -458,7 +460,9 @@ async function request(method, path, body, options) {
|
|
|
458
460
|
throw new HttpError(401, AUTH_EXPIRED_MESSAGE);
|
|
459
461
|
}
|
|
460
462
|
const errorBody = await response.json().catch(() => null);
|
|
461
|
-
|
|
463
|
+
const errorRecord = typeof errorBody === "object" && errorBody !== null && !Array.isArray(errorBody) ? errorBody : void 0;
|
|
464
|
+
const message = typeof errorRecord?.error === "string" ? errorRecord.error : `HTTP ${response.status}`;
|
|
465
|
+
throw new HttpError(response.status, message, errorBody);
|
|
462
466
|
}
|
|
463
467
|
return response.json();
|
|
464
468
|
}
|
|
@@ -962,9 +966,12 @@ function validDecisionUrl(value) {
|
|
|
962
966
|
return void 0;
|
|
963
967
|
}
|
|
964
968
|
}
|
|
965
|
-
function decisionLink(label, decisionUrl) {
|
|
969
|
+
function decisionLink(label, decisionUrl, plain) {
|
|
966
970
|
const url = validDecisionUrl(decisionUrl);
|
|
967
|
-
|
|
971
|
+
if (plain || !url) {
|
|
972
|
+
return label;
|
|
973
|
+
}
|
|
974
|
+
return `\x1B]8;;${url}\x07\x1B[34;4m${label}\x1B[0m\x1B]8;;\x07`;
|
|
968
975
|
}
|
|
969
976
|
function formatLabeled(labels, cap) {
|
|
970
977
|
if (labels === void 0) {
|
|
@@ -981,13 +988,13 @@ function formatLabeled(labels, cap) {
|
|
|
981
988
|
function formatTeammates(names, cap) {
|
|
982
989
|
return formatLabeled(names?.map(stripControlChars), cap);
|
|
983
990
|
}
|
|
984
|
-
function formatTeammatesWithArea(teammates, cap) {
|
|
991
|
+
function formatTeammatesWithArea(teammates, cap, plainLinks = false) {
|
|
985
992
|
return formatLabeled(
|
|
986
993
|
teammates?.map((t) => {
|
|
987
994
|
const name = stripControlChars(t.name);
|
|
988
995
|
const area = stripControlChars(t.area ?? "").trim();
|
|
989
996
|
const label = area ? `${name} - ${area}` : name;
|
|
990
|
-
return decisionLink(label, t.decisionUrl);
|
|
997
|
+
return decisionLink(label, t.decisionUrl, plainLinks);
|
|
991
998
|
}),
|
|
992
999
|
cap
|
|
993
1000
|
);
|
|
@@ -995,25 +1002,26 @@ function formatTeammatesWithArea(teammates, cap) {
|
|
|
995
1002
|
|
|
996
1003
|
// src/lib/statusline-render.ts
|
|
997
1004
|
var STATUSLINE_NAME_CAP = 3;
|
|
998
|
-
function formatStatusline(version, snapshot, resolveIngestionStatus) {
|
|
1005
|
+
function formatStatusline(version, snapshot, resolveIngestionStatus, options = {}) {
|
|
1006
|
+
const ingestionSuffix = (status) => status === void 0 ? "" : ` \xB7 Decision ingestion ${status}`;
|
|
1007
|
+
const ingestionStatus = snapshot === null || snapshot.healthy === false ? options.includeIngestionWhenUnavailable ? resolveIngestionStatus() : void 0 : resolveIngestionStatus();
|
|
999
1008
|
if (!snapshot) {
|
|
1000
|
-
return `primitive ${version} (daemon: down)`;
|
|
1009
|
+
return `primitive ${version} (daemon: down${ingestionSuffix(ingestionStatus)})`;
|
|
1001
1010
|
}
|
|
1002
1011
|
if (snapshot.healthy === false) {
|
|
1003
1012
|
if (snapshot.needsReauth) {
|
|
1004
|
-
return `primitive ${version} (daemon: paused \xB7 run \`prim auth login
|
|
1013
|
+
return `primitive ${version} (daemon: paused \xB7 run \`prim auth login\`${ingestionSuffix(ingestionStatus)})`;
|
|
1005
1014
|
}
|
|
1006
1015
|
if (snapshot.ingestion?.healthy === false) {
|
|
1007
1016
|
const pending = snapshot.ingestion.pendingCount;
|
|
1008
1017
|
const qualifier = snapshot.ingestion.pendingSampled ? "at least " : "";
|
|
1009
|
-
return `primitive ${version} (daemon: degraded \xB7 delivery: stalled${typeof pending === "number" ? ` \xB7 ${qualifier}${String(pending)} pending` : ""})`;
|
|
1018
|
+
return `primitive ${version} (daemon: degraded \xB7 delivery: stalled${typeof pending === "number" ? ` \xB7 ${qualifier}${String(pending)} pending` : ""}${ingestionSuffix(ingestionStatus)})`;
|
|
1010
1019
|
}
|
|
1011
1020
|
if (snapshot.heartbeat?.healthy === false) {
|
|
1012
|
-
return `primitive ${version} (daemon: degraded \xB7 presence: unavailable)`;
|
|
1021
|
+
return `primitive ${version} (daemon: degraded \xB7 presence: unavailable${ingestionSuffix(ingestionStatus)})`;
|
|
1013
1022
|
}
|
|
1014
|
-
return `primitive ${version} (daemon: starting)`;
|
|
1023
|
+
return `primitive ${version} (daemon: starting${ingestionSuffix(ingestionStatus)})`;
|
|
1015
1024
|
}
|
|
1016
|
-
const ingestionStatus = resolveIngestionStatus();
|
|
1017
1025
|
if (snapshot.envMismatch) {
|
|
1018
1026
|
return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus} \xB7 presence: other env)`;
|
|
1019
1027
|
}
|
|
@@ -1022,7 +1030,7 @@ function formatStatusline(version, snapshot, resolveIngestionStatus) {
|
|
|
1022
1030
|
}
|
|
1023
1031
|
let team;
|
|
1024
1032
|
if (snapshot.onlineTeammates !== void 0) {
|
|
1025
|
-
team = `team: ${formatTeammatesWithArea(snapshot.onlineTeammates, STATUSLINE_NAME_CAP)}`;
|
|
1033
|
+
team = `team: ${formatTeammatesWithArea(snapshot.onlineTeammates, STATUSLINE_NAME_CAP, options.plainLinks === true)}`;
|
|
1026
1034
|
} else if (snapshot.onlineNames !== void 0) {
|
|
1027
1035
|
team = `team: ${formatTeammates(snapshot.onlineNames, STATUSLINE_NAME_CAP)}`;
|
|
1028
1036
|
} else if (typeof snapshot.onlineCount === "number") {
|
|
@@ -1143,6 +1151,56 @@ function daemonRequest(method, params = {}, opts = {}) {
|
|
|
1143
1151
|
});
|
|
1144
1152
|
}
|
|
1145
1153
|
|
|
1154
|
+
// src/daemon/decision-digest-cache.ts
|
|
1155
|
+
var DECISION_DIGEST_CACHE_WARMING = "daemon Decision cache warming";
|
|
1156
|
+
var DECISION_DIGEST_CACHE_LIMIT = 100;
|
|
1157
|
+
var DECISION_DIGEST_CACHE_WINDOW = "24h";
|
|
1158
|
+
var DECISION_DIGEST_CACHE_PATH = `/api/cli/decisions/recent?limit=${String(DECISION_DIGEST_CACHE_LIMIT)}&since=${DECISION_DIGEST_CACHE_WINDOW}`;
|
|
1159
|
+
function errorMessage(error) {
|
|
1160
|
+
return error instanceof Error ? error.message : String(error);
|
|
1161
|
+
}
|
|
1162
|
+
var DecisionDigestCache = class {
|
|
1163
|
+
constructor(load, now = Date.now) {
|
|
1164
|
+
this.load = load;
|
|
1165
|
+
this.now = now;
|
|
1166
|
+
}
|
|
1167
|
+
snapshot = {
|
|
1168
|
+
decisions: [],
|
|
1169
|
+
unavailable: DECISION_DIGEST_CACHE_WARMING
|
|
1170
|
+
};
|
|
1171
|
+
refreshInFlight;
|
|
1172
|
+
read() {
|
|
1173
|
+
return { ...this.snapshot, decisions: [...this.snapshot.decisions] };
|
|
1174
|
+
}
|
|
1175
|
+
refresh() {
|
|
1176
|
+
if (this.refreshInFlight) return this.refreshInFlight;
|
|
1177
|
+
this.refreshInFlight = this.performRefresh().finally(() => {
|
|
1178
|
+
this.refreshInFlight = void 0;
|
|
1179
|
+
});
|
|
1180
|
+
return this.refreshInFlight;
|
|
1181
|
+
}
|
|
1182
|
+
async performRefresh() {
|
|
1183
|
+
try {
|
|
1184
|
+
const value = await this.load();
|
|
1185
|
+
if (typeof value !== "object" || value === null || !Array.isArray(value.decisions)) {
|
|
1186
|
+
throw new Error("malformed Decision feed response");
|
|
1187
|
+
}
|
|
1188
|
+
this.snapshot = {
|
|
1189
|
+
decisions: value.decisions,
|
|
1190
|
+
cachedAt: this.now(),
|
|
1191
|
+
unavailable: typeof value.unavailable === "string" ? value.unavailable : void 0
|
|
1192
|
+
};
|
|
1193
|
+
} catch (error) {
|
|
1194
|
+
if (this.snapshot.cachedAt === void 0) {
|
|
1195
|
+
this.snapshot = {
|
|
1196
|
+
decisions: [],
|
|
1197
|
+
unavailable: `daemon Decision cache unavailable: ${errorMessage(error)}`
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
};
|
|
1203
|
+
|
|
1146
1204
|
// src/daemon/env-binding.ts
|
|
1147
1205
|
function normalizeApiUrl(url) {
|
|
1148
1206
|
return url.trim().replace(/\/+$/, "");
|
|
@@ -1424,6 +1482,7 @@ var CONFIG_DIR = join9(homedir6(), ".config", "prim");
|
|
|
1424
1482
|
var SOCK_PATH2 = join9(CONFIG_DIR, "sock");
|
|
1425
1483
|
var HEARTBEAT_INTERVAL_MS = 3e4;
|
|
1426
1484
|
var INGESTION_POLL_INTERVAL_MS = 15e3;
|
|
1485
|
+
var DECISION_DIGEST_POLL_INTERVAL_MS = 5e3;
|
|
1427
1486
|
var TOKEN_CHECK_INTERVAL_MS = 6e4;
|
|
1428
1487
|
var TOKEN_REFRESH_THRESHOLD_MS = 9e4;
|
|
1429
1488
|
var HTTP_PROXY_TIMEOUT_MS = 1e4;
|
|
@@ -1435,6 +1494,11 @@ var client = getClient();
|
|
|
1435
1494
|
var runtimeVersion = resolveRuntimeVersion();
|
|
1436
1495
|
var daemonHealth = createDaemonHealthState(runtimeVersion, process.pid, startedAt);
|
|
1437
1496
|
var statuslineIngestionCache = new StatuslineIngestionCache(decisionIngestionStatus);
|
|
1497
|
+
var decisionDigestCache = new DecisionDigestCache(
|
|
1498
|
+
async () => await client.get(DECISION_DIGEST_CACHE_PATH, {
|
|
1499
|
+
signal: AbortSignal.timeout(HTTP_PROXY_TIMEOUT_MS)
|
|
1500
|
+
})
|
|
1501
|
+
);
|
|
1438
1502
|
var activeSessionId = process.env.PRIM_DAEMON_SESSION_ID ?? `daemon-${process.pid}`;
|
|
1439
1503
|
var lastHeartbeatAt;
|
|
1440
1504
|
var lastOnlineCount;
|
|
@@ -1443,6 +1507,7 @@ var lastOnlineTeammates;
|
|
|
1443
1507
|
var lastOkAtLocal;
|
|
1444
1508
|
var heartbeatTimer;
|
|
1445
1509
|
var ingestionTimer;
|
|
1510
|
+
var decisionDigestTimer;
|
|
1446
1511
|
var tokenCheckTimer;
|
|
1447
1512
|
var heartbeatInFlight;
|
|
1448
1513
|
var heartbeatRerunRequested = false;
|
|
@@ -1462,7 +1527,7 @@ function resolveRuntimeVersion() {
|
|
|
1462
1527
|
return "unknown";
|
|
1463
1528
|
}
|
|
1464
1529
|
}
|
|
1465
|
-
function
|
|
1530
|
+
function errorMessage2(err) {
|
|
1466
1531
|
return err instanceof Error ? err.message : String(err);
|
|
1467
1532
|
}
|
|
1468
1533
|
function persistHealth() {
|
|
@@ -1471,7 +1536,7 @@ function persistHealth() {
|
|
|
1471
1536
|
try {
|
|
1472
1537
|
writeDaemonHealthState(daemonHealth);
|
|
1473
1538
|
} catch (err) {
|
|
1474
|
-
process.stderr.write(`[prim-daemon] health-state error: ${
|
|
1539
|
+
process.stderr.write(`[prim-daemon] health-state error: ${errorMessage2(err)}
|
|
1475
1540
|
`);
|
|
1476
1541
|
}
|
|
1477
1542
|
}
|
|
@@ -1496,9 +1561,13 @@ function enterReauthHold() {
|
|
|
1496
1561
|
clearTimeout(ingestionTimer);
|
|
1497
1562
|
ingestionTimer = void 0;
|
|
1498
1563
|
}
|
|
1564
|
+
if (decisionDigestTimer) {
|
|
1565
|
+
clearTimeout(decisionDigestTimer);
|
|
1566
|
+
decisionDigestTimer = void 0;
|
|
1567
|
+
}
|
|
1499
1568
|
persistHealth();
|
|
1500
1569
|
process.stderr.write(
|
|
1501
|
-
"[prim-daemon] authentication ended \u2014 halting heartbeat + ingestion until `prim auth login` (captures continue to the journal)\n"
|
|
1570
|
+
"[prim-daemon] authentication ended \u2014 halting heartbeat + ingestion + Decision cache until `prim auth login` (captures continue to the journal)\n"
|
|
1502
1571
|
);
|
|
1503
1572
|
}
|
|
1504
1573
|
function exitReauthHold() {
|
|
@@ -1513,10 +1582,11 @@ function exitReauthHold() {
|
|
|
1513
1582
|
daemonHealth.ingestion.lastError = void 0;
|
|
1514
1583
|
persistHealth();
|
|
1515
1584
|
process.stderr.write(
|
|
1516
|
-
"[prim-daemon] re-authentication detected \u2014 resuming heartbeat + ingestion\n"
|
|
1585
|
+
"[prim-daemon] re-authentication detected \u2014 resuming heartbeat + ingestion + Decision cache\n"
|
|
1517
1586
|
);
|
|
1518
1587
|
void sendHeartbeat();
|
|
1519
1588
|
void runIngestionLoop();
|
|
1589
|
+
void runDecisionDigestLoop();
|
|
1520
1590
|
}
|
|
1521
1591
|
async function takeOwnership() {
|
|
1522
1592
|
const now = Date.now();
|
|
@@ -1566,7 +1636,7 @@ function cleanup() {
|
|
|
1566
1636
|
try {
|
|
1567
1637
|
releaseDaemonOwnership(ownership, SOCK_PATH2);
|
|
1568
1638
|
} catch (err) {
|
|
1569
|
-
process.stderr.write(`[prim-daemon] cleanup error: ${
|
|
1639
|
+
process.stderr.write(`[prim-daemon] cleanup error: ${errorMessage2(err)}
|
|
1570
1640
|
`);
|
|
1571
1641
|
}
|
|
1572
1642
|
}
|
|
@@ -1599,14 +1669,14 @@ async function performHeartbeat() {
|
|
|
1599
1669
|
}
|
|
1600
1670
|
persistHealth();
|
|
1601
1671
|
} catch (err) {
|
|
1602
|
-
daemonHealth.heartbeat.lastError =
|
|
1672
|
+
daemonHealth.heartbeat.lastError = errorMessage2(err);
|
|
1603
1673
|
daemonHealth.heartbeat.consecutiveFailures += 1;
|
|
1604
1674
|
persistHealth();
|
|
1605
1675
|
if (isSessionEnded()) {
|
|
1606
1676
|
enterReauthHold();
|
|
1607
1677
|
return;
|
|
1608
1678
|
}
|
|
1609
|
-
process.stderr.write(`[prim-daemon] heartbeat error: ${
|
|
1679
|
+
process.stderr.write(`[prim-daemon] heartbeat error: ${errorMessage2(err)}
|
|
1610
1680
|
`);
|
|
1611
1681
|
}
|
|
1612
1682
|
}
|
|
@@ -1677,7 +1747,7 @@ async function runIngestionLoop() {
|
|
|
1677
1747
|
const failures = daemonHealth.ingestion.consecutiveFailures + 1;
|
|
1678
1748
|
const delay = ingestionRetryDelayMs(failures);
|
|
1679
1749
|
daemonHealth.ingestion.consecutiveFailures = failures;
|
|
1680
|
-
daemonHealth.ingestion.lastError = `journal scan failed: ${
|
|
1750
|
+
daemonHealth.ingestion.lastError = `journal scan failed: ${errorMessage2(err)}`;
|
|
1681
1751
|
daemonHealth.ingestion.nextRetryAt = Date.now() + delay;
|
|
1682
1752
|
persistHealth();
|
|
1683
1753
|
scheduleIngestion(delay);
|
|
@@ -1712,7 +1782,7 @@ async function runIngestionLoop() {
|
|
|
1712
1782
|
const delay = ingestionRetryDelayMs(failures);
|
|
1713
1783
|
daemonHealth.ingestion.lastAcknowledgedCount = err instanceof FlushError ? err.flushed : 0;
|
|
1714
1784
|
daemonHealth.ingestion.consecutiveFailures = failures;
|
|
1715
|
-
daemonHealth.ingestion.lastError =
|
|
1785
|
+
daemonHealth.ingestion.lastError = errorMessage2(err);
|
|
1716
1786
|
daemonHealth.ingestion.nextRetryAt = Date.now() + delay;
|
|
1717
1787
|
try {
|
|
1718
1788
|
updatePendingHealth();
|
|
@@ -1723,11 +1793,25 @@ async function runIngestionLoop() {
|
|
|
1723
1793
|
enterReauthHold();
|
|
1724
1794
|
return;
|
|
1725
1795
|
}
|
|
1726
|
-
process.stderr.write(`[prim-daemon] ingestion error: ${
|
|
1796
|
+
process.stderr.write(`[prim-daemon] ingestion error: ${errorMessage2(err)}
|
|
1727
1797
|
`);
|
|
1728
1798
|
scheduleIngestion(delay);
|
|
1729
1799
|
}
|
|
1730
1800
|
}
|
|
1801
|
+
function scheduleDecisionDigestRefresh() {
|
|
1802
|
+
if (shuttingDown || reauthHold) return;
|
|
1803
|
+
decisionDigestTimer = setTimeout(() => {
|
|
1804
|
+
decisionDigestTimer = void 0;
|
|
1805
|
+
void runDecisionDigestLoop();
|
|
1806
|
+
}, DECISION_DIGEST_POLL_INTERVAL_MS);
|
|
1807
|
+
}
|
|
1808
|
+
function refreshDecisionDigest() {
|
|
1809
|
+
return shuttingDown || reauthHold ? Promise.resolve() : decisionDigestCache.refresh();
|
|
1810
|
+
}
|
|
1811
|
+
async function runDecisionDigestLoop() {
|
|
1812
|
+
await refreshDecisionDigest();
|
|
1813
|
+
scheduleDecisionDigestRefresh();
|
|
1814
|
+
}
|
|
1731
1815
|
function pathParam(params) {
|
|
1732
1816
|
if (typeof params.path !== "string" || !params.path.startsWith("/api/cli/")) {
|
|
1733
1817
|
throw new Error("proxy request requires `path: string` under /api/cli/");
|
|
@@ -1814,6 +1898,7 @@ async function dispatchRequest(req) {
|
|
|
1814
1898
|
activeSessionId = sid;
|
|
1815
1899
|
}
|
|
1816
1900
|
await sendHeartbeat();
|
|
1901
|
+
void refreshDecisionDigest();
|
|
1817
1902
|
return { id, ok: true, result: { sessionId: activeSessionId } };
|
|
1818
1903
|
}
|
|
1819
1904
|
case "session_end": {
|
|
@@ -1821,6 +1906,12 @@ async function dispatchRequest(req) {
|
|
|
1821
1906
|
}
|
|
1822
1907
|
case "status_snapshot":
|
|
1823
1908
|
return { id, ok: true, result: handleStatusSnapshot(req.params ?? {}) };
|
|
1909
|
+
case "decision_digest_snapshot": {
|
|
1910
|
+
assertCallerEnvMatches(req.params?.callerEnv, getSiteUrl());
|
|
1911
|
+
const result = decisionDigestCache.read();
|
|
1912
|
+
void refreshDecisionDigest();
|
|
1913
|
+
return { id, ok: true, result };
|
|
1914
|
+
}
|
|
1824
1915
|
case "statusline_invalidate":
|
|
1825
1916
|
statuslineIngestionCache.clear();
|
|
1826
1917
|
return { id, ok: true, result: { ack: true } };
|
|
@@ -1955,6 +2046,7 @@ function startTimers() {
|
|
|
1955
2046
|
persistHealth();
|
|
1956
2047
|
void sendHeartbeat();
|
|
1957
2048
|
void runIngestionLoop();
|
|
2049
|
+
void runDecisionDigestLoop();
|
|
1958
2050
|
}
|
|
1959
2051
|
void runTokenCheckLoop();
|
|
1960
2052
|
}
|
|
@@ -1983,6 +2075,9 @@ function stopTimers() {
|
|
|
1983
2075
|
if (ingestionTimer) {
|
|
1984
2076
|
clearTimeout(ingestionTimer);
|
|
1985
2077
|
}
|
|
2078
|
+
if (decisionDigestTimer) {
|
|
2079
|
+
clearTimeout(decisionDigestTimer);
|
|
2080
|
+
}
|
|
1986
2081
|
if (tokenCheckTimer) {
|
|
1987
2082
|
clearTimeout(tokenCheckTimer);
|
|
1988
2083
|
}
|
|
@@ -1997,14 +2092,14 @@ function installSignalHandlers() {
|
|
|
1997
2092
|
shutdown(EXIT_CRASH, `[prim-daemon] uncaught: ${err.message}`);
|
|
1998
2093
|
});
|
|
1999
2094
|
process.on("unhandledRejection", (err) => {
|
|
2000
|
-
shutdown(EXIT_CRASH, `[prim-daemon] unhandled rejection: ${
|
|
2095
|
+
shutdown(EXIT_CRASH, `[prim-daemon] unhandled rejection: ${errorMessage2(err)}`);
|
|
2001
2096
|
});
|
|
2002
2097
|
}
|
|
2003
2098
|
async function main() {
|
|
2004
2099
|
try {
|
|
2005
2100
|
await takeOwnership();
|
|
2006
2101
|
} catch (err) {
|
|
2007
|
-
process.stderr.write(`[prim-daemon] ${
|
|
2102
|
+
process.stderr.write(`[prim-daemon] ${errorMessage2(err)}
|
|
2008
2103
|
`);
|
|
2009
2104
|
cleanup();
|
|
2010
2105
|
process.exit(EXIT_CRASH);
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
appendMove,
|
|
8
8
|
resolveOrg
|
|
9
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-UN5O5SYG.js";
|
|
10
10
|
import {
|
|
11
11
|
getOrCreateWorkspaceId
|
|
12
12
|
} from "../chunk-UVIWZWM6.js";
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
repoSyncId,
|
|
17
17
|
resolveRepositoryContext
|
|
18
18
|
} from "../chunk-TDRXHBEX.js";
|
|
19
|
-
import "../chunk-
|
|
19
|
+
import "../chunk-RTLFE6EZ.js";
|
|
20
20
|
|
|
21
21
|
// src/hooks/post-commit.ts
|
|
22
22
|
import { execFileSync, spawn } from "child_process";
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
appendMove,
|
|
8
8
|
resolveOrg
|
|
9
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-UN5O5SYG.js";
|
|
10
10
|
import {
|
|
11
11
|
getOrCreateWorkspaceId
|
|
12
12
|
} from "../chunk-UVIWZWM6.js";
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
repoSyncId,
|
|
17
17
|
resolveRepositoryContext
|
|
18
18
|
} from "../chunk-TDRXHBEX.js";
|
|
19
|
-
import "../chunk-
|
|
19
|
+
import "../chunk-RTLFE6EZ.js";
|
|
20
20
|
|
|
21
21
|
// src/hooks/post-rewrite.ts
|
|
22
22
|
import { execFileSync, spawn } from "child_process";
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
bold,
|
|
4
|
-
color,
|
|
5
3
|
requireDurableIngestAcknowledgement
|
|
6
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-XGADQPCI.js";
|
|
7
5
|
import {
|
|
8
6
|
enrichHookPayloadWithFileRefs,
|
|
9
7
|
preserveHookFileMetadata
|
|
@@ -16,7 +14,7 @@ import {
|
|
|
16
14
|
import {
|
|
17
15
|
appendMove,
|
|
18
16
|
resolveOrg
|
|
19
|
-
} from "../chunk-
|
|
17
|
+
} from "../chunk-UN5O5SYG.js";
|
|
20
18
|
import {
|
|
21
19
|
getOrCreateWorkspaceId
|
|
22
20
|
} from "../chunk-UVIWZWM6.js";
|
|
@@ -24,8 +22,14 @@ import {
|
|
|
24
22
|
scrubFromCwd
|
|
25
23
|
} from "../chunk-G5D3FF3D.js";
|
|
26
24
|
import {
|
|
25
|
+
appendCodexContext,
|
|
26
|
+
prepareCodexContext
|
|
27
|
+
} from "../chunk-I5BHJQH2.js";
|
|
28
|
+
import {
|
|
29
|
+
bold,
|
|
30
|
+
color,
|
|
27
31
|
warmBinCache
|
|
28
|
-
} from "../chunk-
|
|
32
|
+
} from "../chunk-K3AYW6VZ.js";
|
|
29
33
|
import {
|
|
30
34
|
isRepoActiveForCapture,
|
|
31
35
|
repoSyncId,
|
|
@@ -33,11 +37,12 @@ import {
|
|
|
33
37
|
} from "../chunk-TDRXHBEX.js";
|
|
34
38
|
import {
|
|
35
39
|
getClient
|
|
36
|
-
} from "../chunk-
|
|
40
|
+
} from "../chunk-RTLFE6EZ.js";
|
|
37
41
|
import {
|
|
38
42
|
normalizeEnvelope,
|
|
39
43
|
parseAgent
|
|
40
44
|
} from "../chunk-TCDUH7AN.js";
|
|
45
|
+
import "../chunk-UTKQTZHL.js";
|
|
41
46
|
|
|
42
47
|
// src/hooks/post-tool-use.ts
|
|
43
48
|
import { readFileSync } from "fs";
|
|
@@ -112,8 +117,19 @@ function readStdin() {
|
|
|
112
117
|
});
|
|
113
118
|
});
|
|
114
119
|
}
|
|
115
|
-
function emit() {
|
|
116
|
-
|
|
120
|
+
async function emit(output = {}) {
|
|
121
|
+
return await new Promise((resolve) => {
|
|
122
|
+
try {
|
|
123
|
+
process.stdout.write(`${JSON.stringify(output)}
|
|
124
|
+
`, (error) => resolve(!error));
|
|
125
|
+
} catch {
|
|
126
|
+
resolve(false);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
async function emitWithAcknowledgment(output, acknowledge) {
|
|
131
|
+
const handedOff = await emit(output);
|
|
132
|
+
await acknowledge?.(handedOff);
|
|
117
133
|
}
|
|
118
134
|
function debug(msg) {
|
|
119
135
|
if (process.env.PRIM_HOOK_VERBOSE === "1") {
|
|
@@ -128,31 +144,31 @@ async function main() {
|
|
|
128
144
|
try {
|
|
129
145
|
raw = await readStdin();
|
|
130
146
|
} catch {
|
|
131
|
-
emit();
|
|
147
|
+
await emit();
|
|
132
148
|
return;
|
|
133
149
|
}
|
|
134
150
|
let parsed;
|
|
135
151
|
try {
|
|
136
152
|
parsed = normalizeEnvelope(JSON.parse(raw), agent);
|
|
137
153
|
} catch {
|
|
138
|
-
emit();
|
|
154
|
+
await emit();
|
|
139
155
|
return;
|
|
140
156
|
}
|
|
141
157
|
let envelope = parsed;
|
|
142
158
|
const isToolResult = envelope.hook_event_name === "PostToolUse" || agent === "claude_code" && envelope.hook_event_name === "PostToolUseFailure";
|
|
143
159
|
const isHermesDenial = agent === "hermes" && envelope.hook_event_name === "post_approval_response" && toolOutcomeFor(parsed, agent) === "prevented";
|
|
144
160
|
if (!isToolResult && !isHermesDenial) {
|
|
145
|
-
emit();
|
|
161
|
+
await emit();
|
|
146
162
|
return;
|
|
147
163
|
}
|
|
148
164
|
const invocationId = postToolInvocationId(parsed, agent);
|
|
149
165
|
if (isHermesDenial && !invocationId) {
|
|
150
|
-
emit();
|
|
166
|
+
await emit();
|
|
151
167
|
return;
|
|
152
168
|
}
|
|
153
169
|
const toolName = typeof envelope.tool_name === "string" ? envelope.tool_name : "";
|
|
154
170
|
if (!isHermesDenial && !editingToolsFor(agent).has(toolName)) {
|
|
155
|
-
emit();
|
|
171
|
+
await emit();
|
|
156
172
|
return;
|
|
157
173
|
}
|
|
158
174
|
if (isHermesDenial && (typeof envelope.session_id !== "string" || envelope.session_id.length === 0) && typeof envelope.extra?.session_key === "string" && envelope.extra.session_key.length > 0) {
|
|
@@ -160,17 +176,17 @@ async function main() {
|
|
|
160
176
|
envelope = parsed;
|
|
161
177
|
}
|
|
162
178
|
if (typeof envelope.session_id !== "string" || envelope.session_id.length === 0) {
|
|
163
|
-
emit();
|
|
179
|
+
await emit();
|
|
164
180
|
return;
|
|
165
181
|
}
|
|
166
182
|
const cwd = parsed.cwd ?? process.cwd();
|
|
167
183
|
if (!isRepoActiveForCapture(cwd)) {
|
|
168
|
-
emit();
|
|
184
|
+
await emit();
|
|
169
185
|
return;
|
|
170
186
|
}
|
|
171
187
|
const resolvedRepository = resolveRepositoryContext(cwd);
|
|
172
188
|
if (!resolvedRepository) {
|
|
173
|
-
emit();
|
|
189
|
+
await emit();
|
|
174
190
|
return;
|
|
175
191
|
}
|
|
176
192
|
const repository = { ...resolvedRepository, repoSyncId: repoSyncId(cwd) };
|
|
@@ -183,11 +199,11 @@ async function main() {
|
|
|
183
199
|
const { resolution } = enrichment;
|
|
184
200
|
if (!isHermesDenial) {
|
|
185
201
|
if (resolution.shellMutation === "none") {
|
|
186
|
-
emit();
|
|
202
|
+
await emit();
|
|
187
203
|
return;
|
|
188
204
|
}
|
|
189
205
|
if (resolution.shellMutation === void 0 && resolution.fileRefs.length === 0) {
|
|
190
|
-
emit();
|
|
206
|
+
await emit();
|
|
191
207
|
return;
|
|
192
208
|
}
|
|
193
209
|
}
|
|
@@ -201,18 +217,32 @@ async function main() {
|
|
|
201
217
|
payload: preserveHookFileMetadata(scrubbed, resolution)
|
|
202
218
|
};
|
|
203
219
|
const { orgId } = resolveOrg({ sessionId: move.sessionId, cwd: move.env.cwd });
|
|
220
|
+
let verdictFooter = false;
|
|
204
221
|
try {
|
|
205
222
|
const result = await deliverPostToolMove(move, orgId);
|
|
206
223
|
debug(`durably ingested ${move.moveId} (${toolName})`);
|
|
207
224
|
if (isVerdictFooterContext(result.verdictFooter)) {
|
|
225
|
+
verdictFooter = true;
|
|
208
226
|
process.stderr.write(`${renderVerdictFooter(result.verdictFooter)}
|
|
209
227
|
`);
|
|
210
228
|
}
|
|
211
229
|
} catch (err) {
|
|
212
230
|
debug(`ingest failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
213
231
|
}
|
|
214
|
-
|
|
232
|
+
if (agent === "codex" && verdictFooter) {
|
|
233
|
+
try {
|
|
234
|
+
const context = await prepareCodexContext({
|
|
235
|
+
cwd,
|
|
236
|
+
sessionId: envelope.session_id,
|
|
237
|
+
includeDigest: false
|
|
238
|
+
});
|
|
239
|
+
await emitWithAcknowledgment(appendCodexContext({}, context.context), context.acknowledge);
|
|
240
|
+
return;
|
|
241
|
+
} catch {
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
await emit();
|
|
215
245
|
}
|
|
216
|
-
main().catch(() => {
|
|
217
|
-
emit();
|
|
246
|
+
main().catch(async () => {
|
|
247
|
+
await emit();
|
|
218
248
|
});
|
package/dist/hooks/pre-commit.js
CHANGED