@usepowerplant/cli 0.4.20 → 0.4.21

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/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="9f447c4d-0ccb-5f45-8960-6853e28aadfb")}catch(e){}}();
3
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="9ee35b05-7453-5338-85ff-5dc1b6010221")}catch(e){}}();
4
4
  const __cliNodeMajor = Number(process.versions.node.split('.')[0]);
5
5
  if (__cliNodeMajor < 20) {
6
6
  console.error(`powerplant needs Node 20 or newer — this is Node ${process.versions.node}.`);
@@ -23886,7 +23886,7 @@ var require_mock_interceptor = __commonJS({
23886
23886
  var require_mock_client = __commonJS({
23887
23887
  "../../node_modules/.pnpm/undici@7.29.1/node_modules/undici/lib/mock/mock-client.js"(exports, module2) {
23888
23888
  "use strict";
23889
- var { promisify: promisify2 } = __require("node:util");
23889
+ var { promisify: promisify3 } = __require("node:util");
23890
23890
  var Client2 = require_client();
23891
23891
  var { buildMockDispatch } = require_mock_utils();
23892
23892
  var {
@@ -23934,7 +23934,7 @@ var require_mock_client = __commonJS({
23934
23934
  this[kDispatches] = [];
23935
23935
  }
23936
23936
  async [kClose]() {
23937
- await promisify2(this[kOriginalClose])();
23937
+ await promisify3(this[kOriginalClose])();
23938
23938
  this[kConnected] = 0;
23939
23939
  this[kMockAgent][Symbols.kClients].delete(this[kOrigin]);
23940
23940
  }
@@ -24147,7 +24147,7 @@ var require_mock_call_history = __commonJS({
24147
24147
  var require_mock_pool = __commonJS({
24148
24148
  "../../node_modules/.pnpm/undici@7.29.1/node_modules/undici/lib/mock/mock-pool.js"(exports, module2) {
24149
24149
  "use strict";
24150
- var { promisify: promisify2 } = __require("node:util");
24150
+ var { promisify: promisify3 } = __require("node:util");
24151
24151
  var Pool = require_pool();
24152
24152
  var { buildMockDispatch } = require_mock_utils();
24153
24153
  var {
@@ -24195,7 +24195,7 @@ var require_mock_pool = __commonJS({
24195
24195
  this[kDispatches] = [];
24196
24196
  }
24197
24197
  async [kClose]() {
24198
- await promisify2(this[kOriginalClose])();
24198
+ await promisify3(this[kOriginalClose])();
24199
24199
  this[kConnected] = 0;
24200
24200
  this[kMockAgent][Symbols.kClients].delete(this[kOrigin]);
24201
24201
  }
@@ -59131,30 +59131,99 @@ var SECRET_QUERY_PARAM_PATTERN = /^(?:token|access_token|refresh_token|id_token|
59131
59131
  var BEARER_TOKEN_PATTERN = /\bBearer\s+[\w\-._~+/=]+/gi;
59132
59132
  var JWT_PATTERN = /\b[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g;
59133
59133
  var EMBEDDED_QUERY_SECRET_PATTERN = /([?&](?:token|access_token|refresh_token|id_token|api_key|apikey|key|code|signature|password|secret|client_secret|state)=)[^&\s#]+/gi;
59134
- var URL_FIELD_NAMES = /* @__PURE__ */ new Set(["url", "to", "from", "href", "request_url"]);
59134
+ var URL_FIELD_NAMES = /* @__PURE__ */ new Set([
59135
+ "url",
59136
+ "to",
59137
+ "from",
59138
+ "href",
59139
+ "request_url",
59140
+ "referer",
59141
+ "url.full",
59142
+ "http.url",
59143
+ "http.target",
59144
+ "url.path"
59145
+ ]);
59146
+ var KEPT_HEADER_NAMES = /* @__PURE__ */ new Set([
59147
+ "accept",
59148
+ "content-encoding",
59149
+ "content-length",
59150
+ "content-type",
59151
+ "host",
59152
+ "mcp-protocol-version",
59153
+ "referer",
59154
+ "user-agent",
59155
+ "x-github-event",
59156
+ "x-powerplant-cli-version"
59157
+ ]);
59158
+ var KEPT_HTTP_ATTRIBUTES = /* @__PURE__ */ new Set([
59159
+ "http.method",
59160
+ "http.request.method",
59161
+ "http.request.method_original",
59162
+ "http.route",
59163
+ "http.status_code",
59164
+ "http.status_text",
59165
+ "http.response.status_code",
59166
+ "http.scheme",
59167
+ "http.host",
59168
+ "http.flavor",
59169
+ "http.request_content_length_uncompressed",
59170
+ "url.scheme",
59171
+ "url.full",
59172
+ "http.url",
59173
+ "http.target",
59174
+ "url.path"
59175
+ ]);
59176
+ var HTTP_ATTRIBUTE_PREFIX = /^(?:http|url)\./;
59177
+ var URL_PARTS = /^([a-z][a-z\d+.-]*:\/\/)([^/?#]*)([^?#]*)/i;
59178
+ var PATH_KEPT_HOSTS = /(?:^|\.)(?:supabase\.co|usepowerplant\.com)$/i;
59179
+ var OUTGOING_ROUTES = [
59180
+ // GitHub REST API
59181
+ "/app/installations/{installation_id}/access_tokens",
59182
+ "/app/installations/{installation_id}",
59183
+ "/installation/repositories",
59184
+ "/orgs/{org}/memberships/{username}",
59185
+ "/repos/{owner}/{repo}/contents/{path}",
59186
+ "/user/installations",
59187
+ "/user/{account_id}",
59188
+ // GitHub sign-in
59189
+ "/login/oauth/access_token",
59190
+ // Cursor API
59191
+ "/v1/agents",
59192
+ "/v1/agents/{agent_id}",
59193
+ "/v1/agents/{agent_id}/runs",
59194
+ "/v1/agents/{agent_id}/runs/{run_id}",
59195
+ "/v1/agents/{agent_id}/runs/{run_id}/stream",
59196
+ "/teams/filtered-usage-events"
59197
+ ];
59198
+ var OUTGOING_URL_FIELD_NAMES = ["url", "url.full", "http.url"];
59199
+ var OUTGOING_PATH_FIELD_NAMES = ["url.path", "http.target"];
59135
59200
  var MAX_DEPTH = 10;
59136
59201
  function scrubString(value) {
59137
59202
  return value.replace(POWERPLANT_TOKEN_PATTERN, REDACTED3).replace(BEARER_TOKEN_PATTERN, `Bearer ${REDACTED3}`).replace(JWT_PATTERN, REDACTED3).replace(EMBEDDED_QUERY_SECRET_PATTERN, `$1${REDACTED3}`);
59138
59203
  }
59139
59204
  function scrubUrl(url2) {
59140
- if (!url2) return url2;
59141
- const PLACEHOLDER_ORIGIN = "https://scrub.invalid";
59142
- let parsed;
59143
- try {
59144
- parsed = new URL(url2, PLACEHOLDER_ORIGIN);
59145
- } catch {
59146
- return url2;
59147
- }
59148
- let mutated = false;
59149
- for (const key of [...parsed.searchParams.keys()]) {
59150
- if (SECRET_QUERY_PARAM_PATTERN.test(key)) {
59151
- parsed.searchParams.set(key, REDACTED3);
59152
- mutated = true;
59153
- }
59154
- }
59155
- if (!mutated) return url2;
59156
- const result = parsed.toString();
59157
- return url2.startsWith(PLACEHOLDER_ORIGIN) || !url2.includes("://") ? result.replace(PLACEHOLDER_ORIGIN, "") : result;
59205
+ const queryStart = url2.search(/[?#]/);
59206
+ return queryStart === -1 ? url2 : url2.slice(0, queryStart);
59207
+ }
59208
+ function scrubOutgoingUrl(url2) {
59209
+ const parts = URL_PARTS.exec(url2);
59210
+ if (!parts) return REDACTED3;
59211
+ const [, scheme = "", authority = "", path38 = ""] = parts;
59212
+ const host = authority.slice(authority.lastIndexOf("@") + 1);
59213
+ const origin = `${scheme}${host}`;
59214
+ if (PATH_KEPT_HOSTS.test(host.replace(/:\d*$/, ""))) return `${origin}${path38}`;
59215
+ const route = OUTGOING_ROUTES.find((template) => fitsRoute(path38, template));
59216
+ return `${origin}${route ?? ""}`;
59217
+ }
59218
+ function fitsRoute(path38, template) {
59219
+ const segments = path38.replace(/(.)\/$/, "$1").split("/");
59220
+ const route = template.split("/");
59221
+ const hasRest = route.at(-1) === "{path}";
59222
+ const fixed = hasRest ? route.slice(0, -1) : route;
59223
+ if (hasRest ? segments.length < fixed.length : segments.length !== fixed.length) return false;
59224
+ return fixed.every(
59225
+ (part, index) => part.startsWith("{") ? segments[index] !== "" : part === segments[index]
59226
+ );
59158
59227
  }
59159
59228
  function scrubValue(value, depth = 0) {
59160
59229
  if (depth > MAX_DEPTH || value == null) return value;
@@ -59163,9 +59232,10 @@ function scrubValue(value, depth = 0) {
59163
59232
  if (typeof value !== "object") return value;
59164
59233
  const out = {};
59165
59234
  for (const [key, child] of Object.entries(value)) {
59166
- if (SECRET_KEY_PATTERN.test(key) || SECRET_QUERY_PARAM_PATTERN.test(key)) {
59235
+ const name = key.toLowerCase();
59236
+ if (SECRET_KEY_PATTERN.test(key) || SECRET_QUERY_PARAM_PATTERN.test(key) || HTTP_ATTRIBUTE_PREFIX.test(name) && !KEPT_HTTP_ATTRIBUTES.has(name)) {
59167
59237
  out[key] = REDACTED3;
59168
- } else if (URL_FIELD_NAMES.has(key.toLowerCase()) && typeof child === "string") {
59238
+ } else if (URL_FIELD_NAMES.has(name) && typeof child === "string") {
59169
59239
  out[key] = scrubUrl(child);
59170
59240
  } else {
59171
59241
  out[key] = scrubValue(child, depth + 1);
@@ -59178,17 +59248,21 @@ function scrubSentryEvent(event) {
59178
59248
  if (typeof event.request.url === "string") {
59179
59249
  event.request.url = scrubUrl(event.request.url);
59180
59250
  }
59181
- if (typeof event.request.query_string === "string") {
59182
- event.request.query_string = scrubUrl(`?${event.request.query_string}`).replace(/^\?/, "");
59251
+ if (event.request.query_string !== void 0) {
59252
+ event.request.query_string = REDACTED3;
59183
59253
  }
59184
59254
  if (event.request.headers) {
59185
- event.request.headers = scrubValue(event.request.headers);
59255
+ const headers = Object.entries(event.request.headers).map(([name, value]) => [
59256
+ name,
59257
+ KEPT_HEADER_NAMES.has(name.toLowerCase()) ? value : REDACTED3
59258
+ ]);
59259
+ event.request.headers = scrubValue(Object.fromEntries(headers));
59186
59260
  }
59187
59261
  if (event.request.cookies !== void 0) {
59188
59262
  event.request.cookies = REDACTED3;
59189
59263
  }
59190
59264
  if (event.request.data !== void 0) {
59191
- event.request.data = scrubValue(event.request.data);
59265
+ event.request.data = REDACTED3;
59192
59266
  }
59193
59267
  }
59194
59268
  if (event.exception?.values) {
@@ -59210,14 +59284,45 @@ function scrubSentryEvent(event) {
59210
59284
  if (Array.isArray(event.breadcrumbs)) {
59211
59285
  event.breadcrumbs = event.breadcrumbs.map(scrubBreadcrumb);
59212
59286
  }
59287
+ for (const span of event.spans ?? []) {
59288
+ if (typeof span.description === "string") {
59289
+ span.description = scrubString(span.description);
59290
+ }
59291
+ if (span.data) {
59292
+ span.data = scrubValue(span.data);
59293
+ }
59294
+ if (span.op === "http.client") {
59295
+ scrubOutgoingRequestSpan(span);
59296
+ }
59297
+ }
59213
59298
  return event;
59214
59299
  }
59300
+ function scrubOutgoingRequestSpan(span) {
59301
+ if (typeof span.description === "string") {
59302
+ const [method, ...target] = span.description.split(" ");
59303
+ if (target.length > 0) {
59304
+ span.description = `${method} ${scrubOutgoingUrl(target.join(" "))}`;
59305
+ }
59306
+ }
59307
+ const data = span.data;
59308
+ if (!data) return;
59309
+ for (const name of OUTGOING_URL_FIELD_NAMES) {
59310
+ const value = data[name];
59311
+ if (typeof value === "string") data[name] = scrubOutgoingUrl(value);
59312
+ }
59313
+ for (const name of OUTGOING_PATH_FIELD_NAMES) {
59314
+ if (name in data) data[name] = REDACTED3;
59315
+ }
59316
+ }
59215
59317
  function scrubBreadcrumb(breadcrumb) {
59216
59318
  if (typeof breadcrumb.message === "string") {
59217
59319
  breadcrumb.message = scrubString(breadcrumb.message);
59218
59320
  }
59219
59321
  if (breadcrumb.data) {
59220
59322
  breadcrumb.data = scrubValue(breadcrumb.data);
59323
+ if (breadcrumb.category === "http" && typeof breadcrumb.data.url === "string") {
59324
+ breadcrumb.data.url = scrubOutgoingUrl(breadcrumb.data.url);
59325
+ }
59221
59326
  }
59222
59327
  return breadcrumb;
59223
59328
  }
@@ -59416,9 +59521,10 @@ function parseCodexChildThread(sessionMeta) {
59416
59521
  // ../core/src/recentSessionPage.ts
59417
59522
  var RECENT_SESSION_WINDOW_MS = 72 * 60 * 60 * 1e3;
59418
59523
  var RECENT_SESSION_VISIBLE_LIMIT = 12;
59524
+ var RECENT_SESSION_PAGE_MAX = 40;
59419
59525
  var recentSessionOptionsSchema = external_exports.object({
59420
59526
  windowHours: external_exports.coerce.number().int().min(1).max(120).default(72),
59421
- limit: external_exports.coerce.number().int().min(1).max(40).default(12)
59527
+ limit: external_exports.coerce.number().int().min(1).max(RECENT_SESSION_PAGE_MAX).default(12)
59422
59528
  });
59423
59529
  var recentSessionCursorSchema = external_exports.object({
59424
59530
  at: external_exports.iso.datetime({ offset: true }),
@@ -86582,40 +86688,45 @@ function readCodexTranscriptDiscoveryRecords(home, roots) {
86582
86688
  } catch {
86583
86689
  return [];
86584
86690
  }
86585
- const result = [];
86586
- for (const file2 of files) {
86587
- if (!/^[a-f0-9]{64}\.json$/.test(file2)) continue;
86691
+ return files.flatMap((file2) => {
86692
+ if (!/^[a-f0-9]{64}\.json$/.test(file2)) return [];
86693
+ const record2 = readRecord(path2.join(dir, file2), roots);
86694
+ return record2 === null ? [] : [record2];
86695
+ });
86696
+ }
86697
+ function readCodexTranscriptDiscoveryRecord(home, sessionId, roots) {
86698
+ return readRecord(codexTranscriptRecordPath(home, sessionId), roots);
86699
+ }
86700
+ function readRecord(recordPath, roots) {
86701
+ try {
86702
+ if (!lstatSync(recordPath).isFile()) return null;
86703
+ const fd = openSync(recordPath, "r");
86704
+ let text;
86588
86705
  try {
86589
- const recordPath = path2.join(dir, file2);
86590
- if (!lstatSync(recordPath).isFile()) continue;
86591
- const fd = openSync(recordPath, "r");
86592
- let text;
86593
- try {
86594
- const buffer = Buffer.alloc(32 * 1024 + 1);
86595
- const read = readSync(fd, buffer, 0, buffer.length, null);
86596
- if (read === buffer.length) continue;
86597
- text = buffer.subarray(0, read).toString("utf8");
86598
- } finally {
86599
- closeSync(fd);
86600
- }
86601
- const record2 = transcriptDiscoveryRecordSchema.parse(JSON.parse(text));
86602
- if (file2 !== `${createHash("sha256").update(record2.sessionId).digest("hex")}.json`) continue;
86603
- if (record2.transcriptPath === null || !path2.isAbsolute(record2.transcriptPath)) continue;
86604
- const rolloutPath = path2.normalize(record2.transcriptPath);
86605
- if (path2.extname(rolloutPath) !== ".jsonl") continue;
86606
- const rootIndex = roots.findIndex((root2) => {
86607
- const relative2 = path2.relative(root2, rolloutPath);
86608
- return relative2 !== "" && relative2 !== ".." && !relative2.startsWith(`..${path2.sep}`) && !path2.isAbsolute(relative2);
86609
- });
86610
- if (rootIndex < 0 || !lstatSync(rolloutPath).isFile()) continue;
86611
- const root = roots[rootIndex];
86612
- if (realpathSync(rolloutPath) !== path2.join(realpathSync(root), path2.relative(root, rolloutPath)))
86613
- continue;
86614
- result.push({ rolloutPath, sessionId: record2.sessionId, archived: rootIndex === 1 });
86615
- } catch {
86616
- }
86706
+ const buffer = Buffer.alloc(32 * 1024 + 1);
86707
+ const read = readSync(fd, buffer, 0, buffer.length, null);
86708
+ if (read === buffer.length) return null;
86709
+ text = buffer.subarray(0, read).toString("utf8");
86710
+ } finally {
86711
+ closeSync(fd);
86712
+ }
86713
+ const record2 = transcriptDiscoveryRecordSchema.parse(JSON.parse(text));
86714
+ if (path2.basename(recordPath) !== `${createHash("sha256").update(record2.sessionId).digest("hex")}.json`) return null;
86715
+ if (record2.transcriptPath === null || !path2.isAbsolute(record2.transcriptPath)) return null;
86716
+ const rolloutPath = path2.normalize(record2.transcriptPath);
86717
+ if (path2.extname(rolloutPath) !== ".jsonl") return null;
86718
+ const rootIndex = roots.findIndex((root2) => {
86719
+ const relative2 = path2.relative(root2, rolloutPath);
86720
+ return relative2 !== "" && relative2 !== ".." && !relative2.startsWith(`..${path2.sep}`) && !path2.isAbsolute(relative2);
86721
+ });
86722
+ if (rootIndex < 0 || !lstatSync(rolloutPath).isFile()) return null;
86723
+ const root = roots[rootIndex];
86724
+ if (realpathSync(rolloutPath) !== path2.join(realpathSync(root), path2.relative(root, rolloutPath)))
86725
+ return null;
86726
+ return { rolloutPath, sessionId: record2.sessionId, archived: rootIndex === 1 };
86727
+ } catch {
86728
+ return null;
86617
86729
  }
86618
- return result;
86619
86730
  }
86620
86731
 
86621
86732
  // ../../apps/syncservice/src/diagnostics.ts
@@ -86624,7 +86735,7 @@ import { createHmac } from "node:crypto";
86624
86735
  // ../../apps/syncservice/src/http.ts
86625
86736
  var import_undici = __toESM(require_undici(), 1);
86626
86737
  var CLI_VERSION_HEADER = "x-powerplant-cli-version";
86627
- var cliVersion = true ? "0.4.20" : "0.0.0-checkout";
86738
+ var cliVersion = true ? "0.4.21" : "0.0.0-checkout";
86628
86739
  function cliVersionHeaders() {
86629
86740
  return { [CLI_VERSION_HEADER]: cliVersion, "user-agent": `powerplant-cli/${cliVersion}` };
86630
86741
  }
@@ -87717,7 +87828,7 @@ function mayBeParentOf(parent, childMadeMs) {
87717
87828
  function mayBeChildOf(threadTimeMs, parentTimeMs) {
87718
87829
  return threadTimeMs === void 0 || parentTimeMs === void 0 || threadTimeMs + THREAD_ID_TIME_MARGIN_MS >= parentTimeMs;
87719
87830
  }
87720
- var CodexThreadLinks = class {
87831
+ var CodexThreadLinks = class _CodexThreadLinks {
87721
87832
  /** `readLink` is a test seam for the first-record read. */
87722
87833
  constructor(readLink = readThreadLink) {
87723
87834
  this.readLink = readLink;
@@ -87743,6 +87854,19 @@ var CodexThreadLinks = class {
87743
87854
  * (settleRecoveries), with the session the last pass made of it and how
87744
87855
  * many uploads of it failed. */
87745
87856
  recovering = /* @__PURE__ */ new Map();
87857
+ /** A copy for a pass that runs beside the watcher's own passes (the
87858
+ * new-session pass), so neither changes what the other knows. It keeps
87859
+ * what each rollout's first record showed, and leaves out the rollouts
87860
+ * and threads owed another read or a corrected capture, which only the
87861
+ * watcher's own passes settle. */
87862
+ copyForSidePass() {
87863
+ const copy = new _CodexThreadLinks(this.readLink);
87864
+ for (const [rolloutPath, link] of this.byPath) copy.byPath.set(rolloutPath, link);
87865
+ for (const [rolloutPath, changedMs] of this.failedAt) copy.failedAt.set(rolloutPath, changedMs);
87866
+ for (const [rolloutPath, state] of this.incomplete) copy.incomplete.set(rolloutPath, { ...state });
87867
+ for (const rolloutPath of this.faulted) copy.faulted.add(rolloutPath);
87868
+ return copy;
87869
+ }
87746
87870
  /** Keeps what another read of the same first record found. */
87747
87871
  remember(rolloutPath, link) {
87748
87872
  this.byPath.set(rolloutPath, link);
@@ -87980,7 +88104,7 @@ var CodexThreadLinks = class {
87980
88104
  return true;
87981
88105
  }
87982
88106
  };
87983
- var CodexThreadReads = class {
88107
+ var CodexThreadReads = class _CodexThreadReads {
87984
88108
  byThread = /* @__PURE__ */ new Map();
87985
88109
  /** What a full read found when the thread's files were `files`
87986
88110
  * (threadFilesKey): its summary and prefix, null for a thread holding no
@@ -87992,6 +88116,14 @@ var CodexThreadReads = class {
87992
88116
  set(threadId2, files, read) {
87993
88117
  this.byThread.set(threadId2, { files, read });
87994
88118
  }
88119
+ /** A copy for a pass that runs beside the watcher's own passes. Each read
88120
+ * is kept for exactly the files it was made from, so a copy never serves
88121
+ * a read of files that have since changed. */
88122
+ copyForSidePass() {
88123
+ const copy = new _CodexThreadReads();
88124
+ for (const [threadId2, kept] of this.byThread) copy.byThread.set(threadId2, kept);
88125
+ return copy;
88126
+ }
87995
88127
  /** Forgets the threads no longer on this machine. */
87996
88128
  keepOnly(threadIds) {
87997
88129
  for (const threadId2 of this.byThread.keys()) if (!threadIds.has(threadId2)) this.byThread.delete(threadId2);
@@ -90295,8 +90427,10 @@ var collectorStatusSchema = external_exports.object({
90295
90427
  /** Last pass that completed without error — everything eligible had been
90296
90428
  * uploaded as of this time. */
90297
90429
  lastSyncAt: external_exports.string().optional(),
90298
- /** Last pass that upserted at least one session; missing = no session
90299
- * has uploaded in this daemon's lifetime, not "never". */
90430
+ /** When the server last stored a changed session record, from any pass,
90431
+ * a recent catch-up inside one included. The next run of the same
90432
+ * enrollment carries it (carriedUploadClock). Missing: no upload in this
90433
+ * run or the one before it, not "never". */
90300
90434
  lastUploadAt: external_exports.string().optional(),
90301
90435
  consecutiveFailures: external_exports.number().int().nonnegative(),
90302
90436
  /** Whether the hooks this daemon installed are firing, per harness
@@ -90867,7 +91001,7 @@ function saveCredentials(appEnv, credentials) {
90867
91001
  function loadCredentials(appEnv) {
90868
91002
  if (process.platform === "darwin") {
90869
91003
  const raw2 = new MacosCredentialStore(appEnv).load();
90870
- return raw2 === null ? null : parseStoredCredentials(raw2, appEnv);
91004
+ return raw2 === null ? null : withCurrentApiUrl(appEnv, parseStoredCredentials(raw2, appEnv));
90871
91005
  }
90872
91006
  let raw;
90873
91007
  try {
@@ -90879,7 +91013,19 @@ function loadCredentials(appEnv) {
90879
91013
  { cause }
90880
91014
  );
90881
91015
  }
90882
- return parseStoredCredentials(raw, appEnv);
91016
+ return withCurrentApiUrl(appEnv, parseStoredCredentials(raw, appEnv));
91017
+ }
91018
+ var RETIRED_API_URLS = {
91019
+ production: { retired: "https://api.powerplant.sh", replacement: "https://api.usepowerplant.com" },
91020
+ staging: {
91021
+ retired: "https://staging.api.powerplant.sh",
91022
+ replacement: "https://staging.api.usepowerplant.com"
91023
+ }
91024
+ };
91025
+ function withCurrentApiUrl(appEnv, credentials) {
91026
+ const moved = RETIRED_API_URLS[appEnv];
91027
+ if (moved === void 0 || credentials.apiUrl !== moved.retired) return credentials;
91028
+ return { ...credentials, apiUrl: moved.replacement };
90883
91029
  }
90884
91030
  function isCredentialStoreAbsent(cause) {
90885
91031
  return cause instanceof Error && "code" in cause && cause.code === "ENOENT";
@@ -90912,7 +91058,9 @@ import os14 from "node:os";
90912
91058
 
90913
91059
  // ../../apps/syncservice/src/upload.ts
90914
91060
  import { readFileSync as readFileSync10 } from "node:fs";
90915
- import { gzipSync } from "node:zlib";
91061
+ import { setImmediate as yieldToEventLoop3 } from "node:timers/promises";
91062
+ import { promisify as promisify2 } from "node:util";
91063
+ import { gzip } from "node:zlib";
90916
91064
 
90917
91065
  // ../../apps/syncservice/src/claudeCode.ts
90918
91066
  import { readdirSync as readdirSync6 } from "node:fs";
@@ -91490,8 +91638,20 @@ async function uploadPreviewRegistration(apiUrl, machineToken, registration) {
91490
91638
  }
91491
91639
  return "registered";
91492
91640
  }
91493
- function encodeRawTranscript(text) {
91494
- return gzipSync(redactJsonl(text));
91641
+ var gzipAsync = promisify2(gzip);
91642
+ var REDACT_SLICE_CHARS = 64 * 1024;
91643
+ async function encodeRawTranscript(text) {
91644
+ const lines = text.split("\n");
91645
+ let sliceChars = 0;
91646
+ for (const [index, line] of lines.entries()) {
91647
+ lines[index] = redactJsonl(line);
91648
+ sliceChars += line.length;
91649
+ if (sliceChars >= REDACT_SLICE_CHARS) {
91650
+ await yieldToEventLoop3();
91651
+ sliceChars = 0;
91652
+ }
91653
+ }
91654
+ return gzipAsync(lines.join("\n"));
91495
91655
  }
91496
91656
  function readCollectedTranscript(file2, prefix) {
91497
91657
  return prefix === void 0 ? readFileSync10(file2, "utf8") : readTranscriptPrefix(file2, prefix, false);
@@ -91501,8 +91661,8 @@ function readUnchangedTranscript(file2, prefix) {
91501
91661
  }
91502
91662
  var BUNDLE_HEADER_BYTES = 256;
91503
91663
  var BUNDLE_HEADER_BYTES_PER_PART = 256;
91504
- function rawUploadBody(session, readTranscript, maxBytes = MAX_RAW_UPLOAD_BYTES) {
91505
- const main2 = encodeRawTranscript(readTranscript(session.transcriptPath, session.transcriptPrefix));
91664
+ async function rawUploadBody(session, readTranscript, maxBytes = MAX_RAW_UPLOAD_BYTES) {
91665
+ const main2 = await encodeRawTranscript(readTranscript(session.transcriptPath, session.transcriptPrefix));
91506
91666
  if (session.subagents === void 0) {
91507
91667
  return main2.byteLength > maxBytes ? "too-large" : { body: main2, main: main2, contentType: "application/gzip", skippedSubagentFiles: 0, unreadableSubagentFiles: 0 };
91508
91668
  }
@@ -91514,7 +91674,7 @@ function rawUploadBody(session, readTranscript, maxBytes = MAX_RAW_UPLOAD_BYTES)
91514
91674
  for (const file2 of session.subagents.files) {
91515
91675
  let body;
91516
91676
  try {
91517
- body = encodeRawTranscript(readTranscript(file2.path, file2.prefix));
91677
+ body = await encodeRawTranscript(readTranscript(file2.path, file2.prefix));
91518
91678
  } catch (error63) {
91519
91679
  if (error63 instanceof TranscriptChangedError) throw error63;
91520
91680
  skipped += 1;
@@ -91588,7 +91748,7 @@ async function uploadRawSessions(apiUrl, machineToken, transcriptUploadSources,
91588
91748
  continue;
91589
91749
  }
91590
91750
  try {
91591
- const encoded = rawUploadBody(session, readTranscript);
91751
+ const encoded = await rawUploadBody(session, readTranscript);
91592
91752
  if (encoded === "too-large") {
91593
91753
  summary.skippedTooLarge += 1;
91594
91754
  summary.outcomes.set(key, "too-large");
@@ -92523,7 +92683,16 @@ var codexLocal = {
92523
92683
  transcriptSession(transcriptPath) {
92524
92684
  const identity = rolloutIdentity(transcriptPath);
92525
92685
  return identity.page ? { hookId: identity.threadId } : { externalId: path18.basename(transcriptPath, ".jsonl") };
92526
- }
92686
+ },
92687
+ // The hook records the rollout of each thread before it appends the
92688
+ // journal line (.claude/hooks/powerplant-codex-transcript.mjs).
92689
+ hookTranscriptPath(threadId2, homeDir) {
92690
+ const sessionsDir = path18.join(homeDir, ".codex", "sessions");
92691
+ return readCodexTranscriptDiscoveryRecord(homeDir, threadId2, [sessionsDir, codexArchivedSessionsDir(sessionsDir)])?.rolloutPath;
92692
+ },
92693
+ // The Codex app holds the rollout of each open thread for writing
92694
+ // (lsof, 2026-09-27), and macOS reports a write to it only on close.
92695
+ holdsTranscriptOpen: true
92527
92696
  };
92528
92697
  var cursorLocal = {
92529
92698
  harness: "cursor-local",
@@ -93429,6 +93598,7 @@ function readIfExists2(p) {
93429
93598
  }
93430
93599
 
93431
93600
  // ../../apps/syncservice/src/previewRegistration.ts
93601
+ import { setImmediate as yieldToEventLoop4 } from "node:timers/promises";
93432
93602
  async function registerAnnouncedPreviews(apiUrl, machineToken, collected, memo2, deps = {}) {
93433
93603
  const readTranscript = deps.readTranscript ?? readIfExists;
93434
93604
  const upload = deps.upload ?? uploadPreviewRegistration;
@@ -93444,6 +93614,7 @@ async function registerAnnouncedPreviews(apiUrl, machineToken, collected, memo2,
93444
93614
  const state = memo2.get(key);
93445
93615
  if (state?.foreignOwner || state?.previewsOff) continue;
93446
93616
  if (state?.examinedMtimeMs === session.mtimeMs) continue;
93617
+ await yieldToEventLoop4();
93447
93618
  try {
93448
93619
  const text = readTranscript(session.transcriptPath);
93449
93620
  if (text === null) continue;
@@ -93489,6 +93660,22 @@ async function registerAnnouncedPreviews(apiUrl, machineToken, collected, memo2,
93489
93660
  }
93490
93661
 
93491
93662
  // ../../apps/syncservice/src/sync.ts
93663
+ var RecordTurns = class {
93664
+ last = Promise.resolve();
93665
+ /** Waits for the passes ahead, then returns `end`, which lets the next
93666
+ * pass take its turn. Calling `end` again does nothing. */
93667
+ async take() {
93668
+ let end = () => {
93669
+ };
93670
+ const ended = new Promise((resolve2) => {
93671
+ end = resolve2;
93672
+ });
93673
+ const ahead = this.last;
93674
+ this.last = ahead.then(() => ended);
93675
+ await ahead;
93676
+ return end;
93677
+ }
93678
+ };
93492
93679
  var defaultPolicyDeps = {
93493
93680
  fetchPolicy: fetchCapturePolicy,
93494
93681
  load: loadCredentials,
@@ -93590,7 +93777,17 @@ async function resolveCapturePolicy(config2, credentials, log3, deps = defaultPo
93590
93777
  environment
93591
93778
  };
93592
93779
  }
93593
- async function runSyncPassInternal(config2, options, diagnostic) {
93780
+ function recordTurn(turns) {
93781
+ let end = () => {
93782
+ };
93783
+ return {
93784
+ start: async () => {
93785
+ if (turns) end = await turns.take();
93786
+ },
93787
+ end: () => end()
93788
+ };
93789
+ }
93790
+ async function runSyncPassInternal(config2, options, diagnostic, turn) {
93594
93791
  const log3 = options.log ?? console.log;
93595
93792
  const credentials = loadCredentials(config2.appEnv);
93596
93793
  if (!credentials) {
@@ -93616,6 +93813,7 @@ async function runSyncPassInternal(config2, options, diagnostic) {
93616
93813
  diagnostic.sinceMs = sinceMs;
93617
93814
  diagnostic.deepSweep = window2.deepSweep;
93618
93815
  const maxSessions = window2.deepSweep ? DEEP_FIRST_SYNC_MAX_SESSIONS : void 0;
93816
+ await turn.start();
93619
93817
  diagnostic.stage = "claude";
93620
93818
  const claude = await collectClaudeCodeSessions({
93621
93819
  sinceMs,
@@ -93726,12 +93924,14 @@ async function runSyncPassInternal(config2, options, diagnostic) {
93726
93924
  credentials.token,
93727
93925
  items
93728
93926
  );
93927
+ turn.end();
93729
93928
  const foreignKeys = new Set(foreignOwned.map(sessionKey));
93730
93929
  const writable = collected.filter(({ item }) => !foreignKeys.has(sessionKey(item)));
93731
93930
  for (const { harness, harnessSessionId } of foreignOwned) {
93732
93931
  log3(`Skipped ${harness} session ${harnessSessionId}: already synced from another machine.`);
93733
93932
  }
93734
93933
  Object.assign(diagnostic, { upserted, unchanged, outOfPolicy, deferred });
93934
+ if (upserted > 0) options.onRecordsUpserted?.();
93735
93935
  log3(
93736
93936
  `Uploaded: ${upserted} session(s) upserted` + (unchanged > 0 ? `, ${unchanged} unchanged (no writes issued)` : "") + "."
93737
93937
  );
@@ -93863,8 +94063,9 @@ async function runSyncPass(config2, options = {}) {
93863
94063
  }
93864
94064
  };
93865
94065
  notify();
94066
+ const turn = recordTurn(options.recordTurns);
93866
94067
  try {
93867
- const result = await runSyncPassInternal(config2, options, diagnostic);
94068
+ const result = await runSyncPassInternal(config2, options, diagnostic, turn);
93868
94069
  diagnostic.outcome = "complete";
93869
94070
  diagnostic.stage = "complete";
93870
94071
  diagnostic.upserted = result.upserted;
@@ -93877,6 +94078,7 @@ async function runSyncPass(config2, options = {}) {
93877
94078
  if (error63 instanceof Error) failedPasses.set(error63, diagnostic);
93878
94079
  throw error63;
93879
94080
  } finally {
94081
+ turn.end();
93880
94082
  diagnostic.durationMs = Math.max(0, Date.now() - start);
93881
94083
  notify();
93882
94084
  }
@@ -93939,7 +94141,7 @@ function unsupportedVersionNotice(cliVersion2, oldestSupportedVersion) {
93939
94141
  }
93940
94142
 
93941
94143
  // ../../apps/syncservice/src/watch.ts
93942
- import { existsSync as existsSync10, mkdirSync as mkdirSync11, watch as fsWatch2 } from "node:fs";
94144
+ import { existsSync as existsSync10, mkdirSync as mkdirSync11, watch as fsWatch3 } from "node:fs";
93943
94145
  import os19 from "node:os";
93944
94146
  import path21 from "node:path";
93945
94147
 
@@ -93973,7 +94175,7 @@ function defaultStat(file2) {
93973
94175
  }
93974
94176
 
93975
94177
  // ../../apps/syncservice/src/sessionStatusFeed.ts
93976
- import { statSync as statSync5 } from "node:fs";
94178
+ import { statSync as statSync5, watch as fsWatch2 } from "node:fs";
93977
94179
 
93978
94180
  // ../../apps/syncservice/src/sessionStatusModel.ts
93979
94181
  function createSessionStatusModel(now = Date.now) {
@@ -94024,11 +94226,13 @@ function createSessionStatusModel(now = Date.now) {
94024
94226
  // ../../apps/syncservice/src/sessionStatusFeed.ts
94025
94227
  var TRANSCRIPT_WRITE_DELAY_MS = 500;
94026
94228
  var HELD_HOOK_ENTRIES_MAX = 500;
94229
+ var TRANSCRIPT_FILE_WATCHES_MAX = 32;
94027
94230
  var HOOK_ID_PAIRS_MAX = 1e4;
94028
94231
  var MAX_TIMER_MS = 2 ** 31 - 1;
94029
94232
  function createSessionStatusFeed(deps) {
94030
94233
  const now = deps.now ?? Date.now;
94031
94234
  const adapters = deps.adapters ?? HARNESS_ADAPTERS;
94235
+ const watchFile = deps.watchFile ?? watchFileWrites;
94032
94236
  const model = createSessionStatusModel(now);
94033
94237
  const adapterOf = (harness) => adapters.find((adapter) => adapter.harness === harness);
94034
94238
  const hookIdOf = (harness) => adapterOf(harness)?.hookSessionId;
@@ -94040,8 +94244,34 @@ function createSessionStatusFeed(deps) {
94040
94244
  let readQueued = false;
94041
94245
  let readFailing = false;
94042
94246
  let statFailing = false;
94247
+ let watchFailing = false;
94043
94248
  const writeTimers = /* @__PURE__ */ new Map();
94249
+ const hookFilesRead = /* @__PURE__ */ new Set();
94250
+ const reportedFiles = /* @__PURE__ */ new Map();
94251
+ const fileWatches = /* @__PURE__ */ new Map();
94044
94252
  let stopped = false;
94253
+ function applyJournalEntry(entry) {
94254
+ if (learnFromHookFile(entry.harness, entry.sessionId)) releaseHeld();
94255
+ apply(entry);
94256
+ }
94257
+ function learnFromHookFile(harness, hookId) {
94258
+ const adapter = adapterOf(harness);
94259
+ const hookIdOfStored = adapter?.hookSessionId;
94260
+ const key = pairKey(harness, hookId);
94261
+ if (adapter?.hookTranscriptPath === void 0 || hookIdOfStored === void 0 || hookFilesRead.has(key)) {
94262
+ return false;
94263
+ }
94264
+ hookFilesRead.add(key);
94265
+ const file2 = adapter.hookTranscriptPath(hookId, deps.home);
94266
+ const session = file2 === void 0 ? void 0 : adapter.transcriptSession?.(file2);
94267
+ if (file2 === void 0 || session === void 0) return false;
94268
+ const fileHookId = "externalId" in session ? hookIdOfStored(session.externalId) : session.hookId;
94269
+ if (pairKey(harness, fileHookId) !== key) return false;
94270
+ const externalId = "externalId" in session ? session.externalId : storedIds.get(key);
94271
+ if (externalId === void 0) return false;
94272
+ reportFile(file2, { harness, externalId });
94273
+ return "externalId" in session && learnPair(harness, externalId);
94274
+ }
94045
94275
  function stored(entry) {
94046
94276
  if (hookIdOf(entry.harness) === void 0) return entry;
94047
94277
  const storedId = storedIds.get(pairKey(entry.harness, entry.sessionId));
@@ -94083,8 +94313,66 @@ function createSessionStatusFeed(deps) {
94083
94313
  if (mtimeMs === null) return;
94084
94314
  const learned = "externalId" in session && learnPair(harness, externalId);
94085
94315
  applyWrite(harness, externalId, mtimeMs);
94316
+ reportFile(transcriptPath, { harness, externalId });
94086
94317
  if (learned) releaseHeld();
94087
94318
  }
94319
+ function reportFile(file2, session) {
94320
+ if (adapterOf(session.harness)?.holdsTranscriptOpen !== true) return;
94321
+ reportedFiles.delete(file2);
94322
+ reportedFiles.set(file2, session);
94323
+ }
94324
+ function needsFileWatch(session) {
94325
+ const status = model.status(session.harness, session.externalId);
94326
+ return status !== void 0 && status !== "ended";
94327
+ }
94328
+ function watchReportedFiles() {
94329
+ for (const [file2, watched] of fileWatches) {
94330
+ if (!needsFileWatch(watched)) closeFileWatch(file2);
94331
+ }
94332
+ const newest = [...reportedFiles].filter(([, session]) => needsFileWatch(session)).slice(-TRANSCRIPT_FILE_WATCHES_MAX);
94333
+ reportedFiles.clear();
94334
+ for (const [file2, session] of newest) {
94335
+ const watched = fileWatches.get(file2);
94336
+ if (watched === void 0) {
94337
+ openFileWatch(file2, session);
94338
+ } else {
94339
+ fileWatches.delete(file2);
94340
+ fileWatches.set(file2, watched);
94341
+ }
94342
+ }
94343
+ while (fileWatches.size > TRANSCRIPT_FILE_WATCHES_MAX) closeFileWatch(fileWatches.keys().next().value);
94344
+ }
94345
+ function openFileWatch(file2, session) {
94346
+ let close2;
94347
+ try {
94348
+ close2 = watchFile(
94349
+ file2,
94350
+ () => noteWrite(session.harness, file2),
94351
+ (error63) => {
94352
+ closeFileWatch(file2);
94353
+ noteWatchFailure(error63);
94354
+ }
94355
+ );
94356
+ } catch (error63) {
94357
+ if (errorCode(error63) !== "ENOENT") noteWatchFailure(error63);
94358
+ return;
94359
+ }
94360
+ if (watchFailing) deps.log("transcript files watched again");
94361
+ watchFailing = false;
94362
+ fileWatches.set(file2, { ...session, close: close2 });
94363
+ const mtimeMs = modifiedMs(file2);
94364
+ if (mtimeMs !== null) applyWrite(session.harness, session.externalId, mtimeMs);
94365
+ }
94366
+ function closeFileWatch(file2) {
94367
+ fileWatches.get(file2)?.close();
94368
+ fileWatches.delete(file2);
94369
+ }
94370
+ function noteWatchFailure(error63) {
94371
+ if (!watchFailing) {
94372
+ deps.log(`\u26A0 transcript file not watched (${errorCode(error63)}), its status waits for hook events and sync passes`);
94373
+ }
94374
+ watchFailing = true;
94375
+ }
94088
94376
  function modifiedMs(transcriptPath) {
94089
94377
  try {
94090
94378
  const mtimeMs = statSync5(transcriptPath).mtimeMs;
@@ -94092,7 +94380,10 @@ function createSessionStatusFeed(deps) {
94092
94380
  statFailing = false;
94093
94381
  return mtimeMs;
94094
94382
  } catch (error63) {
94095
- if (errorCode(error63) === "ENOENT") return null;
94383
+ if (errorCode(error63) === "ENOENT") {
94384
+ closeFileWatch(transcriptPath);
94385
+ return null;
94386
+ }
94096
94387
  if (!statFailing) {
94097
94388
  deps.log(`\u26A0 transcript file not read (${errorCode(error63)}), its status waits for the next sync pass`);
94098
94389
  }
@@ -94111,8 +94402,10 @@ function createSessionStatusFeed(deps) {
94111
94402
  timer.unref?.();
94112
94403
  writeTimers.set(transcriptPath, timer);
94113
94404
  }
94114
- const journal = createLifecycleJournalReader(deps.home, void 0, apply);
94405
+ const journal = createLifecycleJournalReader(deps.home, void 0, applyJournalEntry);
94115
94406
  function settle2() {
94407
+ watchReportedFiles();
94408
+ hookFilesRead.clear();
94116
94409
  armDeadline();
94117
94410
  if (!changedInBatch) return;
94118
94411
  changedInBatch = false;
@@ -94169,6 +94462,7 @@ function createSessionStatusFeed(deps) {
94169
94462
  const floorMs = lifecycleJournalRetentionFloorMs(now());
94170
94463
  model.forgetBefore(floorMs);
94171
94464
  held = held.filter((entry) => Date.parse(entry.at) >= floorMs);
94465
+ settle2();
94172
94466
  journal.deleteExpired(now());
94173
94467
  },
94174
94468
  status: (harness, externalId) => model.status(harness, externalId),
@@ -94177,9 +94471,17 @@ function createSessionStatusFeed(deps) {
94177
94471
  if (deadlineTimer) clearTimeout(deadlineTimer);
94178
94472
  for (const timer of writeTimers.values()) clearTimeout(timer);
94179
94473
  writeTimers.clear();
94474
+ for (const watched of fileWatches.values()) watched.close();
94475
+ fileWatches.clear();
94180
94476
  }
94181
94477
  };
94182
94478
  }
94479
+ function watchFileWrites(file2, onWrite, onError2) {
94480
+ const watcher = fsWatch2(file2, onWrite);
94481
+ watcher.on("error", onError2);
94482
+ watcher.unref();
94483
+ return () => watcher.close();
94484
+ }
94183
94485
  function errorCode(error63) {
94184
94486
  return error63 instanceof Error && "code" in error63 && typeof error63.code === "string" ? error63.code : "unknown";
94185
94487
  }
@@ -94238,6 +94540,8 @@ function createWatchLoop(deps) {
94238
94540
  const newTranscripts = /* @__PURE__ */ new Map();
94239
94541
  let newSessionTimer = null;
94240
94542
  let newSessionPassDue = false;
94543
+ let newSessionRunning = false;
94544
+ let failedBesidePass = null;
94241
94545
  function onEvent(change) {
94242
94546
  if (stopped) return;
94243
94547
  if (change === "lifecycle") lifecycleChanged = true;
@@ -94269,12 +94573,19 @@ function createWatchLoop(deps) {
94269
94573
  newSessionPassDue = false;
94270
94574
  return;
94271
94575
  }
94272
- if (running || startupOwed || now() < retryNotBefore) return;
94576
+ if (newSessionRunning) return;
94577
+ if (now() < retryNotBefore) {
94578
+ newSessionTimer ??= setTimeout(() => {
94579
+ newSessionTimer = null;
94580
+ void newSessionPass();
94581
+ }, retryNotBefore - now());
94582
+ return;
94583
+ }
94273
94584
  newSessionPassDue = false;
94274
94585
  const transcripts = [...newTranscripts];
94275
94586
  newTranscripts.clear();
94276
94587
  if (transcripts.length === 0) return;
94277
- running = true;
94588
+ newSessionRunning = true;
94278
94589
  deps.log(`${transcripts.length} new transcript file(s), uploading new session records now`);
94279
94590
  const startedAt = now();
94280
94591
  const known = new Set(knownSessions);
@@ -94284,21 +94595,22 @@ function createWatchLoop(deps) {
94284
94595
  isNew: (session) => !known.has(sessionKey(session.item))
94285
94596
  });
94286
94597
  for (const session of summary.sessions) rememberSession(session);
94287
- if (summary.upserted > 0) deps.onNewSessionsUploaded?.(now());
94288
94598
  } catch (error63) {
94289
94599
  const holdMs = Math.max(FAILED_PASS_RETRY_MS, retryAfterMs(error63) ?? 0);
94290
94600
  retryNotBefore = Math.max(retryNotBefore, now() + holdMs);
94291
- deps.onPassResult?.(failedPassResult("new-session", startedAt, error63));
94601
+ const result = failedPassResult("new-session", startedAt, error63);
94602
+ if (running) failedBesidePass = result;
94603
+ else deps.onPassResult?.(result);
94292
94604
  deps.log(
94293
94605
  `new session records not uploaded (${describeError(error63)}), the next pass uploads them in ${describeDelay(holdMs)}`
94294
94606
  );
94295
94607
  }
94296
94608
  for (const [transcript] of transcripts) rememberTranscript(transcript);
94297
- running = false;
94298
- runNext("new-session");
94609
+ newSessionRunning = false;
94610
+ void newSessionPass();
94299
94611
  }
94300
- function runNext(finished) {
94301
- if (queued === null || finished === "regular" && newSessionPassDue) void newSessionPass();
94612
+ function runNext() {
94613
+ void newSessionPass();
94302
94614
  if (queued === null || running) return;
94303
94615
  const next = queued === "steady" ? "net" : "change";
94304
94616
  queued = null;
@@ -94385,6 +94697,7 @@ function createWatchLoop(deps) {
94385
94697
  upserted: summary.upserted,
94386
94698
  ...summary.diagnostic && { diagnostic: summary.diagnostic }
94387
94699
  });
94700
+ if (failedBesidePass !== null && now() < retryNotBefore) deps.onPassResult?.(failedBesidePass);
94388
94701
  } catch (error63) {
94389
94702
  consecutiveFailures += 1;
94390
94703
  const signature = errorSignature(error63);
@@ -94400,7 +94713,7 @@ function createWatchLoop(deps) {
94400
94713
  Math.min(FAILED_PASS_RETRY_MS * 2 ** (streakCount - 1), FAILED_PASS_BACKOFF_CAP_MS),
94401
94714
  retryAfterMs(error63) ?? 0
94402
94715
  );
94403
- retryNotBefore = now() + holdMs;
94716
+ retryNotBefore = Math.max(retryNotBefore, now() + holdMs);
94404
94717
  lifecycleChanged ||= consultLifecycle;
94405
94718
  deps.onPassResult?.(failedPassResult(kind, startedAt, error63));
94406
94719
  const failLine = `${kind} pass failed (${describeError(error63)})`;
@@ -94429,8 +94742,9 @@ function createWatchLoop(deps) {
94429
94742
  }
94430
94743
  }
94431
94744
  running = false;
94745
+ failedBesidePass = null;
94432
94746
  scheduleCompletionCheck();
94433
- runNext("regular");
94747
+ runNext();
94434
94748
  }
94435
94749
  function scheduleCompletionCheck() {
94436
94750
  if (completionTimer) {
@@ -94517,7 +94831,7 @@ function watchEventChange(root, filename) {
94517
94831
  }
94518
94832
  function watchSessionStores(roots, onEvent, onLost) {
94519
94833
  const watchers = roots.map((root) => {
94520
- const watcher = fsWatch2(root.dir, { recursive: root.recursive }, (_type, filename) => {
94834
+ const watcher = fsWatch3(root.dir, { recursive: root.recursive }, (_type, filename) => {
94521
94835
  const change = watchEventChange(root, filename);
94522
94836
  if (change !== "ignore") onEvent(change);
94523
94837
  });
@@ -94531,10 +94845,12 @@ function watchSessionStores(roots, onEvent, onLost) {
94531
94845
  function newSessionSyncOptions(shared, sinceMs, isNew) {
94532
94846
  return {
94533
94847
  lifecycleJournal: shared.lifecycleJournal,
94534
- codexThreadLinks: shared.codexThreadLinks,
94535
- codexThreadReads: shared.codexThreadReads,
94536
94848
  log: shared.log,
94537
94849
  enrollCommand: shared.enrollCommand,
94850
+ onRecordsUpserted: shared.onRecordsUpserted,
94851
+ recordTurns: shared.recordTurns,
94852
+ codexThreadLinks: shared.codexThreadLinks?.copyForSidePass(),
94853
+ codexThreadReads: shared.codexThreadReads?.copyForSidePass(),
94538
94854
  explicitSince: String(STEADY_SYNC_WINDOW_DAYS),
94539
94855
  narrowSinceMs: sinceMs,
94540
94856
  isNewSession: isNew
@@ -94653,6 +94969,7 @@ function startWatch(config2, homeDir, runtime = { version: "checkout" }) {
94653
94969
  });
94654
94970
  const writeStatus = (patch) => statusWriter.write(patch);
94655
94971
  writeStatus({});
94972
+ const recordUpload = () => writeStatus({ lastUploadAt: (/* @__PURE__ */ new Date()).toISOString() });
94656
94973
  const diagnostics = createDiagnosticsReporter(() => loadCredentials(config2.appEnv));
94657
94974
  const checkIn = () => {
94658
94975
  let current;
@@ -94734,6 +95051,7 @@ function startWatch(config2, homeDir, runtime = { version: "checkout" }) {
94734
95051
  const previewRegistrations = /* @__PURE__ */ new Map();
94735
95052
  const codexThreadLinks = new CodexThreadLinks();
94736
95053
  const codexThreadReads = new CodexThreadReads();
95054
+ const recordTurns = new RecordTurns();
94737
95055
  const loop = createWatchLoop({
94738
95056
  runPass: async (options) => {
94739
95057
  let uploaded = 0;
@@ -94753,7 +95071,9 @@ function startWatch(config2, homeDir, runtime = { version: "checkout" }) {
94753
95071
  uploaded += 1;
94754
95072
  if (uploaded === 1 || uploaded % 10 === 0)
94755
95073
  log3(`Transcript progress: ${uploaded} uploaded this pass.`);
94756
- }
95074
+ },
95075
+ onRecordsUpserted: recordUpload,
95076
+ recordTurns
94757
95077
  };
94758
95078
  const catchUp = createRecentCatchup(async (narrowSinceMs) => {
94759
95079
  if (readCollectorControl(config2.appEnv, home).paused) return false;
@@ -94798,10 +95118,15 @@ function startWatch(config2, homeDir, runtime = { version: "checkout" }) {
94798
95118
  }
94799
95119
  return summary;
94800
95120
  },
94801
- runNewSessionPass: ({ sinceMs, isNew }) => runSyncPass(config2, newSessionSyncOptions({ lifecycleJournal: journal, codexThreadLinks, codexThreadReads, log: log3, enrollCommand: runtime.enrollCommand }, sinceMs, isNew)),
94802
- // An upload changes what the menu shows, so this write wakes the menu,
94803
- // and the menu reads the new session from the server.
94804
- onNewSessionsUploaded: (at) => writeStatus({ lastUploadAt: new Date(at).toISOString() }),
95121
+ runNewSessionPass: ({ sinceMs, isNew }) => runSyncPass(config2, newSessionSyncOptions({
95122
+ lifecycleJournal: journal,
95123
+ log: log3,
95124
+ enrollCommand: runtime.enrollCommand,
95125
+ onRecordsUpserted: recordUpload,
95126
+ recordTurns,
95127
+ codexThreadLinks,
95128
+ codexThreadReads
95129
+ }, sinceMs, isNew)),
94805
95130
  paused: () => readCollectorControl(config2.appEnv, home).paused,
94806
95131
  onPassResult: (result) => writeStatus({
94807
95132
  lastPass: {
@@ -94814,7 +95139,6 @@ function startWatch(config2, homeDir, runtime = { version: "checkout" }) {
94814
95139
  ...result.diagnostic && { diagnostic: result.diagnostic }
94815
95140
  },
94816
95141
  ...result.ok && { lastSyncAt: new Date(result.at).toISOString() },
94817
- ...result.ok && result.upserted > 0 && { lastUploadAt: new Date(result.at).toISOString() },
94818
95142
  consecutiveFailures: result.ok ? 0 : statusWriter.current.consecutiveFailures + 1
94819
95143
  }),
94820
95144
  reportError: reportCollectorError,
@@ -95231,7 +95555,7 @@ function applyLocalSessionFlags(sessions, machineId, localFlags, localKey, claud
95231
95555
  };
95232
95556
  });
95233
95557
  }
95234
- async function fetchMemberSessions(credentials, fetchImpl = fetch, options = { windowHours: RECENT_SESSION_WINDOW_MS / 36e5, limit: RECENT_SESSION_VISIBLE_LIMIT }) {
95558
+ async function fetchMemberSessions(credentials, fetchImpl = fetch, options = { windowHours: RECENT_SESSION_WINDOW_MS / 36e5, limit: RECENT_SESSION_PAGE_MAX }) {
95235
95559
  const { windowHours, limit } = recentSessionOptionsSchema.parse(options);
95236
95560
  try {
95237
95561
  const query = new URLSearchParams({ view: "recent", windowHours: String(windowHours), limit: String(limit) });
@@ -95457,7 +95781,7 @@ async function probeClaudeDesktopSessions(wanted, installed, reportFault, root =
95457
95781
 
95458
95782
  // src/menuModel.ts
95459
95783
  import { randomUUID as randomUUID5 } from "node:crypto";
95460
- import { mkdirSync as mkdirSync17, rmSync as rmSync10, watch as fsWatch3 } from "node:fs";
95784
+ import { mkdirSync as mkdirSync17, rmSync as rmSync10, watch as fsWatch4 } from "node:fs";
95461
95785
  import path34 from "node:path";
95462
95786
 
95463
95787
  // src/menu.ts
@@ -95487,7 +95811,7 @@ import { accessSync, constants as constants2, readFileSync as readFileSync15 } f
95487
95811
  import path28 from "node:path";
95488
95812
 
95489
95813
  // src/version.ts
95490
- var CLI_VERSION = true ? "0.4.20" : "0.0.0-checkout";
95814
+ var CLI_VERSION = true ? "0.4.21" : "0.0.0-checkout";
95491
95815
  var CLI_RELEASE_BUILD = true ? true : false;
95492
95816
  var CLI_PACKAGE_NAME = "@usepowerplant/cli";
95493
95817
 
@@ -96177,8 +96501,8 @@ function syncHeader(i) {
96177
96501
  if (i.credentials && (i.credentials.captureMode === "scope" || i.credentials.allowedRepos !== void 0 || i.credentials.scopePaths?.length || (i.credentials.workspacePolicies?.length ?? 0) > 1 || !i.credentials.allowedOwners?.length)) {
96178
96502
  details.push(describeStoredCaptureSummary(i.credentials));
96179
96503
  }
96180
- const lastSyncAt = "status" in i.daemon ? i.daemon.status.lastSyncAt : void 0;
96181
- if (!i.control.paused && (lastSyncAt === void 0 || i.now - Date.parse(lastSyncAt) > 36e5)) {
96504
+ const uploadedAt = lastUploadTime(i);
96505
+ if (!i.control.paused && (uploadedAt === void 0 || i.now - uploadedAt > 36e5)) {
96182
96506
  details.push(lastUploadLine(i));
96183
96507
  }
96184
96508
  return { type: "header", title, ...details.length > 0 && { subtitle: details.join(" \xB7 ") } };
@@ -96186,7 +96510,7 @@ function syncHeader(i) {
96186
96510
  var AGENT_TITLE_MAX = 48;
96187
96511
  function recentAgentItems(i) {
96188
96512
  if (i.memberSessions === null) return null;
96189
- const recent = i.memberSessions.filter((session) => i.now - Date.parse(session.lastActivityAt) < RECENT_SESSION_WINDOW_MS).filter((session) => session.archived !== true && session.subagent !== true && session.standaloneReview !== true).sort((a, b2) => Date.parse(b2.lastActivityAt) - Date.parse(a.lastActivityAt)).map((session) => agentRow(session, i));
96513
+ const recent = i.memberSessions.filter((session) => i.now - Date.parse(session.lastActivityAt) < RECENT_SESSION_WINDOW_MS).filter((session) => session.archived !== true && session.subagent !== true && session.standaloneReview !== true).sort((a, b2) => Date.parse(b2.lastActivityAt) - Date.parse(a.lastActivityAt)).map((session) => agentRow(session, i)).filter((row) => row.agent?.status !== "ended");
96190
96514
  const rows = recent.length === 0 ? [{ type: "status", mark: "muted", title: i.memberSessionsIncomplete ? "No sessions found in the partial list" : "No eligible sessions in the latest results" }] : groupedAgentRows(recent.slice(0, RECENT_SESSION_VISIBLE_LIMIT));
96191
96515
  if (i.memberSessionsIncomplete) rows.push({
96192
96516
  type: "status",
@@ -96214,7 +96538,7 @@ function agentRow(session, i) {
96214
96538
  const harness = isLegacySessionSource(session.source) ? harnessFromLegacySessionSource(session.source) : null;
96215
96539
  const harnessName = harness?.kind === "harness" ? harness.harness : session.source;
96216
96540
  const status = localMachine && harness?.kind === "harness" && i.sessionStatus !== void 0 ? i.sessionStatus(harness.harness, session.externalId) ?? "unknown" : void 0;
96217
- const statusLabel = status === void 0 ? null : LOCAL_SESSION_STATUS_LABELS[status];
96541
+ const statusLabel = status === "waiting" ? LOCAL_SESSION_STATUS_LABELS.waiting : null;
96218
96542
  const desktopSessionId = localMachine && session.source === "claude-code" ? i.claudeDesktopSessions?.get(session.externalId)?.sessionId : void 0;
96219
96543
  const app = desktopSessionId !== void 0 ? "Claude desktop" : isLegacySessionSource(session.source) ? describeSessionSource(session.source) : session.source;
96220
96544
  const links = sessionLinks(session, i.config.webUrl, {
@@ -96298,10 +96622,16 @@ function menuContentKey(payload) {
96298
96622
  });
96299
96623
  return JSON.stringify({ ...shown, desktopDiagnostic: void 0 });
96300
96624
  }
96625
+ function lastUploadTime(i) {
96626
+ if (!("status" in i.daemon)) return void 0;
96627
+ const { lastSyncAt, lastUploadAt } = i.daemon.status;
96628
+ const times = [lastSyncAt, lastUploadAt].map((at) => Date.parse(at ?? "")).filter((at) => !Number.isNaN(at));
96629
+ return times.length === 0 ? void 0 : Math.max(...times);
96630
+ }
96301
96631
  function lastUploadLine(i) {
96302
- const lastSyncAt = "status" in i.daemon ? i.daemon.status.lastSyncAt : void 0;
96303
- if (lastSyncAt === void 0) return "No successful upload yet";
96304
- return `Last upload ${ago(i.now - Date.parse(lastSyncAt))}`;
96632
+ const uploadedAt = lastUploadTime(i);
96633
+ if (uploadedAt === void 0) return "No successful upload yet";
96634
+ return `Last upload ${ago(i.now - uploadedAt)}`;
96305
96635
  }
96306
96636
  function connectionRow(connection, envArgs, now) {
96307
96637
  const name = CLIENT_NAMES[connection.client];
@@ -97894,7 +98224,7 @@ function startMenuModel(sources) {
97894
98224
  const dir = path34.dirname(menuRefreshRequestPath(appEnv, host.home));
97895
98225
  mkdirSync17(dir, { recursive: true, mode: 448 });
97896
98226
  const openFile = path34.basename(menuOpenRequestPath(appEnv, host.home));
97897
- const watcher = fsWatch3(dir, (_event, filename) => onRequest(filename === openFile ? "menu-open" : "command"));
98227
+ const watcher = fsWatch4(dir, (_event, filename) => onRequest(filename === openFile ? "menu-open" : "command"));
97898
98228
  watcher.on("error", onLost);
97899
98229
  return () => watcher.close();
97900
98230
  },
@@ -97960,7 +98290,7 @@ function renderDoctorReport(i) {
97960
98290
  );
97961
98291
  const unsupported = unsupportedVersionNotice(i.cliVersion, i.oldestSupportedVersion);
97962
98292
  if (unsupported !== null) lines.push(unsupported);
97963
- lines.push(`Environment: ${i.config.appEnv} (API ${i.config.apiUrl})`);
98293
+ lines.push(`Environment: ${i.config.appEnv} (API ${i.credentials?.apiUrl ?? i.config.apiUrl})`);
97964
98294
  lines.push(`Node: ${i.nodeVersion} (${i.platform})`);
97965
98295
  lines.push(installLine(i.install));
97966
98296
  lines.push(enrollmentLine(i));
@@ -100093,4 +100423,4 @@ undici/lib/web/websocket/frame.js:
100093
100423
  (*! ws. MIT License. Einar Otto Stangvik <einaros@gmail.com> *)
100094
100424
  */
100095
100425
 
100096
- //# debugId=9f447c4d-0ccb-5f45-8960-6853e28aadfb
100426
+ //# debugId=9ee35b05-7453-5338-85ff-5dc1b6010221