@primitive.ai/prim 0.1.0-alpha.67 → 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-KKP2NYZ3.js +505 -0
- package/dist/{chunk-JQFN6LBO.js → chunk-LEMX3IOG.js} +6 -37
- 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-XGADQPCI.js +26 -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 +11804 -470
- 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 +64 -32
- package/dist/hooks/pre-commit.js +4 -3
- package/dist/hooks/pre-tool-use.js +118 -38
- package/dist/hooks/prim-hook.js +72 -27
- package/dist/hooks/session-end.js +5 -4
- package/dist/hooks/session-start.js +36 -25
- package/dist/index.js +15039 -2555
- package/dist/statusline-main.js +488 -96
- package/package.json +11 -5
- package/dist/chunk-CZ24W5QU.js +0 -123
- package/dist/chunk-RTLFE6EZ.js +0 -518
- package/dist/chunk-UTKQTZHL.js +0 -88
|
@@ -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" };
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getClient,
|
|
3
|
-
getSiteUrl
|
|
4
|
-
|
|
3
|
+
getSiteUrl,
|
|
4
|
+
terminalSafeLine
|
|
5
|
+
} from "./chunk-Z7YPFWAZ.js";
|
|
5
6
|
import {
|
|
6
7
|
daemonRequest
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-CUE2LKLR.js";
|
|
8
9
|
|
|
9
10
|
// src/daemon/proxy.ts
|
|
10
11
|
var DAEMON_HTTP_TIMEOUT_MS = 1e4;
|
|
@@ -93,7 +94,7 @@ var FILES_PREVIEW_LIMIT = 3;
|
|
|
93
94
|
function formatDecisionsWarning(result) {
|
|
94
95
|
const lines = [];
|
|
95
96
|
if (result.unavailable !== void 0) {
|
|
96
|
-
lines.push(`[prim] decision check not verified \u2014 ${result.unavailable}`);
|
|
97
|
+
lines.push(`[prim] decision check not verified \u2014 ${terminalSafeLine(result.unavailable)}`);
|
|
97
98
|
}
|
|
98
99
|
if (result.decisions.length > 0) {
|
|
99
100
|
lines.push(
|
|
@@ -101,12 +102,12 @@ function formatDecisionsWarning(result) {
|
|
|
101
102
|
);
|
|
102
103
|
for (const d of result.decisions) {
|
|
103
104
|
const statusMark = d.status === "under_review" ? " (under review)" : "";
|
|
104
|
-
const preview = d.matchedFiles.slice(0, FILES_PREVIEW_LIMIT).join(", ");
|
|
105
|
+
const preview = d.matchedFiles.slice(0, FILES_PREVIEW_LIMIT).map(terminalSafeLine).join(", ");
|
|
105
106
|
const overflow = d.matchedFiles.length > FILES_PREVIEW_LIMIT ? ` (+${String(d.matchedFiles.length - FILES_PREVIEW_LIMIT)} more)` : "";
|
|
106
|
-
lines.push(` \xB7 ${d.intent}${statusMark}`);
|
|
107
|
+
lines.push(` \xB7 ${terminalSafeLine(d.intent)}${statusMark}`);
|
|
107
108
|
lines.push(` files: ${preview}${overflow}`);
|
|
108
109
|
if (d.rationale) {
|
|
109
|
-
lines.push(` rationale: ${d.rationale}`);
|
|
110
|
+
lines.push(` rationale: ${terminalSafeLine(d.rationale)}`);
|
|
110
111
|
}
|
|
111
112
|
}
|
|
112
113
|
}
|
|
@@ -4,6 +4,8 @@ import { createHash } from "crypto";
|
|
|
4
4
|
import { lstatSync, realpathSync } from "fs";
|
|
5
5
|
import { basename, dirname, isAbsolute, relative, resolve, sep } from "path";
|
|
6
6
|
var GIT_TIMEOUT_MS = 1e3;
|
|
7
|
+
var GITHUB_OWNER_PATTERN = /^(?![A-Za-z0-9-]*--)[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/u;
|
|
8
|
+
var GITHUB_REPOSITORY_PATTERN = /^[A-Za-z0-9._-]{1,100}$/u;
|
|
7
9
|
function gitToplevel(cwd) {
|
|
8
10
|
try {
|
|
9
11
|
return execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
@@ -38,7 +40,9 @@ function githubRepositoryFullName(cwd) {
|
|
|
38
40
|
const path = match?.[1] ?? match?.[2];
|
|
39
41
|
if (!path) return null;
|
|
40
42
|
const parts = path.replace(/^\/+|\/+$/g, "").replace(/\.git$/i, "").split("/");
|
|
41
|
-
if (parts.length !== 2 ||
|
|
43
|
+
if (parts.length !== 2 || !GITHUB_OWNER_PATTERN.test(parts[0] ?? "") || !GITHUB_REPOSITORY_PATTERN.test(parts[1] ?? "")) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
42
46
|
return `${parts[0]}/${parts[1]}`;
|
|
43
47
|
}
|
|
44
48
|
function canonicalGitRoot(cwd) {
|
|
@@ -186,6 +190,8 @@ import { homedir } from "os";
|
|
|
186
190
|
import { join } from "path";
|
|
187
191
|
var PRIM_ACTIVE_KEY = "prim.active";
|
|
188
192
|
var PRIM_REPO_SYNC_ID_KEY = "prim.repoSyncId";
|
|
193
|
+
var PRIM_REPO_SYNC_REPOSITORY_KEY = "prim.repoSyncRepository";
|
|
194
|
+
var PRIM_REPO_BINDING_STATE_KEY = "prim.repoBindingState";
|
|
189
195
|
var GIT_TIMEOUT_MS2 = 1e3;
|
|
190
196
|
var REPO_SYNC_ID_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
|
|
191
197
|
var PROJECT_INSTALL_FILES = [".claude/settings.json", ".codex/hooks.json"];
|
|
@@ -218,20 +224,26 @@ function setRepoActive(cwd, active) {
|
|
|
218
224
|
function isValidRepoSyncId(value) {
|
|
219
225
|
return typeof value === "string" && REPO_SYNC_ID_RE.test(value);
|
|
220
226
|
}
|
|
221
|
-
function
|
|
227
|
+
function localGitConfigValue(cwd, key) {
|
|
222
228
|
try {
|
|
223
|
-
const value = execFileSync2("git", ["config", "--local", "--get",
|
|
229
|
+
const value = execFileSync2("git", ["config", "--local", "--get", key], {
|
|
224
230
|
cwd,
|
|
225
231
|
encoding: "utf-8",
|
|
226
232
|
stdio: ["ignore", "pipe", "ignore"],
|
|
227
233
|
timeout: GIT_TIMEOUT_MS2
|
|
228
234
|
}).trim();
|
|
229
|
-
return
|
|
235
|
+
return value || void 0;
|
|
230
236
|
} catch {
|
|
231
237
|
return void 0;
|
|
232
238
|
}
|
|
233
239
|
}
|
|
234
|
-
function
|
|
240
|
+
function repoSyncId(cwd) {
|
|
241
|
+
const value = localGitConfigValue(cwd, PRIM_REPO_SYNC_ID_KEY);
|
|
242
|
+
if (!isValidRepoSyncId(value)) return void 0;
|
|
243
|
+
const repositoryFullName = localGitConfigValue(cwd, PRIM_REPO_SYNC_REPOSITORY_KEY);
|
|
244
|
+
return repositoryFullName === githubRepositoryFullName(cwd) ? value : void 0;
|
|
245
|
+
}
|
|
246
|
+
function setRepoSyncId(cwd, value, repositoryFullName) {
|
|
235
247
|
if (!isValidRepoSyncId(value)) {
|
|
236
248
|
throw new Error("invalid repository binding returned by server");
|
|
237
249
|
}
|
|
@@ -240,20 +252,30 @@ function setRepoSyncId(cwd, value) {
|
|
|
240
252
|
stdio: ["ignore", "ignore", "pipe"],
|
|
241
253
|
timeout: GIT_TIMEOUT_MS2
|
|
242
254
|
});
|
|
255
|
+
execFileSync2("git", ["config", "--local", PRIM_REPO_SYNC_REPOSITORY_KEY, repositoryFullName], {
|
|
256
|
+
cwd,
|
|
257
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
258
|
+
timeout: GIT_TIMEOUT_MS2
|
|
259
|
+
});
|
|
243
260
|
}
|
|
244
|
-
function
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
cwd,
|
|
248
|
-
stdio: ["ignore", "ignore", "pipe"],
|
|
249
|
-
timeout: GIT_TIMEOUT_MS2
|
|
250
|
-
});
|
|
251
|
-
} catch (error) {
|
|
252
|
-
if (typeof error === "object" && error !== null && "status" in error && error.status === 5) {
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
throw error;
|
|
261
|
+
function setRepositoryBindingState(cwd, state) {
|
|
262
|
+
if (state !== "connected" && state !== "unbound" && state !== "invalid") {
|
|
263
|
+
throw new Error("invalid repository binding state");
|
|
256
264
|
}
|
|
265
|
+
execFileSync2("git", ["config", "--local", PRIM_REPO_BINDING_STATE_KEY, state], {
|
|
266
|
+
cwd,
|
|
267
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
268
|
+
timeout: GIT_TIMEOUT_MS2
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
function repositoryBindingState(cwd) {
|
|
272
|
+
const rawId = localGitConfigValue(cwd, PRIM_REPO_SYNC_ID_KEY);
|
|
273
|
+
const marker = localGitConfigValue(cwd, PRIM_REPO_BINDING_STATE_KEY);
|
|
274
|
+
if (rawId !== void 0 && !isValidRepoSyncId(rawId)) return "invalid";
|
|
275
|
+
if (marker === "unbound" || marker === "invalid") return marker;
|
|
276
|
+
if (marker === "connected") return repoSyncId(cwd) ? "connected" : "invalid";
|
|
277
|
+
if (marker !== void 0) return "invalid";
|
|
278
|
+
return rawId === void 0 ? "unbound" : repoSyncId(cwd) ? void 0 : "unbound";
|
|
257
279
|
}
|
|
258
280
|
function hasProjectPrimInstall(cwd) {
|
|
259
281
|
const root = gitToplevel(cwd);
|
|
@@ -292,7 +314,8 @@ export {
|
|
|
292
314
|
isValidRepoSyncId,
|
|
293
315
|
repoSyncId,
|
|
294
316
|
setRepoSyncId,
|
|
295
|
-
|
|
317
|
+
setRepositoryBindingState,
|
|
318
|
+
repositoryBindingState,
|
|
296
319
|
isRepoActiveForCapture,
|
|
297
320
|
decisionIngestionStatus
|
|
298
321
|
};
|
|
@@ -9,6 +9,7 @@ function parseAgent(argv) {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
// src/hooks/normalize.ts
|
|
12
|
+
import { createHash } from "crypto";
|
|
12
13
|
var HERMES_EVENT_MAP = {
|
|
13
14
|
on_session_start: "SessionStart",
|
|
14
15
|
on_session_end: "SessionEnd",
|
|
@@ -19,6 +20,22 @@ var HERMES_EVENT_MAP = {
|
|
|
19
20
|
subagent_stop: "SubagentStop"
|
|
20
21
|
};
|
|
21
22
|
function normalizeEnvelope(parsed, agent) {
|
|
23
|
+
if (agent === "codex") {
|
|
24
|
+
const event = parsed.hook_event_name;
|
|
25
|
+
if ((event === "PreToolUse" || event === "PostToolUse") && (typeof parsed.tool_use_id !== "string" || parsed.tool_use_id.length === 0)) {
|
|
26
|
+
const stableToolIdentity = JSON.stringify([
|
|
27
|
+
parsed.session_id ?? null,
|
|
28
|
+
parsed.turn_id ?? null,
|
|
29
|
+
parsed.tool_name ?? null,
|
|
30
|
+
parsed.tool_input ?? null
|
|
31
|
+
]);
|
|
32
|
+
return {
|
|
33
|
+
...parsed,
|
|
34
|
+
tool_use_id: `codex:fallback:v1:${createHash("sha256").update(stableToolIdentity).digest("hex")}`
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return parsed;
|
|
38
|
+
}
|
|
22
39
|
if (agent !== "hermes") {
|
|
23
40
|
return parsed;
|
|
24
41
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// src/ingest-response.ts
|
|
2
|
+
var IngestAcknowledgementError = class extends Error {
|
|
3
|
+
constructor(message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "IngestAcknowledgementError";
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
function requireDurableIngestAcknowledgement(value, expected) {
|
|
9
|
+
if (typeof value !== "object" || value === null) {
|
|
10
|
+
throw new IngestAcknowledgementError("ingest response was not an object");
|
|
11
|
+
}
|
|
12
|
+
const response = value;
|
|
13
|
+
if (response.disposition !== "persisted") {
|
|
14
|
+
throw new IngestAcknowledgementError("ingest response did not confirm persistence");
|
|
15
|
+
}
|
|
16
|
+
if (response.acknowledged !== expected) {
|
|
17
|
+
throw new IngestAcknowledgementError(
|
|
18
|
+
`ingest acknowledged ${String(response.acknowledged)} of ${String(expected)} moves`
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
requireDurableIngestAcknowledgement
|
|
26
|
+
};
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getSiteUrl
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-Z7YPFWAZ.js";
|
|
4
|
+
import {
|
|
5
|
+
jwtOrganizationId,
|
|
6
|
+
primConfigDirectory,
|
|
7
|
+
resolveAuthCredential
|
|
8
|
+
} from "./chunk-3EEFL6NJ.js";
|
|
4
9
|
|
|
5
10
|
// src/journal.ts
|
|
6
11
|
import {
|
|
@@ -15,9 +20,8 @@ import {
|
|
|
15
20
|
readSync,
|
|
16
21
|
readdirSync
|
|
17
22
|
} from "fs";
|
|
18
|
-
import { homedir } from "os";
|
|
19
23
|
import { dirname, join } from "path";
|
|
20
|
-
var JOURNAL_DIR = join(
|
|
24
|
+
var JOURNAL_DIR = join(primConfigDirectory(), "moves");
|
|
21
25
|
var UNBOUND_BUCKET = "_unbound";
|
|
22
26
|
var JOURNAL_BASENAME = "journal.ndjson";
|
|
23
27
|
var JOURNAL_STATS_SAMPLE_BYTES = 64 * 1024;
|
|
@@ -139,7 +143,9 @@ function sampleJournalFile(path, maxBytes = JOURNAL_STATS_SAMPLE_BYTES) {
|
|
|
139
143
|
function parseFlushingPid(name) {
|
|
140
144
|
const segments = name.slice(FLUSHING_PREFIX.length).split(".");
|
|
141
145
|
const last = segments.length >= 2 ? segments[segments.length - 1] : void 0;
|
|
142
|
-
|
|
146
|
+
if (last === void 0 || !/^[0-9]+$/.test(last)) return void 0;
|
|
147
|
+
const pid = Number(last);
|
|
148
|
+
return Number.isSafeInteger(pid) && pid > 0 ? pid : void 0;
|
|
143
149
|
}
|
|
144
150
|
function listFlushingInDir(dir, bucket, options = {}) {
|
|
145
151
|
if (!existsSync(dir)) {
|
|
@@ -253,18 +259,10 @@ function pendingJournalStats() {
|
|
|
253
259
|
|
|
254
260
|
// src/binding.ts
|
|
255
261
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
256
|
-
import { homedir as homedir2 } from "os";
|
|
257
262
|
import { dirname as dirname2, join as join2 } from "path";
|
|
258
|
-
var PRIM_CONFIG_DIR =
|
|
259
|
-
var TOKEN_PATH = join2(PRIM_CONFIG_DIR, "token");
|
|
263
|
+
var PRIM_CONFIG_DIR = primConfigDirectory();
|
|
260
264
|
var SESSIONS_DIR = join2(PRIM_CONFIG_DIR, "sessions");
|
|
261
265
|
var WORKSPACE_FILE = ".prim/workspace.json";
|
|
262
|
-
var JWT_PARTS = 3;
|
|
263
|
-
var BASE64_PAD_4 = 4;
|
|
264
|
-
var ENV_TOKEN_LINE = /^\s*PRIM_TOKEN\s*=\s*(.*)$/m;
|
|
265
|
-
var SURROUNDING_QUOTES = /^["']|["']$/g;
|
|
266
|
-
var BASE64URL_MINUS = /-/g;
|
|
267
|
-
var BASE64URL_UNDERSCORE = /_/g;
|
|
268
266
|
function readJsonSafe(path) {
|
|
269
267
|
if (!existsSync2(path)) {
|
|
270
268
|
return;
|
|
@@ -296,46 +294,9 @@ function fromWorkspaceFile(startDir) {
|
|
|
296
294
|
dir = parent;
|
|
297
295
|
}
|
|
298
296
|
}
|
|
299
|
-
function
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
return fromEnv.trim();
|
|
303
|
-
}
|
|
304
|
-
if (existsSync2(TOKEN_PATH)) {
|
|
305
|
-
return readFileSync2(TOKEN_PATH, "utf-8").trim();
|
|
306
|
-
}
|
|
307
|
-
const envLocal = join2(cwd, ".env.local");
|
|
308
|
-
if (existsSync2(envLocal)) {
|
|
309
|
-
const match = readFileSync2(envLocal, "utf-8").match(ENV_TOKEN_LINE);
|
|
310
|
-
if (match) {
|
|
311
|
-
return match[1].trim().replace(SURROUNDING_QUOTES, "");
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
function base64UrlDecode(s) {
|
|
317
|
-
const padded = s.padEnd(
|
|
318
|
-
s.length + (BASE64_PAD_4 - s.length % BASE64_PAD_4) % BASE64_PAD_4,
|
|
319
|
-
"="
|
|
320
|
-
);
|
|
321
|
-
const normalized = padded.replace(BASE64URL_MINUS, "+").replace(BASE64URL_UNDERSCORE, "/");
|
|
322
|
-
return Buffer.from(normalized, "base64").toString("utf-8");
|
|
323
|
-
}
|
|
324
|
-
function fromDefaultOrg(cwd) {
|
|
325
|
-
const token = readToken(cwd);
|
|
326
|
-
if (!token) {
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
const parts = token.split(".");
|
|
330
|
-
if (parts.length !== JWT_PARTS) {
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
try {
|
|
334
|
-
const claims = JSON.parse(base64UrlDecode(parts[1]));
|
|
335
|
-
return claims.org_id;
|
|
336
|
-
} catch {
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
297
|
+
function fromDefaultOrg(_cwd) {
|
|
298
|
+
const credential = resolveAuthCredential();
|
|
299
|
+
return credential ? jwtOrganizationId(credential.token) : void 0;
|
|
339
300
|
}
|
|
340
301
|
function resolveOrg(args) {
|
|
341
302
|
const fromSession = fromSessionMarker(args.sessionId);
|