@primitive.ai/prim 0.1.0-alpha.49 → 0.1.0-alpha.51
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/SKILL.md +1 -1
- package/dist/{chunk-S5NUHFAD.js → chunk-4GLVJLEQ.js} +5 -5
- package/dist/{chunk-5K725K35.js → chunk-BOTKPLTI.js} +1 -1
- package/dist/{chunk-TPROV45L.js → chunk-DWDEQRWN.js} +12 -1
- package/dist/chunk-UJEQX52M.js +520 -0
- package/dist/{chunk-WRPKAPVE.js → chunk-VDLAAUWV.js} +1 -1
- package/dist/{chunk-CK75QBCB.js → chunk-WELBNODJ.js} +1 -1
- package/dist/daemon/server.js +46 -18
- package/dist/hooks/post-commit.js +2 -2
- package/dist/hooks/post-tool-use.js +2 -2
- package/dist/hooks/pre-commit.js +2 -2
- package/dist/hooks/pre-tool-use.js +1 -1
- package/dist/hooks/prim-hook.js +4 -4
- package/dist/hooks/session-start.js +107 -61
- package/dist/index.js +57 -332
- package/dist/statusline-main.js +3 -0
- package/package.json +1 -1
package/dist/daemon/server.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { existsSync as existsSync6, readFileSync as readFileSync5, unlinkSync as unlinkSync3 } from "fs";
|
|
5
5
|
import { createServer } from "net";
|
|
6
6
|
import { homedir as homedir5 } from "os";
|
|
7
|
-
import { join as
|
|
7
|
+
import { join as join8 } from "path";
|
|
8
8
|
import { fileURLToPath } from "url";
|
|
9
9
|
|
|
10
10
|
// src/client.ts
|
|
@@ -163,6 +163,7 @@ var CONFIG_DIR_MODE = 448;
|
|
|
163
163
|
var CREDENTIAL_FILE_MODE = 384;
|
|
164
164
|
var REFRESH_THRESHOLD_MS = 6e4;
|
|
165
165
|
var DEFAULT_API_URL = "https://api.getprimitive.ai";
|
|
166
|
+
var AUTH_EXPIRED_MESSAGE = "Authentication expired. Run `prim auth login` to re-authenticate.";
|
|
166
167
|
function loadEnvFile() {
|
|
167
168
|
const envVars = {};
|
|
168
169
|
for (const file of [".env.local", ".env"]) {
|
|
@@ -319,6 +320,7 @@ async function performTokenRefresh(options = {}) {
|
|
|
319
320
|
if (selected?.source !== "token_file") return void 0;
|
|
320
321
|
const startingGeneration = readTrimmed(REFRESH_TOKEN_PATH);
|
|
321
322
|
if (!startingGeneration || isSessionEnded()) return void 0;
|
|
323
|
+
const startingAccessToken = selected.token;
|
|
322
324
|
return withCredentialLock(
|
|
323
325
|
async () => {
|
|
324
326
|
const currentCredential = resolveAuthCredential();
|
|
@@ -328,6 +330,9 @@ async function performTokenRefresh(options = {}) {
|
|
|
328
330
|
if (currentGeneration !== startingGeneration) {
|
|
329
331
|
return currentCredential.token;
|
|
330
332
|
}
|
|
333
|
+
if (currentCredential.token !== startingAccessToken) {
|
|
334
|
+
return currentCredential.token;
|
|
335
|
+
}
|
|
331
336
|
if (isSessionEnded()) return void 0;
|
|
332
337
|
const response = await fetch(`${getSiteUrl()}/mcp/broker/refresh`, {
|
|
333
338
|
method: "POST",
|
|
@@ -412,6 +417,12 @@ async function request(method, path, body, options) {
|
|
|
412
417
|
});
|
|
413
418
|
if (token) credential = { token, source: "token_file" };
|
|
414
419
|
}
|
|
420
|
+
if (!credential) {
|
|
421
|
+
throw new HttpError(401, AUTH_EXPIRED_MESSAGE);
|
|
422
|
+
}
|
|
423
|
+
if (isTokenExpiringSoon(credential) && isSessionEnded()) {
|
|
424
|
+
throw new HttpError(401, AUTH_EXPIRED_MESSAGE);
|
|
425
|
+
}
|
|
415
426
|
const doFetch = (token) => {
|
|
416
427
|
const headers = { "Content-Type": "application/json" };
|
|
417
428
|
if (token) headers.Authorization = `Bearer ${token}`;
|
|
@@ -441,7 +452,7 @@ async function request(method, path, body, options) {
|
|
|
441
452
|
}
|
|
442
453
|
if (!response.ok) {
|
|
443
454
|
if (response.status === 401) {
|
|
444
|
-
throw new HttpError(401,
|
|
455
|
+
throw new HttpError(401, AUTH_EXPIRED_MESSAGE);
|
|
445
456
|
}
|
|
446
457
|
const errorBody = await response.json().catch(() => null);
|
|
447
458
|
throw new HttpError(response.status, errorBody?.error ?? `HTTP ${response.status}`);
|
|
@@ -457,6 +468,7 @@ function getClient() {
|
|
|
457
468
|
|
|
458
469
|
// src/flusher.ts
|
|
459
470
|
import { createReadStream, renameSync as renameSync2, unlinkSync } from "fs";
|
|
471
|
+
import { dirname as dirname4, join as join4 } from "path";
|
|
460
472
|
import { createInterface } from "readline";
|
|
461
473
|
|
|
462
474
|
// src/ingest-response.ts
|
|
@@ -824,11 +836,23 @@ async function flushOnce() {
|
|
|
824
836
|
return { flushed: total };
|
|
825
837
|
}
|
|
826
838
|
var flushInFlight;
|
|
839
|
+
var FLUSH_LOCK_PATH = join4(dirname4(JOURNAL_DIR), ".flush.lock");
|
|
840
|
+
var FLUSH_LOCK_TIMEOUT_MS = 250;
|
|
841
|
+
function isFlushLockContended(error) {
|
|
842
|
+
return error instanceof Error && error.message.startsWith("timed out waiting for file lock");
|
|
843
|
+
}
|
|
827
844
|
function flush() {
|
|
828
845
|
if (flushInFlight) {
|
|
829
846
|
return flushInFlight;
|
|
830
847
|
}
|
|
831
|
-
const attempt =
|
|
848
|
+
const attempt = withFileLock(FLUSH_LOCK_PATH, flushOnce, {
|
|
849
|
+
timeoutMs: FLUSH_LOCK_TIMEOUT_MS
|
|
850
|
+
}).catch((error) => {
|
|
851
|
+
if (isFlushLockContended(error)) {
|
|
852
|
+
return { flushed: 0, skipped: true };
|
|
853
|
+
}
|
|
854
|
+
throw error;
|
|
855
|
+
}).finally(() => {
|
|
832
856
|
if (flushInFlight === attempt) {
|
|
833
857
|
flushInFlight = void 0;
|
|
834
858
|
}
|
|
@@ -840,8 +864,8 @@ function flush() {
|
|
|
840
864
|
// src/daemon/client.ts
|
|
841
865
|
import { createConnection } from "net";
|
|
842
866
|
import { homedir as homedir3 } from "os";
|
|
843
|
-
import { join as
|
|
844
|
-
var SOCK_PATH =
|
|
867
|
+
import { join as join5 } from "path";
|
|
868
|
+
var SOCK_PATH = join5(homedir3(), ".config", "prim", "sock");
|
|
845
869
|
var DEFAULT_TIMEOUT_MS2 = 250;
|
|
846
870
|
var nextRequestId = 1;
|
|
847
871
|
function daemonRequest(method, params = {}, opts = {}) {
|
|
@@ -936,7 +960,7 @@ function assertCallerEnvMatches(callerEnv, boundEnv) {
|
|
|
936
960
|
// src/daemon/health.ts
|
|
937
961
|
import { chmodSync as chmodSync2, existsSync as existsSync4, mkdirSync as mkdirSync4, renameSync as renameSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
938
962
|
import { homedir as homedir4 } from "os";
|
|
939
|
-
import { dirname as
|
|
963
|
+
import { dirname as dirname5, join as join6 } from "path";
|
|
940
964
|
var CONFIG_DIR_MODE2 = 448;
|
|
941
965
|
var HEALTH_FILE_MODE = 384;
|
|
942
966
|
var INGESTION_SLA_MS = 3e4;
|
|
@@ -944,7 +968,7 @@ var HEARTBEAT_FRESH_MS = 9e4;
|
|
|
944
968
|
var HEARTBEAT_RETRY_CAP_MS = 3e4;
|
|
945
969
|
var INGESTION_RETRY_BASE_MS = 5e3;
|
|
946
970
|
var INGESTION_RETRY_CAP_MS = 15e3;
|
|
947
|
-
var DAEMON_HEALTH_PATH =
|
|
971
|
+
var DAEMON_HEALTH_PATH = join6(homedir4(), ".config", "prim", "daemon-health.json");
|
|
948
972
|
function createDaemonHealthState(version, pid, startedAt2) {
|
|
949
973
|
return {
|
|
950
974
|
schemaVersion: 1,
|
|
@@ -982,11 +1006,11 @@ function ingestionRetryDelayMs(consecutiveFailures, random = Math.random) {
|
|
|
982
1006
|
return retryDelayMs(consecutiveFailures, INGESTION_RETRY_CAP_MS, random);
|
|
983
1007
|
}
|
|
984
1008
|
function writeDaemonHealthState(state, path = DAEMON_HEALTH_PATH) {
|
|
985
|
-
const dir =
|
|
1009
|
+
const dir = dirname5(path);
|
|
986
1010
|
if (!existsSync4(dir)) {
|
|
987
1011
|
mkdirSync4(dir, { recursive: true, mode: CONFIG_DIR_MODE2 });
|
|
988
1012
|
}
|
|
989
|
-
const tmp =
|
|
1013
|
+
const tmp = join6(dir, `.${process.pid}.daemon-health.tmp`);
|
|
990
1014
|
writeFileSync3(tmp, `${JSON.stringify(state, null, 2)}
|
|
991
1015
|
`, { mode: HEALTH_FILE_MODE });
|
|
992
1016
|
chmodSync2(tmp, HEALTH_FILE_MODE);
|
|
@@ -1006,7 +1030,7 @@ import {
|
|
|
1006
1030
|
unlinkSync as unlinkSync2,
|
|
1007
1031
|
writeFileSync as writeFileSync4
|
|
1008
1032
|
} from "fs";
|
|
1009
|
-
import { join as
|
|
1033
|
+
import { join as join7 } from "path";
|
|
1010
1034
|
var DIR_MODE = 448;
|
|
1011
1035
|
var FILE_MODE2 = 384;
|
|
1012
1036
|
var DAEMON_STARTUP_GRACE_MS = 5e3;
|
|
@@ -1037,10 +1061,10 @@ function daemonProcessIsAlive(pid) {
|
|
|
1037
1061
|
}
|
|
1038
1062
|
}
|
|
1039
1063
|
function readDaemonOwner(configDir) {
|
|
1040
|
-
return readLockRecord(
|
|
1064
|
+
return readLockRecord(join7(configDir, "daemon.lock", "owner.json"));
|
|
1041
1065
|
}
|
|
1042
1066
|
function readDaemonPid(configDir) {
|
|
1043
|
-
const path =
|
|
1067
|
+
const path = join7(configDir, "daemon.pid");
|
|
1044
1068
|
const pid = numericPid(path);
|
|
1045
1069
|
if (pid === void 0) {
|
|
1046
1070
|
return;
|
|
@@ -1055,9 +1079,9 @@ function acquireDaemonOwnership(configDir, opts = {}) {
|
|
|
1055
1079
|
const pid = opts.pid ?? process.pid;
|
|
1056
1080
|
const now = opts.now ?? Date.now();
|
|
1057
1081
|
const isAlive = opts.isAlive ?? daemonProcessIsAlive;
|
|
1058
|
-
const lockDir =
|
|
1059
|
-
const ownerPath2 =
|
|
1060
|
-
const pidPath =
|
|
1082
|
+
const lockDir = join7(configDir, "daemon.lock");
|
|
1083
|
+
const ownerPath2 = join7(lockDir, "owner.json");
|
|
1084
|
+
const pidPath = join7(configDir, "daemon.pid");
|
|
1061
1085
|
const instanceId = randomUUID();
|
|
1062
1086
|
if (!existsSync5(configDir)) {
|
|
1063
1087
|
mkdirSync5(configDir, { recursive: true, mode: DIR_MODE });
|
|
@@ -1155,8 +1179,8 @@ function releaseDaemonOwnership(ownership2, socketPath) {
|
|
|
1155
1179
|
}
|
|
1156
1180
|
|
|
1157
1181
|
// src/daemon/server.ts
|
|
1158
|
-
var CONFIG_DIR =
|
|
1159
|
-
var SOCK_PATH2 =
|
|
1182
|
+
var CONFIG_DIR = join8(homedir5(), ".config", "prim");
|
|
1183
|
+
var SOCK_PATH2 = join8(CONFIG_DIR, "sock");
|
|
1160
1184
|
var HEARTBEAT_INTERVAL_MS = 3e4;
|
|
1161
1185
|
var INGESTION_POLL_INTERVAL_MS = 15e3;
|
|
1162
1186
|
var TOKEN_CHECK_INTERVAL_MS = 6e4;
|
|
@@ -1190,7 +1214,7 @@ function resolveRuntimeVersion() {
|
|
|
1190
1214
|
}
|
|
1191
1215
|
try {
|
|
1192
1216
|
const here = fileURLToPath(new URL(".", import.meta.url));
|
|
1193
|
-
const pkg = JSON.parse(readFileSync5(
|
|
1217
|
+
const pkg = JSON.parse(readFileSync5(join8(here, "..", "..", "package.json"), "utf-8"));
|
|
1194
1218
|
return pkg.version ?? "unknown";
|
|
1195
1219
|
} catch {
|
|
1196
1220
|
return "unknown";
|
|
@@ -1430,6 +1454,10 @@ async function runIngestionLoop() {
|
|
|
1430
1454
|
persistHealth();
|
|
1431
1455
|
try {
|
|
1432
1456
|
const result = await flush();
|
|
1457
|
+
if (result.skipped) {
|
|
1458
|
+
scheduleIngestion(INGESTION_POLL_INTERVAL_MS);
|
|
1459
|
+
return;
|
|
1460
|
+
}
|
|
1433
1461
|
daemonHealth.ingestion.lastSuccessAt = Date.now();
|
|
1434
1462
|
daemonHealth.ingestion.lastAcknowledgedCount = result.flushed;
|
|
1435
1463
|
daemonHealth.ingestion.consecutiveFailures = 0;
|
|
@@ -5,11 +5,11 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
appendMove,
|
|
7
7
|
resolveOrg
|
|
8
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-BOTKPLTI.js";
|
|
9
9
|
import {
|
|
10
10
|
isRepoActiveForCapture
|
|
11
11
|
} from "../chunk-R5ZJRSLV.js";
|
|
12
|
-
import "../chunk-
|
|
12
|
+
import "../chunk-DWDEQRWN.js";
|
|
13
13
|
|
|
14
14
|
// src/hooks/post-commit.ts
|
|
15
15
|
import { execSync, spawn } from "child_process";
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
import {
|
|
14
14
|
appendMove,
|
|
15
15
|
resolveOrg
|
|
16
|
-
} from "../chunk-
|
|
16
|
+
} from "../chunk-BOTKPLTI.js";
|
|
17
17
|
import {
|
|
18
18
|
getOrCreateWorkspaceId
|
|
19
19
|
} from "../chunk-IMAIBPUC.js";
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
} from "../chunk-R5ZJRSLV.js";
|
|
26
26
|
import {
|
|
27
27
|
getClient
|
|
28
|
-
} from "../chunk-
|
|
28
|
+
} from "../chunk-DWDEQRWN.js";
|
|
29
29
|
import {
|
|
30
30
|
normalizeEnvelope,
|
|
31
31
|
parseAgent
|
package/dist/hooks/pre-commit.js
CHANGED
package/dist/hooks/prim-hook.js
CHANGED
|
@@ -9,17 +9,17 @@ import {
|
|
|
9
9
|
import {
|
|
10
10
|
appendMove,
|
|
11
11
|
resolveOrg
|
|
12
|
-
} from "../chunk-
|
|
12
|
+
} from "../chunk-BOTKPLTI.js";
|
|
13
13
|
import {
|
|
14
14
|
buildHookOutput,
|
|
15
15
|
handoffHookOutput
|
|
16
|
-
} from "../chunk-
|
|
16
|
+
} from "../chunk-4GLVJLEQ.js";
|
|
17
17
|
import {
|
|
18
18
|
FEEDBACK_DEADLINE_MS,
|
|
19
19
|
acknowledgeDecisionFeedback,
|
|
20
20
|
leaseDecisionFeedback,
|
|
21
21
|
renderFeedback
|
|
22
|
-
} from "../chunk-
|
|
22
|
+
} from "../chunk-WELBNODJ.js";
|
|
23
23
|
import {
|
|
24
24
|
getOrCreateWorkspaceId
|
|
25
25
|
} from "../chunk-IMAIBPUC.js";
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
import {
|
|
30
30
|
isRepoActiveForCapture
|
|
31
31
|
} from "../chunk-R5ZJRSLV.js";
|
|
32
|
-
import "../chunk-
|
|
32
|
+
import "../chunk-DWDEQRWN.js";
|
|
33
33
|
import {
|
|
34
34
|
normalizeEnvelope,
|
|
35
35
|
parseAgent
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
refreshClaudePlugins
|
|
4
|
+
} from "../chunk-UJEQX52M.js";
|
|
2
5
|
import {
|
|
3
6
|
buildHookOutput,
|
|
4
7
|
handoffHookOutput
|
|
5
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-4GLVJLEQ.js";
|
|
6
9
|
import {
|
|
7
10
|
FEEDBACK_DEADLINE_MS,
|
|
8
11
|
acknowledgeDecisionFeedback,
|
|
9
12
|
leaseDecisionFeedback,
|
|
10
13
|
renderFeedback
|
|
11
|
-
} from "../chunk-
|
|
14
|
+
} from "../chunk-WELBNODJ.js";
|
|
12
15
|
import {
|
|
13
16
|
getOrCreateWorkspaceId
|
|
14
17
|
} from "../chunk-IMAIBPUC.js";
|
|
@@ -17,11 +20,13 @@ import {
|
|
|
17
20
|
warmBinCache
|
|
18
21
|
} from "../chunk-HSZPTVKU.js";
|
|
19
22
|
import {
|
|
23
|
+
gitToplevel,
|
|
20
24
|
isRepoActiveForCapture
|
|
21
25
|
} from "../chunk-R5ZJRSLV.js";
|
|
22
26
|
import {
|
|
23
|
-
getSiteUrl
|
|
24
|
-
|
|
27
|
+
getSiteUrl,
|
|
28
|
+
isSessionEnded
|
|
29
|
+
} from "../chunk-DWDEQRWN.js";
|
|
25
30
|
import {
|
|
26
31
|
daemonRequest
|
|
27
32
|
} from "../chunk-UTKQTZHL.js";
|
|
@@ -50,59 +55,33 @@ function kickDaemonEnsure(options = {}) {
|
|
|
50
55
|
}
|
|
51
56
|
}
|
|
52
57
|
|
|
53
|
-
// src/hooks/
|
|
54
|
-
var
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return handoffHookOutput(output, acknowledge);
|
|
58
|
+
// src/hooks/reauth-notice.ts
|
|
59
|
+
var REAUTH_NOTICE = "[prim] authentication ended \u2014 run `prim auth login` to resume decision capture";
|
|
60
|
+
function reauthNoticeFields(agent) {
|
|
61
|
+
if (agent === "codex") return { additionalContext: REAUTH_NOTICE };
|
|
62
|
+
if (agent === "claude_code") return { systemMessage: REAUTH_NOTICE };
|
|
63
|
+
return;
|
|
60
64
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}, STDIN_TIMEOUT_MS);
|
|
67
|
-
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
68
|
-
process.stdin.on("end", () => {
|
|
69
|
-
clearTimeout(timer);
|
|
70
|
-
resolve(Buffer.concat(chunks).toString("utf-8"));
|
|
71
|
-
});
|
|
72
|
-
process.stdin.on("error", (err) => {
|
|
73
|
-
clearTimeout(timer);
|
|
74
|
-
reject(err);
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
async function main() {
|
|
79
|
-
const feedbackSignal = AbortSignal.timeout(FEEDBACK_DEADLINE_MS);
|
|
80
|
-
warmBinCache();
|
|
81
|
-
const agent = parseAgent(process.argv);
|
|
82
|
-
let raw;
|
|
83
|
-
try {
|
|
84
|
-
raw = await readStdin();
|
|
85
|
-
} catch {
|
|
86
|
-
await emitOutput(buildHookOutput({}));
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
65
|
+
|
|
66
|
+
// src/hooks/session-start-core.ts
|
|
67
|
+
var DAEMON_TIMEOUT_MS = 250;
|
|
68
|
+
var PRIM_SKILL_REMINDER = "Primitive is active in this repository. When this task chooses between plausible approaches or establishes or changes a lasting goal, priority, constraint, invariant, default, commitment, tradeoff, exception, or shared instruction, invoke the `prim` skill before finishing. Never invoke `prim` for routine implementation that merely follows an existing decision made before this task or for temporary tactics; they never qualify, including for evaluation. When a direct request replaces one lasting default with another but supplies no rationale, complete the work, invoke the skill, and ask one concise rationale question at the task boundary, even if the user requested only implementation or recording fails.";
|
|
69
|
+
async function processSessionStart(raw, agent, feedbackSignal = AbortSignal.timeout(FEEDBACK_DEADLINE_MS)) {
|
|
89
70
|
let envelope;
|
|
90
71
|
try {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
72
|
+
const parsed = JSON.parse(raw);
|
|
73
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
74
|
+
return { output: buildHookOutput({}) };
|
|
75
|
+
}
|
|
76
|
+
envelope = normalizeEnvelope(parsed, agent);
|
|
95
77
|
} catch {
|
|
96
|
-
|
|
97
|
-
return;
|
|
78
|
+
return { output: buildHookOutput({}) };
|
|
98
79
|
}
|
|
99
80
|
if (envelope.hook_event_name !== "SessionStart") {
|
|
100
|
-
|
|
101
|
-
return;
|
|
81
|
+
return { output: buildHookOutput({}) };
|
|
102
82
|
}
|
|
103
83
|
if (typeof envelope.session_id !== "string" || envelope.session_id.length === 0) {
|
|
104
|
-
|
|
105
|
-
return;
|
|
84
|
+
return { output: buildHookOutput({}) };
|
|
106
85
|
}
|
|
107
86
|
kickDaemonEnsure();
|
|
108
87
|
await daemonRequest(
|
|
@@ -110,6 +89,28 @@ async function main() {
|
|
|
110
89
|
{ sessionId: envelope.session_id },
|
|
111
90
|
{ timeoutMs: DAEMON_TIMEOUT_MS }
|
|
112
91
|
);
|
|
92
|
+
const cwd = envelope.cwd ?? process.cwd();
|
|
93
|
+
let active = false;
|
|
94
|
+
let skillState = { installed: 0, refreshed: 0 };
|
|
95
|
+
if (agent === "claude_code") {
|
|
96
|
+
try {
|
|
97
|
+
const projectRoot = gitToplevel(cwd);
|
|
98
|
+
active = projectRoot !== null && isRepoActiveForCapture(cwd);
|
|
99
|
+
skillState = await refreshClaudePlugins(
|
|
100
|
+
cwd,
|
|
101
|
+
active && projectRoot !== null ? { includeProject: true, projectRoot } : { includeProject: false }
|
|
102
|
+
);
|
|
103
|
+
} catch {
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (isSessionEnded()) {
|
|
107
|
+
const notice = reauthNoticeFields(agent);
|
|
108
|
+
if (notice) {
|
|
109
|
+
return {
|
|
110
|
+
output: buildHookOutput({ ...notice, reloadSkills: skillState.refreshed > 0 })
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
113
114
|
if (agent === "codex") {
|
|
114
115
|
const snapshot = await daemonRequest(
|
|
115
116
|
"status_snapshot",
|
|
@@ -119,15 +120,17 @@ async function main() {
|
|
|
119
120
|
{ timeoutMs: DAEMON_TIMEOUT_MS }
|
|
120
121
|
);
|
|
121
122
|
if (snapshot && !snapshot.presenceStale && typeof snapshot.onlineCount === "number") {
|
|
122
|
-
|
|
123
|
-
buildHookOutput({
|
|
124
|
-
|
|
125
|
-
|
|
123
|
+
return {
|
|
124
|
+
output: buildHookOutput({
|
|
125
|
+
additionalContext: `[prim] team: ${snapshot.onlineCount} online`
|
|
126
|
+
})
|
|
127
|
+
};
|
|
126
128
|
}
|
|
127
129
|
}
|
|
128
130
|
if (agent === "claude_code") {
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
+
const additionalContext = active && skillState.installed > 0 ? PRIM_SKILL_REMINDER : void 0;
|
|
132
|
+
const reloadSkills = skillState.refreshed > 0;
|
|
133
|
+
if (active) {
|
|
131
134
|
const identity = getOrCreateWorkspaceId(cwd);
|
|
132
135
|
if (identity.status === "ready") {
|
|
133
136
|
const lease = await leaseDecisionFeedback({
|
|
@@ -136,21 +139,64 @@ async function main() {
|
|
|
136
139
|
signal: feedbackSignal
|
|
137
140
|
});
|
|
138
141
|
const rendered = lease ? renderFeedback(lease) : void 0;
|
|
139
|
-
|
|
140
|
-
buildHookOutput({
|
|
141
|
-
|
|
142
|
+
return {
|
|
143
|
+
output: buildHookOutput({
|
|
144
|
+
systemMessage: rendered?.systemMessage,
|
|
145
|
+
additionalContext,
|
|
146
|
+
reloadSkills
|
|
147
|
+
}),
|
|
148
|
+
acknowledge: rendered ? async () => {
|
|
142
149
|
await acknowledgeDecisionFeedback({
|
|
143
150
|
workspaceId: identity.workspaceId,
|
|
144
151
|
deliveries: rendered.deliveries,
|
|
145
152
|
signal: feedbackSignal
|
|
146
153
|
});
|
|
147
154
|
} : void 0
|
|
148
|
-
|
|
149
|
-
return;
|
|
155
|
+
};
|
|
150
156
|
}
|
|
151
157
|
}
|
|
158
|
+
return { output: buildHookOutput({ additionalContext, reloadSkills }) };
|
|
159
|
+
}
|
|
160
|
+
return { output: buildHookOutput({}) };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// src/hooks/session-start.ts
|
|
164
|
+
var STDIN_TIMEOUT_MS = 1e3;
|
|
165
|
+
var outputAttempted = false;
|
|
166
|
+
function emitOutput(output, acknowledge) {
|
|
167
|
+
outputAttempted = true;
|
|
168
|
+
return handoffHookOutput(output, acknowledge);
|
|
169
|
+
}
|
|
170
|
+
function readStdin() {
|
|
171
|
+
return new Promise((resolve, reject) => {
|
|
172
|
+
const chunks = [];
|
|
173
|
+
const timer = setTimeout(() => {
|
|
174
|
+
reject(new Error("stdin read timeout"));
|
|
175
|
+
}, STDIN_TIMEOUT_MS);
|
|
176
|
+
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
177
|
+
process.stdin.on("end", () => {
|
|
178
|
+
clearTimeout(timer);
|
|
179
|
+
resolve(Buffer.concat(chunks).toString("utf-8"));
|
|
180
|
+
});
|
|
181
|
+
process.stdin.on("error", (err) => {
|
|
182
|
+
clearTimeout(timer);
|
|
183
|
+
reject(err);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
async function main() {
|
|
188
|
+
const feedbackSignal = AbortSignal.timeout(FEEDBACK_DEADLINE_MS);
|
|
189
|
+
warmBinCache();
|
|
190
|
+
const agent = parseAgent(process.argv);
|
|
191
|
+
let raw;
|
|
192
|
+
try {
|
|
193
|
+
raw = await readStdin();
|
|
194
|
+
} catch {
|
|
195
|
+
await emitOutput(buildHookOutput({}));
|
|
196
|
+
return;
|
|
152
197
|
}
|
|
153
|
-
await
|
|
198
|
+
const result = await processSessionStart(raw, agent, feedbackSignal);
|
|
199
|
+
await emitOutput(result.output, result.acknowledge);
|
|
154
200
|
}
|
|
155
201
|
void main().catch(async () => {
|
|
156
202
|
if (!outputAttempted) await emitOutput(buildHookOutput({}));
|