@primitive.ai/prim 0.1.0-alpha.8 → 0.1.0-alpha.81
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 +295 -37
- package/SKILL.md +269 -0
- package/dist/chunk-3EEFL6NJ.js +529 -0
- package/dist/chunk-4CZTNXIS.js +505 -0
- package/dist/chunk-4GLVJLEQ.js +55 -0
- package/dist/chunk-BQUJ4AFJ.js +771 -0
- package/dist/chunk-CUE2LKLR.js +155 -0
- package/dist/chunk-EZXEWFDG.js +210 -0
- package/dist/chunk-FI3LIRCB.js +262 -0
- package/dist/chunk-HWSS4BV5.js +1400 -0
- package/dist/chunk-LEMX3IOG.js +79 -0
- package/dist/chunk-LMRYZL3D.js +209 -0
- package/dist/chunk-OUXH3UEP.js +247 -0
- package/dist/chunk-PMRDE2DA.js +310 -0
- package/dist/chunk-RO3F6JYV.js +126 -0
- package/dist/chunk-TJTVHDJ5.js +321 -0
- package/dist/chunk-WB7AUZ4C.js +52 -0
- package/dist/chunk-XGADQPCI.js +26 -0
- package/dist/chunk-YWO7O6QG.js +327 -0
- package/dist/chunk-Z7YPFWAZ.js +917 -0
- package/dist/chunk-ZOSNEO5N.js +77 -0
- package/dist/daemon/server.js +15329 -0
- package/dist/hook-runtime/daemon/server.js +15329 -0
- package/dist/hook-runtime/hooks/post-commit.js +1128 -0
- package/dist/hook-runtime/hooks/post-rewrite.js +1148 -0
- package/dist/hook-runtime/hooks/post-tool-use.js +7586 -0
- package/dist/hook-runtime/hooks/pre-commit.js +1314 -0
- package/dist/hook-runtime/hooks/pre-tool-use.js +7099 -0
- package/dist/hook-runtime/hooks/prim-hook.js +7691 -0
- package/dist/hook-runtime/hooks/session-end.js +495 -0
- package/dist/hook-runtime/hooks/session-start.js +11313 -0
- package/dist/hook-runtime/index.js +44696 -0
- package/dist/hook-runtime/statusline-main.js +765 -0
- package/dist/hooks/post-commit.js +179 -0
- package/dist/hooks/post-rewrite.js +197 -0
- package/dist/hooks/post-tool-use.js +250 -0
- package/dist/hooks/pre-commit.js +21 -74
- package/dist/hooks/pre-tool-use.js +348 -0
- package/dist/hooks/prim-hook.js +195 -0
- package/dist/hooks/session-end.js +70 -0
- package/dist/hooks/session-start.js +299 -0
- package/dist/index.js +20981 -519
- package/dist/statusline-main.js +765 -0
- package/package.json +35 -13
- package/dist/chunk-3APLWTLB.js +0 -165
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatStatusline,
|
|
3
|
+
packageVersion
|
|
4
|
+
} from "./chunk-PMRDE2DA.js";
|
|
5
|
+
import {
|
|
6
|
+
decisionIngestionStatus,
|
|
7
|
+
gitToplevel,
|
|
8
|
+
repositoryBindingState
|
|
9
|
+
} from "./chunk-TJTVHDJ5.js";
|
|
10
|
+
import {
|
|
11
|
+
getSiteUrl,
|
|
12
|
+
isSessionEnded,
|
|
13
|
+
terminalSafeText,
|
|
14
|
+
withFileLock
|
|
15
|
+
} from "./chunk-Z7YPFWAZ.js";
|
|
16
|
+
import {
|
|
17
|
+
daemonPrincipalsMatch,
|
|
18
|
+
daemonRequest,
|
|
19
|
+
resolveDaemonPrincipal
|
|
20
|
+
} from "./chunk-CUE2LKLR.js";
|
|
21
|
+
import {
|
|
22
|
+
primConfigDirectory
|
|
23
|
+
} from "./chunk-3EEFL6NJ.js";
|
|
24
|
+
|
|
25
|
+
// src/hooks/codex-context.ts
|
|
26
|
+
import { createHash, randomBytes as randomBytes2 } from "crypto";
|
|
27
|
+
import {
|
|
28
|
+
chmodSync,
|
|
29
|
+
existsSync,
|
|
30
|
+
mkdirSync,
|
|
31
|
+
readFileSync,
|
|
32
|
+
readdirSync,
|
|
33
|
+
renameSync,
|
|
34
|
+
rmSync,
|
|
35
|
+
statSync,
|
|
36
|
+
unlinkSync,
|
|
37
|
+
writeFileSync
|
|
38
|
+
} from "fs";
|
|
39
|
+
import { join } from "path";
|
|
40
|
+
|
|
41
|
+
// src/daemon/decision-digest-cache.ts
|
|
42
|
+
import { randomBytes } from "crypto";
|
|
43
|
+
var DECISION_DIGEST_CACHE_LIMIT = 100;
|
|
44
|
+
var DECISION_DIGEST_CACHE_WINDOW = "24h";
|
|
45
|
+
var DECISION_DIGEST_CACHE_PATH = `/api/cli/decisions/recent?limit=${String(DECISION_DIGEST_CACHE_LIMIT)}&since=${DECISION_DIGEST_CACHE_WINDOW}`;
|
|
46
|
+
var DECISION_DRAFT_DIGEST_CACHE_PATH = `${DECISION_DIGEST_CACHE_PATH}&drafts=true`;
|
|
47
|
+
var SAFE_PRIVATE_DRAFT_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/u;
|
|
48
|
+
function isActionablePrivateDraft(row) {
|
|
49
|
+
if (typeof row !== "object" || row === null || Array.isArray(row)) return false;
|
|
50
|
+
const candidate = row;
|
|
51
|
+
return typeof candidate.id === "string" && SAFE_PRIVATE_DRAFT_ID.test(candidate.id) && typeof candidate.intent === "string" && candidate.authorIsSelf === true && candidate.stage === "draft" && (candidate.intentKind === void 0 || candidate.intentKind === "change");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/hooks/codex-context.ts
|
|
55
|
+
var CODEX_CONTEXT_TIMEOUT_MS = 250;
|
|
56
|
+
var CODEX_DIGEST_LIMIT = DECISION_DIGEST_CACHE_LIMIT;
|
|
57
|
+
var CODEX_DIGEST_VISIBLE_CAP = 3;
|
|
58
|
+
var CODEX_DIGEST_OVERLAP_MS = 6e4;
|
|
59
|
+
var CODEX_DIGEST_MAX_SEEN_IDS = 128;
|
|
60
|
+
var CODEX_DRAFT_DIGEST_MAX_SEEN_IDS = 4096;
|
|
61
|
+
var CODEX_DIGEST_STATE_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
62
|
+
var CODEX_DIGEST_STATE_MAX_FILES = 256;
|
|
63
|
+
var STATE_VERSION = 1;
|
|
64
|
+
var STATE_DIRECTORY = ["codex", "decision-digests"];
|
|
65
|
+
var DRAFT_STATE_VERSION = 2;
|
|
66
|
+
var DRAFT_STATE_DIRECTORY = ["codex", "decision-draft-deliveries"];
|
|
67
|
+
var NO_CURSOR = -1;
|
|
68
|
+
function hasVisibleCodexMessage(output) {
|
|
69
|
+
if (typeof output !== "object" || output === null || Array.isArray(output)) return false;
|
|
70
|
+
const candidate = output;
|
|
71
|
+
return Boolean(
|
|
72
|
+
candidate.systemMessage?.trim() || candidate.hookSpecificOutput?.permissionDecisionReason?.trim() || candidate.hookSpecificOutput?.additionalContext?.trim()
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
function appendContext(existing, context) {
|
|
76
|
+
return existing ? `${existing}
|
|
77
|
+
|
|
78
|
+
${context}` : context;
|
|
79
|
+
}
|
|
80
|
+
function appendCodexContext(output, context) {
|
|
81
|
+
if (!context) return output;
|
|
82
|
+
const candidate = output;
|
|
83
|
+
const next = { ...output };
|
|
84
|
+
if (candidate.systemMessage) {
|
|
85
|
+
next.systemMessage = appendContext(candidate.systemMessage, context);
|
|
86
|
+
} else if (candidate.hookSpecificOutput?.permissionDecisionReason) {
|
|
87
|
+
next.systemMessage = context;
|
|
88
|
+
} else if (candidate.hookSpecificOutput?.additionalContext) {
|
|
89
|
+
next.hookSpecificOutput = {
|
|
90
|
+
...candidate.hookSpecificOutput,
|
|
91
|
+
additionalContext: appendContext(candidate.hookSpecificOutput.additionalContext, context)
|
|
92
|
+
};
|
|
93
|
+
} else {
|
|
94
|
+
next.systemMessage = context;
|
|
95
|
+
}
|
|
96
|
+
return next;
|
|
97
|
+
}
|
|
98
|
+
function stateRoot() {
|
|
99
|
+
return join(primConfigDirectory(), ...STATE_DIRECTORY);
|
|
100
|
+
}
|
|
101
|
+
function draftStateRoot() {
|
|
102
|
+
return join(primConfigDirectory(), ...DRAFT_STATE_DIRECTORY);
|
|
103
|
+
}
|
|
104
|
+
function workspaceFor(cwd) {
|
|
105
|
+
try {
|
|
106
|
+
return gitToplevel(cwd) ?? cwd;
|
|
107
|
+
} catch {
|
|
108
|
+
return cwd;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function statePath(args) {
|
|
112
|
+
const identity = `${args.siteUrl}\0${workspaceFor(args.cwd)}\0${args.sessionId}`;
|
|
113
|
+
const key = createHash("sha256").update(identity).digest("hex");
|
|
114
|
+
return join(stateRoot(), `${key}.json`);
|
|
115
|
+
}
|
|
116
|
+
function draftStatePath(args) {
|
|
117
|
+
const identity = `${args.siteUrl}\0${workspaceFor(args.cwd)}\0${args.sessionId}\0${args.principalId}\0${args.organizationId}\0${args.credentialFingerprint}`;
|
|
118
|
+
const key = createHash("sha256").update(identity).digest("hex");
|
|
119
|
+
return join(draftStateRoot(), `${key}.json`);
|
|
120
|
+
}
|
|
121
|
+
function parseState(value) {
|
|
122
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
123
|
+
const record = value;
|
|
124
|
+
if (record.version !== STATE_VERSION || typeof record.sessionId !== "string" || typeof record.siteUrl !== "string" || typeof record.workspace !== "string" || typeof record.startedAt !== "number" || typeof record.watermarkMs !== "number" || !Array.isArray(record.seenIds) || !record.seenIds.every((id) => typeof id === "string") || typeof record.updatedAt !== "number") {
|
|
125
|
+
return void 0;
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
version: STATE_VERSION,
|
|
129
|
+
sessionId: record.sessionId,
|
|
130
|
+
siteUrl: record.siteUrl,
|
|
131
|
+
workspace: record.workspace,
|
|
132
|
+
startedAt: record.startedAt,
|
|
133
|
+
watermarkMs: record.watermarkMs,
|
|
134
|
+
seenIds: record.seenIds.slice(-CODEX_DIGEST_MAX_SEEN_IDS),
|
|
135
|
+
lastReport: typeof record.lastReport === "string" ? record.lastReport : void 0,
|
|
136
|
+
updatedAt: record.updatedAt
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function parseDraftState(value) {
|
|
140
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
141
|
+
const record = value;
|
|
142
|
+
if (record.version !== DRAFT_STATE_VERSION || typeof record.sessionId !== "string" || typeof record.siteUrl !== "string" || typeof record.workspace !== "string" || typeof record.principalId !== "string" || typeof record.organizationId !== "string" || typeof record.credentialFingerprint !== "string" || !Array.isArray(record.seenDraftIds) || !record.seenDraftIds.every((id) => typeof id === "string") || typeof record.updatedAt !== "number") {
|
|
143
|
+
return void 0;
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
version: DRAFT_STATE_VERSION,
|
|
147
|
+
sessionId: record.sessionId,
|
|
148
|
+
siteUrl: record.siteUrl,
|
|
149
|
+
workspace: record.workspace,
|
|
150
|
+
principalId: record.principalId,
|
|
151
|
+
organizationId: record.organizationId,
|
|
152
|
+
credentialFingerprint: record.credentialFingerprint,
|
|
153
|
+
seenDraftIds: record.seenDraftIds.slice(-CODEX_DRAFT_DIGEST_MAX_SEEN_IDS),
|
|
154
|
+
updatedAt: record.updatedAt
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function readState(path) {
|
|
158
|
+
if (!existsSync(path)) return void 0;
|
|
159
|
+
try {
|
|
160
|
+
return parseState(JSON.parse(readFileSync(path, "utf8")));
|
|
161
|
+
} catch {
|
|
162
|
+
return void 0;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function readDraftState(path) {
|
|
166
|
+
if (!existsSync(path)) return void 0;
|
|
167
|
+
try {
|
|
168
|
+
return parseDraftState(JSON.parse(readFileSync(path, "utf8")));
|
|
169
|
+
} catch {
|
|
170
|
+
return void 0;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function writeStateFile(path, directory, state) {
|
|
174
|
+
mkdirSync(directory, { recursive: true, mode: 448 });
|
|
175
|
+
chmodSync(directory, 448);
|
|
176
|
+
const temporary = join(
|
|
177
|
+
directory,
|
|
178
|
+
`.${path.slice(directory.length + 1)}.tmp-${process.pid}-${randomBytes2(8).toString("hex")}`
|
|
179
|
+
);
|
|
180
|
+
try {
|
|
181
|
+
writeFileSync(temporary, `${JSON.stringify(state)}
|
|
182
|
+
`, {
|
|
183
|
+
encoding: "utf8",
|
|
184
|
+
mode: 384,
|
|
185
|
+
flag: "wx"
|
|
186
|
+
});
|
|
187
|
+
chmodSync(temporary, 384);
|
|
188
|
+
renameSync(temporary, path);
|
|
189
|
+
} finally {
|
|
190
|
+
try {
|
|
191
|
+
unlinkSync(temporary);
|
|
192
|
+
} catch {
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function cleanupStateFiles(directory, now) {
|
|
197
|
+
if (!existsSync(directory)) return;
|
|
198
|
+
let names;
|
|
199
|
+
try {
|
|
200
|
+
names = readdirSync(directory);
|
|
201
|
+
} catch {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const files = [];
|
|
205
|
+
const residue = [];
|
|
206
|
+
for (const name of names) {
|
|
207
|
+
const path = join(directory, name);
|
|
208
|
+
try {
|
|
209
|
+
const mtimeMs = statSync(path).mtimeMs;
|
|
210
|
+
if (name.endsWith(".json")) {
|
|
211
|
+
files.push({ path, mtimeMs });
|
|
212
|
+
} else if ((name.includes(".json.tmp-") || name.endsWith(".json.lock")) && now - mtimeMs > CODEX_DIGEST_STATE_RETENTION_MS) {
|
|
213
|
+
residue.push(path);
|
|
214
|
+
}
|
|
215
|
+
} catch {
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const stale = files.filter((file) => now - file.mtimeMs > CODEX_DIGEST_STATE_RETENTION_MS);
|
|
219
|
+
const excess = files.filter((file) => !stale.some((candidate) => candidate.path === file.path)).sort((a, b) => a.mtimeMs - b.mtimeMs).slice(0, Math.max(0, files.length - CODEX_DIGEST_STATE_MAX_FILES));
|
|
220
|
+
for (const file of [...stale, ...excess]) {
|
|
221
|
+
try {
|
|
222
|
+
unlinkSync(file.path);
|
|
223
|
+
} catch {
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
for (const path of residue) {
|
|
227
|
+
try {
|
|
228
|
+
if (now - statSync(path).mtimeMs > CODEX_DIGEST_STATE_RETENTION_MS) {
|
|
229
|
+
rmSync(path, { recursive: true, force: true });
|
|
230
|
+
}
|
|
231
|
+
} catch {
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function isChangeDecision(row) {
|
|
236
|
+
return row.intentKind === void 0 || row.intentKind === "change";
|
|
237
|
+
}
|
|
238
|
+
function safeInline(value, fallback) {
|
|
239
|
+
const clean = terminalSafeText(value ?? "").replace(/\s+/gu, " ").trim();
|
|
240
|
+
if (!clean) return fallback;
|
|
241
|
+
return clean.length <= 240 ? clean : `${clean.slice(0, 239)}\u2026`;
|
|
242
|
+
}
|
|
243
|
+
function renderDecisionDigest(rows, options = {}) {
|
|
244
|
+
if (rows.length === 0) return void 0;
|
|
245
|
+
const visible = rows.slice(0, CODEX_DIGEST_VISIBLE_CAP).map((row) => {
|
|
246
|
+
const author = safeInline(row.authorName, "(unknown)").replace(/[;;;‒-―−]/gu, "-");
|
|
247
|
+
const intent = safeInline(row.intent, "(untitled Decision)").replace(/[`“-‟″‶〝〞]/gu, "'");
|
|
248
|
+
return `${author} \u2014 \u201C${intent}\u201D`;
|
|
249
|
+
});
|
|
250
|
+
const overflow = rows.length > CODEX_DIGEST_VISIBLE_CAP ? ` +${String(rows.length - CODEX_DIGEST_VISIBLE_CAP)}${options.pageTruncated === true ? "+" : ""}` : "";
|
|
251
|
+
return `[prim] Decisions captured since last message: ${visible.join("; ")}${overflow}`;
|
|
252
|
+
}
|
|
253
|
+
function actionablePrivateDrafts(rows) {
|
|
254
|
+
const seen = /* @__PURE__ */ new Set();
|
|
255
|
+
const actionable = [];
|
|
256
|
+
for (const row of rows) {
|
|
257
|
+
if (!isActionablePrivateDraft(row) || seen.has(row.id)) continue;
|
|
258
|
+
seen.add(row.id);
|
|
259
|
+
actionable.push(row);
|
|
260
|
+
}
|
|
261
|
+
return actionable;
|
|
262
|
+
}
|
|
263
|
+
function renderDecisionDraftDigest(rows, options = {}) {
|
|
264
|
+
const eligible = actionablePrivateDrafts(rows);
|
|
265
|
+
if (eligible.length === 0) return void 0;
|
|
266
|
+
const visible = eligible.slice(0, CODEX_DIGEST_VISIBLE_CAP);
|
|
267
|
+
const lines = visible.flatMap((row) => {
|
|
268
|
+
const intent = safeInline(row.intent, "(untitled Decision)").replace(/[“-‟″‶〝〞]/gu, "'");
|
|
269
|
+
return [
|
|
270
|
+
`[prim] private Decision ${row.id} \xB7 stage: draft \xB7 \u201C${intent}\u201D`,
|
|
271
|
+
`[prim] Run \`prim decisions publish ${row.id}\` to share it with your team.`
|
|
272
|
+
];
|
|
273
|
+
});
|
|
274
|
+
if (eligible.length > visible.length) {
|
|
275
|
+
const suffix = options.pageTruncated === true ? "+" : "";
|
|
276
|
+
lines.push(
|
|
277
|
+
`[prim] +${String(eligible.length - visible.length)}${suffix} private Decision drafts remain for later messages.`
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
return { message: lines.join("\n"), deliveredIds: visible.map((row) => row.id) };
|
|
281
|
+
}
|
|
282
|
+
function reauthSnapshot(sessionId) {
|
|
283
|
+
return {
|
|
284
|
+
pid: 0,
|
|
285
|
+
uptimeMs: 0,
|
|
286
|
+
sessionId,
|
|
287
|
+
healthy: false,
|
|
288
|
+
needsReauth: true
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
function mergeState(latest, args) {
|
|
292
|
+
const seen = new Set(latest?.seenIds ?? []);
|
|
293
|
+
for (const id of args.seenIds) seen.add(id);
|
|
294
|
+
return {
|
|
295
|
+
version: STATE_VERSION,
|
|
296
|
+
sessionId: args.sessionId,
|
|
297
|
+
siteUrl: args.siteUrl,
|
|
298
|
+
workspace: args.workspace,
|
|
299
|
+
startedAt: latest?.startedAt ?? args.startedAt,
|
|
300
|
+
// Advance only on an observed page (server classifiedAt time), never the
|
|
301
|
+
// client clock; an unavailable feed keeps the previous cursor — or the
|
|
302
|
+
// NO_CURSOR sentinel, so the startup backlog survives until the first
|
|
303
|
+
// successful fetch.
|
|
304
|
+
watermarkMs: args.feedAvailable ? Math.max(latest?.watermarkMs ?? NO_CURSOR, args.watermarkMs) : latest?.watermarkMs ?? NO_CURSOR,
|
|
305
|
+
seenIds: [...seen].slice(-CODEX_DIGEST_MAX_SEEN_IDS),
|
|
306
|
+
lastReport: args.report,
|
|
307
|
+
updatedAt: args.now
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
async function commitState(path, args) {
|
|
311
|
+
try {
|
|
312
|
+
await withFileLock(
|
|
313
|
+
`${path}.lock`,
|
|
314
|
+
() => {
|
|
315
|
+
const latest = readState(path);
|
|
316
|
+
writeStateFile(path, stateRoot(), mergeState(latest, args));
|
|
317
|
+
cleanupStateFiles(stateRoot(), args.now);
|
|
318
|
+
},
|
|
319
|
+
{ timeoutMs: 50, pollMs: 10 }
|
|
320
|
+
);
|
|
321
|
+
} catch {
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function mergeDraftState(latest, args) {
|
|
325
|
+
const seen = new Set(latest?.seenDraftIds ?? []);
|
|
326
|
+
for (const id of args.deliveredIds) seen.add(id);
|
|
327
|
+
return {
|
|
328
|
+
version: DRAFT_STATE_VERSION,
|
|
329
|
+
sessionId: args.sessionId,
|
|
330
|
+
siteUrl: args.siteUrl,
|
|
331
|
+
workspace: args.workspace,
|
|
332
|
+
principalId: args.principalId,
|
|
333
|
+
organizationId: args.organizationId,
|
|
334
|
+
credentialFingerprint: args.credentialFingerprint,
|
|
335
|
+
seenDraftIds: [...seen].slice(-CODEX_DRAFT_DIGEST_MAX_SEEN_IDS),
|
|
336
|
+
updatedAt: args.now
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
async function commitDraftState(path, args) {
|
|
340
|
+
try {
|
|
341
|
+
let next;
|
|
342
|
+
await withFileLock(
|
|
343
|
+
`${path}.lock`,
|
|
344
|
+
() => {
|
|
345
|
+
const latest = readDraftState(path);
|
|
346
|
+
next = mergeDraftState(latest, args);
|
|
347
|
+
writeStateFile(path, draftStateRoot(), next);
|
|
348
|
+
cleanupStateFiles(draftStateRoot(), args.now);
|
|
349
|
+
},
|
|
350
|
+
{ timeoutMs: 50, pollMs: 10 }
|
|
351
|
+
);
|
|
352
|
+
return next;
|
|
353
|
+
} catch {
|
|
354
|
+
return void 0;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
async function prepareCodexContext(options) {
|
|
358
|
+
const siteUrl = getSiteUrl();
|
|
359
|
+
const workspace = workspaceFor(options.cwd);
|
|
360
|
+
const path = statePath({ cwd: options.cwd, sessionId: options.sessionId, siteUrl });
|
|
361
|
+
const previous = readState(path);
|
|
362
|
+
const principal = resolveDaemonPrincipal();
|
|
363
|
+
const privatePath = principal ? draftStatePath({
|
|
364
|
+
cwd: options.cwd,
|
|
365
|
+
sessionId: options.sessionId,
|
|
366
|
+
siteUrl,
|
|
367
|
+
principalId: principal.principalId,
|
|
368
|
+
organizationId: principal.organizationId,
|
|
369
|
+
credentialFingerprint: principal.credentialFingerprint
|
|
370
|
+
}) : void 0;
|
|
371
|
+
const previousDraft = privatePath ? readDraftState(privatePath) : void 0;
|
|
372
|
+
const startup = options.startup === true;
|
|
373
|
+
const includeDigest = options.includeDigest !== false;
|
|
374
|
+
const startedAt = previous?.startedAt ?? Date.now();
|
|
375
|
+
const terminalAuth = isSessionEnded();
|
|
376
|
+
let snapshot = null;
|
|
377
|
+
let recent = null;
|
|
378
|
+
let recentDrafts = null;
|
|
379
|
+
if (terminalAuth) {
|
|
380
|
+
snapshot = reauthSnapshot(options.sessionId);
|
|
381
|
+
} else {
|
|
382
|
+
[snapshot, recent, recentDrafts] = await Promise.all([
|
|
383
|
+
daemonRequest(
|
|
384
|
+
"status_snapshot",
|
|
385
|
+
{ callerEnv: siteUrl },
|
|
386
|
+
{ timeoutMs: CODEX_CONTEXT_TIMEOUT_MS }
|
|
387
|
+
),
|
|
388
|
+
includeDigest ? daemonRequest(
|
|
389
|
+
"decision_digest_snapshot",
|
|
390
|
+
{ callerEnv: siteUrl },
|
|
391
|
+
{ timeoutMs: CODEX_CONTEXT_TIMEOUT_MS }
|
|
392
|
+
) : Promise.resolve(null),
|
|
393
|
+
includeDigest && principal !== void 0 ? daemonRequest(
|
|
394
|
+
"decision_draft_digest_snapshot",
|
|
395
|
+
{ callerEnv: siteUrl },
|
|
396
|
+
{ timeoutMs: CODEX_CONTEXT_TIMEOUT_MS }
|
|
397
|
+
) : Promise.resolve(null)
|
|
398
|
+
]);
|
|
399
|
+
if (principal !== void 0 && !daemonPrincipalsMatch(principal, resolveDaemonPrincipal())) {
|
|
400
|
+
snapshot = null;
|
|
401
|
+
recent = null;
|
|
402
|
+
recentDrafts = null;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
let ingestionStatus;
|
|
406
|
+
const resolveIngestionStatus = () => {
|
|
407
|
+
ingestionStatus ??= decisionIngestionStatus(options.cwd);
|
|
408
|
+
return ingestionStatus;
|
|
409
|
+
};
|
|
410
|
+
const report = formatStatusline(packageVersion() ?? "0.0.0", snapshot, resolveIngestionStatus, {
|
|
411
|
+
includeIngestionWhenUnavailable: true,
|
|
412
|
+
plainLinks: true,
|
|
413
|
+
resolveRepositoryBindingState: () => resolveIngestionStatus() === "enabled" ? repositoryBindingState(options.cwd) : void 0
|
|
414
|
+
});
|
|
415
|
+
const reportChanged = startup || previous?.lastReport !== report;
|
|
416
|
+
let feedAvailable = false;
|
|
417
|
+
let freshRows = [];
|
|
418
|
+
let pageTruncated = false;
|
|
419
|
+
let pageWatermark = NO_CURSOR;
|
|
420
|
+
if (!terminalAuth && includeDigest && recent !== null) {
|
|
421
|
+
feedAvailable = recent.unavailable === void 0 && Array.isArray(recent.decisions);
|
|
422
|
+
if (feedAvailable) {
|
|
423
|
+
const seen = new Set(previous?.seenIds ?? []);
|
|
424
|
+
const lowerBound = previous !== void 0 && previous.watermarkMs >= 0 ? Math.max(0, previous.watermarkMs - CODEX_DIGEST_OVERLAP_MS) : 0;
|
|
425
|
+
freshRows = recent.decisions.filter(
|
|
426
|
+
(row) => isChangeDecision(row) && !seen.has(row.id) && typeof row.classifiedAt === "number" && row.classifiedAt >= lowerBound
|
|
427
|
+
);
|
|
428
|
+
pageTruncated = recent.decisions.length >= CODEX_DIGEST_LIMIT;
|
|
429
|
+
pageWatermark = recent.decisions.reduce(
|
|
430
|
+
(max, row) => typeof row.classifiedAt === "number" && row.classifiedAt > max ? row.classifiedAt : max,
|
|
431
|
+
NO_CURSOR
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
const digest = renderDecisionDigest(freshRows, { pageTruncated });
|
|
436
|
+
let draftDigest;
|
|
437
|
+
let draftPageToken;
|
|
438
|
+
let draftPageActionableIds = [];
|
|
439
|
+
if (!terminalAuth && includeDigest && privatePath !== void 0 && recentDrafts !== null && recentDrafts.unavailable === void 0 && Array.isArray(recentDrafts.decisions)) {
|
|
440
|
+
const allActionableDrafts = actionablePrivateDrafts(recentDrafts.decisions);
|
|
441
|
+
const seenDrafts = new Set(previousDraft?.seenDraftIds ?? []);
|
|
442
|
+
const freshDrafts = allActionableDrafts.filter((row) => !seenDrafts.has(row.id));
|
|
443
|
+
draftDigest = renderDecisionDraftDigest(freshDrafts, {
|
|
444
|
+
pageTruncated: recentDrafts.pageDone === false
|
|
445
|
+
});
|
|
446
|
+
draftPageToken = typeof recentDrafts.pageToken === "string" ? recentDrafts.pageToken : void 0;
|
|
447
|
+
draftPageActionableIds = allActionableDrafts.map((row) => row.id);
|
|
448
|
+
}
|
|
449
|
+
const combinedDigest = [digest, draftDigest?.message].filter((value) => value !== void 0).join("\n\n");
|
|
450
|
+
const context = [reportChanged ? report : void 0, combinedDigest || void 0].filter((value) => value !== void 0).join("\n\n") || void 0;
|
|
451
|
+
let acknowledged = false;
|
|
452
|
+
return {
|
|
453
|
+
context,
|
|
454
|
+
decisionDigest: combinedDigest || void 0,
|
|
455
|
+
feedAvailable,
|
|
456
|
+
acknowledge: async (handedOff) => {
|
|
457
|
+
if (!handedOff || acknowledged) return;
|
|
458
|
+
acknowledged = true;
|
|
459
|
+
await commitState(path, {
|
|
460
|
+
sessionId: options.sessionId,
|
|
461
|
+
siteUrl,
|
|
462
|
+
workspace,
|
|
463
|
+
startedAt,
|
|
464
|
+
watermarkMs: pageWatermark,
|
|
465
|
+
seenIds: freshRows.map((row) => row.id),
|
|
466
|
+
report,
|
|
467
|
+
feedAvailable,
|
|
468
|
+
now: Date.now()
|
|
469
|
+
});
|
|
470
|
+
if (privatePath === void 0 || principal === void 0 || draftPageToken === void 0) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
let persistedDraft = previousDraft;
|
|
474
|
+
if (draftDigest?.deliveredIds.length) {
|
|
475
|
+
persistedDraft = await commitDraftState(privatePath, {
|
|
476
|
+
sessionId: options.sessionId,
|
|
477
|
+
siteUrl,
|
|
478
|
+
workspace,
|
|
479
|
+
principalId: principal.principalId,
|
|
480
|
+
organizationId: principal.organizationId,
|
|
481
|
+
credentialFingerprint: principal.credentialFingerprint,
|
|
482
|
+
deliveredIds: draftDigest.deliveredIds,
|
|
483
|
+
now: Date.now()
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
const seenDrafts = new Set(persistedDraft?.seenDraftIds ?? []);
|
|
487
|
+
const deliveredOnThisPage = draftPageActionableIds.filter((id) => seenDrafts.has(id));
|
|
488
|
+
await daemonRequest(
|
|
489
|
+
"decision_draft_digest_acknowledge",
|
|
490
|
+
{
|
|
491
|
+
callerEnv: siteUrl,
|
|
492
|
+
pageToken: draftPageToken,
|
|
493
|
+
deliveredIds: deliveredOnThisPage
|
|
494
|
+
},
|
|
495
|
+
{ timeoutMs: CODEX_CONTEXT_TIMEOUT_MS }
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
export {
|
|
502
|
+
hasVisibleCodexMessage,
|
|
503
|
+
appendCodexContext,
|
|
504
|
+
prepareCodexContext
|
|
505
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// src/hooks/decision-feedback-core.ts
|
|
2
|
+
function buildHookOutput(options) {
|
|
3
|
+
const output = {};
|
|
4
|
+
if (options.systemMessage) output.systemMessage = options.systemMessage;
|
|
5
|
+
const fields = {};
|
|
6
|
+
if (options.additionalContext) fields.additionalContext = options.additionalContext;
|
|
7
|
+
if (options.reloadSkills) fields.reloadSkills = true;
|
|
8
|
+
if (Object.keys(fields).length > 0) {
|
|
9
|
+
output.hookSpecificOutput = { hookEventName: "SessionStart", ...fields };
|
|
10
|
+
}
|
|
11
|
+
return output;
|
|
12
|
+
}
|
|
13
|
+
function writeHookOutput(output, stream = process.stdout) {
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
let settled = false;
|
|
16
|
+
const finish = (ok, retainErrorListener = false) => {
|
|
17
|
+
if (settled) return;
|
|
18
|
+
settled = true;
|
|
19
|
+
if (!retainErrorListener) stream.off("error", onError);
|
|
20
|
+
resolve(ok);
|
|
21
|
+
};
|
|
22
|
+
const onError = () => {
|
|
23
|
+
if (settled) {
|
|
24
|
+
stream.off("error", onError);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
finish(false);
|
|
28
|
+
};
|
|
29
|
+
stream.once("error", onError);
|
|
30
|
+
try {
|
|
31
|
+
stream.write(
|
|
32
|
+
`${JSON.stringify(output)}
|
|
33
|
+
`,
|
|
34
|
+
(error) => error ? finish(false, true) : finish(true)
|
|
35
|
+
);
|
|
36
|
+
} catch {
|
|
37
|
+
finish(false);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
async function handoffHookOutput(output, acknowledge, stream = process.stdout) {
|
|
42
|
+
const handedOff = await writeHookOutput(output, stream);
|
|
43
|
+
if (handedOff && acknowledge) {
|
|
44
|
+
try {
|
|
45
|
+
await acknowledge();
|
|
46
|
+
} catch {
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return handedOff;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export {
|
|
53
|
+
buildHookOutput,
|
|
54
|
+
handoffHookOutput
|
|
55
|
+
};
|