@usepowerplant/cli 0.4.6 → 0.4.7

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
@@ -55700,10 +55700,21 @@ function createDigestTurnParser(options = {}) {
55700
55700
  const turns = [];
55701
55701
  let pendingTools = [];
55702
55702
  let pendingUsage = null;
55703
+ let pendingAt = null;
55703
55704
  let currentId = null;
55704
55705
  let currentCarrier = null;
55705
55706
  let currentBase = null;
55706
55707
  let currentCounted = false;
55708
+ const envelopes = [];
55709
+ let pendingEnvelopes = [];
55710
+ let currentEnvelope = null;
55711
+ const landPending = (turn) => {
55712
+ for (const envelope of pendingEnvelopes) {
55713
+ envelope.turn = turn;
55714
+ envelopes.push(envelope);
55715
+ }
55716
+ pendingEnvelopes = [];
55717
+ };
55707
55718
  return {
55708
55719
  pushLine(line) {
55709
55720
  const trimmed = line.trim();
@@ -55717,14 +55728,16 @@ function createDigestTurnParser(options = {}) {
55717
55728
  return;
55718
55729
  }
55719
55730
  if (record2.isMeta === true) return;
55731
+ const at = recordTime(record2);
55720
55732
  if (record2.type === "user") {
55721
55733
  const text = userText(record2);
55722
- if (text) turns.push({ kind: "human_prompt", text, toolCalls: [], usage: null });
55734
+ if (text) turns.push({ kind: "human_prompt", text, toolCalls: [], usage: null, at });
55723
55735
  return;
55724
55736
  }
55725
55737
  if (record2.type !== "assistant") return;
55726
55738
  const message2 = record2.message;
55727
55739
  if (!message2 || message2.model === "<synthetic>" || !Array.isArray(message2.content)) return;
55740
+ pendingAt ??= at;
55728
55741
  const id = messageIdOf(record2, message2);
55729
55742
  if (id === null || id !== currentId) {
55730
55743
  currentId = id;
@@ -55738,12 +55751,17 @@ function createDigestTurnParser(options = {}) {
55738
55751
  const updated = mergeUsage(currentBase, parsedUsage);
55739
55752
  if (currentCarrier) currentCarrier.usage = updated;
55740
55753
  else pendingUsage = updated;
55754
+ if (currentEnvelope) currentEnvelope.usage = parsedUsage;
55741
55755
  } else if (currentCarrier) {
55742
55756
  currentCarrier.usage = mergeUsage(currentCarrier.usage, parsedUsage);
55743
55757
  currentCounted = true;
55758
+ currentEnvelope = { turn: turns.indexOf(currentCarrier), at, usage: parsedUsage };
55759
+ envelopes.push(currentEnvelope);
55744
55760
  } else {
55745
55761
  pendingUsage = mergeUsage(pendingUsage, parsedUsage);
55746
55762
  currentCounted = true;
55763
+ currentEnvelope = { turn: -1, at, usage: parsedUsage };
55764
+ pendingEnvelopes.push(currentEnvelope);
55747
55765
  }
55748
55766
  }
55749
55767
  const model = typeof message2.model === "string" ? message2.model : null;
@@ -55758,32 +55776,50 @@ function createDigestTurnParser(options = {}) {
55758
55776
  }
55759
55777
  if (part.type === "text" && typeof part.text === "string" && part.text.trim() !== "") {
55760
55778
  if (currentCarrier === null) {
55761
- const turn = { kind: "agent_text", text: part.text, toolCalls: pendingTools, usage: pendingUsage };
55779
+ const turn = {
55780
+ kind: "agent_text",
55781
+ text: part.text,
55782
+ toolCalls: pendingTools,
55783
+ usage: pendingUsage,
55784
+ at: pendingAt
55785
+ };
55762
55786
  turns.push(turn);
55763
55787
  currentCarrier = turn;
55764
55788
  pendingUsage = null;
55789
+ landPending(turns.length - 1);
55765
55790
  } else {
55766
55791
  turns.push({
55767
55792
  kind: "agent_text",
55768
55793
  text: part.text,
55769
55794
  toolCalls: pendingTools,
55770
- usage: currentCounted ? zeroUsage(model) : null
55795
+ usage: currentCounted ? zeroUsage(model) : null,
55796
+ // A sibling block of the same record began when the record did.
55797
+ at: pendingAt ?? at
55771
55798
  });
55772
55799
  }
55773
55800
  pendingTools = [];
55801
+ pendingAt = null;
55774
55802
  }
55775
55803
  }
55776
55804
  },
55777
55805
  finish() {
55778
55806
  if (pendingTools.length > 0 || pendingUsage !== null) {
55779
- turns.push({ kind: "agent_text", text: "", toolCalls: pendingTools, usage: pendingUsage });
55807
+ turns.push({ kind: "agent_text", text: "", toolCalls: pendingTools, usage: pendingUsage, at: pendingAt });
55808
+ landPending(turns.length - 1);
55780
55809
  pendingTools = [];
55781
55810
  pendingUsage = null;
55811
+ pendingAt = null;
55782
55812
  }
55783
55813
  return turns;
55814
+ },
55815
+ usageEnvelopes() {
55816
+ return envelopes;
55784
55817
  }
55785
55818
  };
55786
55819
  }
55820
+ function recordTime(record2) {
55821
+ return typeof record2.timestamp === "string" && record2.timestamp !== "" ? record2.timestamp : null;
55822
+ }
55787
55823
  function isRecord(value) {
55788
55824
  return value !== null && typeof value === "object" && !Array.isArray(value);
55789
55825
  }
@@ -55920,10 +55956,12 @@ function promptText(content) {
55920
55956
  function createCodexTurnParser(options = {}) {
55921
55957
  const turns = [];
55922
55958
  let pendingTools = [];
55959
+ let pendingAt = null;
55923
55960
  const flushPendingTools = () => {
55924
55961
  if (pendingTools.length === 0) return;
55925
- turns.push({ kind: "agent_text", text: "", toolCalls: pendingTools, usage: null });
55962
+ turns.push({ kind: "agent_text", text: "", toolCalls: pendingTools, usage: null, at: pendingAt });
55926
55963
  pendingTools = [];
55964
+ pendingAt = null;
55927
55965
  };
55928
55966
  return {
55929
55967
  pushLine(line) {
@@ -55938,6 +55976,7 @@ function createCodexTurnParser(options = {}) {
55938
55976
  return;
55939
55977
  }
55940
55978
  if (record2.type !== "response_item") return;
55979
+ const at = typeof record2.timestamp === "string" && record2.timestamp !== "" ? record2.timestamp : null;
55941
55980
  const item = record2.payload;
55942
55981
  if (item === null || typeof item !== "object" || Array.isArray(item)) return;
55943
55982
  const responseItem = item;
@@ -55949,6 +55988,7 @@ function createCodexTurnParser(options = {}) {
55949
55988
  if (args !== null) call.input = boundedToolInput(args);
55950
55989
  }
55951
55990
  pendingTools.push(call);
55991
+ pendingAt ??= at;
55952
55992
  return;
55953
55993
  }
55954
55994
  if (responseItem.type !== "message") return;
@@ -55956,15 +55996,16 @@ function createCodexTurnParser(options = {}) {
55956
55996
  const text2 = promptText(responseItem.content);
55957
55997
  if (text2 !== null) {
55958
55998
  flushPendingTools();
55959
- turns.push({ kind: "human_prompt", text: text2, toolCalls: [], usage: null });
55999
+ turns.push({ kind: "human_prompt", text: text2, toolCalls: [], usage: null, at });
55960
56000
  }
55961
56001
  return;
55962
56002
  }
55963
56003
  if (responseItem.role !== "assistant") return;
55964
56004
  const text = outputTextBlocks(responseItem.content).join("\n");
55965
56005
  if (text.trim() === "") return;
55966
- turns.push({ kind: "agent_text", text, toolCalls: pendingTools, usage: null });
56006
+ turns.push({ kind: "agent_text", text, toolCalls: pendingTools, usage: null, at: pendingAt ?? at });
55967
56007
  pendingTools = [];
56008
+ pendingAt = null;
55968
56009
  },
55969
56010
  finish() {
55970
56011
  flushPendingTools();
@@ -55977,10 +56018,12 @@ function createCodexTurnParser(options = {}) {
55977
56018
  function createCursorTurnParser(options = {}) {
55978
56019
  const turns = [];
55979
56020
  let pendingTools = [];
56021
+ let pendingAt = null;
55980
56022
  const flushPendingTools = () => {
55981
56023
  if (pendingTools.length === 0) return;
55982
- turns.push({ kind: "agent_text", text: "", toolCalls: pendingTools, usage: null });
56024
+ turns.push({ kind: "agent_text", text: "", toolCalls: pendingTools, usage: null, at: pendingAt });
55983
56025
  pendingTools = [];
56026
+ pendingAt = null;
55984
56027
  };
55985
56028
  return {
55986
56029
  pushLine(line) {
@@ -55994,9 +56037,10 @@ function createCursorTurnParser(options = {}) {
55994
56037
  } catch {
55995
56038
  return;
55996
56039
  }
56040
+ const at = typeof record2.createdAt === "string" && record2.createdAt !== "" ? record2.createdAt : null;
55997
56041
  if (record2.type === "user" && typeof record2.text === "string" && record2.text.trim() !== "") {
55998
56042
  flushPendingTools();
55999
- turns.push({ kind: "human_prompt", text: record2.text, toolCalls: [], usage: null });
56043
+ turns.push({ kind: "human_prompt", text: record2.text, toolCalls: [], usage: null, at });
56000
56044
  return;
56001
56045
  }
56002
56046
  if (record2.type === "tool" && typeof record2.name === "string") {
@@ -56006,11 +56050,13 @@ function createCursorTurnParser(options = {}) {
56006
56050
  call.input = boundedToolInput(record2.args);
56007
56051
  }
56008
56052
  pendingTools.push(call);
56053
+ pendingAt ??= at;
56009
56054
  return;
56010
56055
  }
56011
56056
  if (record2.type === "agent" && typeof record2.text === "string" && record2.text.trim() !== "") {
56012
- turns.push({ kind: "agent_text", text: record2.text, toolCalls: pendingTools, usage: null });
56057
+ turns.push({ kind: "agent_text", text: record2.text, toolCalls: pendingTools, usage: null, at: pendingAt ?? at });
56013
56058
  pendingTools = [];
56059
+ pendingAt = null;
56014
56060
  }
56015
56061
  },
56016
56062
  finish() {
@@ -56076,7 +56122,7 @@ function parseCursorRunEventsDetailed(runs, options = {}) {
56076
56122
  let text = "";
56077
56123
  const flush2 = () => {
56078
56124
  if (text !== "" || pendingTools.length > 0 || pendingTokens > 0) {
56079
- turns.push({ kind: "agent_text", text, toolCalls: pendingTools, usage: null });
56125
+ turns.push({ kind: "agent_text", text, toolCalls: pendingTools, usage: null, at: null });
56080
56126
  turnTokens.push(pendingTokens);
56081
56127
  }
56082
56128
  text = "";
@@ -56126,7 +56172,7 @@ function parseCursorRunEventsDetailed(runs, options = {}) {
56126
56172
  const prompt = stringField(asRecord3(data?.userMessage), "text");
56127
56173
  if (!prompt) continue;
56128
56174
  if (text !== "") flush2();
56129
- turns.push({ kind: "human_prompt", text: prompt, toolCalls: [], usage: null });
56175
+ turns.push({ kind: "human_prompt", text: prompt, toolCalls: [], usage: null, at: null });
56130
56176
  turnTokens.push(0);
56131
56177
  continue;
56132
56178
  }
@@ -56183,10 +56229,16 @@ function normalizeUrl(raw) {
56183
56229
 
56184
56230
  // ../core/src/sessionTurns.ts
56185
56231
  function parseSessionTurns(source, transcript, options = {}) {
56186
- if (source === "cursor-cloud") return parseCursorRunEvents(envelopeRuns(transcript), options);
56232
+ return parseSessionTurnsDetailed(source, transcript, options).turns;
56233
+ }
56234
+ function parseSessionTurnsDetailed(source, transcript, options = {}) {
56235
+ if (source === "cursor-cloud") {
56236
+ return { turns: parseCursorRunEvents(envelopeRuns(transcript), options), envelopes: [] };
56237
+ }
56187
56238
  const parser = createSessionTurnParser(source, options);
56188
56239
  for (const line of transcript.split("\n")) parser.pushLine(line);
56189
- return parser.finish();
56240
+ const turns = parser.finish();
56241
+ return { turns, envelopes: parser.usageEnvelopes?.() ?? [] };
56190
56242
  }
56191
56243
  function createSessionTurnParser(source, options = {}) {
56192
56244
  if (source === "codex") return createCodexTurnParser(options);
@@ -83371,7 +83423,7 @@ import os6 from "node:os";
83371
83423
  // ../../apps/collector/src/http.ts
83372
83424
  var import_undici = __toESM(require_undici(), 1);
83373
83425
  var CLI_VERSION_HEADER = "x-powerplant-cli-version";
83374
- var cliVersion = true ? "0.4.6" : "0.0.0-checkout";
83426
+ var cliVersion = true ? "0.4.7" : "0.0.0-checkout";
83375
83427
  function cliVersionHeaders() {
83376
83428
  return { [CLI_VERSION_HEADER]: cliVersion, "user-agent": `powerplant-cli/${cliVersion}` };
83377
83429
  }
@@ -85071,6 +85123,7 @@ function readCodexTranscriptHints(home, roots) {
85071
85123
  }
85072
85124
 
85073
85125
  // ../../apps/collector/src/codexCli.ts
85126
+ import { closeSync as closeSync3, mkdirSync as mkdirSync2, openSync as openSync3, renameSync, rmSync, utimesSync, writeSync } from "node:fs";
85074
85127
  import os8 from "node:os";
85075
85128
  import path7 from "node:path";
85076
85129
  import { setImmediate as yieldToEventLoop2 } from "node:timers/promises";
@@ -85145,19 +85198,96 @@ function parseCodexRollout(text) {
85145
85198
  ...typeof git?.repository_url === "string" && repoFullNameFrom(git.repository_url)
85146
85199
  };
85147
85200
  }
85148
- function parseRolloutFile(rolloutPath) {
85149
- const text = readIfExists(rolloutPath);
85150
- return text === null ? null : parseCodexRollout(text);
85151
- }
85201
+ function foldRolloutPages(pages) {
85202
+ const first = (key) => pages.find((page) => page[key] !== void 0)?.[key];
85203
+ const last = (key) => [...pages].reverse().find((page) => page[key] !== void 0)?.[key];
85204
+ const sum2 = (measured2) => measured2.length === 0 ? null : measured2.reduce((total, page) => addCodexTotals(total, page));
85205
+ const measured = pages.map((page) => page.totals).filter((totals) => totals !== null);
85206
+ const measuredFast = pages.filter((page) => page.totals !== null).map((page) => page.fastTotals);
85207
+ const sessionId = first("sessionId");
85208
+ const startedAt = first("startedAt");
85209
+ const endedAt = last("endedAt");
85210
+ const model = last("model");
85211
+ const speedMode = last("speedMode");
85212
+ const cwd = first("cwd");
85213
+ const gitBranch = first("gitBranch");
85214
+ const repoName = first("repoName");
85215
+ const repoFullName = first("repoFullName");
85216
+ return {
85217
+ kind: pages[0].kind,
85218
+ ...sessionId !== void 0 && { sessionId },
85219
+ ...startedAt !== void 0 && { startedAt },
85220
+ ...endedAt !== void 0 && { endedAt },
85221
+ ...model !== void 0 && { model },
85222
+ totals: sum2(measured),
85223
+ fastTotals: measuredFast.every((fast) => fast !== null) ? sum2(measuredFast) : null,
85224
+ ...speedMode !== void 0 && { speedMode },
85225
+ ...cwd !== void 0 && { cwd },
85226
+ ...gitBranch !== void 0 && { gitBranch },
85227
+ ...repoName !== void 0 && { repoName },
85228
+ ...repoFullName !== void 0 && { repoFullName }
85229
+ };
85230
+ }
85231
+ function rolloutIdentity(rolloutPath) {
85232
+ const name = path7.basename(rolloutPath, ".jsonl");
85233
+ const [head, pageId, ...rest] = name.split("_");
85234
+ const threadId2 = head.slice(-36);
85235
+ const conventional = rest.length === 0 && head.startsWith("rollout-") && head.at(-37) === "-" && SESSION_UUID.test(threadId2) && (pageId === void 0 || SESSION_UUID.test(pageId));
85236
+ if (!conventional) return { threadId: name, page: false };
85237
+ return { threadId: threadId2.toLowerCase(), page: pageId !== void 0 };
85238
+ }
85239
+ function codexSpoolDir(home) {
85240
+ return path7.join(home, ".config", "powerplant", "codex");
85241
+ }
85242
+ function mergedSpoolIsFresh(spoolPath, mtimeMs) {
85243
+ const existing = statIfExists(spoolPath);
85244
+ return existing !== null && Math.abs(existing.mtimeMs - mtimeMs) < 1;
85245
+ }
85246
+ var MergedSpoolWriter = class {
85247
+ constructor(spoolPath) {
85248
+ this.spoolPath = spoolPath;
85249
+ mkdirSync2(path7.dirname(spoolPath), { recursive: true, mode: 448 });
85250
+ this.tmpPath = `${spoolPath}.${process.pid}.tmp`;
85251
+ this.fd = openSync3(this.tmpPath, "w", 384);
85252
+ }
85253
+ spoolPath;
85254
+ tmpPath;
85255
+ fd;
85256
+ bytes = 0;
85257
+ append(pageText) {
85258
+ if (this.fd === void 0) throw new Error("merged transcript already closed");
85259
+ const text = pageText.endsWith("\n") ? pageText : `${pageText}
85260
+ `;
85261
+ this.bytes += writeSync(this.fd, text);
85262
+ }
85263
+ /** Stamps the newest page's mtime and publishes the file; returns its size. */
85264
+ finish(mtimeMs) {
85265
+ this.close();
85266
+ utimesSync(this.tmpPath, mtimeMs / 1e3, mtimeMs / 1e3);
85267
+ renameSync(this.tmpPath, this.spoolPath);
85268
+ return this.bytes;
85269
+ }
85270
+ abandon() {
85271
+ this.close();
85272
+ rmSync(this.tmpPath, { force: true });
85273
+ }
85274
+ close() {
85275
+ if (this.fd === void 0) return;
85276
+ closeSync3(this.fd);
85277
+ this.fd = void 0;
85278
+ }
85279
+ };
85152
85280
  function rolloutLocation(rolloutPath) {
85153
85281
  const record2 = readFirstRecord(rolloutPath, (candidate) => candidate.type === "session_meta");
85154
85282
  const meta3 = record2?.payload;
85155
85283
  if (!meta3 || typeof meta3 !== "object" || Array.isArray(meta3)) return { kind: "session", totals: null, fastTotals: null };
85156
85284
  const { cwd, id, session_id: fallbackId } = meta3;
85157
85285
  const sessionId = id ?? fallbackId;
85286
+ const startedAt = record2?.timestamp;
85158
85287
  return {
85159
85288
  kind: isSidecarThread(meta3) ? "subagent" : "session",
85160
85289
  ...typeof sessionId === "string" && { sessionId },
85290
+ ...typeof startedAt === "string" && { startedAt },
85161
85291
  totals: null,
85162
85292
  fastTotals: null,
85163
85293
  ...typeof cwd === "string" && cwd !== "" && { cwd }
@@ -85212,89 +85342,128 @@ async function collectCodexSessions(options = {}) {
85212
85342
  const now = options.now ?? Date.now();
85213
85343
  const capture = options.capture ?? captureEverythingUnresolved;
85214
85344
  const maxBytes = options.maxTranscriptBytes ?? MAX_TRANSCRIPT_FILE_BYTES;
85345
+ const spoolDir = options.spoolDir ?? codexSpoolDir(options.homeDir ?? os8.homedir());
85215
85346
  const items = [];
85216
85347
  const candidates = [];
85217
85348
  const unreadable = [];
85218
85349
  const skips = { skippedOutOfScope: 0, skippedNoRepo: 0 };
85219
85350
  let skippedSubagentThreads = 0;
85220
85351
  let skippedTooLarge = 0;
85352
+ let mergedThreads = 0;
85353
+ let largestMergedBytes = 0;
85221
85354
  const externalIdOf = (rolloutPath) => path7.basename(rolloutPath, ".jsonl");
85222
- const seen = /* @__PURE__ */ new Set();
85223
- const admitFrom = async (files, archived, sessionId) => {
85224
- for (const rolloutPath of files) {
85225
- if (seen.has(rolloutPath)) continue;
85355
+ const files = /* @__PURE__ */ new Map();
85356
+ const observe = async (paths, archived, hintSessionId) => {
85357
+ for (const rolloutPath of paths) {
85358
+ const known = files.get(rolloutPath);
85359
+ if (known !== void 0) {
85360
+ if (hintSessionId === void 0) delete known.hintSessionIds;
85361
+ else known.hintSessionIds?.push(hintSessionId);
85362
+ continue;
85363
+ }
85226
85364
  await yieldToEventLoop2();
85227
85365
  const stat = statIfExists(rolloutPath);
85228
85366
  if (!stat) continue;
85229
- seen.add(rolloutPath);
85230
- if (options.sinceMs !== void 0 && Math.max(stat.mtimeMs, stat.ctimeMs) < options.sinceMs)
85231
- continue;
85232
- if (stat.size > maxBytes) {
85367
+ files.set(rolloutPath, {
85368
+ rolloutPath,
85369
+ archived,
85370
+ identity: rolloutIdentity(rolloutPath),
85371
+ mtimeMs: stat.mtimeMs,
85372
+ changedMs: Math.max(stat.mtimeMs, stat.ctimeMs),
85373
+ sizeBytes: stat.size,
85374
+ ...hintSessionId !== void 0 && { hintSessionIds: [hintSessionId] }
85375
+ });
85376
+ }
85377
+ };
85378
+ const archiveDir = codexArchivedSessionsDir(sessionsDir);
85379
+ const hintHome = options.homeDir ?? (options.codexDir === void 0 ? os8.homedir() : void 0);
85380
+ if (hintHome !== void 0) {
85381
+ for (const hint of readCodexTranscriptHints(hintHome, [sessionsDir, archiveDir])) {
85382
+ await observe([hint.rolloutPath], hint.archived, hint.sessionId);
85383
+ }
85384
+ }
85385
+ await observe(rolloutFiles(sessionsDir), false);
85386
+ await observe(rolloutFiles(archiveDir), true);
85387
+ const byThread = /* @__PURE__ */ new Map();
85388
+ for (const file2 of files.values()) {
85389
+ const thread = byThread.get(file2.identity.threadId);
85390
+ if (thread === void 0) byThread.set(file2.identity.threadId, [file2]);
85391
+ else thread.push(file2);
85392
+ }
85393
+ for (const [threadId2, observed] of byThread) {
85394
+ const stored = dedupeStoreCopies(observed);
85395
+ let externalId = externalIdOf((stored.find((file2) => !file2.identity.page) ?? stored[0]).rolloutPath);
85396
+ try {
85397
+ const spoolPath = stored.length > 1 ? path7.join(spoolDir, `${threadId2}.jsonl`) : void 0;
85398
+ const changedMs = Math.max(...stored.map((file2) => file2.changedMs));
85399
+ if (options.sinceMs !== void 0 && changedMs < options.sinceMs) {
85400
+ const neverMerged = spoolPath !== void 0 && !options.locationsOnly && statIfExists(spoolPath) === null;
85401
+ if (!neverMerged) continue;
85402
+ }
85403
+ await yieldToEventLoop2();
85404
+ const named = stored.filter((file2) => {
85405
+ if (file2.hintSessionIds === void 0) return true;
85406
+ const { sessionId } = rolloutLocation(file2.rolloutPath);
85407
+ return sessionId !== void 0 && file2.hintSessionIds.includes(sessionId);
85408
+ });
85409
+ if (named.length === 0) continue;
85410
+ const threadFiles = named.length > 1 ? orderPages(named) : named;
85411
+ const base = threadFiles[0];
85412
+ externalId = externalIdOf(base.rolloutPath);
85413
+ const mtimeMs = Math.max(...threadFiles.map((file2) => file2.mtimeMs));
85414
+ const sizeBytes = threadFiles.reduce((sum2, file2) => sum2 + file2.sizeBytes, 0);
85415
+ if (sizeBytes > maxBytes) {
85233
85416
  skippedTooLarge += 1;
85234
85417
  observeFileDiagnostic(options.onFileDiagnostic, {
85235
- path: rolloutPath,
85418
+ path: base.rolloutPath,
85236
85419
  reason: "too_large",
85237
- sizeBytes: stat.size
85420
+ sizeBytes
85238
85421
  });
85239
85422
  continue;
85240
85423
  }
85241
- try {
85242
- const location = rolloutLocation(rolloutPath);
85243
- if (sessionId !== void 0 && location.sessionId !== sessionId) {
85244
- seen.delete(rolloutPath);
85245
- continue;
85246
- }
85247
- if (location.kind === "subagent") {
85248
- skippedSubagentThreads += 1;
85249
- continue;
85250
- }
85251
- const decision = captureDecision(location.cwd, capture);
85252
- if (!decision.capture) {
85253
- countCaptureSkip(skips, decision.reason);
85254
- continue;
85255
- }
85256
- if (options.locationsOnly) {
85257
- items.push({
85258
- mtimeMs: stat.mtimeMs,
85259
- transcriptPath: rolloutPath,
85260
- ...location.cwd && { cwd: location.cwd },
85261
- item: {
85262
- source: "codex",
85263
- externalId: externalIdOf(rolloutPath),
85264
- status: now - stat.mtimeMs < ACTIVE_GRACE_MS ? "active" : "inactive",
85265
- archived,
85266
- capturedVia: decision.via
85267
- }
85268
- });
85269
- continue;
85270
- }
85271
- candidates.push({
85272
- rolloutPath,
85273
- archived,
85274
- mtimeMs: stat.mtimeMs,
85424
+ const location = rolloutLocation(base.rolloutPath);
85425
+ if (location.kind === "subagent") {
85426
+ skippedSubagentThreads += 1;
85427
+ continue;
85428
+ }
85429
+ const decision = captureDecision(location.cwd, capture);
85430
+ if (!decision.capture) {
85431
+ countCaptureSkip(skips, decision.reason);
85432
+ continue;
85433
+ }
85434
+ const thread = {
85435
+ externalId,
85436
+ files: threadFiles,
85437
+ ...threadFiles.length > 1 && spoolPath !== void 0 && { spoolPath },
85438
+ mtimeMs,
85439
+ archived: threadFiles.every((file2) => file2.archived)
85440
+ };
85441
+ if (options.locationsOnly) {
85442
+ items.push({
85443
+ mtimeMs,
85444
+ transcriptPath: base.rolloutPath,
85275
85445
  ...location.cwd && { cwd: location.cwd },
85276
- decision
85277
- });
85278
- } catch (error62) {
85279
- unreadable.push(`${externalIdOf(rolloutPath)}: ${describeReadError(error62)}`);
85280
- observeFileDiagnostic(options.onFileDiagnostic, {
85281
- path: rolloutPath,
85282
- reason: "unreadable",
85283
- sizeBytes: null,
85284
- errorCode: diagnosticErrorCode(error62)
85446
+ item: {
85447
+ source: "codex",
85448
+ externalId,
85449
+ status: now - mtimeMs < ACTIVE_GRACE_MS ? "active" : "inactive",
85450
+ archived: thread.archived,
85451
+ capturedVia: decision.via
85452
+ }
85285
85453
  });
85454
+ continue;
85286
85455
  }
85287
- }
85288
- };
85289
- const archiveDir = codexArchivedSessionsDir(sessionsDir);
85290
- const hintHome = options.homeDir ?? (options.codexDir === void 0 ? os8.homedir() : void 0);
85291
- if (hintHome !== void 0) {
85292
- for (const hint of readCodexTranscriptHints(hintHome, [sessionsDir, archiveDir])) {
85293
- await admitFrom([hint.rolloutPath], hint.archived, hint.sessionId);
85456
+ candidates.push({ thread, ...location.cwd && { cwd: location.cwd }, decision });
85457
+ } catch (error62) {
85458
+ unreadable.push(`${externalId}: ${describeReadError(error62)}`);
85459
+ observeFileDiagnostic(options.onFileDiagnostic, {
85460
+ path: stored[0].rolloutPath,
85461
+ reason: "unreadable",
85462
+ sizeBytes: null,
85463
+ errorCode: diagnosticErrorCode(error62)
85464
+ });
85294
85465
  }
85295
85466
  }
85296
- await admitFrom(rolloutFiles(sessionsDir), false);
85297
- await admitFrom(rolloutFiles(archiveDir), true);
85298
85467
  if (options.locationsOnly) {
85299
85468
  return {
85300
85469
  sessions: items.sort((a, b2) => b2.mtimeMs - a.mtimeMs),
@@ -85302,30 +85471,59 @@ async function collectCodexSessions(options = {}) {
85302
85471
  skippedSubagentThreads,
85303
85472
  skippedTooLarge,
85304
85473
  unreadable,
85305
- heldBack: 0
85474
+ heldBack: 0,
85475
+ mergedThreads: 0,
85476
+ largestMergedBytes: 0
85306
85477
  };
85307
85478
  }
85308
- candidates.sort((a, b2) => b2.mtimeMs - a.mtimeMs);
85479
+ candidates.sort((a, b2) => b2.thread.mtimeMs - a.thread.mtimeMs);
85309
85480
  const chosen = options.maxSessions === void 0 ? candidates : candidates.slice(0, options.maxSessions);
85310
85481
  const threadNames = chosen.length > 0 ? loadThreadNames(sessionsDir) : /* @__PURE__ */ new Map();
85311
85482
  for (const candidate of chosen) {
85312
85483
  await yieldToEventLoop2();
85313
- const { rolloutPath, archived, mtimeMs, cwd, decision } = candidate;
85484
+ const { thread, cwd, decision } = candidate;
85485
+ let writer;
85314
85486
  try {
85315
- const summary = parseRolloutFile(rolloutPath);
85316
- if (!summary) continue;
85487
+ const rewrite = thread.spoolPath !== void 0 && !mergedSpoolIsFresh(thread.spoolPath, thread.mtimeMs);
85488
+ if (thread.spoolPath !== void 0 && rewrite) writer = new MergedSpoolWriter(thread.spoolPath);
85489
+ const pages = [];
85490
+ let vanished = false;
85491
+ for (const file2 of thread.files) {
85492
+ const text = readIfExists(file2.rolloutPath);
85493
+ if (text === null) {
85494
+ vanished = true;
85495
+ break;
85496
+ }
85497
+ const page = parseCodexRollout(text);
85498
+ if (page === null) continue;
85499
+ pages.push(page);
85500
+ writer?.append(text);
85501
+ }
85502
+ if (vanished || pages.length === 0) {
85503
+ writer?.abandon();
85504
+ continue;
85505
+ }
85506
+ const summary = foldRolloutPages(pages);
85507
+ let transcriptPath = thread.files[0].rolloutPath;
85508
+ if (thread.spoolPath !== void 0) {
85509
+ const mergedBytes = writer ? writer.finish(thread.mtimeMs) : statIfExists(thread.spoolPath)?.size ?? 0;
85510
+ writer = void 0;
85511
+ transcriptPath = thread.spoolPath;
85512
+ mergedThreads += 1;
85513
+ largestMergedBytes = Math.max(largestMergedBytes, mergedBytes);
85514
+ }
85317
85515
  const repoDir = summary.repoName ?? (cwd ? path7.basename(cwd) : void 0);
85318
85516
  const repoFullName = decision.repoFullName ?? summary.repoFullName;
85319
85517
  const title = summary.sessionId ? threadNames.get(summary.sessionId) : void 0;
85320
85518
  items.push({
85321
- mtimeMs,
85322
- transcriptPath: rolloutPath,
85519
+ mtimeMs: thread.mtimeMs,
85520
+ transcriptPath,
85323
85521
  ...cwd && { cwd },
85324
85522
  item: {
85325
85523
  source: "codex",
85326
- externalId: externalIdOf(rolloutPath),
85327
- status: now - mtimeMs < ACTIVE_GRACE_MS ? "active" : "inactive",
85328
- archived,
85524
+ externalId: thread.externalId,
85525
+ status: now - thread.mtimeMs < ACTIVE_GRACE_MS ? "active" : "inactive",
85526
+ archived: thread.archived,
85329
85527
  ...summary.startedAt && { startedAt: summary.startedAt },
85330
85528
  ...summary.endedAt && { endedAt: summary.endedAt },
85331
85529
  ...summary.model && { model: summary.model },
@@ -85350,9 +85548,10 @@ async function collectCodexSessions(options = {}) {
85350
85548
  }
85351
85549
  });
85352
85550
  } catch (error62) {
85353
- unreadable.push(`${externalIdOf(rolloutPath)}: ${describeReadError(error62)}`);
85551
+ writer?.abandon();
85552
+ unreadable.push(`${thread.externalId}: ${describeReadError(error62)}`);
85354
85553
  observeFileDiagnostic(options.onFileDiagnostic, {
85355
- path: rolloutPath,
85554
+ path: thread.files[0].rolloutPath,
85356
85555
  reason: "unreadable",
85357
85556
  sizeBytes: null,
85358
85557
  errorCode: diagnosticErrorCode(error62)
@@ -85365,15 +85564,32 @@ async function collectCodexSessions(options = {}) {
85365
85564
  skippedSubagentThreads,
85366
85565
  skippedTooLarge,
85367
85566
  unreadable,
85368
- heldBack: candidates.length - chosen.length
85567
+ heldBack: candidates.length - chosen.length,
85568
+ mergedThreads,
85569
+ largestMergedBytes
85369
85570
  };
85370
85571
  }
85572
+ function dedupeStoreCopies(threadFiles) {
85573
+ const byName = /* @__PURE__ */ new Map();
85574
+ for (const file2 of threadFiles) {
85575
+ const name = path7.basename(file2.rolloutPath);
85576
+ const known = byName.get(name);
85577
+ if (known === void 0 || file2.archived) byName.set(name, file2);
85578
+ }
85579
+ return [...byName.values()];
85580
+ }
85581
+ function orderPages(threadFiles) {
85582
+ const orderOf = (file2) => rolloutLocation(file2.rolloutPath).startedAt ?? "";
85583
+ return threadFiles.map((file2) => ({ file: file2, order: orderOf(file2) })).sort(
85584
+ (a, b2) => Number(a.file.identity.page) - Number(b2.file.identity.page) || a.order.localeCompare(b2.order) || a.file.rolloutPath.localeCompare(b2.file.rolloutPath)
85585
+ ).map((entry) => entry.file);
85586
+ }
85371
85587
  function codexThreadCwd(threadId2, sessionsDir = path7.join(os8.homedir(), ".codex", "sessions")) {
85372
85588
  if (!SESSION_UUID.test(threadId2)) return void 0;
85373
- const suffix = `-${threadId2.toLowerCase()}.jsonl`;
85589
+ const wanted = threadId2.toLowerCase();
85374
85590
  for (const root of [sessionsDir, codexArchivedSessionsDir(sessionsDir)]) {
85375
85591
  for (const rolloutPath of rolloutFiles(root)) {
85376
- if (!path7.basename(rolloutPath).toLowerCase().endsWith(suffix)) continue;
85592
+ if (rolloutIdentity(rolloutPath).threadId !== wanted) continue;
85377
85593
  const cwd = rolloutLocation(rolloutPath).cwd;
85378
85594
  if (cwd !== void 0) return cwd;
85379
85595
  }
@@ -85387,7 +85603,7 @@ import path10 from "node:path";
85387
85603
 
85388
85604
  // ../../apps/collector/src/cursorLocal.ts
85389
85605
  import { createHash as createHash4 } from "node:crypto";
85390
- import { existsSync as existsSync2, mkdirSync as mkdirSync3, utimesSync as utimesSync2, writeFileSync as writeFileSync3 } from "node:fs";
85606
+ import { existsSync as existsSync2, mkdirSync as mkdirSync4, utimesSync as utimesSync3, writeFileSync as writeFileSync3 } from "node:fs";
85391
85607
  import os10 from "node:os";
85392
85608
  import path9 from "node:path";
85393
85609
  import { fileURLToPath } from "node:url";
@@ -85395,15 +85611,15 @@ import { fileURLToPath } from "node:url";
85395
85611
  // ../../apps/collector/src/cursorTranscriptFile.ts
85396
85612
  import { createHash as createHash3 } from "node:crypto";
85397
85613
  import {
85398
- closeSync as closeSync3,
85614
+ closeSync as closeSync4,
85399
85615
  existsSync,
85400
85616
  lstatSync as lstatSync2,
85401
- mkdirSync as mkdirSync2,
85402
- openSync as openSync3,
85617
+ mkdirSync as mkdirSync3,
85618
+ openSync as openSync4,
85403
85619
  readSync as readSync3,
85404
85620
  readdirSync as readdirSync3,
85405
85621
  realpathSync as realpathSync2,
85406
- utimesSync,
85622
+ utimesSync as utimesSync2,
85407
85623
  writeFileSync as writeFileSync2
85408
85624
  } from "node:fs";
85409
85625
  import os9 from "node:os";
@@ -85472,7 +85688,7 @@ var hintSchema2 = external_exports.object({
85472
85688
  model: external_exports.string().max(256).optional()
85473
85689
  });
85474
85690
  function readBounded(file2, maxBytes) {
85475
- const fd = openSync3(file2, "r");
85691
+ const fd = openSync4(file2, "r");
85476
85692
  try {
85477
85693
  const chunks = [];
85478
85694
  let bytes = 0;
@@ -85485,7 +85701,7 @@ function readBounded(file2, maxBytes) {
85485
85701
  }
85486
85702
  return null;
85487
85703
  } finally {
85488
- closeSync3(fd);
85704
+ closeSync4(fd);
85489
85705
  }
85490
85706
  }
85491
85707
  function collectCursorTranscriptFiles(options, isDatabaseSession, result, databaseSpoolDir, spoolName) {
@@ -85589,9 +85805,9 @@ function collectCursorTranscriptFiles(options, isDatabaseSession, result, databa
85589
85805
  result.skippedTooLarge++;
85590
85806
  continue;
85591
85807
  }
85592
- mkdirSync2(spoolDir, { recursive: true, mode: 448 });
85808
+ mkdirSync3(spoolDir, { recursive: true, mode: 448 });
85593
85809
  writeFileSync2(spool, body, { mode: 384 });
85594
- utimesSync(spool, stat.atime, stat.mtime);
85810
+ utimesSync2(spool, stat.atime, stat.mtime);
85595
85811
  }
85596
85812
  result.sessions.push({
85597
85813
  cwd,
@@ -85942,13 +86158,13 @@ function collectFromStore(db, userDir, options, result, knownIds) {
85942
86158
  ...transcript.bubblesOmitted > 0 && { bubblesOmitted: transcript.bubblesOmitted }
85943
86159
  };
85944
86160
  if (!spoolDirReady) {
85945
- mkdirSync3(spoolDir, { recursive: true });
86161
+ mkdirSync4(spoolDir, { recursive: true });
85946
86162
  spoolDirReady = true;
85947
86163
  }
85948
86164
  const body = [meta3, ...transcript.lines].map((line) => JSON.stringify(line)).join("\n");
85949
86165
  writeFileSync3(spoolPath, `${body}
85950
86166
  `, "utf8");
85951
- utimesSync2(spoolPath, new Date(lastUpdatedAt), new Date(lastUpdatedAt));
86167
+ utimesSync3(spoolPath, new Date(lastUpdatedAt), new Date(lastUpdatedAt));
85952
86168
  }
85953
86169
  const repoSourceDir = facts.repoPath ?? cwd;
85954
86170
  const startBranch = facts.branches[0];
@@ -86165,7 +86381,7 @@ function allowProtectedRepoDiscovery(config2, found, environment = {}, deps = de
86165
86381
  }
86166
86382
 
86167
86383
  // ../../apps/collector/src/hookInstall.ts
86168
- import { existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync6, rmSync, writeFileSync as writeFileSync4 } from "node:fs";
86384
+ import { existsSync as existsSync3, mkdirSync as mkdirSync5, readFileSync as readFileSync6, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "node:fs";
86169
86385
  import os12 from "node:os";
86170
86386
  import path11 from "node:path";
86171
86387
  import { fileURLToPath as fileURLToPath2 } from "node:url";
@@ -86233,7 +86449,7 @@ function hookSourcePath(script) {
86233
86449
  }
86234
86450
  function installCollectorHooks(homeDir = os12.homedir()) {
86235
86451
  const hookDir = path11.join(homeDir, ".config", "powerplant", "hooks");
86236
- mkdirSync4(hookDir, { recursive: true });
86452
+ mkdirSync5(hookDir, { recursive: true });
86237
86453
  for (const script of HOOK_SCRIPTS) {
86238
86454
  writeFileSync4(path11.join(hookDir, script), readFileSync6(hookSourcePath(script), "utf8"), {
86239
86455
  mode: 493
@@ -86249,12 +86465,12 @@ function installCollectorHooks(homeDir = os12.homedir()) {
86249
86465
  }
86250
86466
  const merged = mergeCollectorHooks(settingsText, (script) => `node "${path11.join(hookDir, script)}"`);
86251
86467
  if (merged.action === "write") {
86252
- mkdirSync4(settingsDir, { recursive: true });
86468
+ mkdirSync5(settingsDir, { recursive: true });
86253
86469
  writeFileSync4(settingsPath, merged.text);
86254
86470
  }
86255
86471
  if (merged.action !== "skip") {
86256
86472
  for (const retired of RETIRED_HOOK_WIRINGS) {
86257
- rmSync(path11.join(hookDir, retired.script), { force: true });
86473
+ rmSync2(path11.join(hookDir, retired.script), { force: true });
86258
86474
  }
86259
86475
  }
86260
86476
  return {
@@ -86266,7 +86482,7 @@ function installCollectorHooks(homeDir = os12.homedir()) {
86266
86482
 
86267
86483
  // ../../apps/collector/src/service.ts
86268
86484
  import { execFileSync as execFileSync3 } from "node:child_process";
86269
- import { chmodSync as chmodSync2, existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync7, rmSync as rmSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "node:fs";
86485
+ import { chmodSync as chmodSync2, existsSync as existsSync4, mkdirSync as mkdirSync6, readFileSync as readFileSync7, rmSync as rmSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "node:fs";
86270
86486
  import os13 from "node:os";
86271
86487
  import path12 from "node:path";
86272
86488
  var RESTART_THROTTLE_SECONDS = 60;
@@ -86346,11 +86562,11 @@ function installService(config2, command) {
86346
86562
  );
86347
86563
  }
86348
86564
  const launcher = launcherPath(config2.appEnv);
86349
- mkdirSync5(path12.dirname(launcher), { recursive: true });
86565
+ mkdirSync6(path12.dirname(launcher), { recursive: true });
86350
86566
  writeFileSync5(launcher, buildLauncherScript(command));
86351
86567
  chmodSync2(launcher, 493);
86352
86568
  const file2 = plistPath(config2.appEnv);
86353
- mkdirSync5(path12.dirname(file2), { recursive: true });
86569
+ mkdirSync6(path12.dirname(file2), { recursive: true });
86354
86570
  writeFileSync5(file2, buildLaunchdPlist(config2.appEnv, installerPathValue()));
86355
86571
  runLaunchctl(["bootstrap", `gui/${uid()}`, file2]);
86356
86572
  return `${file2} (watching for session changes; log: ${logPath(config2.appEnv)})`;
@@ -86388,7 +86604,7 @@ function serviceUpToDate(config2, command) {
86388
86604
  function uninstallService(config2) {
86389
86605
  requireMacos();
86390
86606
  const existed = bootout(config2.appEnv);
86391
- rmSync2(launcherPath(config2.appEnv), { force: true });
86607
+ rmSync3(launcherPath(config2.appEnv), { force: true });
86392
86608
  if (existsSync4(plistPath(config2.appEnv))) {
86393
86609
  unlinkSync2(plistPath(config2.appEnv));
86394
86610
  return true;
@@ -86458,7 +86674,7 @@ function xmlEscape(value) {
86458
86674
  }
86459
86675
 
86460
86676
  // ../../apps/collector/src/status.ts
86461
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync8, renameSync, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "node:fs";
86677
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync8, renameSync as renameSync2, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "node:fs";
86462
86678
  import os14 from "node:os";
86463
86679
  import path13 from "node:path";
86464
86680
  var HEARTBEAT_INTERVAL_MS = 6e4;
@@ -86524,10 +86740,10 @@ function controlFilePath(appEnv, homeDir) {
86524
86740
  return path13.join(collectorStateDir(homeDir), `collector-control-${appEnv}.json`);
86525
86741
  }
86526
86742
  function writeAtomically(file2, payload) {
86527
- mkdirSync6(path13.dirname(file2), { recursive: true });
86743
+ mkdirSync7(path13.dirname(file2), { recursive: true });
86528
86744
  const tmp = `${file2}.${process.pid}.tmp`;
86529
86745
  writeFileSync6(tmp, payload);
86530
- renameSync(tmp, file2);
86746
+ renameSync2(tmp, file2);
86531
86747
  }
86532
86748
  function writeCollectorStatus(appEnv, status, homeDir) {
86533
86749
  writeAtomically(statusFilePath(appEnv, homeDir), JSON.stringify(status, null, 2));
@@ -86568,10 +86784,10 @@ function writeCollectorControl(appEnv, control, homeDir) {
86568
86784
  writeAtomically(controlFilePath(appEnv, homeDir), JSON.stringify(control, null, 2));
86569
86785
  }
86570
86786
  function clearCollectorControl(appEnv, homeDir) {
86571
- rmSync3(controlFilePath(appEnv, homeDir), { force: true });
86787
+ rmSync4(controlFilePath(appEnv, homeDir), { force: true });
86572
86788
  }
86573
86789
  function clearCollectorStatus(appEnv, homeDir) {
86574
- rmSync3(statusFilePath(appEnv, homeDir), { force: true });
86790
+ rmSync4(statusFilePath(appEnv, homeDir), { force: true });
86575
86791
  }
86576
86792
  function describeDaemonState(read, probes = {}) {
86577
86793
  if (read.file === "invalid") return { state: "unreadable" };
@@ -86944,7 +87160,10 @@ async function runSyncPassInternal(config2, options, diagnostic) {
86944
87160
  const active = items.filter((s) => s.status === "active").length;
86945
87161
  const tooLarge = (count2, what) => count2 > 0 ? ` \xB7 ${count2} ${what} skipped (over ${MAX_TRANSCRIPT_FILE_BYTES / (1024 * 1024)}MB, too large to read)` : "";
86946
87162
  log3(
86947
- `Found ${inWindow.length} local session(s) (${claude.sessions.length} claude, ${codex.sessions.length} codex, ${cursor.sessions.length} cursor) ` + describeScanReach(window2, options.narrowSinceMs) + (active > 0 ? ` (${active} still active)` : "") + (skippedOutOfScope > 0 ? ` \xB7 ${skippedOutOfScope} ${describeSkipReason(capture)}` : "") + (skippedNoRepo > 0 ? ` \xB7 ${skippedNoRepo} ${describeNoRepoSkipReason(capture)}` : "") + (codex.skippedSubagentThreads > 0 ? ` \xB7 ${codex.skippedSubagentThreads} codex subagent thread(s) skipped (spend unmeasured)` : "") + (cursor.skippedSubagentComposers > 0 ? ` \xB7 ${cursor.skippedSubagentComposers} cursor subagent thread(s) skipped` : "") + (cursor.skippedNonLocalComposers > 0 ? ` \xB7 ${cursor.skippedNonLocalComposers} cursor session(s) skipped (not local to this machine)` : "") + (cursor.skippedUnreadable > 0 ? ` \xB7 ${cursor.skippedUnreadable} cursor session(s) unreadable, skipped` : "") + tooLarge(claude.skippedTooLarge, "claude transcript(s)") + (claude.unreadable.length > 0 ? ` \xB7 ${claude.unreadable.length} claude transcript(s) unreadable, skipped` : "") + tooLarge(codex.skippedTooLarge, "codex rollout(s)") + (codex.unreadable.length > 0 ? ` \xB7 ${codex.unreadable.length} codex rollout(s) unreadable, skipped` : "") + tooLarge(cursor.skippedTooLarge, "cursor transcript(s)") + (cursor.truncatedTranscripts > 0 ? ` \xB7 ${cursor.truncatedTranscripts} cursor transcript(s) cut at ${MAX_TRANSCRIPT_FILE_BYTES / (1024 * 1024)}MB (the rest of the conversation stays local)` : "") + (heldBack > 0 ? ` \xB7 ${heldBack} older session(s) held back, the first sync backfills the ${DEEP_FIRST_SYNC_MAX_SESSIONS} most recently active` : "") + "."
87163
+ `Found ${inWindow.length} local session(s) (${claude.sessions.length} claude, ${codex.sessions.length} codex, ${cursor.sessions.length} cursor) ` + describeScanReach(window2, options.narrowSinceMs) + (active > 0 ? ` (${active} still active)` : "") + (skippedOutOfScope > 0 ? ` \xB7 ${skippedOutOfScope} ${describeSkipReason(capture)}` : "") + (skippedNoRepo > 0 ? ` \xB7 ${skippedNoRepo} ${describeNoRepoSkipReason(capture)}` : "") + (codex.skippedSubagentThreads > 0 ? ` \xB7 ${codex.skippedSubagentThreads} codex subagent thread(s) skipped (spend unmeasured)` : "") + (cursor.skippedSubagentComposers > 0 ? ` \xB7 ${cursor.skippedSubagentComposers} cursor subagent thread(s) skipped` : "") + (cursor.skippedNonLocalComposers > 0 ? ` \xB7 ${cursor.skippedNonLocalComposers} cursor session(s) skipped (not local to this machine)` : "") + (cursor.skippedUnreadable > 0 ? ` \xB7 ${cursor.skippedUnreadable} cursor session(s) unreadable, skipped` : "") + tooLarge(claude.skippedTooLarge, "claude transcript(s)") + (claude.unreadable.length > 0 ? ` \xB7 ${claude.unreadable.length} claude transcript(s) unreadable, skipped` : "") + tooLarge(codex.skippedTooLarge, "codex rollout(s)") + (codex.unreadable.length > 0 ? ` \xB7 ${codex.unreadable.length} codex rollout(s) unreadable, skipped` : "") + // The measure for POW-79's follow-up: a merged thread re-uploads whole
87164
+ // on every change, so once the largest nears the cap, per-page upload
87165
+ // is due.
87166
+ (codex.mergedThreads > 0 ? ` \xB7 ${codex.mergedThreads} codex thread(s) merged from split rollouts (largest ${(codex.largestMergedBytes / (1024 * 1024)).toFixed(1)}MB of the ${MAX_TRANSCRIPT_FILE_BYTES / (1024 * 1024)}MB cap)` : "") + tooLarge(cursor.skippedTooLarge, "cursor transcript(s)") + (cursor.truncatedTranscripts > 0 ? ` \xB7 ${cursor.truncatedTranscripts} cursor transcript(s) cut at ${MAX_TRANSCRIPT_FILE_BYTES / (1024 * 1024)}MB (the rest of the conversation stays local)` : "") + (heldBack > 0 ? ` \xB7 ${heldBack} older session(s) held back, the first sync backfills the ${DEEP_FIRST_SYNC_MAX_SESSIONS} most recently active` : "") + "."
86948
87167
  );
86949
87168
  for (const failure of claude.unreadable) log3(` claude transcript skipped, ${failure}`);
86950
87169
  for (const failure of codex.unreadable) log3(` codex rollout skipped, ${failure}`);
@@ -87191,10 +87410,10 @@ import path16 from "node:path";
87191
87410
  import {
87192
87411
  existsSync as existsSync5,
87193
87412
  lstatSync as lstatSync3,
87194
- mkdirSync as mkdirSync7,
87413
+ mkdirSync as mkdirSync8,
87195
87414
  readFileSync as readFileSync10,
87196
- renameSync as renameSync2,
87197
- rmSync as rmSync4,
87415
+ renameSync as renameSync3,
87416
+ rmSync as rmSync5,
87198
87417
  writeFileSync as writeFileSync7
87199
87418
  } from "node:fs";
87200
87419
  import path15 from "node:path";
@@ -87275,7 +87494,7 @@ function installProviderHook(provider, home) {
87275
87494
  `${shellQuote2(process.execPath)} ${shellQuote2(scriptPath)}`
87276
87495
  );
87277
87496
  if (merged.action === "skip") return { provider, settings: "skip", detail: merged.reason };
87278
- mkdirSync7(hookDir, { recursive: true });
87497
+ mkdirSync8(hookDir, { recursive: true });
87279
87498
  writeAtomic(scriptPath, readFileSync10(hookSourcePath(script), "utf8"), 448);
87280
87499
  if (merged.action === "write") {
87281
87500
  const current = existsSync5(configPath) ? readFileSync10(configPath, "utf8") : null;
@@ -87293,9 +87512,9 @@ function writeAtomic(file2, text, mode) {
87293
87512
  const temporary = `${file2}.${randomUUID()}.tmp`;
87294
87513
  try {
87295
87514
  writeFileSync7(temporary, text, { mode, flag: "wx" });
87296
- renameSync2(temporary, file2);
87515
+ renameSync3(temporary, file2);
87297
87516
  } finally {
87298
- rmSync4(temporary, { force: true });
87517
+ rmSync5(temporary, { force: true });
87299
87518
  }
87300
87519
  }
87301
87520
  function installProviderHooks(home) {
@@ -87844,7 +88063,7 @@ import os16 from "node:os";
87844
88063
  import path18 from "node:path";
87845
88064
 
87846
88065
  // ../../apps/collector/src/codexReviewFacts.ts
87847
- import { closeSync as closeSync4, lstatSync as lstatSync4, openSync as openSync4, readSync as readSync4, realpathSync as realpathSync3 } from "node:fs";
88066
+ import { closeSync as closeSync5, lstatSync as lstatSync4, openSync as openSync5, readSync as readSync4, realpathSync as realpathSync3 } from "node:fs";
87848
88067
  import path17 from "node:path";
87849
88068
  var pointerSchema = external_exports.object({
87850
88069
  source: external_exports.unknown().optional(),
@@ -87882,7 +88101,7 @@ function readCodexReviewFacts(row, id, home) {
87882
88101
  }
87883
88102
  }
87884
88103
  function readPrefix(file2) {
87885
- const fd = openSync4(file2, "r");
88104
+ const fd = openSync5(file2, "r");
87886
88105
  const buffer = Buffer.alloc(MAX_PREFIX_BYTES);
87887
88106
  let used = 0;
87888
88107
  let first = -1;
@@ -87903,7 +88122,7 @@ function readPrefix(file2) {
87903
88122
  used = end;
87904
88123
  }
87905
88124
  } finally {
87906
- closeSync4(fd);
88125
+ closeSync5(fd);
87907
88126
  }
87908
88127
  if (first < 0 || second < 0) return null;
87909
88128
  try {
@@ -88283,7 +88502,7 @@ async function probeClaudeDesktopSessions(wanted, installed, reportFault, root =
88283
88502
 
88284
88503
  // src/cli.ts
88285
88504
  import { spawn as spawn2, spawnSync as spawnSync5 } from "node:child_process";
88286
- import { existsSync as existsSync12, mkdirSync as mkdirSync11, openSync as openSync6, realpathSync as realpathSync5, rmSync as rmSync6 } from "node:fs";
88505
+ import { existsSync as existsSync12, mkdirSync as mkdirSync12, openSync as openSync7, realpathSync as realpathSync5, rmSync as rmSync7 } from "node:fs";
88287
88506
  import path28 from "node:path";
88288
88507
  import { parseArgs } from "node:util";
88289
88508
 
@@ -88291,7 +88510,7 @@ import { parseArgs } from "node:util";
88291
88510
  import { execFileSync as execFileSync5, spawnSync } from "node:child_process";
88292
88511
  import {
88293
88512
  existsSync as existsSync9,
88294
- mkdirSync as mkdirSync8,
88513
+ mkdirSync as mkdirSync9,
88295
88514
  mkdtempSync,
88296
88515
  readdirSync as readdirSync5,
88297
88516
  readFileSync as readFileSync11,
@@ -89330,7 +89549,7 @@ function realReadTextFileStrict(filePath) {
89330
89549
  }
89331
89550
  }
89332
89551
  function realWriteTextFile(filePath, content) {
89333
- mkdirSync8(path21.dirname(filePath), { recursive: true });
89552
+ mkdirSync9(path21.dirname(filePath), { recursive: true });
89334
89553
  writeFileSync8(filePath, content);
89335
89554
  }
89336
89555
  function codexConfigPath(homeDir = os19.homedir()) {
@@ -89395,7 +89614,7 @@ function realRunInteractive(bin, args, env) {
89395
89614
 
89396
89615
  // src/connections.ts
89397
89616
  import { execFileSync as execFileSync6, spawnSync as spawnSync2 } from "node:child_process";
89398
- import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync9 } from "node:fs";
89617
+ import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "node:fs";
89399
89618
  import path22 from "node:path";
89400
89619
  function probeConnections(clients, deps) {
89401
89620
  const reports = [];
@@ -89524,7 +89743,7 @@ function detectedClientsPath(homeDir) {
89524
89743
  function saveDetectedClients(clients, pathValue, homeDir) {
89525
89744
  try {
89526
89745
  const file2 = detectedClientsPath(homeDir);
89527
- mkdirSync9(path22.dirname(file2), { recursive: true });
89746
+ mkdirSync10(path22.dirname(file2), { recursive: true });
89528
89747
  const record2 = {
89529
89748
  detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
89530
89749
  path: pathValue,
@@ -89608,7 +89827,7 @@ function whichAllWith(pathValue) {
89608
89827
  }
89609
89828
 
89610
89829
  // src/doctor.ts
89611
- import { closeSync as closeSync5, fstatSync, openSync as openSync5, readSync as readSync5 } from "node:fs";
89830
+ import { closeSync as closeSync6, fstatSync, openSync as openSync6, readSync as readSync5 } from "node:fs";
89612
89831
 
89613
89832
  // src/update.ts
89614
89833
  import path24 from "node:path";
@@ -89633,7 +89852,7 @@ import { accessSync, constants as constants2 } from "node:fs";
89633
89852
  import path23 from "node:path";
89634
89853
 
89635
89854
  // src/version.ts
89636
- var CLI_VERSION = true ? "0.4.6" : "0.0.0-checkout";
89855
+ var CLI_VERSION = true ? "0.4.7" : "0.0.0-checkout";
89637
89856
  var CLI_RELEASE_BUILD = true ? true : false;
89638
89857
  var CLI_PACKAGE_NAME = "@usepowerplant/cli";
89639
89858
 
@@ -89937,7 +90156,7 @@ function redactedLogTail(raw) {
89937
90156
  function readDoctorLogTail(path29) {
89938
90157
  let fd;
89939
90158
  try {
89940
- fd = openSync5(path29, "r");
90159
+ fd = openSync6(path29, "r");
89941
90160
  } catch (error62) {
89942
90161
  if (error62.code === "ENOENT") return { path: path29, lines: null };
89943
90162
  return { path: path29, lines: null, readError: error62 instanceof Error ? error62.message : String(error62) };
@@ -89953,7 +90172,7 @@ function readDoctorLogTail(path29) {
89953
90172
  } catch (error62) {
89954
90173
  return { path: path29, lines: null, readError: error62 instanceof Error ? error62.message : String(error62) };
89955
90174
  } finally {
89956
- closeSync5(fd);
90175
+ closeSync6(fd);
89957
90176
  }
89958
90177
  }
89959
90178
  function renderDoctorReport(i) {
@@ -90489,6 +90708,8 @@ function renderMenu(i) {
90489
90708
  }
90490
90709
  if (items.length > 0) items.push({ type: "separator" });
90491
90710
  const progress = updateProgressView(i.update.progress, i.cliVersion, i.now);
90711
+ let updateStart;
90712
+ let updateCount = 0;
90492
90713
  const unsupported = isUnsupportedVersion(i.cliVersion, i.oldestSupportedVersion);
90493
90714
  if (unsupported) {
90494
90715
  items.push(
@@ -90497,7 +90718,9 @@ function renderMenu(i) {
90497
90718
  `You have ${i.cliVersion}; the oldest supported is ${i.oldestSupportedVersion}. Update now.`
90498
90719
  )
90499
90720
  );
90721
+ updateStart ??= items.length;
90500
90722
  items.push(...updateItems(i, progress, envArgs, problem));
90723
+ updateCount = items.length - updateStart;
90501
90724
  items.push({ type: "separator" });
90502
90725
  }
90503
90726
  if (i.credentialsError !== void 0) {
@@ -90530,6 +90753,7 @@ function renderMenu(i) {
90530
90753
  }
90531
90754
  if (!unsupported && (isUpdateAvailable(i.cliVersion, i.latestVersion) || progress.kind !== "none")) {
90532
90755
  items.push({ type: "separator" });
90756
+ updateStart = items.length;
90533
90757
  if (progress.kind === "none") {
90534
90758
  items.push({
90535
90759
  type: "status",
@@ -90539,6 +90763,7 @@ function renderMenu(i) {
90539
90763
  });
90540
90764
  }
90541
90765
  items.push(...updateItems(i, progress, envArgs, problem));
90766
+ updateCount = items.length - updateStart;
90542
90767
  }
90543
90768
  items.push({ type: "separator" });
90544
90769
  items.push({ type: "action", title: "Open Powerplant", url: i.config.webUrl });
@@ -90548,6 +90773,17 @@ function renderMenu(i) {
90548
90773
  const icon = attention ? "attention" : i.control.paused ? "paused" : "running";
90549
90774
  return {
90550
90775
  contract: MENU_CONTRACT,
90776
+ ...i.update.inPlace && updateStart !== void 0 && {
90777
+ cliUpdate: {
90778
+ startIndex: updateStart,
90779
+ itemCount: updateCount,
90780
+ state: progress.kind === "none" ? "available" : progress.kind,
90781
+ target: progress.kind === "none" ? i.latestVersion : progress.target,
90782
+ ...progress.kind === "failed" && { detail: progress.detail },
90783
+ run: ["update", "--background", ...envArgs],
90784
+ logPath: updateLogPath()
90785
+ }
90786
+ },
90551
90787
  ...i.desktopDiagnostic && { desktopDiagnostic: i.desktopDiagnostic },
90552
90788
  appEnv: i.config.appEnv,
90553
90789
  cliVersion: i.cliVersion,
@@ -91315,7 +91551,7 @@ function createTerminalOutput(options) {
91315
91551
  }
91316
91552
 
91317
91553
  // src/menubarApp.ts
91318
- import { existsSync as existsSync11, mkdirSync as mkdirSync10, renameSync as renameSync3, rmSync as rmSync5 } from "node:fs";
91554
+ import { existsSync as existsSync11, mkdirSync as mkdirSync11, renameSync as renameSync4, rmSync as rmSync6 } from "node:fs";
91319
91555
  import os21 from "node:os";
91320
91556
  import path27 from "node:path";
91321
91557
  import { spawnSync as spawnSync4 } from "node:child_process";
@@ -91383,11 +91619,11 @@ function realMenubarAppOps(input2) {
91383
91619
  },
91384
91620
  replace: (zip, appPath) => {
91385
91621
  const parent = path27.dirname(appPath);
91386
- mkdirSync10(parent, { recursive: true });
91622
+ mkdirSync11(parent, { recursive: true });
91387
91623
  const staging = path27.join(parent, `.${MENUBAR_APP_NAME}.swap-${process.pid}`);
91388
91624
  const retired = path27.join(parent, `.${MENUBAR_APP_NAME}.old-${process.pid}`);
91389
- rmSync5(staging, { recursive: true, force: true });
91390
- rmSync5(retired, { recursive: true, force: true });
91625
+ rmSync6(staging, { recursive: true, force: true });
91626
+ rmSync6(retired, { recursive: true, force: true });
91391
91627
  try {
91392
91628
  const extract = spawnSync4("ditto", ["-xk", zip, staging]);
91393
91629
  if (extract.status !== 0) {
@@ -91400,16 +91636,16 @@ function realMenubarAppOps(input2) {
91400
91636
  throw new Error(`${zip} does not contain ${MENUBAR_APP_NAME}`);
91401
91637
  }
91402
91638
  const hadOld = existsSync11(appPath);
91403
- if (hadOld) renameSync3(appPath, retired);
91639
+ if (hadOld) renameSync4(appPath, retired);
91404
91640
  try {
91405
- renameSync3(extracted, appPath);
91641
+ renameSync4(extracted, appPath);
91406
91642
  } catch (error62) {
91407
- if (hadOld) renameSync3(retired, appPath);
91643
+ if (hadOld) renameSync4(retired, appPath);
91408
91644
  throw error62;
91409
91645
  }
91410
91646
  } finally {
91411
- rmSync5(staging, { recursive: true, force: true });
91412
- rmSync5(retired, { recursive: true, force: true });
91647
+ rmSync6(staging, { recursive: true, force: true });
91648
+ rmSync6(retired, { recursive: true, force: true });
91413
91649
  }
91414
91650
  },
91415
91651
  launch: (appPath) => {
@@ -92139,8 +92375,8 @@ function startBackgroundUpdate(config2, target) {
92139
92375
  readProgress: updateProgress.read,
92140
92376
  writeProgress: updateProgress.write,
92141
92377
  spawnChild: () => {
92142
- mkdirSync11(path28.dirname(updateLogPath()), { recursive: true });
92143
- const log3 = openSync6(updateLogPath(), "a");
92378
+ mkdirSync12(path28.dirname(updateLogPath()), { recursive: true });
92379
+ const log3 = openSync7(updateLogPath(), "a");
92144
92380
  const child = spawn2(process.execPath, [entryPath(), "update", ...envArgsFor(config2)], {
92145
92381
  detached: true,
92146
92382
  stdio: ["ignore", log3, log3],
@@ -92202,7 +92438,7 @@ function restartSyncOnNewCode(config2) {
92202
92438
  var updateProgress = {
92203
92439
  read: () => readUpdateProgress(realReadTextFile),
92204
92440
  write: (record2) => realWriteTextFile(updateProgressPath(), JSON.stringify(record2, null, 2)),
92205
- clear: () => rmSync6(updateProgressPath(), { force: true })
92441
+ clear: () => rmSync7(updateProgressPath(), { force: true })
92206
92442
  };
92207
92443
  function envArgsFor(config2) {
92208
92444
  return config2.appEnv === "production" ? [] : ["--env", config2.appEnv];
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usepowerplant/cli",
3
- "version": "0.4.6",
3
+ "version": "0.4.7",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },