@primitive.ai/prim 0.1.0-alpha.44 → 0.1.0-alpha.45

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.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  getClient,
3
3
  getSiteUrl
4
- } from "./chunk-W6PAKEDO.js";
4
+ } from "./chunk-PI6LDYNJ.js";
5
5
  import {
6
6
  daemonRequest
7
7
  } from "./chunk-UTKQTZHL.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getClient
3
- } from "./chunk-W6PAKEDO.js";
3
+ } from "./chunk-PI6LDYNJ.js";
4
4
 
5
5
  // src/decisions/feedback.ts
6
6
  var FEEDBACK_PROTOCOL_VERSION = 1;
@@ -79,6 +79,7 @@ function getSiteUrl() {
79
79
  }
80
80
  return DEFAULT_API_URL;
81
81
  }
82
+ var _endedRefreshToken;
82
83
  async function performTokenRefresh(options = {}) {
83
84
  if (!existsSync(REFRESH_TOKEN_PATH)) {
84
85
  return void 0;
@@ -87,6 +88,9 @@ async function performTokenRefresh(options = {}) {
87
88
  if (!refreshTokenValue) {
88
89
  return void 0;
89
90
  }
91
+ if (_endedRefreshToken !== void 0 && refreshTokenValue === _endedRefreshToken) {
92
+ return void 0;
93
+ }
90
94
  const siteUrl = getSiteUrl();
91
95
  const response = await fetch(`${siteUrl}/mcp/broker/refresh`, {
92
96
  method: "POST",
@@ -95,18 +99,23 @@ async function performTokenRefresh(options = {}) {
95
99
  signal: options.signal
96
100
  });
97
101
  if (!response.ok) {
98
- if (options.quiet) return void 0;
99
102
  const detail = (await response.text().catch(() => "")).slice(0, 200);
100
- process.stderr.write(
101
- `[prim] token refresh rejected by broker: ${response.status} ${response.statusText}${detail ? ` \u2014 ${detail}` : ""}
103
+ if (detail.includes("invalid_grant") || detail.includes("Session has already ended")) {
104
+ _endedRefreshToken = refreshTokenValue;
105
+ }
106
+ if (!options.quiet) {
107
+ process.stderr.write(
108
+ `[prim] token refresh rejected by broker: ${response.status} ${response.statusText}${detail ? ` \u2014 ${detail}` : ""}
102
109
  `
103
- );
110
+ );
111
+ }
104
112
  return void 0;
105
113
  }
106
114
  const data = await response.json();
107
115
  if (!data.access_token) {
108
116
  return void 0;
109
117
  }
118
+ _endedRefreshToken = void 0;
110
119
  writeFileSync(TOKEN_FILE_PATH, data.access_token, { mode: 384 });
111
120
  if (data.refresh_token) {
112
121
  writeFileSync(REFRESH_TOKEN_PATH, data.refresh_token, { mode: 384 });
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getSiteUrl
3
- } from "./chunk-W6PAKEDO.js";
3
+ } from "./chunk-PI6LDYNJ.js";
4
4
 
5
5
  // src/journal.ts
6
6
  import {
@@ -88,6 +88,17 @@ function getSiteUrl() {
88
88
  }
89
89
  return DEFAULT_API_URL;
90
90
  }
91
+ var _endedRefreshToken;
92
+ function isSessionEnded() {
93
+ if (_endedRefreshToken === void 0) {
94
+ return false;
95
+ }
96
+ if (!existsSync(REFRESH_TOKEN_PATH)) {
97
+ return false;
98
+ }
99
+ const current = readFileSync(REFRESH_TOKEN_PATH, "utf-8").trim();
100
+ return current === _endedRefreshToken;
101
+ }
91
102
  async function performTokenRefresh(options = {}) {
92
103
  if (!existsSync(REFRESH_TOKEN_PATH)) {
93
104
  return void 0;
@@ -96,6 +107,9 @@ async function performTokenRefresh(options = {}) {
96
107
  if (!refreshTokenValue) {
97
108
  return void 0;
98
109
  }
110
+ if (_endedRefreshToken !== void 0 && refreshTokenValue === _endedRefreshToken) {
111
+ return void 0;
112
+ }
99
113
  const siteUrl = getSiteUrl();
100
114
  const response = await fetch(`${siteUrl}/mcp/broker/refresh`, {
101
115
  method: "POST",
@@ -104,18 +118,23 @@ async function performTokenRefresh(options = {}) {
104
118
  signal: options.signal
105
119
  });
106
120
  if (!response.ok) {
107
- if (options.quiet) return void 0;
108
121
  const detail = (await response.text().catch(() => "")).slice(0, 200);
109
- process.stderr.write(
110
- `[prim] token refresh rejected by broker: ${response.status} ${response.statusText}${detail ? ` \u2014 ${detail}` : ""}
122
+ if (detail.includes("invalid_grant") || detail.includes("Session has already ended")) {
123
+ _endedRefreshToken = refreshTokenValue;
124
+ }
125
+ if (!options.quiet) {
126
+ process.stderr.write(
127
+ `[prim] token refresh rejected by broker: ${response.status} ${response.statusText}${detail ? ` \u2014 ${detail}` : ""}
111
128
  `
112
- );
129
+ );
130
+ }
113
131
  return void 0;
114
132
  }
115
133
  const data = await response.json();
116
134
  if (!data.access_token) {
117
135
  return void 0;
118
136
  }
137
+ _endedRefreshToken = void 0;
119
138
  writeFileSync(TOKEN_FILE_PATH, data.access_token, { mode: 384 });
120
139
  if (data.refresh_token) {
121
140
  writeFileSync(REFRESH_TOKEN_PATH, data.refresh_token, { mode: 384 });
@@ -719,7 +738,7 @@ function createDaemonHealthState(version, pid, startedAt2) {
719
738
  function refreshDaemonHealth(state, now) {
720
739
  state.heartbeat.healthy = state.heartbeat.consecutiveFailures === 0 && state.heartbeat.lastSuccessAt !== void 0 && now - state.heartbeat.lastSuccessAt < HEARTBEAT_FRESH_MS;
721
740
  state.ingestion.healthy = state.ingestion.consecutiveFailures === 0 && !state.ingestion.pendingSampled && (state.ingestion.pendingCount === 0 || state.ingestion.oldestPendingAt !== void 0 && now - state.ingestion.oldestPendingAt <= INGESTION_SLA_MS);
722
- state.healthy = state.heartbeat.healthy && state.ingestion.healthy;
741
+ state.healthy = state.heartbeat.healthy && state.ingestion.healthy && !state.needsReauth;
723
742
  }
724
743
  function retryDelayMs(consecutiveFailures, capMs, random) {
725
744
  const exponent = Math.max(0, consecutiveFailures - 1);
@@ -935,6 +954,7 @@ var heartbeatRerunRequested = false;
935
954
  var ownership;
936
955
  var socketServer;
937
956
  var shuttingDown = false;
957
+ var reauthHold = false;
938
958
  function resolveRuntimeVersion() {
939
959
  if (process.env.PRIM_RUNTIME_VERSION) {
940
960
  return process.env.PRIM_RUNTIME_VERSION;
@@ -967,6 +987,42 @@ function updatePendingHealth() {
967
987
  daemonHealth.ingestion.oldestPendingAt = pending.oldestPendingAt;
968
988
  daemonHealth.ingestion.strandedCount = pending.strandedCount;
969
989
  }
990
+ function enterReauthHold() {
991
+ if (reauthHold) {
992
+ return;
993
+ }
994
+ reauthHold = true;
995
+ daemonHealth.needsReauth = true;
996
+ if (heartbeatTimer) {
997
+ clearTimeout(heartbeatTimer);
998
+ heartbeatTimer = void 0;
999
+ }
1000
+ if (ingestionTimer) {
1001
+ clearTimeout(ingestionTimer);
1002
+ ingestionTimer = void 0;
1003
+ }
1004
+ persistHealth();
1005
+ process.stderr.write(
1006
+ "[prim-daemon] authentication ended \u2014 halting heartbeat + ingestion until `prim auth login` (captures continue to the journal)\n"
1007
+ );
1008
+ }
1009
+ function exitReauthHold() {
1010
+ if (!reauthHold) {
1011
+ return;
1012
+ }
1013
+ reauthHold = false;
1014
+ daemonHealth.needsReauth = false;
1015
+ daemonHealth.heartbeat.consecutiveFailures = 0;
1016
+ daemonHealth.ingestion.consecutiveFailures = 0;
1017
+ daemonHealth.heartbeat.lastError = void 0;
1018
+ daemonHealth.ingestion.lastError = void 0;
1019
+ persistHealth();
1020
+ process.stderr.write(
1021
+ "[prim-daemon] re-authentication detected \u2014 resuming heartbeat + ingestion\n"
1022
+ );
1023
+ void sendHeartbeat();
1024
+ void runIngestionLoop();
1025
+ }
970
1026
  async function takeOwnership() {
971
1027
  const now = Date.now();
972
1028
  const recordedOwner = readDaemonOwner(CONFIG_DIR);
@@ -1051,6 +1107,10 @@ async function performHeartbeat() {
1051
1107
  daemonHealth.heartbeat.lastError = errorMessage(err);
1052
1108
  daemonHealth.heartbeat.consecutiveFailures += 1;
1053
1109
  persistHealth();
1110
+ if (isSessionEnded()) {
1111
+ enterReauthHold();
1112
+ return;
1113
+ }
1054
1114
  process.stderr.write(`[prim-daemon] heartbeat error: ${errorMessage(err)}
1055
1115
  `);
1056
1116
  }
@@ -1076,7 +1136,7 @@ function sendHeartbeat() {
1076
1136
  return heartbeatInFlight;
1077
1137
  }
1078
1138
  function scheduleHeartbeat() {
1079
- if (shuttingDown) {
1139
+ if (shuttingDown || reauthHold) {
1080
1140
  return;
1081
1141
  }
1082
1142
  const failures = daemonHealth.heartbeat.consecutiveFailures;
@@ -1101,7 +1161,7 @@ async function ensureTokenFresh() {
1101
1161
  }
1102
1162
  }
1103
1163
  function scheduleIngestion(delayMs) {
1104
- if (shuttingDown) {
1164
+ if (shuttingDown || reauthHold) {
1105
1165
  return;
1106
1166
  }
1107
1167
  ingestionTimer = setTimeout(() => {
@@ -1154,6 +1214,10 @@ async function runIngestionLoop() {
1154
1214
  } catch {
1155
1215
  }
1156
1216
  persistHealth();
1217
+ if (isSessionEnded()) {
1218
+ enterReauthHold();
1219
+ return;
1220
+ }
1157
1221
  process.stderr.write(`[prim-daemon] ingestion error: ${errorMessage(err)}
1158
1222
  `);
1159
1223
  scheduleIngestion(delay);
@@ -1198,6 +1262,7 @@ function handleStatusSnapshot(params) {
1198
1262
  lastHeartbeatAt,
1199
1263
  version: runtimeVersion,
1200
1264
  healthy: daemonHealth.healthy,
1265
+ needsReauth: daemonHealth.needsReauth === true,
1201
1266
  heartbeat: { ...daemonHealth.heartbeat },
1202
1267
  ingestion: { ...daemonHealth.ingestion }
1203
1268
  };
@@ -1338,7 +1403,16 @@ function startTimers() {
1338
1403
  void runTokenCheckLoop();
1339
1404
  }
1340
1405
  async function runTokenCheckLoop() {
1341
- await ensureTokenFresh();
1406
+ if (reauthHold) {
1407
+ if (!isSessionEnded()) {
1408
+ exitReauthHold();
1409
+ }
1410
+ } else {
1411
+ await ensureTokenFresh();
1412
+ if (isSessionEnded()) {
1413
+ enterReauthHold();
1414
+ }
1415
+ }
1342
1416
  if (!shuttingDown) {
1343
1417
  tokenCheckTimer = setTimeout(() => {
1344
1418
  tokenCheckTimer = void 0;
@@ -5,8 +5,8 @@ import {
5
5
  import {
6
6
  appendMove,
7
7
  resolveOrg
8
- } from "../chunk-OKUXEBPT.js";
9
- import "../chunk-W6PAKEDO.js";
8
+ } from "../chunk-ZWTZMTV5.js";
9
+ import "../chunk-PI6LDYNJ.js";
10
10
 
11
11
  // src/hooks/post-commit.ts
12
12
  import { execSync, spawn } from "child_process";
@@ -13,7 +13,7 @@ import {
13
13
  import {
14
14
  appendMove,
15
15
  resolveOrg
16
- } from "../chunk-OKUXEBPT.js";
16
+ } from "../chunk-ZWTZMTV5.js";
17
17
  import {
18
18
  getOrCreateWorkspaceId
19
19
  } from "../chunk-IMAIBPUC.js";
@@ -23,7 +23,7 @@ import {
23
23
  } from "../chunk-F7O7V6ZM.js";
24
24
  import {
25
25
  getClient
26
- } from "../chunk-W6PAKEDO.js";
26
+ } from "../chunk-PI6LDYNJ.js";
27
27
  import {
28
28
  normalizeEnvelope,
29
29
  parseAgent
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  checkAffectedDecisions,
4
4
  formatDecisionsWarning
5
- } from "../chunk-BSZGI5RL.js";
6
- import "../chunk-W6PAKEDO.js";
5
+ } from "../chunk-6MKLCOEH.js";
6
+ import "../chunk-PI6LDYNJ.js";
7
7
  import "../chunk-UTKQTZHL.js";
8
8
 
9
9
  // src/hooks/pre-commit.ts
@@ -6,7 +6,7 @@ import {
6
6
  import {
7
7
  getClient,
8
8
  getSiteUrl
9
- } from "../chunk-W6PAKEDO.js";
9
+ } from "../chunk-PI6LDYNJ.js";
10
10
  import {
11
11
  daemonRequest
12
12
  } from "../chunk-UTKQTZHL.js";
@@ -9,7 +9,7 @@ import {
9
9
  import {
10
10
  appendMove,
11
11
  resolveOrg
12
- } from "../chunk-OKUXEBPT.js";
12
+ } from "../chunk-ZWTZMTV5.js";
13
13
  import {
14
14
  buildHookOutput,
15
15
  handoffHookOutput
@@ -19,7 +19,7 @@ import {
19
19
  acknowledgeDecisionFeedback,
20
20
  leaseDecisionFeedback,
21
21
  renderFeedback
22
- } from "../chunk-UISU3A7W.js";
22
+ } from "../chunk-6TRRGGLD.js";
23
23
  import {
24
24
  getOrCreateWorkspaceId
25
25
  } from "../chunk-IMAIBPUC.js";
@@ -27,7 +27,7 @@ import {
27
27
  isRepoActiveForCapture,
28
28
  warmBinCache
29
29
  } from "../chunk-F7O7V6ZM.js";
30
- import "../chunk-W6PAKEDO.js";
30
+ import "../chunk-PI6LDYNJ.js";
31
31
  import {
32
32
  normalizeEnvelope,
33
33
  parseAgent
@@ -8,7 +8,7 @@ import {
8
8
  acknowledgeDecisionFeedback,
9
9
  leaseDecisionFeedback,
10
10
  renderFeedback
11
- } from "../chunk-UISU3A7W.js";
11
+ } from "../chunk-6TRRGGLD.js";
12
12
  import {
13
13
  getOrCreateWorkspaceId
14
14
  } from "../chunk-IMAIBPUC.js";
@@ -19,7 +19,7 @@ import {
19
19
  } from "../chunk-F7O7V6ZM.js";
20
20
  import {
21
21
  getSiteUrl
22
- } from "../chunk-W6PAKEDO.js";
22
+ } from "../chunk-PI6LDYNJ.js";
23
23
  import {
24
24
  daemonRequest
25
25
  } from "../chunk-UTKQTZHL.js";
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  checkAffectedDecisions,
13
13
  daemonOrDirectGet,
14
14
  formatDecisionsWarning
15
- } from "./chunk-BSZGI5RL.js";
15
+ } from "./chunk-6MKLCOEH.js";
16
16
  import {
17
17
  JOURNAL_DIR,
18
18
  SESSIONS_DIR,
@@ -21,10 +21,10 @@ import {
21
21
  listFlushing,
22
22
  pendingJournalStats,
23
23
  readMovesFromPath
24
- } from "./chunk-OKUXEBPT.js";
24
+ } from "./chunk-ZWTZMTV5.js";
25
25
  import {
26
26
  fetchFeedbackCapability
27
- } from "./chunk-UISU3A7W.js";
27
+ } from "./chunk-6TRRGGLD.js";
28
28
  import {
29
29
  inspectWorkspaceId
30
30
  } from "./chunk-IMAIBPUC.js";
@@ -48,7 +48,7 @@ import {
48
48
  getSiteUrl,
49
49
  getTokenExpiresAt,
50
50
  saveTokenExpiry
51
- } from "./chunk-W6PAKEDO.js";
51
+ } from "./chunk-PI6LDYNJ.js";
52
52
  import {
53
53
  daemonIsLive,
54
54
  daemonRequest
@@ -3195,6 +3195,13 @@ function classifyDaemonHealth(snapshot, options = {}) {
3195
3195
  detail: `launchd does not own the daemon socket (launchd ${String(options.service.pid ?? "none")} \xB7 socket ${String(snapshot.pid ?? "none")})`
3196
3196
  };
3197
3197
  }
3198
+ if (snapshot.needsReauth) {
3199
+ return {
3200
+ name: "daemon",
3201
+ status: "fail",
3202
+ detail: "authentication ended \u2014 run `prim auth login`"
3203
+ };
3204
+ }
3198
3205
  if (!snapshot.heartbeat?.healthy) {
3199
3206
  return {
3200
3207
  name: "daemon",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primitive.ai/prim",
3
- "version": "0.1.0-alpha.44",
3
+ "version": "0.1.0-alpha.45",
4
4
  "description": "CLI for Primitive's decision graph — passive decision capture, conflict gate, and team presence",
5
5
  "type": "module",
6
6
  "license": "MIT",