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