@primitive.ai/prim 0.1.0-alpha.90 → 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.
Files changed (45) hide show
  1. package/README.md +20 -12
  2. package/SKILL.md +6 -6
  3. package/dist/{chunk-LMRYZL3D.js → chunk-5AFRMOPX.js} +1 -1
  4. package/dist/{chunk-RO3F6JYV.js → chunk-5GYQ4J3D.js} +2 -2
  5. package/dist/{chunk-3EEFL6NJ.js → chunk-67VNLLBN.js} +261 -1
  6. package/dist/{chunk-LEMX3IOG.js → chunk-BTNNEH2W.js} +1 -1
  7. package/dist/chunk-EWZMDD44.js +1474 -0
  8. package/dist/{chunk-YWO7O6QG.js → chunk-FBAYCS3X.js} +2 -2
  9. package/dist/{chunk-R26A7TWY.js → chunk-FKXFUUWJ.js} +1 -1
  10. package/dist/{chunk-C7T7B45R.js → chunk-FOLKHXLJ.js} +150 -21
  11. package/dist/chunk-H4UNYFUC.js +196 -0
  12. package/dist/chunk-HU4PW6I6.js +264 -0
  13. package/dist/{chunk-CUE2LKLR.js → chunk-KFXF2V5G.js} +1 -1
  14. package/dist/{chunk-6IWNH776.js → chunk-LFFLHSQO.js} +4 -3
  15. package/dist/{chunk-MAZWKJGL.js → chunk-M6RIJVRE.js} +42 -232
  16. package/dist/{chunk-2JRX7ZR4.js → chunk-NAHHFSLH.js} +16 -5
  17. package/dist/{chunk-FI3LIRCB.js → chunk-OAAP2MY6.js} +2 -2
  18. package/dist/{chunk-Z7YPFWAZ.js → chunk-PCVGBE5B.js} +10 -268
  19. package/dist/{chunk-UZPBB7V4.js → chunk-VW3FDYRT.js} +60 -48
  20. package/dist/chunk-WX52U7Z2.js +112 -0
  21. package/dist/{chunk-PPQYQEIR.js → chunk-ZB4F75SZ.js} +28 -0
  22. package/dist/daemon/server.js +20 -20
  23. package/dist/hook-runtime/daemon/server.js +20 -20
  24. package/dist/hook-runtime/hooks/post-commit.js +20 -20
  25. package/dist/hook-runtime/hooks/post-rewrite.js +20 -20
  26. package/dist/hook-runtime/hooks/post-tool-use.js +651 -132
  27. package/dist/hook-runtime/hooks/pre-tool-use.js +644 -96
  28. package/dist/hook-runtime/hooks/prim-hook.js +612 -96
  29. package/dist/hook-runtime/hooks/session-end.js +610 -27
  30. package/dist/hook-runtime/hooks/session-start.js +1132 -533
  31. package/dist/hook-runtime/index.js +735 -264
  32. package/dist/hook-runtime/statusline-main.js +48 -4
  33. package/dist/hooks/post-commit.js +11 -9
  34. package/dist/hooks/post-rewrite.js +11 -9
  35. package/dist/hooks/post-tool-use.js +73 -48
  36. package/dist/hooks/pre-commit.js +4 -4
  37. package/dist/hooks/pre-tool-use.js +46 -44
  38. package/dist/hooks/prim-hook.js +47 -25
  39. package/dist/hooks/session-end.js +15 -26
  40. package/dist/hooks/session-start.js +59 -32
  41. package/dist/index.js +558 -1436
  42. package/dist/statusline-main.js +48 -4
  43. package/package.json +1 -1
  44. package/dist/chunk-PMRDE2DA.js +0 -310
  45. package/dist/chunk-WB7AUZ4C.js +0 -52
@@ -3,10 +3,10 @@ import {
3
3
  } from "./chunk-EZXEWFDG.js";
4
4
  import {
5
5
  scrubEnvironmentPaths
6
- } from "./chunk-FI3LIRCB.js";
6
+ } from "./chunk-OAAP2MY6.js";
7
7
  import {
8
8
  isValidRepoSyncId
9
- } from "./chunk-MAZWKJGL.js";
9
+ } from "./chunk-M6RIJVRE.js";
10
10
 
11
11
  // src/hooks/prim-hook-core.ts
12
12
  import { createHash, randomUUID } from "crypto";
@@ -45,6 +45,17 @@ function toolOutcomeFor(parsed, agent) {
45
45
  if (agent === "codex") {
46
46
  return event === "PostToolUse" ? "returned" : void 0;
47
47
  }
48
+ if (agent === "cursor") {
49
+ if (event === "PostToolUse") return "succeeded";
50
+ if (event === "PostToolUseFailure") {
51
+ if (parsed.is_interrupt === true) return "interrupted";
52
+ const failureType = lowerString(parsed.failure_type);
53
+ if (failureType === "permission_denied") return "prevented";
54
+ if (failureType === "error" || failureType === "timeout") return "failed";
55
+ return "unknown";
56
+ }
57
+ return void 0;
58
+ }
48
59
  const extra = parsed.extra;
49
60
  if (event === "post_approval_response") {
50
61
  const choice = lowerString(extra?.choice);
@@ -75,7 +86,7 @@ function toMove(parsed, cliVersion, agent = "claude_code", workspaceId, reposito
75
86
  let moveId;
76
87
  if (CORRELATED_TOOL_EVENTS.has(eventType) && sessionId && invocationId) {
77
88
  moveId = `posttool:v1:${createHash("sha256").update(JSON.stringify([agent, sessionId, eventType, invocationId])).digest("hex")}`;
78
- } else if (CORRELATED_TOOL_EVENTS.has(eventType) && sessionId) {
89
+ } else if (CORRELATED_TOOL_EVENTS.has(eventType) && sessionId && agent !== "cursor") {
79
90
  moveId = `posttool:fallback:v1:${createHash("sha256").update(
80
91
  JSON.stringify([
81
92
  agent,
@@ -231,8 +242,8 @@ function toRewriteMove(rewrite, cliVersion, cwd, context) {
231
242
  }
232
243
  return moves;
233
244
  }
234
- function shouldFlushAfter(eventType) {
235
- return eventType === "SessionEnd";
245
+ function shouldFlushAfter(eventType, agent = "claude_code") {
246
+ return eventType === "SessionEnd" || agent === "cursor" && eventType === "Stop";
236
247
  }
237
248
 
238
249
  export {
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  boundedHealthError
3
- } from "./chunk-LEMX3IOG.js";
3
+ } from "./chunk-BTNNEH2W.js";
4
4
  import {
5
5
  terminalSafeLine
6
- } from "./chunk-Z7YPFWAZ.js";
6
+ } from "./chunk-PCVGBE5B.js";
7
7
 
8
8
  // src/hooks/redact.ts
9
9
  import { existsSync, readFileSync, statSync } from "fs";
@@ -10,268 +10,16 @@ import {
10
10
  TOKEN_EXPIRES_PATH,
11
11
  TOKEN_FILE_PATH,
12
12
  WORKOS_CONNECT_RESPONSE_MAX_BYTES,
13
+ atomicWriteFile,
13
14
  jwtExpiresAt,
14
15
  parseWorkosConnectTokens,
15
16
  readBoundedJson,
16
17
  readCredentialMigrationState,
17
18
  readStoredCredentialMetadata,
18
19
  resolveAuthCredential,
20
+ withFileLock,
19
21
  workosConnectTokenEndpoint
20
- } from "./chunk-3EEFL6NJ.js";
21
-
22
- // src/lib/atomic-file.ts
23
- import { randomUUID } from "crypto";
24
- import {
25
- constants,
26
- closeSync,
27
- fchmodSync,
28
- fsyncSync,
29
- mkdirSync,
30
- openSync,
31
- renameSync,
32
- rmSync,
33
- writeFileSync
34
- } from "fs";
35
- import { dirname } from "path";
36
- var DEFAULT_FILE_MODE = 438;
37
- function syncDirectory(path) {
38
- const fd = openSync(path, constants.O_RDONLY);
39
- try {
40
- fsyncSync(fd);
41
- } finally {
42
- closeSync(fd);
43
- }
44
- }
45
- function syncFile(path) {
46
- const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
47
- try {
48
- fsyncSync(fd);
49
- } finally {
50
- closeSync(fd);
51
- }
52
- }
53
- function syncCreatedParentChain(firstCreatedParent, parent) {
54
- const createdDirectories = [];
55
- let current = parent;
56
- while (current !== firstCreatedParent) {
57
- createdDirectories.push(current);
58
- const next = dirname(current);
59
- if (next === current) {
60
- throw new Error("recursive mkdir returned a path outside the target parent chain");
61
- }
62
- current = next;
63
- }
64
- createdDirectories.push(firstCreatedParent);
65
- syncDirectory(dirname(firstCreatedParent));
66
- for (const directory of createdDirectories.reverse().slice(0, -1)) {
67
- syncDirectory(directory);
68
- }
69
- }
70
- function ensureDurableDirectory(path, mode) {
71
- const firstCreatedParent = mkdirSync(path, { recursive: true, mode });
72
- if (firstCreatedParent) {
73
- syncCreatedParentChain(firstCreatedParent, path);
74
- }
75
- }
76
- function atomicWriteFile(target, content, options = {}) {
77
- const parent = dirname(target);
78
- if (options.ensureParent) {
79
- ensureDurableDirectory(parent);
80
- }
81
- const temporaryPath = `${target}.${randomUUID()}.tmp`;
82
- let temporaryCreated = false;
83
- try {
84
- const flags = constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW;
85
- const fd = openSync(temporaryPath, flags, options.mode ?? DEFAULT_FILE_MODE);
86
- temporaryCreated = true;
87
- try {
88
- if (options.mode !== void 0) fchmodSync(fd, options.mode);
89
- writeFileSync(fd, content);
90
- fsyncSync(fd);
91
- } finally {
92
- closeSync(fd);
93
- }
94
- options.validate?.(temporaryPath);
95
- renameSync(temporaryPath, target);
96
- syncDirectory(parent);
97
- } catch (error) {
98
- if (temporaryCreated) {
99
- try {
100
- rmSync(temporaryPath, { force: true });
101
- } catch {
102
- }
103
- }
104
- throw error;
105
- }
106
- }
107
-
108
- // src/lib/process-liveness.ts
109
- function processIsAlive(pid, probe = process.kill) {
110
- if (!Number.isSafeInteger(pid) || pid <= 0) return false;
111
- try {
112
- probe(pid, 0);
113
- return true;
114
- } catch (error) {
115
- return error.code === "EPERM";
116
- }
117
- }
118
-
119
- // src/lib/file-lock.ts
120
- import { randomBytes } from "crypto";
121
- import { existsSync, mkdirSync as mkdirSync2, readFileSync, rmSync as rmSync2, statSync, writeFileSync as writeFileSync2 } from "fs";
122
- import { dirname as dirname2, join } from "path";
123
- var DEFAULT_TIMEOUT_MS = 3e4;
124
- var DEFAULT_POLL_MS = 50;
125
- var DEFAULT_INIT_GRACE_MS = 2e3;
126
- var DIRECTORY_MODE = 448;
127
- var FILE_MODE = 384;
128
- function ownerPath(lockDir) {
129
- return join(lockDir, "owner.json");
130
- }
131
- function readOwner(lockDir) {
132
- try {
133
- const owner = JSON.parse(readFileSync(ownerPath(lockDir), "utf8"));
134
- if (!Number.isInteger(owner.pid) || owner.pid <= 0 || typeof owner.nonce !== "string" || typeof owner.createdAt !== "number") {
135
- return null;
136
- }
137
- return owner;
138
- } catch {
139
- return null;
140
- }
141
- }
142
- function sameOwner(a, b) {
143
- return a?.pid === b.pid && a.nonce === b.nonce && a.createdAt === b.createdAt;
144
- }
145
- function tryTakeLock(lockDir, now) {
146
- try {
147
- mkdirSync2(lockDir, { mode: DIRECTORY_MODE });
148
- } catch (error) {
149
- if (error.code === "EEXIST") return null;
150
- throw error;
151
- }
152
- const owner = {
153
- pid: process.pid,
154
- nonce: randomBytes(16).toString("hex"),
155
- createdAt: now
156
- };
157
- try {
158
- writeFileSync2(ownerPath(lockDir), `${JSON.stringify(owner)}
159
- `, {
160
- encoding: "utf8",
161
- mode: FILE_MODE,
162
- flag: "wx"
163
- });
164
- return owner;
165
- } catch (error) {
166
- rmSync2(lockDir, { recursive: true, force: true });
167
- throw error;
168
- }
169
- }
170
- function recoverStaleLock(lockDir, now, alive, initGraceMs) {
171
- const owner = readOwner(lockDir);
172
- if (owner) {
173
- if (alive(owner.pid)) return false;
174
- if (!sameOwner(readOwner(lockDir), owner)) return false;
175
- rmSync2(lockDir, { recursive: true, force: true });
176
- return true;
177
- }
178
- try {
179
- const before = statSync(lockDir).mtimeMs;
180
- if (now - before < initGraceMs) return false;
181
- if (readOwner(lockDir)) return false;
182
- if (statSync(lockDir).mtimeMs !== before) return false;
183
- rmSync2(lockDir, { recursive: true, force: true });
184
- return true;
185
- } catch {
186
- return true;
187
- }
188
- }
189
- function abortError(signal) {
190
- if (signal.reason instanceof Error) return signal.reason;
191
- const error = new Error("The operation was aborted");
192
- error.name = "AbortError";
193
- return error;
194
- }
195
- function throwIfAborted(signal) {
196
- if (signal?.aborted) throw abortError(signal);
197
- }
198
- async function defaultSleep(ms, signal) {
199
- if (!signal) {
200
- await new Promise((resolve2) => setTimeout(resolve2, ms));
201
- return;
202
- }
203
- throwIfAborted(signal);
204
- await new Promise((resolve2, reject) => {
205
- const timer = setTimeout(() => {
206
- signal.removeEventListener("abort", onAbort);
207
- resolve2();
208
- }, ms);
209
- const onAbort = () => {
210
- clearTimeout(timer);
211
- reject(abortError(signal));
212
- };
213
- signal.addEventListener("abort", onAbort, { once: true });
214
- });
215
- }
216
- var syncSleeper = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
217
- function defaultSleepSync(ms) {
218
- Atomics.wait(syncSleeper, 0, 0, ms);
219
- }
220
- function withFileLockSync(lockDir, operation, options = {}) {
221
- mkdirSync2(dirname2(lockDir), { recursive: true, mode: DIRECTORY_MODE });
222
- const nowMs = options.nowMs ?? Date.now;
223
- const alive = options.processAlive ?? processIsAlive;
224
- const deadline = nowMs() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
225
- let owner = null;
226
- while (!owner) {
227
- const now = nowMs();
228
- owner = tryTakeLock(lockDir, now);
229
- if (owner) break;
230
- recoverStaleLock(lockDir, now, alive, options.initGraceMs ?? DEFAULT_INIT_GRACE_MS);
231
- if (now >= deadline) {
232
- throw new Error(`timed out waiting for file lock ${lockDir}`);
233
- }
234
- (options.sleep ?? defaultSleepSync)(options.pollMs ?? DEFAULT_POLL_MS);
235
- }
236
- try {
237
- return operation();
238
- } finally {
239
- if (sameOwner(readOwner(lockDir), owner)) {
240
- rmSync2(lockDir, { recursive: true, force: true });
241
- }
242
- }
243
- }
244
- async function withFileLock(lockDir, operation, options = {}) {
245
- mkdirSync2(dirname2(lockDir), { recursive: true, mode: DIRECTORY_MODE });
246
- const nowMs = options.nowMs ?? Date.now;
247
- const alive = options.processAlive ?? processIsAlive;
248
- const deadline = nowMs() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
249
- let owner = null;
250
- while (!owner) {
251
- throwIfAborted(options.signal);
252
- const now = nowMs();
253
- owner = tryTakeLock(lockDir, now);
254
- if (owner) break;
255
- recoverStaleLock(lockDir, now, alive, options.initGraceMs ?? DEFAULT_INIT_GRACE_MS);
256
- if (now >= deadline) {
257
- throw new Error(`timed out waiting for file lock ${lockDir}`);
258
- }
259
- if (options.sleep) {
260
- await options.sleep(options.pollMs ?? DEFAULT_POLL_MS);
261
- throwIfAborted(options.signal);
262
- } else {
263
- await defaultSleep(options.pollMs ?? DEFAULT_POLL_MS, options.signal);
264
- }
265
- }
266
- try {
267
- throwIfAborted(options.signal);
268
- return await operation();
269
- } finally {
270
- if (sameOwner(readOwner(lockDir), owner)) {
271
- rmSync2(lockDir, { recursive: true, force: true });
272
- }
273
- }
274
- }
22
+ } from "./chunk-67VNLLBN.js";
275
23
 
276
24
  // src/lib/terminal-safe.ts
277
25
  function replaceIsolatedSurrogates(value) {
@@ -320,8 +68,8 @@ var stripControlChars = terminalSafeText;
320
68
 
321
69
  // src/client.ts
322
70
  import { createHash } from "crypto";
323
- import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync2, rmSync as rmSync3 } from "fs";
324
- import { dirname as dirname3 } from "path";
71
+ import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync } from "fs";
72
+ import { dirname } from "path";
325
73
  var CONFIG_DIR_MODE = 448;
326
74
  var CREDENTIAL_FILE_MODE = 384;
327
75
  var REFRESH_THRESHOLD_MS = 6e4;
@@ -329,7 +77,7 @@ var DEFAULT_API_URL = "https://api.getprimitive.ai";
329
77
  var AUTH_EXPIRED_MESSAGE = "Authentication expired. Run `prim auth login` to re-authenticate.";
330
78
  function readTrimmed(path) {
331
79
  try {
332
- const value = readFileSync2(path, "utf8").trim();
80
+ const value = readFileSync(path, "utf8").trim();
333
81
  return value || void 0;
334
82
  } catch {
335
83
  return void 0;
@@ -345,8 +93,8 @@ function expiresAtFor(token, expiresIn) {
345
93
  return expiresIn === void 0 ? jwtExpiresAt(token) : Date.now() + expiresIn * 1e3;
346
94
  }
347
95
  function ensureConfigDirectory() {
348
- const directory = dirname3(TOKEN_FILE_PATH);
349
- mkdirSync3(directory, { recursive: true, mode: CONFIG_DIR_MODE });
96
+ const directory = dirname(TOKEN_FILE_PATH);
97
+ mkdirSync(directory, { recursive: true, mode: CONFIG_DIR_MODE });
350
98
  chmodSync(directory, CONFIG_DIR_MODE);
351
99
  }
352
100
  function atomicWrite(path, content) {
@@ -354,8 +102,8 @@ function atomicWrite(path, content) {
354
102
  atomicWriteFile(path, content, { mode: CREDENTIAL_FILE_MODE });
355
103
  }
356
104
  function removeCredentialFile(path) {
357
- if (!existsSync2(path)) return false;
358
- rmSync3(path, { force: true });
105
+ if (!existsSync(path)) return false;
106
+ rmSync(path, { force: true });
359
107
  return true;
360
108
  }
361
109
  function getTokenExpiresAt() {
@@ -892,12 +640,6 @@ async function getPinnedManagementClient(options = {}) {
892
640
  }
893
641
 
894
642
  export {
895
- syncDirectory,
896
- syncFile,
897
- atomicWriteFile,
898
- processIsAlive,
899
- withFileLockSync,
900
- withFileLock,
901
643
  terminalSafeText,
902
644
  terminalSafeLine,
903
645
  isTerminalSafeText,
@@ -1,26 +1,30 @@
1
1
  import {
2
- formatStatusline,
3
- packageVersion
4
- } from "./chunk-PMRDE2DA.js";
2
+ formatStatusline
3
+ } from "./chunk-WX52U7Z2.js";
5
4
  import {
6
5
  decisionIngestionStatus,
7
- gitToplevel,
8
6
  repositoryBindingState
9
- } from "./chunk-MAZWKJGL.js";
7
+ } from "./chunk-M6RIJVRE.js";
10
8
  import {
11
9
  getSiteUrl,
12
10
  isSessionEnded,
13
- terminalSafeText,
14
- withFileLock
15
- } from "./chunk-Z7YPFWAZ.js";
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";
16
19
  import {
17
20
  daemonPrincipalsMatch,
18
21
  daemonRequest,
19
22
  resolveDaemonPrincipal
20
- } from "./chunk-CUE2LKLR.js";
23
+ } from "./chunk-KFXF2V5G.js";
21
24
  import {
22
- primConfigDirectory
23
- } from "./chunk-3EEFL6NJ.js";
25
+ primConfigDirectory,
26
+ withFileLock
27
+ } from "./chunk-67VNLLBN.js";
24
28
 
25
29
  // src/hooks/codex-context.ts
26
30
  import { createHash, randomBytes as randomBytes2 } from "crypto";
@@ -61,9 +65,7 @@ var CODEX_DRAFT_DIGEST_MAX_SEEN_IDS = 4096;
61
65
  var CODEX_DIGEST_STATE_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
62
66
  var CODEX_DIGEST_STATE_MAX_FILES = 256;
63
67
  var STATE_VERSION = 1;
64
- var STATE_DIRECTORY = ["codex", "decision-digests"];
65
68
  var DRAFT_STATE_VERSION = 2;
66
- var DRAFT_STATE_DIRECTORY = ["codex", "decision-draft-deliveries"];
67
69
  var NO_CURSOR = -1;
68
70
  function hasVisibleCodexMessage(output) {
69
71
  if (typeof output !== "object" || output === null || Array.isArray(output)) return false;
@@ -95,11 +97,11 @@ function appendCodexContext(output, context) {
95
97
  }
96
98
  return next;
97
99
  }
98
- function stateRoot() {
99
- return join(primConfigDirectory(), ...STATE_DIRECTORY);
100
+ function stateRoot(namespace) {
101
+ return join(primConfigDirectory(), namespace, "decision-digests");
100
102
  }
101
- function draftStateRoot() {
102
- return join(primConfigDirectory(), ...DRAFT_STATE_DIRECTORY);
103
+ function draftStateRoot(namespace) {
104
+ return join(primConfigDirectory(), namespace, "decision-draft-deliveries");
103
105
  }
104
106
  function workspaceFor(cwd) {
105
107
  try {
@@ -111,12 +113,12 @@ function workspaceFor(cwd) {
111
113
  function statePath(args) {
112
114
  const identity = `${args.siteUrl}\0${workspaceFor(args.cwd)}\0${args.sessionId}`;
113
115
  const key = createHash("sha256").update(identity).digest("hex");
114
- return join(stateRoot(), `${key}.json`);
116
+ return join(stateRoot(args.namespace), `${key}.json`);
115
117
  }
116
118
  function draftStatePath(args) {
117
119
  const identity = `${args.siteUrl}\0${workspaceFor(args.cwd)}\0${args.sessionId}\0${args.principalId}\0${args.organizationId}\0${args.credentialFingerprint}`;
118
120
  const key = createHash("sha256").update(identity).digest("hex");
119
- return join(draftStateRoot(), `${key}.json`);
121
+ return join(draftStateRoot(args.namespace), `${key}.json`);
120
122
  }
121
123
  function parseState(value) {
122
124
  if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
@@ -307,14 +309,14 @@ function mergeState(latest, args) {
307
309
  updatedAt: args.now
308
310
  };
309
311
  }
310
- async function commitState(path, args) {
312
+ async function commitState(path, args, namespace) {
311
313
  try {
312
314
  await withFileLock(
313
315
  `${path}.lock`,
314
316
  () => {
315
317
  const latest = readState(path);
316
- writeStateFile(path, stateRoot(), mergeState(latest, args));
317
- cleanupStateFiles(stateRoot(), args.now);
318
+ writeStateFile(path, stateRoot(namespace), mergeState(latest, args));
319
+ cleanupStateFiles(stateRoot(namespace), args.now);
318
320
  },
319
321
  { timeoutMs: 50, pollMs: 10 }
320
322
  );
@@ -336,7 +338,7 @@ function mergeDraftState(latest, args) {
336
338
  updatedAt: args.now
337
339
  };
338
340
  }
339
- async function commitDraftState(path, args) {
341
+ async function commitDraftState(path, args, namespace) {
340
342
  try {
341
343
  let next;
342
344
  await withFileLock(
@@ -344,8 +346,8 @@ async function commitDraftState(path, args) {
344
346
  () => {
345
347
  const latest = readDraftState(path);
346
348
  next = mergeDraftState(latest, args);
347
- writeStateFile(path, draftStateRoot(), next);
348
- cleanupStateFiles(draftStateRoot(), args.now);
349
+ writeStateFile(path, draftStateRoot(namespace), next);
350
+ cleanupStateFiles(draftStateRoot(namespace), args.now);
349
351
  },
350
352
  { timeoutMs: 50, pollMs: 10 }
351
353
  );
@@ -355,9 +357,10 @@ async function commitDraftState(path, args) {
355
357
  }
356
358
  }
357
359
  async function prepareCodexContext(options) {
360
+ const namespace = options.namespace ?? "codex";
358
361
  const siteUrl = getSiteUrl();
359
362
  const workspace = workspaceFor(options.cwd);
360
- const path = statePath({ cwd: options.cwd, sessionId: options.sessionId, siteUrl });
363
+ const path = statePath({ cwd: options.cwd, sessionId: options.sessionId, siteUrl, namespace });
361
364
  const previous = readState(path);
362
365
  const principal = resolveDaemonPrincipal();
363
366
  const privatePath = principal ? draftStatePath({
@@ -366,7 +369,8 @@ async function prepareCodexContext(options) {
366
369
  siteUrl,
367
370
  principalId: principal.principalId,
368
371
  organizationId: principal.organizationId,
369
- credentialFingerprint: principal.credentialFingerprint
372
+ credentialFingerprint: principal.credentialFingerprint,
373
+ namespace
370
374
  }) : void 0;
371
375
  const previousDraft = privatePath ? readDraftState(privatePath) : void 0;
372
376
  const startup = options.startup === true;
@@ -456,32 +460,40 @@ async function prepareCodexContext(options) {
456
460
  acknowledge: async (handedOff) => {
457
461
  if (!handedOff || acknowledged) return;
458
462
  acknowledged = true;
459
- await commitState(path, {
460
- sessionId: options.sessionId,
461
- siteUrl,
462
- workspace,
463
- startedAt,
464
- watermarkMs: pageWatermark,
465
- seenIds: freshRows.map((row) => row.id),
466
- report,
467
- feedAvailable,
468
- now: Date.now()
469
- });
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
+ );
470
478
  if (privatePath === void 0 || principal === void 0 || draftPageToken === void 0) {
471
479
  return;
472
480
  }
473
481
  let persistedDraft = previousDraft;
474
482
  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
- });
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
+ );
485
497
  }
486
498
  const seenDrafts = new Set(persistedDraft?.seenDraftIds ?? []);
487
499
  const deliveredOnThisPage = draftPageActionableIds.filter((id) => seenDrafts.has(id));