@primitive.ai/prim 0.1.0-alpha.68 → 0.1.0-alpha.69
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 +39 -1
- package/SKILL.md +1 -1
- package/dist/{chunk-XPVNH3J6.js → chunk-2KW7DVFJ.js} +121 -130
- package/dist/chunk-3EEFL6NJ.js +529 -0
- package/dist/chunk-4A2XB2PC.js +310 -0
- package/dist/{chunk-G5D3FF3D.js → chunk-BQUJ4AFJ.js} +1 -83
- package/dist/chunk-CUE2LKLR.js +155 -0
- package/dist/chunk-FI3LIRCB.js +262 -0
- package/dist/{chunk-CPSE3Y3T.js → chunk-H64F6QHV.js} +27 -8
- package/dist/{chunk-I5BHJQH2.js → chunk-KKP2NYZ3.js} +206 -30
- package/dist/chunk-LEMX3IOG.js +79 -0
- package/dist/{chunk-U6L2RU67.js → chunk-LMRYZL3D.js} +52 -45
- package/dist/{chunk-XUAP572Z.js → chunk-RO3F6JYV.js} +8 -7
- package/dist/{chunk-TDRXHBEX.js → chunk-TJTVHDJ5.js} +41 -18
- package/dist/{chunk-TCDUH7AN.js → chunk-WB7AUZ4C.js} +17 -0
- package/dist/{chunk-UN5O5SYG.js → chunk-YWO7O6QG.js} +14 -53
- package/dist/chunk-Z7YPFWAZ.js +917 -0
- package/dist/{chunk-BDGQEJA5.js → chunk-ZOSNEO5N.js} +2 -2
- package/dist/daemon/server.js +11634 -391
- package/dist/hook-runtime/daemon/server.js +13353 -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 +11284 -0
- package/dist/hook-runtime/index.js +42775 -0
- package/dist/hook-runtime/statusline-main.js +765 -0
- package/dist/hooks/post-commit.js +7 -4
- package/dist/hooks/post-rewrite.js +7 -4
- package/dist/hooks/post-tool-use.js +21 -19
- package/dist/hooks/pre-commit.js +4 -3
- package/dist/hooks/pre-tool-use.js +59 -18
- package/dist/hooks/prim-hook.js +39 -56
- package/dist/hooks/session-end.js +3 -2
- package/dist/hooks/session-start.js +15 -12
- package/dist/index.js +15050 -2479
- package/dist/statusline-main.js +477 -89
- package/package.json +11 -5
- package/dist/chunk-K3AYW6VZ.js +0 -300
- package/dist/chunk-RTLFE6EZ.js +0 -518
- package/dist/chunk-UTKQTZHL.js +0 -88
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
import {
|
|
2
2
|
formatStatusline,
|
|
3
|
-
packageVersion
|
|
4
|
-
|
|
5
|
-
} from "./chunk-K3AYW6VZ.js";
|
|
3
|
+
packageVersion
|
|
4
|
+
} from "./chunk-4A2XB2PC.js";
|
|
6
5
|
import {
|
|
7
6
|
decisionIngestionStatus,
|
|
8
|
-
gitToplevel
|
|
9
|
-
|
|
7
|
+
gitToplevel,
|
|
8
|
+
repositoryBindingState
|
|
9
|
+
} from "./chunk-TJTVHDJ5.js";
|
|
10
10
|
import {
|
|
11
11
|
getSiteUrl,
|
|
12
12
|
isSessionEnded,
|
|
13
|
+
terminalSafeText,
|
|
13
14
|
withFileLock
|
|
14
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-Z7YPFWAZ.js";
|
|
15
16
|
import {
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
daemonPrincipalsMatch,
|
|
18
|
+
daemonRequest,
|
|
19
|
+
resolveDaemonPrincipal
|
|
20
|
+
} from "./chunk-CUE2LKLR.js";
|
|
21
|
+
import {
|
|
22
|
+
primConfigDirectory
|
|
23
|
+
} from "./chunk-3EEFL6NJ.js";
|
|
18
24
|
|
|
19
25
|
// src/hooks/codex-context.ts
|
|
20
|
-
import { createHash, randomBytes } from "crypto";
|
|
26
|
+
import { createHash, randomBytes as randomBytes2 } from "crypto";
|
|
21
27
|
import {
|
|
22
28
|
chmodSync,
|
|
23
29
|
existsSync,
|
|
@@ -30,13 +36,20 @@ import {
|
|
|
30
36
|
unlinkSync,
|
|
31
37
|
writeFileSync
|
|
32
38
|
} from "fs";
|
|
33
|
-
import { homedir } from "os";
|
|
34
39
|
import { join } from "path";
|
|
35
40
|
|
|
36
41
|
// src/daemon/decision-digest-cache.ts
|
|
42
|
+
import { randomBytes } from "crypto";
|
|
37
43
|
var DECISION_DIGEST_CACHE_LIMIT = 100;
|
|
38
44
|
var DECISION_DIGEST_CACHE_WINDOW = "24h";
|
|
39
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
|
+
}
|
|
40
53
|
|
|
41
54
|
// src/hooks/codex-context.ts
|
|
42
55
|
var CODEX_CONTEXT_TIMEOUT_MS = 250;
|
|
@@ -44,10 +57,13 @@ var CODEX_DIGEST_LIMIT = DECISION_DIGEST_CACHE_LIMIT;
|
|
|
44
57
|
var CODEX_DIGEST_VISIBLE_CAP = 3;
|
|
45
58
|
var CODEX_DIGEST_OVERLAP_MS = 6e4;
|
|
46
59
|
var CODEX_DIGEST_MAX_SEEN_IDS = 128;
|
|
60
|
+
var CODEX_DRAFT_DIGEST_MAX_SEEN_IDS = 4096;
|
|
47
61
|
var CODEX_DIGEST_STATE_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
48
62
|
var CODEX_DIGEST_STATE_MAX_FILES = 256;
|
|
49
63
|
var STATE_VERSION = 1;
|
|
50
|
-
var STATE_DIRECTORY = ["
|
|
64
|
+
var STATE_DIRECTORY = ["codex", "decision-digests"];
|
|
65
|
+
var DRAFT_STATE_VERSION = 2;
|
|
66
|
+
var DRAFT_STATE_DIRECTORY = ["codex", "decision-draft-deliveries"];
|
|
51
67
|
var NO_CURSOR = -1;
|
|
52
68
|
function hasVisibleCodexMessage(output) {
|
|
53
69
|
if (typeof output !== "object" || output === null || Array.isArray(output)) return false;
|
|
@@ -80,7 +96,10 @@ function appendCodexContext(output, context) {
|
|
|
80
96
|
return next;
|
|
81
97
|
}
|
|
82
98
|
function stateRoot() {
|
|
83
|
-
return join(
|
|
99
|
+
return join(primConfigDirectory(), ...STATE_DIRECTORY);
|
|
100
|
+
}
|
|
101
|
+
function draftStateRoot() {
|
|
102
|
+
return join(primConfigDirectory(), ...DRAFT_STATE_DIRECTORY);
|
|
84
103
|
}
|
|
85
104
|
function workspaceFor(cwd) {
|
|
86
105
|
try {
|
|
@@ -94,6 +113,11 @@ function statePath(args) {
|
|
|
94
113
|
const key = createHash("sha256").update(identity).digest("hex");
|
|
95
114
|
return join(stateRoot(), `${key}.json`);
|
|
96
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
|
+
}
|
|
97
121
|
function parseState(value) {
|
|
98
122
|
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
99
123
|
const record = value;
|
|
@@ -112,6 +136,24 @@ function parseState(value) {
|
|
|
112
136
|
updatedAt: record.updatedAt
|
|
113
137
|
};
|
|
114
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
|
+
}
|
|
115
157
|
function readState(path) {
|
|
116
158
|
if (!existsSync(path)) return void 0;
|
|
117
159
|
try {
|
|
@@ -120,13 +162,20 @@ function readState(path) {
|
|
|
120
162
|
return void 0;
|
|
121
163
|
}
|
|
122
164
|
}
|
|
123
|
-
function
|
|
124
|
-
|
|
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) {
|
|
125
174
|
mkdirSync(directory, { recursive: true, mode: 448 });
|
|
126
175
|
chmodSync(directory, 448);
|
|
127
176
|
const temporary = join(
|
|
128
177
|
directory,
|
|
129
|
-
`.${path.slice(directory.length + 1)}.tmp-${process.pid}-${
|
|
178
|
+
`.${path.slice(directory.length + 1)}.tmp-${process.pid}-${randomBytes2(8).toString("hex")}`
|
|
130
179
|
);
|
|
131
180
|
try {
|
|
132
181
|
writeFileSync(temporary, `${JSON.stringify(state)}
|
|
@@ -144,8 +193,7 @@ function writeState(path, state) {
|
|
|
144
193
|
}
|
|
145
194
|
}
|
|
146
195
|
}
|
|
147
|
-
function cleanupStateFiles(now) {
|
|
148
|
-
const directory = stateRoot();
|
|
196
|
+
function cleanupStateFiles(directory, now) {
|
|
149
197
|
if (!existsSync(directory)) return;
|
|
150
198
|
let names;
|
|
151
199
|
try {
|
|
@@ -188,7 +236,7 @@ function isChangeDecision(row) {
|
|
|
188
236
|
return row.intentKind === void 0 || row.intentKind === "change";
|
|
189
237
|
}
|
|
190
238
|
function safeInline(value, fallback) {
|
|
191
|
-
const clean =
|
|
239
|
+
const clean = terminalSafeText(value ?? "").replace(/\s+/gu, " ").trim();
|
|
192
240
|
if (!clean) return fallback;
|
|
193
241
|
return clean.length <= 240 ? clean : `${clean.slice(0, 239)}\u2026`;
|
|
194
242
|
}
|
|
@@ -196,12 +244,41 @@ function renderDecisionDigest(rows, options = {}) {
|
|
|
196
244
|
if (rows.length === 0) return void 0;
|
|
197
245
|
const visible = rows.slice(0, CODEX_DIGEST_VISIBLE_CAP).map((row) => {
|
|
198
246
|
const author = safeInline(row.authorName, "(unknown)").replace(/[;;;‒-―−]/gu, "-");
|
|
199
|
-
const intent = safeInline(row.intent, "(untitled Decision)").replace(/[
|
|
247
|
+
const intent = safeInline(row.intent, "(untitled Decision)").replace(/[`“-‟″‶〝〞]/gu, "'");
|
|
200
248
|
return `${author} \u2014 \u201C${intent}\u201D`;
|
|
201
249
|
});
|
|
202
250
|
const overflow = rows.length > CODEX_DIGEST_VISIBLE_CAP ? ` +${String(rows.length - CODEX_DIGEST_VISIBLE_CAP)}${options.pageTruncated === true ? "+" : ""}` : "";
|
|
203
251
|
return `[prim] Decisions captured since last message: ${visible.join("; ")}${overflow}`;
|
|
204
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
|
+
}
|
|
205
282
|
function reauthSnapshot(sessionId) {
|
|
206
283
|
return {
|
|
207
284
|
pid: 0,
|
|
@@ -236,29 +313,73 @@ async function commitState(path, args) {
|
|
|
236
313
|
`${path}.lock`,
|
|
237
314
|
() => {
|
|
238
315
|
const latest = readState(path);
|
|
239
|
-
|
|
240
|
-
cleanupStateFiles(args.now);
|
|
316
|
+
writeStateFile(path, stateRoot(), mergeState(latest, args));
|
|
317
|
+
cleanupStateFiles(stateRoot(), args.now);
|
|
241
318
|
},
|
|
242
319
|
{ timeoutMs: 50, pollMs: 10 }
|
|
243
320
|
);
|
|
244
321
|
} catch {
|
|
245
322
|
}
|
|
246
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
|
+
}
|
|
247
357
|
async function prepareCodexContext(options) {
|
|
248
358
|
const siteUrl = getSiteUrl();
|
|
249
359
|
const workspace = workspaceFor(options.cwd);
|
|
250
360
|
const path = statePath({ cwd: options.cwd, sessionId: options.sessionId, siteUrl });
|
|
251
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;
|
|
252
372
|
const startup = options.startup === true;
|
|
253
373
|
const includeDigest = options.includeDigest !== false;
|
|
254
374
|
const startedAt = previous?.startedAt ?? Date.now();
|
|
255
375
|
const terminalAuth = isSessionEnded();
|
|
256
376
|
let snapshot = null;
|
|
257
377
|
let recent = null;
|
|
378
|
+
let recentDrafts = null;
|
|
258
379
|
if (terminalAuth) {
|
|
259
380
|
snapshot = reauthSnapshot(options.sessionId);
|
|
260
381
|
} else {
|
|
261
|
-
[snapshot, recent] = await Promise.all([
|
|
382
|
+
[snapshot, recent, recentDrafts] = await Promise.all([
|
|
262
383
|
daemonRequest(
|
|
263
384
|
"status_snapshot",
|
|
264
385
|
{ callerEnv: siteUrl },
|
|
@@ -268,15 +389,29 @@ async function prepareCodexContext(options) {
|
|
|
268
389
|
"decision_digest_snapshot",
|
|
269
390
|
{ callerEnv: siteUrl },
|
|
270
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 }
|
|
271
397
|
) : Promise.resolve(null)
|
|
272
398
|
]);
|
|
399
|
+
if (principal !== void 0 && !daemonPrincipalsMatch(principal, resolveDaemonPrincipal())) {
|
|
400
|
+
snapshot = null;
|
|
401
|
+
recent = null;
|
|
402
|
+
recentDrafts = null;
|
|
403
|
+
}
|
|
273
404
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
)
|
|
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
|
+
});
|
|
280
415
|
const reportChanged = startup || previous?.lastReport !== report;
|
|
281
416
|
let feedAvailable = false;
|
|
282
417
|
let freshRows = [];
|
|
@@ -298,11 +433,25 @@ async function prepareCodexContext(options) {
|
|
|
298
433
|
}
|
|
299
434
|
}
|
|
300
435
|
const digest = renderDecisionDigest(freshRows, { pageTruncated });
|
|
301
|
-
|
|
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;
|
|
302
451
|
let acknowledged = false;
|
|
303
452
|
return {
|
|
304
453
|
context,
|
|
305
|
-
decisionDigest:
|
|
454
|
+
decisionDigest: combinedDigest || void 0,
|
|
306
455
|
feedAvailable,
|
|
307
456
|
acknowledge: async (handedOff) => {
|
|
308
457
|
if (!handedOff || acknowledged) return;
|
|
@@ -318,6 +467,33 @@ async function prepareCodexContext(options) {
|
|
|
318
467
|
feedAvailable,
|
|
319
468
|
now: Date.now()
|
|
320
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
|
+
);
|
|
321
497
|
}
|
|
322
498
|
};
|
|
323
499
|
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import {
|
|
2
|
+
terminalSafeLine
|
|
3
|
+
} from "./chunk-Z7YPFWAZ.js";
|
|
4
|
+
|
|
5
|
+
// src/lib/ansi.ts
|
|
6
|
+
var ANSI_CODES = {
|
|
7
|
+
yellow: "\x1B[33m",
|
|
8
|
+
magenta: "\x1B[35m",
|
|
9
|
+
blue: "\x1B[34m",
|
|
10
|
+
cyan: "\x1B[36m",
|
|
11
|
+
green: "\x1B[32m",
|
|
12
|
+
red: "\x1B[31m",
|
|
13
|
+
// 256-color approximation of the orange trigger marker (xterm 208).
|
|
14
|
+
orange: "\x1B[38;5;208m",
|
|
15
|
+
gray: "\x1B[90m"
|
|
16
|
+
};
|
|
17
|
+
var ANSI_RESET = "\x1B[0m";
|
|
18
|
+
var ANSI_DIM = "\x1B[2m";
|
|
19
|
+
var ANSI_BOLD = "\x1B[1m";
|
|
20
|
+
function supportsColor() {
|
|
21
|
+
if (process.env.NO_COLOR !== void 0 && process.env.NO_COLOR !== "") {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
return process.stderr.isTTY === true;
|
|
25
|
+
}
|
|
26
|
+
function color(text, c) {
|
|
27
|
+
if (!supportsColor()) {
|
|
28
|
+
return text;
|
|
29
|
+
}
|
|
30
|
+
return `${ANSI_CODES[c]}${text}${ANSI_RESET}`;
|
|
31
|
+
}
|
|
32
|
+
function dim(text) {
|
|
33
|
+
if (!supportsColor()) {
|
|
34
|
+
return text;
|
|
35
|
+
}
|
|
36
|
+
return `${ANSI_DIM}${text}${ANSI_RESET}`;
|
|
37
|
+
}
|
|
38
|
+
function bold(text) {
|
|
39
|
+
if (!supportsColor()) {
|
|
40
|
+
return text;
|
|
41
|
+
}
|
|
42
|
+
return `${ANSI_BOLD}${text}${ANSI_RESET}`;
|
|
43
|
+
}
|
|
44
|
+
var AREA_COLORS = {
|
|
45
|
+
auth: "yellow",
|
|
46
|
+
data: "magenta",
|
|
47
|
+
mobile: "blue",
|
|
48
|
+
infra: "cyan",
|
|
49
|
+
ui: "green",
|
|
50
|
+
billing: "orange",
|
|
51
|
+
api: "blue",
|
|
52
|
+
docs: "gray",
|
|
53
|
+
testing: "gray"
|
|
54
|
+
};
|
|
55
|
+
function colorForArea(area) {
|
|
56
|
+
if (!area) {
|
|
57
|
+
return "gray";
|
|
58
|
+
}
|
|
59
|
+
return AREA_COLORS[area] ?? "gray";
|
|
60
|
+
}
|
|
61
|
+
var MAX_HEALTH_ERROR_LENGTH = 240;
|
|
62
|
+
function boundedHealthError(value) {
|
|
63
|
+
if (!value) return void 0;
|
|
64
|
+
const clean = terminalSafeLine(value);
|
|
65
|
+
if (!clean) return void 0;
|
|
66
|
+
return clean.length <= MAX_HEALTH_ERROR_LENGTH ? clean : `${clean.slice(0, MAX_HEALTH_ERROR_LENGTH - 1)}\u2026`;
|
|
67
|
+
}
|
|
68
|
+
function stripAnsi(text) {
|
|
69
|
+
return text.replace(/\u001b\[[0-9;]*m/g, "");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export {
|
|
73
|
+
color,
|
|
74
|
+
dim,
|
|
75
|
+
bold,
|
|
76
|
+
colorForArea,
|
|
77
|
+
boundedHealthError,
|
|
78
|
+
stripAnsi
|
|
79
|
+
};
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
-
getClient
|
|
3
|
-
|
|
2
|
+
getClient,
|
|
3
|
+
isTerminalSafeText,
|
|
4
|
+
terminalSafeLine,
|
|
5
|
+
terminalSafeText
|
|
6
|
+
} from "./chunk-Z7YPFWAZ.js";
|
|
4
7
|
|
|
5
8
|
// src/decisions/feedback.ts
|
|
6
|
-
var FEEDBACK_PROTOCOL_VERSION =
|
|
9
|
+
var FEEDBACK_PROTOCOL_VERSION = 2;
|
|
10
|
+
var FEEDBACK_MIN_PROTOCOL_VERSION = 1;
|
|
7
11
|
var FEEDBACK_DEADLINE_MS = 3e3;
|
|
8
12
|
var MAX_FEEDBACK_EVENTS = 40;
|
|
9
13
|
var MAX_FEEDBACK_MESSAGE_CODE_POINTS = 8e3;
|
|
@@ -15,38 +19,15 @@ var SHORT_ID = /^[0-9a-f]{8}$/u;
|
|
|
15
19
|
var MAX_EVENT_ID_CHARS = 128;
|
|
16
20
|
var MAX_RAW_INTENT_CODE_UNITS = 512;
|
|
17
21
|
var MAX_FEEDBACK_WEB_URL_CHARS = 2048;
|
|
22
|
+
var SAFE_PUBLISH_DECISION_ID = /^[A-Za-z0-9_-]+$/u;
|
|
18
23
|
function isRecord(value) {
|
|
19
24
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20
25
|
}
|
|
21
|
-
function
|
|
22
|
-
|
|
23
|
-
const bidiControl = point === 1564 || point === 8206 || point === 8207 || point >= 8234 && point <= 8238 || point >= 8294 && point <= 8297;
|
|
24
|
-
return c0OrC1 || bidiControl;
|
|
25
|
-
}
|
|
26
|
-
function replaceIsolatedSurrogates(value) {
|
|
27
|
-
let out = "";
|
|
28
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
29
|
-
const unit = value.charCodeAt(index);
|
|
30
|
-
if (unit >= 55296 && unit <= 56319) {
|
|
31
|
-
const next = value.charCodeAt(index + 1);
|
|
32
|
-
if (next >= 56320 && next <= 57343) {
|
|
33
|
-
out += value[index] + value[index + 1];
|
|
34
|
-
index += 1;
|
|
35
|
-
} else {
|
|
36
|
-
out += "\uFFFD";
|
|
37
|
-
}
|
|
38
|
-
} else if (unit >= 56320 && unit <= 57343) {
|
|
39
|
-
out += "\uFFFD";
|
|
40
|
-
} else {
|
|
41
|
-
out += value[index];
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return out;
|
|
26
|
+
function isSupportedProtocolVersion(value) {
|
|
27
|
+
return Number.isSafeInteger(value) && Number(value) >= FEEDBACK_MIN_PROTOCOL_VERSION && Number(value) <= FEEDBACK_PROTOCOL_VERSION;
|
|
45
28
|
}
|
|
46
29
|
function normalizeFeedbackIntent(value) {
|
|
47
|
-
const
|
|
48
|
-
const whitespace = safeUnicode.replace(/\s+/gu, " ");
|
|
49
|
-
const withoutControls = Array.from(whitespace).filter((character) => !isUnsafeControl(character.codePointAt(0) ?? 0)).join("").replace(/\s+/gu, " ").trim();
|
|
30
|
+
const withoutControls = terminalSafeLine(value);
|
|
50
31
|
const points = Array.from(withoutControls);
|
|
51
32
|
if (points.length <= MAX_FEEDBACK_INTENT_CODE_POINTS) {
|
|
52
33
|
return withoutControls;
|
|
@@ -54,7 +35,7 @@ function normalizeFeedbackIntent(value) {
|
|
|
54
35
|
return `${points.slice(0, MAX_FEEDBACK_INTENT_CODE_POINTS - 1).join("")}\u2026`;
|
|
55
36
|
}
|
|
56
37
|
function parseFeedbackWebUrl(value) {
|
|
57
|
-
if (typeof value !== "string" || value.length === 0 || value.length > MAX_FEEDBACK_WEB_URL_CHARS || !/^https:\/\//iu.test(value) || /\s/u.test(value) ||
|
|
38
|
+
if (typeof value !== "string" || value.length === 0 || value.length > MAX_FEEDBACK_WEB_URL_CHARS || !/^https:\/\//iu.test(value) || /\s/u.test(value) || !isTerminalSafeText(value)) {
|
|
58
39
|
return void 0;
|
|
59
40
|
}
|
|
60
41
|
try {
|
|
@@ -67,9 +48,15 @@ function parseFeedbackWebUrl(value) {
|
|
|
67
48
|
}
|
|
68
49
|
return value;
|
|
69
50
|
}
|
|
70
|
-
function
|
|
51
|
+
function parseFeedbackDecisionId(value) {
|
|
52
|
+
if (typeof value !== "string" || value.length === 0 || value.length > MAX_EVENT_ID_CHARS || terminalSafeText(value) !== value || !SAFE_PUBLISH_DECISION_ID.test(value)) {
|
|
53
|
+
return void 0;
|
|
54
|
+
}
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
function parseEvent(value, protocolVersion) {
|
|
71
58
|
if (!isRecord(value)) return void 0;
|
|
72
|
-
if (typeof value.eventId !== "string" || value.eventId.length === 0 || value.eventId.length > MAX_EVENT_ID_CHARS ||
|
|
59
|
+
if (typeof value.eventId !== "string" || value.eventId.length === 0 || value.eventId.length > MAX_EVENT_ID_CHARS || terminalSafeText(value.eventId) !== value.eventId) {
|
|
73
60
|
return void 0;
|
|
74
61
|
}
|
|
75
62
|
if (!Number.isSafeInteger(value.leaseVersion) || Number(value.leaseVersion) < 1) {
|
|
@@ -85,40 +72,58 @@ function parseEvent(value) {
|
|
|
85
72
|
if (!intent) return void 0;
|
|
86
73
|
const webUrl = value.webUrl === void 0 ? void 0 : parseFeedbackWebUrl(value.webUrl);
|
|
87
74
|
if (value.webUrl !== void 0 && webUrl === void 0) return void 0;
|
|
75
|
+
const kind = value.kind === void 0 && protocolVersion === 1 ? "confirm_prompt" : value.kind;
|
|
76
|
+
if (kind !== "confirm_prompt" && kind !== "publish_prompt") return void 0;
|
|
77
|
+
if (protocolVersion === 1 && kind === "publish_prompt") return void 0;
|
|
78
|
+
const decisionId = value.decisionId === void 0 ? void 0 : parseFeedbackDecisionId(value.decisionId);
|
|
79
|
+
if (value.decisionId !== void 0 && decisionId === void 0) return void 0;
|
|
80
|
+
if (decisionId !== void 0 && (protocolVersion !== 2 || kind !== "publish_prompt")) {
|
|
81
|
+
return void 0;
|
|
82
|
+
}
|
|
83
|
+
if (protocolVersion === 2 && kind === "publish_prompt" && decisionId === void 0) {
|
|
84
|
+
return void 0;
|
|
85
|
+
}
|
|
88
86
|
return {
|
|
89
87
|
eventId: value.eventId,
|
|
90
88
|
leaseVersion: Number(value.leaseVersion),
|
|
91
89
|
shortId: value.shortId,
|
|
92
90
|
intent,
|
|
93
|
-
...webUrl === void 0 ? {} : { webUrl }
|
|
91
|
+
...webUrl === void 0 ? {} : { webUrl },
|
|
92
|
+
...decisionId === void 0 ? {} : { decisionId },
|
|
93
|
+
kind
|
|
94
94
|
};
|
|
95
95
|
}
|
|
96
96
|
function parseFeedbackLease(value) {
|
|
97
|
-
if (!isRecord(value) || value.protocolVersion
|
|
97
|
+
if (!isRecord(value) || !isSupportedProtocolVersion(value.protocolVersion)) return void 0;
|
|
98
|
+
const protocolVersion = value.protocolVersion;
|
|
98
99
|
if (value.status === "empty") {
|
|
99
|
-
return value.hasMore === false ? { events: [], hasMore: false } : void 0;
|
|
100
|
+
return value.hasMore === false ? { protocolVersion, events: [], hasMore: false } : void 0;
|
|
100
101
|
}
|
|
101
102
|
if (value.status === "unavailable") {
|
|
102
|
-
return value.reason === "organization_unbound" ? { events: [], hasMore: false } : void 0;
|
|
103
|
+
return value.reason === "organization_unbound" ? { protocolVersion, events: [], hasMore: false } : void 0;
|
|
103
104
|
}
|
|
104
105
|
if (value.status !== "leased" || typeof value.hasMore !== "boolean") return void 0;
|
|
105
106
|
if (!Array.isArray(value.events) || value.events.length === 0 || value.events.length > MAX_FEEDBACK_EVENTS) {
|
|
106
107
|
return void 0;
|
|
107
108
|
}
|
|
108
|
-
const events = value.events.map(parseEvent);
|
|
109
|
+
const events = value.events.map((event) => parseEvent(event, protocolVersion));
|
|
109
110
|
if (events.some((event) => event === void 0)) return void 0;
|
|
110
111
|
const parsedEvents = events;
|
|
111
112
|
if (new Set(parsedEvents.map((event) => event.eventId)).size !== parsedEvents.length) {
|
|
112
113
|
return void 0;
|
|
113
114
|
}
|
|
114
|
-
return { events: parsedEvents, hasMore: value.hasMore };
|
|
115
|
+
return { protocolVersion, events: parsedEvents, hasMore: value.hasMore };
|
|
115
116
|
}
|
|
116
117
|
function renderFeedback(lease) {
|
|
117
118
|
const lines = [];
|
|
118
119
|
const deliveries = [];
|
|
119
120
|
let pointCount = 0;
|
|
120
121
|
for (const event of lease.events) {
|
|
121
|
-
const
|
|
122
|
+
const decisionId = event.kind === "publish_prompt" ? parseFeedbackDecisionId(event.decisionId) : void 0;
|
|
123
|
+
if (event.kind === "publish_prompt" && decisionId === void 0) continue;
|
|
124
|
+
const identifier = `dec_${event.shortId}`;
|
|
125
|
+
const detail = `${event.intent}${event.webUrl ? ` (${event.webUrl})` : ""}`;
|
|
126
|
+
const line = event.kind === "publish_prompt" ? `[prim] publish this Decision draft (${identifier})? ${detail} Run \`prim decisions publish ${decisionId}\` to share it with your team.` : `[prim] response \u2192 created Decision (${identifier}): ${detail}`;
|
|
122
127
|
const extra = Array.from(line).length + (lines.length === 0 ? 0 : 1);
|
|
123
128
|
if (pointCount + extra > MAX_FEEDBACK_MESSAGE_CODE_POINTS) break;
|
|
124
129
|
lines.push(line);
|
|
@@ -126,7 +131,7 @@ function renderFeedback(lease) {
|
|
|
126
131
|
pointCount += extra;
|
|
127
132
|
}
|
|
128
133
|
if (lines.length === 0) return void 0;
|
|
129
|
-
return { systemMessage: lines.join("\n"), deliveries };
|
|
134
|
+
return { protocolVersion: lease.protocolVersion, systemMessage: lines.join("\n"), deliveries };
|
|
130
135
|
}
|
|
131
136
|
async function leaseDecisionFeedback(input, dependencies = {}) {
|
|
132
137
|
try {
|
|
@@ -152,12 +157,14 @@ async function leaseDecisionFeedback(input, dependencies = {}) {
|
|
|
152
157
|
}
|
|
153
158
|
}
|
|
154
159
|
async function acknowledgeDecisionFeedback(input, dependencies = {}) {
|
|
155
|
-
if (input.deliveries.length === 0 || input.deliveries.length > MAX_FEEDBACK_EVENTS)
|
|
160
|
+
if (!isSupportedProtocolVersion(input.protocolVersion) || input.deliveries.length === 0 || input.deliveries.length > MAX_FEEDBACK_EVENTS) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
156
163
|
try {
|
|
157
164
|
const response = await (dependencies.client ?? getClient()).post(
|
|
158
165
|
ACK_PATH,
|
|
159
166
|
{
|
|
160
|
-
protocolVersion:
|
|
167
|
+
protocolVersion: input.protocolVersion,
|
|
161
168
|
workspaceId: input.workspaceId,
|
|
162
169
|
deliveries: input.deliveries
|
|
163
170
|
},
|
|
@@ -165,7 +172,7 @@ async function acknowledgeDecisionFeedback(input, dependencies = {}) {
|
|
|
165
172
|
);
|
|
166
173
|
const acknowledgedEventIds = isRecord(response) ? response.acknowledgedEventIds : void 0;
|
|
167
174
|
const expectedEventIds = new Set(input.deliveries.map((delivery) => delivery.eventId));
|
|
168
|
-
const valid = isRecord(response) && response.protocolVersion ===
|
|
175
|
+
const valid = isRecord(response) && response.protocolVersion === input.protocolVersion && response.status === "acked" && Array.isArray(acknowledgedEventIds) && acknowledgedEventIds.length <= input.deliveries.length && acknowledgedEventIds.every(
|
|
169
176
|
(id) => typeof id === "string" && id.length > 0 && id.length <= MAX_EVENT_ID_CHARS && expectedEventIds.has(id)
|
|
170
177
|
) && new Set(acknowledgedEventIds).size === acknowledgedEventIds.length;
|
|
171
178
|
if (!valid) {
|
|
@@ -180,7 +187,7 @@ async function acknowledgeDecisionFeedback(input, dependencies = {}) {
|
|
|
180
187
|
}
|
|
181
188
|
}
|
|
182
189
|
function parseFeedbackCapability(value) {
|
|
183
|
-
if (!isRecord(value) || value.protocolVersion
|
|
190
|
+
if (!isRecord(value) || !isSupportedProtocolVersion(value.protocolVersion)) return void 0;
|
|
184
191
|
if (value.status === "available") return { status: "available" };
|
|
185
192
|
if (value.status === "unavailable" && value.reason === "organization_unbound") {
|
|
186
193
|
return { status: "unavailable", reason: "organization_unbound" };
|