@usepowerplant/cli 0.4.20 → 0.4.22

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]="91b4b5e6-fdd5-5127-9372-913ad2bc7af5")}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
  }
@@ -56051,7 +56051,7 @@ function visit(schema, fnOrHandlers) {
56051
56051
  return h ? h(node3, rewritten) : node3;
56052
56052
  };
56053
56053
  const cache = /* @__PURE__ */ new Map();
56054
- function run(s) {
56054
+ function run2(s) {
56055
56055
  const cached3 = cache.get(s);
56056
56056
  if (cached3 === RESOLVING) {
56057
56057
  return new $ZodLazy({
@@ -56077,21 +56077,21 @@ function visit(schema, fnOrHandlers) {
56077
56077
  let changed = false;
56078
56078
  const newShape = {};
56079
56079
  for (const k2 of keys) {
56080
- const mapped = run(oldShape[k2]);
56080
+ const mapped = run2(oldShape[k2]);
56081
56081
  if (mapped !== oldShape[k2])
56082
56082
  changed = true;
56083
56083
  newShape[k2] = mapped;
56084
56084
  }
56085
56085
  let newCatchall = def.catchall;
56086
56086
  if (def.catchall) {
56087
- newCatchall = run(def.catchall);
56087
+ newCatchall = run2(def.catchall);
56088
56088
  if (newCatchall !== def.catchall)
56089
56089
  changed = true;
56090
56090
  }
56091
56091
  return changed ? clone(s, { ...def, shape: newShape, catchall: newCatchall }) : s;
56092
56092
  }
56093
56093
  case "array": {
56094
- const mapped = run(def.element);
56094
+ const mapped = run2(def.element);
56095
56095
  return mapped === def.element ? s : clone(s, { ...def, element: mapped });
56096
56096
  }
56097
56097
  case "tuple": {
@@ -56099,14 +56099,14 @@ function visit(schema, fnOrHandlers) {
56099
56099
  let changed = false;
56100
56100
  const newItems = [];
56101
56101
  for (const item of oldItems) {
56102
- const mapped = run(item);
56102
+ const mapped = run2(item);
56103
56103
  if (mapped !== item)
56104
56104
  changed = true;
56105
56105
  newItems.push(mapped);
56106
56106
  }
56107
56107
  let newRest = def.rest;
56108
56108
  if (def.rest) {
56109
- newRest = run(def.rest);
56109
+ newRest = run2(def.rest);
56110
56110
  if (newRest !== def.rest)
56111
56111
  changed = true;
56112
56112
  }
@@ -56114,12 +56114,12 @@ function visit(schema, fnOrHandlers) {
56114
56114
  }
56115
56115
  case "record":
56116
56116
  case "map": {
56117
- const newKey = run(def.keyType);
56118
- const newVal = run(def.valueType);
56117
+ const newKey = run2(def.keyType);
56118
+ const newVal = run2(def.valueType);
56119
56119
  return newKey === def.keyType && newVal === def.valueType ? s : clone(s, { ...def, keyType: newKey, valueType: newVal });
56120
56120
  }
56121
56121
  case "set": {
56122
- const newVal = run(def.valueType);
56122
+ const newVal = run2(def.valueType);
56123
56123
  return newVal === def.valueType ? s : clone(s, { ...def, valueType: newVal });
56124
56124
  }
56125
56125
  case "union": {
@@ -56127,7 +56127,7 @@ function visit(schema, fnOrHandlers) {
56127
56127
  let changed = false;
56128
56128
  const newOptions = [];
56129
56129
  for (const opt of oldOptions) {
56130
- const mapped = run(opt);
56130
+ const mapped = run2(opt);
56131
56131
  if (mapped !== opt)
56132
56132
  changed = true;
56133
56133
  newOptions.push(mapped);
@@ -56135,8 +56135,8 @@ function visit(schema, fnOrHandlers) {
56135
56135
  return changed ? clone(s, { ...def, options: newOptions }) : s;
56136
56136
  }
56137
56137
  case "intersection": {
56138
- const newLeft = run(def.left);
56139
- const newRight = run(def.right);
56138
+ const newLeft = run2(def.left);
56139
+ const newRight = run2(def.right);
56140
56140
  return newLeft === def.left && newRight === def.right ? s : clone(s, { ...def, left: newLeft, right: newRight });
56141
56141
  }
56142
56142
  case "optional":
@@ -56148,23 +56148,23 @@ function visit(schema, fnOrHandlers) {
56148
56148
  case "nonoptional":
56149
56149
  case "promise":
56150
56150
  case "success": {
56151
- const newInner = run(def.innerType);
56151
+ const newInner = run2(def.innerType);
56152
56152
  return newInner === def.innerType ? s : clone(s, { ...def, innerType: newInner });
56153
56153
  }
56154
56154
  case "pipe": {
56155
- const newIn = run(def.in);
56156
- const newOut = run(def.out);
56155
+ const newIn = run2(def.in);
56156
+ const newOut = run2(def.out);
56157
56157
  return newIn === def.in && newOut === def.out ? s : clone(s, { ...def, in: newIn, out: newOut });
56158
56158
  }
56159
56159
  case "function": {
56160
- const newInput = run(def.input);
56161
- const newOutput = run(def.output);
56160
+ const newInput = run2(def.input);
56161
+ const newOutput = run2(def.output);
56162
56162
  return newInput === def.input && newOutput === def.output ? s : clone(s, { ...def, input: newInput, output: newOutput });
56163
56163
  }
56164
56164
  case "lazy": {
56165
56165
  const original = def.getter;
56166
56166
  const { _cachedInner, ...rest } = def;
56167
- return clone(s, { ...rest, getter: () => run(original()) });
56167
+ return clone(s, { ...rest, getter: () => run2(original()) });
56168
56168
  }
56169
56169
  // A leaf by choice: `parts` are regex fragments, not data positions.
56170
56170
  case "template_literal":
@@ -56195,7 +56195,7 @@ function visit(schema, fnOrHandlers) {
56195
56195
  }
56196
56196
  }
56197
56197
  }
56198
- return run(schema);
56198
+ return run2(schema);
56199
56199
  }
56200
56200
 
56201
56201
  // ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/classic/deep-partial.js
@@ -56630,9 +56630,9 @@ function isImageBase64Payload(record2, key) {
56630
56630
  if (key === "base64") return isEncodedImage(data, record2.type);
56631
56631
  return false;
56632
56632
  }
56633
- function describeImageBlock(block) {
56634
- if (block.type !== "image") return null;
56635
- const source = asRecord(block.source);
56633
+ function describeImageBlock(block2) {
56634
+ if (block2.type !== "image") return null;
56635
+ const source = asRecord(block2.source);
56636
56636
  const mediaType = typeof source?.media_type === "string" ? source.media_type : null;
56637
56637
  if (source?.type === "base64" && typeof source.data === "string") {
56638
56638
  return { mediaType, bytes: base64ByteLength(source.data) };
@@ -56805,7 +56805,7 @@ function jsonTokens(text) {
56805
56805
  let backslashes = 0;
56806
56806
  while (text[index - backslashes - 1] === "\\") backslashes += 1;
56807
56807
  let level = 0;
56808
- for (let run = backslashes + 1; run % 2 === 0; run /= 2) level += 1;
56808
+ for (let run2 = backslashes + 1; run2 % 2 === 0; run2 /= 2) level += 1;
56809
56809
  tokens.push({ index, level });
56810
56810
  }
56811
56811
  return tokens;
@@ -57641,8 +57641,8 @@ function createDigestTurnParser(options = {}) {
57641
57641
  }
57642
57642
  const model = typeof message2.model === "string" ? message2.model : null;
57643
57643
  const facts = originFacts(record2);
57644
- for (const block of message2.content) {
57645
- const part = block;
57644
+ for (const block2 of message2.content) {
57645
+ const part = block2;
57646
57646
  if (part.type === "tool_use" && typeof part.name === "string") {
57647
57647
  const call = { name: part.name, ...toolFilePath(part.input) };
57648
57648
  if (options.keepToolInputs === true && isRecord(part.input)) {
@@ -57757,19 +57757,19 @@ function toolResults(record2, evidence, tasks, line) {
57757
57757
  const result = isRecord(record2.toolUseResult) ? record2.toolUseResult : {};
57758
57758
  const file2 = isRecord(result.file) ? result.file : {};
57759
57759
  const task = isRecord(result.task) ? result.task : {};
57760
- for (const block of content) {
57761
- if (!isRecord(block) || block.type !== "tool_result" || typeof block.tool_use_id !== "string") continue;
57762
- if (!evidence.watching(block.tool_use_id)) continue;
57763
- const reads = tasks.reads.get(block.tool_use_id);
57764
- tasks.reads.delete(block.tool_use_id);
57760
+ for (const block2 of content) {
57761
+ if (!isRecord(block2) || block2.type !== "tool_result" || typeof block2.tool_use_id !== "string") continue;
57762
+ if (!evidence.watching(block2.tool_use_id)) continue;
57763
+ const reads = tasks.reads.get(block2.tool_use_id);
57764
+ tasks.reads.delete(block2.tool_use_id);
57765
57765
  const started = typeof result.backgroundTaskId === "string" ? result.backgroundTaskId : null;
57766
57766
  if (started !== null) keepBounded(tasks.started, started, reads ?? started);
57767
- const failed2 = block.is_error === true || result.interrupted === true;
57767
+ const failed2 = block2.is_error === true || result.interrupted === true;
57768
57768
  const taskExited = typeof task.exitCode === "number" && task.task_id === reads;
57769
57769
  const status = reads !== void 0 ? { succeeded: taskExited ? task.exitCode === 0 : null, running: taskExited ? null : reads } : { succeeded: failed2 ? false : started !== null ? null : true, running: started };
57770
- evidence.result(block.tool_use_id, {
57770
+ evidence.result(block2.tool_use_id, {
57771
57771
  ...status,
57772
- text: [result.stdout, result.stderr, file2.content, task.output].filter((part) => typeof part === "string").concat(contentText(block.content)).join("\n"),
57772
+ text: [result.stdout, result.stderr, file2.content, task.output].filter((part) => typeof part === "string").concat(contentText(block2.content)).join("\n"),
57773
57773
  at: recordTime(record2),
57774
57774
  line
57775
57775
  });
@@ -57778,7 +57778,7 @@ function toolResults(record2, evidence, tasks, line) {
57778
57778
  function contentText(content) {
57779
57779
  if (typeof content === "string") return content;
57780
57780
  if (!Array.isArray(content)) return "";
57781
- return content.map((block) => isRecord(block) && typeof block.text === "string" ? block.text : "").join("\n");
57781
+ return content.map((block2) => isRecord(block2) && typeof block2.text === "string" ? block2.text : "").join("\n");
57782
57782
  }
57783
57783
  function messageIdOf(record2, message2) {
57784
57784
  if (typeof message2.id === "string") return message2.id;
@@ -57845,13 +57845,13 @@ function userText(record2) {
57845
57845
  if (typeof content === "string") return content.trim() === "" ? null : content;
57846
57846
  if (!Array.isArray(content)) return null;
57847
57847
  const parts = [];
57848
- for (const block of content) {
57849
- if (!isRecord(block)) continue;
57850
- if (block.type === "text" && typeof block.text === "string" && block.text.trim() !== "") {
57851
- parts.push(block.text);
57848
+ for (const block2 of content) {
57849
+ if (!isRecord(block2)) continue;
57850
+ if (block2.type === "text" && typeof block2.text === "string" && block2.text.trim() !== "") {
57851
+ parts.push(block2.text);
57852
57852
  continue;
57853
57853
  }
57854
- const image = describeImageBlock(block);
57854
+ const image = describeImageBlock(block2);
57855
57855
  if (image) parts.push(imageLabel(image));
57856
57856
  }
57857
57857
  return parts.length > 0 ? parts.join("\n") : null;
@@ -58374,6 +58374,7 @@ var MACHINE_ENROLL_CALLBACK_URL = `http://127.0.0.1:${MACHINE_ENROLL_CALLBACK_PO
58374
58374
 
58375
58375
  // ../core/src/sessionRooms.ts
58376
58376
  var ROOM_QUIET_AFTER_MS = 7 * 24 * 36e5;
58377
+ var RECENT_ACTIVITY_WINDOW_MS = 5 * 24 * 36e5;
58377
58378
  function capturedSessionEvents(turns, anchor2) {
58378
58379
  const events = [];
58379
58380
  const base = {
@@ -58459,22 +58460,22 @@ function safeJsonRecord(text) {
58459
58460
  function outputTextBlocks(content) {
58460
58461
  if (!Array.isArray(content)) return [];
58461
58462
  return content.filter(
58462
- (block) => typeof block === "object" && block !== null && block.type === "output_text" && typeof block.text === "string"
58463
- ).map((block) => block.text);
58463
+ (block2) => typeof block2 === "object" && block2 !== null && block2.type === "output_text" && typeof block2.text === "string"
58464
+ ).map((block2) => block2.text);
58464
58465
  }
58465
58466
  function promptText(content) {
58466
58467
  if (!Array.isArray(content)) return null;
58467
58468
  const kept = [];
58468
58469
  const textOrigins = [];
58469
58470
  for (const raw of content) {
58470
- const block = asRecord2(raw);
58471
- if (!block) continue;
58472
- if (block.type === "input_image") {
58473
- kept.push(imageLabel(describeImageUrl(block.image_url)));
58471
+ const block2 = asRecord2(raw);
58472
+ if (!block2) continue;
58473
+ if (block2.type === "input_image") {
58474
+ kept.push(imageLabel(describeImageUrl(block2.image_url)));
58474
58475
  continue;
58475
58476
  }
58476
- if (block.type !== "input_text" || typeof block.text !== "string") continue;
58477
- const parsed = codexPromptBlock(block.text);
58477
+ if (block2.type !== "input_text" || typeof block2.text !== "string") continue;
58478
+ const parsed = codexPromptBlock(block2.text);
58478
58479
  if (parsed.origin === "harness") continue;
58479
58480
  textOrigins.push(parsed.origin);
58480
58481
  if (parsed.ask !== "") kept.push(parsed.ask);
@@ -58689,8 +58690,8 @@ function scriptResult(evidence, script, header, blocks, at, line) {
58689
58690
  function outputBlocks(output2) {
58690
58691
  if (typeof output2 === "string") return [output2];
58691
58692
  if (!Array.isArray(output2)) return [];
58692
- return output2.map((block) => {
58693
- const text = asRecord2(block)?.text;
58693
+ return output2.map((block2) => {
58694
+ const text = asRecord2(block2)?.text;
58694
58695
  return typeof text === "string" ? text : "";
58695
58696
  });
58696
58697
  }
@@ -58818,9 +58819,9 @@ function parseCursorRunEventsDetailed(runs, options = {}) {
58818
58819
  pendingTools = [];
58819
58820
  pendingTokens = 0;
58820
58821
  };
58821
- for (const run of runs) {
58822
+ for (const run2 of runs) {
58822
58823
  const openCalls = /* @__PURE__ */ new Map();
58823
- for (const raw of run.events) {
58824
+ for (const raw of run2.events) {
58824
58825
  const evt = asRawEvent(raw);
58825
58826
  if (!evt) continue;
58826
58827
  if (evt.event === "tool_call") {
@@ -59110,9 +59111,9 @@ function envelopeRuns(transcript) {
59110
59111
  if (!parsed || !Array.isArray(parsed.runs)) return [];
59111
59112
  const runs = [];
59112
59113
  for (const item of parsed.runs) {
59113
- const run = item;
59114
- if (typeof run?.runId !== "string" || typeof run.sse !== "string") continue;
59115
- runs.push({ runId: run.runId, events: redactCursorRunEvents(parseSseEvents(run.sse), { servedOnly: true }) });
59114
+ const run2 = item;
59115
+ if (typeof run2?.runId !== "string" || typeof run2.sse !== "string") continue;
59116
+ runs.push({ runId: run2.runId, events: redactCursorRunEvents(parseSseEvents(run2.sse), { servedOnly: true }) });
59116
59117
  }
59117
59118
  return runs;
59118
59119
  }
@@ -59131,30 +59132,104 @@ var SECRET_QUERY_PARAM_PATTERN = /^(?:token|access_token|refresh_token|id_token|
59131
59132
  var BEARER_TOKEN_PATTERN = /\bBearer\s+[\w\-._~+/=]+/gi;
59132
59133
  var JWT_PATTERN = /\b[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g;
59133
59134
  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"]);
59135
+ var URL_USER_INFO_PATTERN = /(:\/\/)(?:[^\s/?#@"<>:[]+:(?!\d*(?:[/?#\s"<>]|$))[^\s"<>]{0,256}|[^\s/?#"<>]+)@/g;
59136
+ var URL_FIELD_NAMES = /* @__PURE__ */ new Set([
59137
+ "url",
59138
+ "to",
59139
+ "from",
59140
+ "href",
59141
+ "request_url",
59142
+ "referer",
59143
+ "url.full",
59144
+ "http.url",
59145
+ "http.target",
59146
+ "url.path"
59147
+ ]);
59148
+ var KEPT_HEADER_NAMES = /* @__PURE__ */ new Set([
59149
+ "accept",
59150
+ "content-encoding",
59151
+ "content-length",
59152
+ "content-type",
59153
+ "host",
59154
+ "mcp-protocol-version",
59155
+ "referer",
59156
+ "user-agent",
59157
+ "x-github-event",
59158
+ "x-powerplant-cli-version"
59159
+ ]);
59160
+ var KEPT_HTTP_ATTRIBUTES = /* @__PURE__ */ new Set([
59161
+ "http.method",
59162
+ "http.request.method",
59163
+ "http.request.method_original",
59164
+ "http.route",
59165
+ "http.status_code",
59166
+ "http.status_text",
59167
+ "http.response.status_code",
59168
+ "http.scheme",
59169
+ "http.host",
59170
+ "http.flavor",
59171
+ "http.request_content_length_uncompressed",
59172
+ "url.scheme",
59173
+ "url.full",
59174
+ "http.url",
59175
+ "http.target",
59176
+ "url.path"
59177
+ ]);
59178
+ var HTTP_ATTRIBUTE_PREFIX = /^(?:http|url)\./;
59179
+ var URL_PARTS = /^([a-z][a-z\d+.-]*:\/\/)([^/?#]*)([^?#]*)/i;
59180
+ var PATH_KEPT_HOSTS = /(?:^|\.)(?:supabase\.co|usepowerplant\.com)$/i;
59181
+ var OUTGOING_ROUTES = [
59182
+ // GitHub REST API
59183
+ "/app/installations/{installation_id}/access_tokens",
59184
+ "/app/installations/{installation_id}",
59185
+ "/installation/repositories",
59186
+ "/orgs/{org}/memberships/{username}",
59187
+ "/repos/{owner}/{repo}/contents/{path}",
59188
+ "/user/installations",
59189
+ "/user/{account_id}",
59190
+ // GitHub sign-in
59191
+ "/login/oauth/access_token",
59192
+ // Cursor API
59193
+ "/v1/agents",
59194
+ "/v1/agents/{agent_id}",
59195
+ "/v1/agents/{agent_id}/runs",
59196
+ "/v1/agents/{agent_id}/runs/{run_id}",
59197
+ "/v1/agents/{agent_id}/runs/{run_id}/stream",
59198
+ "/teams/filtered-usage-events"
59199
+ ];
59200
+ var OUTGOING_URL_FIELD_NAMES = ["url", "url.full", "http.url"];
59201
+ var OUTGOING_PATH_FIELD_NAMES = ["url.path", "http.target"];
59135
59202
  var MAX_DEPTH = 10;
59136
59203
  function scrubString(value) {
59137
- return value.replace(POWERPLANT_TOKEN_PATTERN, REDACTED3).replace(BEARER_TOKEN_PATTERN, `Bearer ${REDACTED3}`).replace(JWT_PATTERN, REDACTED3).replace(EMBEDDED_QUERY_SECRET_PATTERN, `$1${REDACTED3}`);
59204
+ return scrubUserInfo(value).replace(POWERPLANT_TOKEN_PATTERN, REDACTED3).replace(BEARER_TOKEN_PATTERN, `Bearer ${REDACTED3}`).replace(JWT_PATTERN, REDACTED3).replace(EMBEDDED_QUERY_SECRET_PATTERN, `$1${REDACTED3}`);
59138
59205
  }
59139
59206
  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;
59207
+ const scrubbed = scrubUserInfo(url2);
59208
+ const queryStart = scrubbed.search(/[?#]/);
59209
+ return queryStart === -1 ? scrubbed : scrubbed.slice(0, queryStart);
59210
+ }
59211
+ function scrubUserInfo(value) {
59212
+ return value.replace(URL_USER_INFO_PATTERN, `$1${REDACTED3}@`);
59213
+ }
59214
+ function scrubOutgoingUrl(url2) {
59215
+ const parts = URL_PARTS.exec(url2);
59216
+ if (!parts) return REDACTED3;
59217
+ const [, scheme = "", authority = "", path38 = ""] = parts;
59218
+ const host = authority.slice(authority.lastIndexOf("@") + 1);
59219
+ const origin = `${scheme}${host}`;
59220
+ if (PATH_KEPT_HOSTS.test(host.replace(/:\d*$/, ""))) return `${origin}${path38}`;
59221
+ const route = OUTGOING_ROUTES.find((template) => fitsRoute(path38, template));
59222
+ return `${origin}${route ?? ""}`;
59223
+ }
59224
+ function fitsRoute(path38, template) {
59225
+ const segments = path38.replace(/(.)\/$/, "$1").split("/");
59226
+ const route = template.split("/");
59227
+ const hasRest = route.at(-1) === "{path}";
59228
+ const fixed = hasRest ? route.slice(0, -1) : route;
59229
+ if (hasRest ? segments.length < fixed.length : segments.length !== fixed.length) return false;
59230
+ return fixed.every(
59231
+ (part, index) => part.startsWith("{") ? segments[index] !== "" : part === segments[index]
59232
+ );
59158
59233
  }
59159
59234
  function scrubValue(value, depth = 0) {
59160
59235
  if (depth > MAX_DEPTH || value == null) return value;
@@ -59163,9 +59238,10 @@ function scrubValue(value, depth = 0) {
59163
59238
  if (typeof value !== "object") return value;
59164
59239
  const out = {};
59165
59240
  for (const [key, child] of Object.entries(value)) {
59166
- if (SECRET_KEY_PATTERN.test(key) || SECRET_QUERY_PARAM_PATTERN.test(key)) {
59241
+ const name = key.toLowerCase();
59242
+ if (SECRET_KEY_PATTERN.test(key) || SECRET_QUERY_PARAM_PATTERN.test(key) || HTTP_ATTRIBUTE_PREFIX.test(name) && !KEPT_HTTP_ATTRIBUTES.has(name)) {
59167
59243
  out[key] = REDACTED3;
59168
- } else if (URL_FIELD_NAMES.has(key.toLowerCase()) && typeof child === "string") {
59244
+ } else if (URL_FIELD_NAMES.has(name) && typeof child === "string") {
59169
59245
  out[key] = scrubUrl(child);
59170
59246
  } else {
59171
59247
  out[key] = scrubValue(child, depth + 1);
@@ -59178,17 +59254,21 @@ function scrubSentryEvent(event) {
59178
59254
  if (typeof event.request.url === "string") {
59179
59255
  event.request.url = scrubUrl(event.request.url);
59180
59256
  }
59181
- if (typeof event.request.query_string === "string") {
59182
- event.request.query_string = scrubUrl(`?${event.request.query_string}`).replace(/^\?/, "");
59257
+ if (event.request.query_string !== void 0) {
59258
+ event.request.query_string = REDACTED3;
59183
59259
  }
59184
59260
  if (event.request.headers) {
59185
- event.request.headers = scrubValue(event.request.headers);
59261
+ const headers = Object.entries(event.request.headers).map(([name, value]) => [
59262
+ name,
59263
+ KEPT_HEADER_NAMES.has(name.toLowerCase()) ? value : REDACTED3
59264
+ ]);
59265
+ event.request.headers = scrubValue(Object.fromEntries(headers));
59186
59266
  }
59187
59267
  if (event.request.cookies !== void 0) {
59188
59268
  event.request.cookies = REDACTED3;
59189
59269
  }
59190
59270
  if (event.request.data !== void 0) {
59191
- event.request.data = scrubValue(event.request.data);
59271
+ event.request.data = REDACTED3;
59192
59272
  }
59193
59273
  }
59194
59274
  if (event.exception?.values) {
@@ -59210,14 +59290,45 @@ function scrubSentryEvent(event) {
59210
59290
  if (Array.isArray(event.breadcrumbs)) {
59211
59291
  event.breadcrumbs = event.breadcrumbs.map(scrubBreadcrumb);
59212
59292
  }
59293
+ for (const span of event.spans ?? []) {
59294
+ if (typeof span.description === "string") {
59295
+ span.description = scrubString(span.description);
59296
+ }
59297
+ if (span.data) {
59298
+ span.data = scrubValue(span.data);
59299
+ }
59300
+ if (span.op === "http.client") {
59301
+ scrubOutgoingRequestSpan(span);
59302
+ }
59303
+ }
59213
59304
  return event;
59214
59305
  }
59306
+ function scrubOutgoingRequestSpan(span) {
59307
+ if (typeof span.description === "string") {
59308
+ const [method, ...target] = span.description.split(" ");
59309
+ if (target.length > 0) {
59310
+ span.description = `${method} ${scrubOutgoingUrl(target.join(" "))}`;
59311
+ }
59312
+ }
59313
+ const data = span.data;
59314
+ if (!data) return;
59315
+ for (const name of OUTGOING_URL_FIELD_NAMES) {
59316
+ const value = data[name];
59317
+ if (typeof value === "string") data[name] = scrubOutgoingUrl(value);
59318
+ }
59319
+ for (const name of OUTGOING_PATH_FIELD_NAMES) {
59320
+ if (name in data) data[name] = REDACTED3;
59321
+ }
59322
+ }
59215
59323
  function scrubBreadcrumb(breadcrumb) {
59216
59324
  if (typeof breadcrumb.message === "string") {
59217
59325
  breadcrumb.message = scrubString(breadcrumb.message);
59218
59326
  }
59219
59327
  if (breadcrumb.data) {
59220
59328
  breadcrumb.data = scrubValue(breadcrumb.data);
59329
+ if (breadcrumb.category === "http" && typeof breadcrumb.data.url === "string") {
59330
+ breadcrumb.data.url = scrubOutgoingUrl(breadcrumb.data.url);
59331
+ }
59221
59332
  }
59222
59333
  return breadcrumb;
59223
59334
  }
@@ -59256,11 +59367,15 @@ function addLocalSessionEvent(evidence, event) {
59256
59367
  const writes = writesAfterTurnEndMs.includes(event.atMs) ? writesAfterTurnEndMs : [...writesAfterTurnEndMs, event.atMs].sort((a, b2) => a - b2).slice(-WRITES_KEPT_AFTER_TURN_END);
59257
59368
  return { ...evidence, lastWriteMs, writesAfterTurnEndMs: writes };
59258
59369
  }
59259
- case "turn-end": {
59370
+ case "turn-end":
59371
+ case "transcript-turn-end": {
59372
+ const inTranscript = event.kind === "transcript-turn-end";
59373
+ if (event.atMs === lastTurnEndMs && inTranscript) return { ...evidence, lastTurnEndInTranscript: true };
59260
59374
  if (lastTurnEndMs !== void 0 && event.atMs <= lastTurnEndMs) return evidence;
59261
59375
  return {
59262
59376
  ...evidence,
59263
59377
  lastTurnEndMs: event.atMs,
59378
+ lastTurnEndInTranscript: inTranscript,
59264
59379
  lastWriteByTurnEndMs: latest(evidence.lastWriteByTurnEndMs, writesAfterTurnEndMs.findLast((ms) => ms <= event.atMs)),
59265
59380
  writesAfterTurnEndMs: writesAfterTurnEndMs.filter((ms) => ms > event.atMs)
59266
59381
  };
@@ -59284,16 +59399,19 @@ function deadlineAfterLastEvent({ lastWriteMs, lastTurnEndMs, lastBoundary }) {
59284
59399
  function statusFromEvents({
59285
59400
  lastWriteMs,
59286
59401
  lastTurnEndMs,
59402
+ lastTurnEndInTranscript,
59287
59403
  lastWriteByTurnEndMs,
59288
59404
  lastBoundary
59289
59405
  }) {
59290
- const isNewWork = (writeMs, eventMs) => writeMs !== void 0 && writeMs > eventMs + END_OBSERVATION_WRITE_SLACK_MS;
59406
+ const isNewWork = (writeMs, eventMs, slackMs2 = END_OBSERVATION_WRITE_SLACK_MS) => writeMs !== void 0 && writeMs > eventMs + slackMs2;
59291
59407
  const endMs = lastBoundary?.event === "session-end" ? lastBoundary.atMs : void 0;
59292
59408
  const startMs = lastBoundary?.event === "session-start" ? lastBoundary.atMs : void 0;
59293
59409
  if (endMs !== void 0 && !isNewWork(lastWriteMs, endMs)) return "ended";
59294
59410
  const turnEndCounts = endMs === void 0 || isNewWork(lastWriteByTurnEndMs, endMs);
59295
- const waitingSinceMs = latest(turnEndCounts ? lastTurnEndMs : void 0, startMs);
59296
- if (waitingSinceMs !== void 0 && !isNewWork(lastWriteMs, waitingSinceMs)) return "waiting";
59411
+ const turnEndMs = turnEndCounts ? lastTurnEndMs : void 0;
59412
+ const waitingSinceMs = latest(turnEndMs, startMs);
59413
+ const slackMs = waitingSinceMs === turnEndMs && lastTurnEndInTranscript === true ? 0 : END_OBSERVATION_WRITE_SLACK_MS;
59414
+ if (waitingSinceMs !== void 0 && !isNewWork(lastWriteMs, waitingSinceMs, slackMs)) return "waiting";
59297
59415
  return lastWriteMs === void 0 ? "unknown" : "working";
59298
59416
  }
59299
59417
  function latest(a, b2) {
@@ -59416,9 +59534,10 @@ function parseCodexChildThread(sessionMeta) {
59416
59534
  // ../core/src/recentSessionPage.ts
59417
59535
  var RECENT_SESSION_WINDOW_MS = 72 * 60 * 60 * 1e3;
59418
59536
  var RECENT_SESSION_VISIBLE_LIMIT = 12;
59537
+ var RECENT_SESSION_PAGE_MAX = 40;
59419
59538
  var recentSessionOptionsSchema = external_exports.object({
59420
59539
  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)
59540
+ limit: external_exports.coerce.number().int().min(1).max(RECENT_SESSION_PAGE_MAX).default(12)
59422
59541
  });
59423
59542
  var recentSessionCursorSchema = external_exports.object({
59424
59543
  at: external_exports.iso.datetime({ offset: true }),
@@ -78610,10 +78729,10 @@ function dispatchEngineSpan(engineSpan, allSpans, linkIds, ignoreSpanTypes) {
78610
78729
  }
78611
78730
  );
78612
78731
  }
78613
- function endSpan2(span, run) {
78732
+ function endSpan2(span, run2) {
78614
78733
  let result;
78615
78734
  try {
78616
- result = run();
78735
+ result = run2();
78617
78736
  } catch (reason) {
78618
78737
  span.end();
78619
78738
  throw reason;
@@ -80582,7 +80701,7 @@ var KafkaJsInstrumentation = class extends import_instrumentation9.Instrumentati
80582
80701
  _getConsumerRunPatch() {
80583
80702
  const instrumentation = this;
80584
80703
  return (original) => {
80585
- return function run(...args) {
80704
+ return function run2(...args) {
80586
80705
  const config2 = args[0];
80587
80706
  if (config2?.eachMessage) {
80588
80707
  if ((0, import_instrumentation9.isWrapped)(config2.eachMessage)) {
@@ -86582,40 +86701,45 @@ function readCodexTranscriptDiscoveryRecords(home, roots) {
86582
86701
  } catch {
86583
86702
  return [];
86584
86703
  }
86585
- const result = [];
86586
- for (const file2 of files) {
86587
- if (!/^[a-f0-9]{64}\.json$/.test(file2)) continue;
86704
+ return files.flatMap((file2) => {
86705
+ if (!/^[a-f0-9]{64}\.json$/.test(file2)) return [];
86706
+ const record2 = readRecord(path2.join(dir, file2), roots);
86707
+ return record2 === null ? [] : [record2];
86708
+ });
86709
+ }
86710
+ function readCodexTranscriptDiscoveryRecord(home, sessionId, roots) {
86711
+ return readRecord(codexTranscriptRecordPath(home, sessionId), roots);
86712
+ }
86713
+ function readRecord(recordPath, roots) {
86714
+ try {
86715
+ if (!lstatSync(recordPath).isFile()) return null;
86716
+ const fd = openSync(recordPath, "r");
86717
+ let text;
86588
86718
  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
- }
86719
+ const buffer = Buffer.alloc(32 * 1024 + 1);
86720
+ const read = readSync(fd, buffer, 0, buffer.length, null);
86721
+ if (read === buffer.length) return null;
86722
+ text = buffer.subarray(0, read).toString("utf8");
86723
+ } finally {
86724
+ closeSync(fd);
86725
+ }
86726
+ const record2 = transcriptDiscoveryRecordSchema.parse(JSON.parse(text));
86727
+ if (path2.basename(recordPath) !== `${createHash("sha256").update(record2.sessionId).digest("hex")}.json`) return null;
86728
+ if (record2.transcriptPath === null || !path2.isAbsolute(record2.transcriptPath)) return null;
86729
+ const rolloutPath = path2.normalize(record2.transcriptPath);
86730
+ if (path2.extname(rolloutPath) !== ".jsonl") return null;
86731
+ const rootIndex = roots.findIndex((root2) => {
86732
+ const relative2 = path2.relative(root2, rolloutPath);
86733
+ return relative2 !== "" && relative2 !== ".." && !relative2.startsWith(`..${path2.sep}`) && !path2.isAbsolute(relative2);
86734
+ });
86735
+ if (rootIndex < 0 || !lstatSync(rolloutPath).isFile()) return null;
86736
+ const root = roots[rootIndex];
86737
+ if (realpathSync(rolloutPath) !== path2.join(realpathSync(root), path2.relative(root, rolloutPath)))
86738
+ return null;
86739
+ return { rolloutPath, sessionId: record2.sessionId, archived: rootIndex === 1 };
86740
+ } catch {
86741
+ return null;
86617
86742
  }
86618
- return result;
86619
86743
  }
86620
86744
 
86621
86745
  // ../../apps/syncservice/src/diagnostics.ts
@@ -86624,7 +86748,7 @@ import { createHmac } from "node:crypto";
86624
86748
  // ../../apps/syncservice/src/http.ts
86625
86749
  var import_undici = __toESM(require_undici(), 1);
86626
86750
  var CLI_VERSION_HEADER = "x-powerplant-cli-version";
86627
- var cliVersion = true ? "0.4.20" : "0.0.0-checkout";
86751
+ var cliVersion = true ? "0.4.22" : "0.0.0-checkout";
86628
86752
  function cliVersionHeaders() {
86629
86753
  return { [CLI_VERSION_HEADER]: cliVersion, "user-agent": `powerplant-cli/${cliVersion}` };
86630
86754
  }
@@ -87717,7 +87841,7 @@ function mayBeParentOf(parent, childMadeMs) {
87717
87841
  function mayBeChildOf(threadTimeMs, parentTimeMs) {
87718
87842
  return threadTimeMs === void 0 || parentTimeMs === void 0 || threadTimeMs + THREAD_ID_TIME_MARGIN_MS >= parentTimeMs;
87719
87843
  }
87720
- var CodexThreadLinks = class {
87844
+ var CodexThreadLinks = class _CodexThreadLinks {
87721
87845
  /** `readLink` is a test seam for the first-record read. */
87722
87846
  constructor(readLink = readThreadLink) {
87723
87847
  this.readLink = readLink;
@@ -87743,6 +87867,19 @@ var CodexThreadLinks = class {
87743
87867
  * (settleRecoveries), with the session the last pass made of it and how
87744
87868
  * many uploads of it failed. */
87745
87869
  recovering = /* @__PURE__ */ new Map();
87870
+ /** A copy for a pass that runs beside the watcher's own passes (the
87871
+ * new-session pass), so neither changes what the other knows. It keeps
87872
+ * what each rollout's first record showed, and leaves out the rollouts
87873
+ * and threads owed another read or a corrected capture, which only the
87874
+ * watcher's own passes settle. */
87875
+ copyForSidePass() {
87876
+ const copy = new _CodexThreadLinks(this.readLink);
87877
+ for (const [rolloutPath, link] of this.byPath) copy.byPath.set(rolloutPath, link);
87878
+ for (const [rolloutPath, changedMs] of this.failedAt) copy.failedAt.set(rolloutPath, changedMs);
87879
+ for (const [rolloutPath, state] of this.incomplete) copy.incomplete.set(rolloutPath, { ...state });
87880
+ for (const rolloutPath of this.faulted) copy.faulted.add(rolloutPath);
87881
+ return copy;
87882
+ }
87746
87883
  /** Keeps what another read of the same first record found. */
87747
87884
  remember(rolloutPath, link) {
87748
87885
  this.byPath.set(rolloutPath, link);
@@ -87980,7 +88117,7 @@ var CodexThreadLinks = class {
87980
88117
  return true;
87981
88118
  }
87982
88119
  };
87983
- var CodexThreadReads = class {
88120
+ var CodexThreadReads = class _CodexThreadReads {
87984
88121
  byThread = /* @__PURE__ */ new Map();
87985
88122
  /** What a full read found when the thread's files were `files`
87986
88123
  * (threadFilesKey): its summary and prefix, null for a thread holding no
@@ -87992,6 +88129,14 @@ var CodexThreadReads = class {
87992
88129
  set(threadId2, files, read) {
87993
88130
  this.byThread.set(threadId2, { files, read });
87994
88131
  }
88132
+ /** A copy for a pass that runs beside the watcher's own passes. Each read
88133
+ * is kept for exactly the files it was made from, so a copy never serves
88134
+ * a read of files that have since changed. */
88135
+ copyForSidePass() {
88136
+ const copy = new _CodexThreadReads();
88137
+ for (const [threadId2, kept] of this.byThread) copy.byThread.set(threadId2, kept);
88138
+ return copy;
88139
+ }
87995
88140
  /** Forgets the threads no longer on this machine. */
87996
88141
  keepOnly(threadIds) {
87997
88142
  for (const threadId2 of this.byThread.keys()) if (!threadIds.has(threadId2)) this.byThread.delete(threadId2);
@@ -90295,8 +90440,10 @@ var collectorStatusSchema = external_exports.object({
90295
90440
  /** Last pass that completed without error — everything eligible had been
90296
90441
  * uploaded as of this time. */
90297
90442
  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". */
90443
+ /** When the server last stored a changed session record, from any pass,
90444
+ * a recent catch-up inside one included. The next run of the same
90445
+ * enrollment carries it (carriedUploadClock). Missing: no upload in this
90446
+ * run or the one before it, not "never". */
90300
90447
  lastUploadAt: external_exports.string().optional(),
90301
90448
  consecutiveFailures: external_exports.number().int().nonnegative(),
90302
90449
  /** Whether the hooks this daemon installed are firing, per harness
@@ -90867,7 +91014,7 @@ function saveCredentials(appEnv, credentials) {
90867
91014
  function loadCredentials(appEnv) {
90868
91015
  if (process.platform === "darwin") {
90869
91016
  const raw2 = new MacosCredentialStore(appEnv).load();
90870
- return raw2 === null ? null : parseStoredCredentials(raw2, appEnv);
91017
+ return raw2 === null ? null : withCurrentApiUrl(appEnv, parseStoredCredentials(raw2, appEnv));
90871
91018
  }
90872
91019
  let raw;
90873
91020
  try {
@@ -90879,7 +91026,19 @@ function loadCredentials(appEnv) {
90879
91026
  { cause }
90880
91027
  );
90881
91028
  }
90882
- return parseStoredCredentials(raw, appEnv);
91029
+ return withCurrentApiUrl(appEnv, parseStoredCredentials(raw, appEnv));
91030
+ }
91031
+ var RETIRED_API_URLS = {
91032
+ production: { retired: "https://api.powerplant.sh", replacement: "https://api.usepowerplant.com" },
91033
+ staging: {
91034
+ retired: "https://staging.api.powerplant.sh",
91035
+ replacement: "https://staging.api.usepowerplant.com"
91036
+ }
91037
+ };
91038
+ function withCurrentApiUrl(appEnv, credentials) {
91039
+ const moved = RETIRED_API_URLS[appEnv];
91040
+ if (moved === void 0 || credentials.apiUrl !== moved.retired) return credentials;
91041
+ return { ...credentials, apiUrl: moved.replacement };
90883
91042
  }
90884
91043
  function isCredentialStoreAbsent(cause) {
90885
91044
  return cause instanceof Error && "code" in cause && cause.code === "ENOENT";
@@ -90912,7 +91071,9 @@ import os14 from "node:os";
90912
91071
 
90913
91072
  // ../../apps/syncservice/src/upload.ts
90914
91073
  import { readFileSync as readFileSync10 } from "node:fs";
90915
- import { gzipSync } from "node:zlib";
91074
+ import { setImmediate as yieldToEventLoop3 } from "node:timers/promises";
91075
+ import { promisify as promisify2 } from "node:util";
91076
+ import { gzip } from "node:zlib";
90916
91077
 
90917
91078
  // ../../apps/syncservice/src/claudeCode.ts
90918
91079
  import { readdirSync as readdirSync6 } from "node:fs";
@@ -90929,7 +91090,9 @@ function parseClaudeCodeTranscript(text) {
90929
91090
  let entrypoint;
90930
91091
  let anonCounter = 0;
90931
91092
  let speedMode;
91093
+ let endsWithAbortedTurn = false;
90932
91094
  for (const record2 of parseJsonl(text)) {
91095
+ endsWithAbortedTurn = abortedTurnOf(record2) ?? endsWithAbortedTurn;
90933
91096
  if (record2.type === "custom-title" && typeof record2.customTitle === "string") {
90934
91097
  title = record2.customTitle;
90935
91098
  continue;
@@ -90990,8 +91153,38 @@ function parseClaudeCodeTranscript(text) {
90990
91153
  turnCount: usageByMessage.size,
90991
91154
  cwd,
90992
91155
  gitBranch,
90993
- entrypoint
90994
- };
91156
+ entrypoint,
91157
+ endsWithAbortedTurn
91158
+ };
91159
+ }
91160
+ var STOPPED_TURN_TEXT = "[Request interrupted by user";
91161
+ var LOCAL_COMMAND_TEXT = /^\s*<(?:local-command-caveat|command-name|local-command-stdout|local-command-stderr)>/;
91162
+ var messageContentSchema = external_exports.object({ content: external_exports.union([external_exports.string(), external_exports.array(external_exports.unknown())]) });
91163
+ var textBlockSchema = external_exports.object({ type: external_exports.literal("text"), text: external_exports.string() });
91164
+ function abortedTurnOf(record2) {
91165
+ if (record2.type !== "user" && record2.type !== "assistant" || record2.isSidechain === true) return void 0;
91166
+ if (record2.type === "assistant") return record2.isApiErrorMessage === true;
91167
+ const text = firstText(record2.message);
91168
+ return LOCAL_COMMAND_TEXT.test(text) ? void 0 : text.startsWith(STOPPED_TURN_TEXT);
91169
+ }
91170
+ function claudeCodeEndsWithAbortedTurn(tail) {
91171
+ const records = [...parseJsonl(tail)];
91172
+ for (let index = records.length - 1; index >= 0; index -= 1) {
91173
+ const aborted2 = abortedTurnOf(records[index]);
91174
+ if (aborted2 !== void 0) return aborted2;
91175
+ }
91176
+ return false;
91177
+ }
91178
+ function firstText(message2) {
91179
+ const parsed = messageContentSchema.safeParse(message2);
91180
+ if (!parsed.success) return "";
91181
+ const { content } = parsed.data;
91182
+ if (typeof content === "string") return content;
91183
+ for (const block2 of content) {
91184
+ const text = textBlockSchema.safeParse(block2);
91185
+ if (text.success) return text.data.text;
91186
+ }
91187
+ return "";
90995
91188
  }
90996
91189
  var AGENT_SDK_ENTRYPOINTS = /* @__PURE__ */ new Set(["sdk-py", "sdk-ts"]);
90997
91190
  function isAutomationEntrypoint(entrypoint) {
@@ -91101,6 +91294,7 @@ async function collectClaudeCodeSessions(options = {}) {
91101
91294
  const endedAt = endObservedAt ?? laterTimestamp(summary.endedAt, usage.endedAt);
91102
91295
  items.push({
91103
91296
  mtimeMs,
91297
+ endsWithAbortedTurn: summary.endsWithAbortedTurn,
91104
91298
  transcriptPath,
91105
91299
  transcriptPrefix: transcript.prefix,
91106
91300
  subagents: {
@@ -91219,9 +91413,9 @@ function listWorkflowTranscripts(workflowsDir) {
91219
91413
  failedToRead();
91220
91414
  return [];
91221
91415
  };
91222
- for (const run of list(workflowsDir)) {
91223
- if (!run.isDirectory()) continue;
91224
- const runDir = path15.join(workflowsDir, run.name);
91416
+ for (const run2 of list(workflowsDir)) {
91417
+ if (!run2.isDirectory()) continue;
91418
+ const runDir = path15.join(workflowsDir, run2.name);
91225
91419
  for (const file2 of list(runDir)) {
91226
91420
  if (!file2.isFile() || !file2.name.endsWith(".jsonl")) continue;
91227
91421
  try {
@@ -91490,8 +91684,20 @@ async function uploadPreviewRegistration(apiUrl, machineToken, registration) {
91490
91684
  }
91491
91685
  return "registered";
91492
91686
  }
91493
- function encodeRawTranscript(text) {
91494
- return gzipSync(redactJsonl(text));
91687
+ var gzipAsync = promisify2(gzip);
91688
+ var REDACT_SLICE_CHARS = 64 * 1024;
91689
+ async function encodeRawTranscript(text) {
91690
+ const lines = text.split("\n");
91691
+ let sliceChars = 0;
91692
+ for (const [index, line] of lines.entries()) {
91693
+ lines[index] = redactJsonl(line);
91694
+ sliceChars += line.length;
91695
+ if (sliceChars >= REDACT_SLICE_CHARS) {
91696
+ await yieldToEventLoop3();
91697
+ sliceChars = 0;
91698
+ }
91699
+ }
91700
+ return gzipAsync(lines.join("\n"));
91495
91701
  }
91496
91702
  function readCollectedTranscript(file2, prefix) {
91497
91703
  return prefix === void 0 ? readFileSync10(file2, "utf8") : readTranscriptPrefix(file2, prefix, false);
@@ -91501,8 +91707,8 @@ function readUnchangedTranscript(file2, prefix) {
91501
91707
  }
91502
91708
  var BUNDLE_HEADER_BYTES = 256;
91503
91709
  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));
91710
+ async function rawUploadBody(session, readTranscript, maxBytes = MAX_RAW_UPLOAD_BYTES) {
91711
+ const main2 = await encodeRawTranscript(readTranscript(session.transcriptPath, session.transcriptPrefix));
91506
91712
  if (session.subagents === void 0) {
91507
91713
  return main2.byteLength > maxBytes ? "too-large" : { body: main2, main: main2, contentType: "application/gzip", skippedSubagentFiles: 0, unreadableSubagentFiles: 0 };
91508
91714
  }
@@ -91514,7 +91720,7 @@ function rawUploadBody(session, readTranscript, maxBytes = MAX_RAW_UPLOAD_BYTES)
91514
91720
  for (const file2 of session.subagents.files) {
91515
91721
  let body;
91516
91722
  try {
91517
- body = encodeRawTranscript(readTranscript(file2.path, file2.prefix));
91723
+ body = await encodeRawTranscript(readTranscript(file2.path, file2.prefix));
91518
91724
  } catch (error63) {
91519
91725
  if (error63 instanceof TranscriptChangedError) throw error63;
91520
91726
  skipped += 1;
@@ -91588,7 +91794,7 @@ async function uploadRawSessions(apiUrl, machineToken, transcriptUploadSources,
91588
91794
  continue;
91589
91795
  }
91590
91796
  try {
91591
- const encoded = rawUploadBody(session, readTranscript);
91797
+ const encoded = await rawUploadBody(session, readTranscript);
91592
91798
  if (encoded === "too-large") {
91593
91799
  summary.skippedTooLarge += 1;
91594
91800
  summary.outcomes.set(key, "too-large");
@@ -92478,7 +92684,9 @@ var claudeLocal = {
92478
92684
  },
92479
92685
  registration: jsonServerEntry,
92480
92686
  // <project folder>/<session id>.jsonl
92481
- transcriptSession: (transcriptPath) => ({ externalId: path18.basename(transcriptPath, ".jsonl") })
92687
+ transcriptSession: (transcriptPath) => ({ externalId: path18.basename(transcriptPath, ".jsonl") }),
92688
+ // No Stop hook runs when a person stops a turn or the API fails it.
92689
+ endsWithAbortedTurn: claudeCodeEndsWithAbortedTurn
92482
92690
  };
92483
92691
  var codexLocal = {
92484
92692
  harness: "codex-local",
@@ -92523,7 +92731,16 @@ var codexLocal = {
92523
92731
  transcriptSession(transcriptPath) {
92524
92732
  const identity = rolloutIdentity(transcriptPath);
92525
92733
  return identity.page ? { hookId: identity.threadId } : { externalId: path18.basename(transcriptPath, ".jsonl") };
92526
- }
92734
+ },
92735
+ // The hook records the rollout of each thread before it appends the
92736
+ // journal line (.claude/hooks/powerplant-codex-transcript.mjs).
92737
+ hookTranscriptPath(threadId2, homeDir) {
92738
+ const sessionsDir = path18.join(homeDir, ".codex", "sessions");
92739
+ return readCodexTranscriptDiscoveryRecord(homeDir, threadId2, [sessionsDir, codexArchivedSessionsDir(sessionsDir)])?.rolloutPath;
92740
+ },
92741
+ // The Codex app holds the rollout of each open thread for writing
92742
+ // (lsof, 2026-09-27), and macOS reports a write to it only on close.
92743
+ holdsTranscriptOpen: true
92527
92744
  };
92528
92745
  var cursorLocal = {
92529
92746
  harness: "cursor-local",
@@ -93204,7 +93421,7 @@ function watchHarnessConnections(deps) {
93204
93421
  }
93205
93422
  }
93206
93423
  };
93207
- const run = async (harnesses) => {
93424
+ const run2 = async (harnesses) => {
93208
93425
  running = true;
93209
93426
  try {
93210
93427
  const before = /* @__PURE__ */ new Map();
@@ -93226,7 +93443,7 @@ function watchHarnessConnections(deps) {
93226
93443
  if (queued !== null && !stopped) {
93227
93444
  const next = queued === "all" ? void 0 : [...queued];
93228
93445
  queued = null;
93229
- void run(next);
93446
+ void run2(next);
93230
93447
  }
93231
93448
  };
93232
93449
  const request2 = (harnesses) => {
@@ -93235,7 +93452,7 @@ function watchHarnessConnections(deps) {
93235
93452
  queued = harnesses === void 0 || queued === "all" ? "all" : /* @__PURE__ */ new Set([...queued ?? [], ...harnesses]);
93236
93453
  return;
93237
93454
  }
93238
- void run(harnesses);
93455
+ void run2(harnesses);
93239
93456
  };
93240
93457
  request2(void 0);
93241
93458
  const timer = setInterval(() => request2(void 0), SIGN_IN_CHECK_INTERVAL_MS);
@@ -93311,14 +93528,14 @@ function startHarnessConnectionChecks(options) {
93311
93528
  }
93312
93529
 
93313
93530
  // ../../apps/syncservice/src/recentCatchup.ts
93314
- function createRecentCatchup(run, now = Date.now) {
93531
+ function createRecentCatchup(run2, now = Date.now) {
93315
93532
  let coveredSince = now();
93316
93533
  let nextCheck = coveredSince + 15e3;
93317
93534
  return async () => {
93318
93535
  const started = now();
93319
93536
  if (started < nextCheck) return;
93320
93537
  try {
93321
- const result = await run(coveredSince - 1e3);
93538
+ const result = await run2(coveredSince - 1e3);
93322
93539
  if (result === false) return;
93323
93540
  const diagnostic = result.diagnostic;
93324
93541
  if (diagnostic?.rawFailed !== 0 || diagnostic.rawMissingMetadata !== 0 || diagnostic.deferred !== 0 || diagnostic.rawUnconfigured !== false) return;
@@ -93429,6 +93646,7 @@ function readIfExists2(p) {
93429
93646
  }
93430
93647
 
93431
93648
  // ../../apps/syncservice/src/previewRegistration.ts
93649
+ import { setImmediate as yieldToEventLoop4 } from "node:timers/promises";
93432
93650
  async function registerAnnouncedPreviews(apiUrl, machineToken, collected, memo2, deps = {}) {
93433
93651
  const readTranscript = deps.readTranscript ?? readIfExists;
93434
93652
  const upload = deps.upload ?? uploadPreviewRegistration;
@@ -93444,6 +93662,7 @@ async function registerAnnouncedPreviews(apiUrl, machineToken, collected, memo2,
93444
93662
  const state = memo2.get(key);
93445
93663
  if (state?.foreignOwner || state?.previewsOff) continue;
93446
93664
  if (state?.examinedMtimeMs === session.mtimeMs) continue;
93665
+ await yieldToEventLoop4();
93447
93666
  try {
93448
93667
  const text = readTranscript(session.transcriptPath);
93449
93668
  if (text === null) continue;
@@ -93489,6 +93708,22 @@ async function registerAnnouncedPreviews(apiUrl, machineToken, collected, memo2,
93489
93708
  }
93490
93709
 
93491
93710
  // ../../apps/syncservice/src/sync.ts
93711
+ var RecordTurns = class {
93712
+ last = Promise.resolve();
93713
+ /** Waits for the passes ahead, then returns `end`, which lets the next
93714
+ * pass take its turn. Calling `end` again does nothing. */
93715
+ async take() {
93716
+ let end = () => {
93717
+ };
93718
+ const ended = new Promise((resolve2) => {
93719
+ end = resolve2;
93720
+ });
93721
+ const ahead = this.last;
93722
+ this.last = ahead.then(() => ended);
93723
+ await ahead;
93724
+ return end;
93725
+ }
93726
+ };
93492
93727
  var defaultPolicyDeps = {
93493
93728
  fetchPolicy: fetchCapturePolicy,
93494
93729
  load: loadCredentials,
@@ -93590,7 +93825,17 @@ async function resolveCapturePolicy(config2, credentials, log3, deps = defaultPo
93590
93825
  environment
93591
93826
  };
93592
93827
  }
93593
- async function runSyncPassInternal(config2, options, diagnostic) {
93828
+ function recordTurn(turns) {
93829
+ let end = () => {
93830
+ };
93831
+ return {
93832
+ start: async () => {
93833
+ if (turns) end = await turns.take();
93834
+ },
93835
+ end: () => end()
93836
+ };
93837
+ }
93838
+ async function runSyncPassInternal(config2, options, diagnostic, turn) {
93594
93839
  const log3 = options.log ?? console.log;
93595
93840
  const credentials = loadCredentials(config2.appEnv);
93596
93841
  if (!credentials) {
@@ -93616,6 +93861,7 @@ async function runSyncPassInternal(config2, options, diagnostic) {
93616
93861
  diagnostic.sinceMs = sinceMs;
93617
93862
  diagnostic.deepSweep = window2.deepSweep;
93618
93863
  const maxSessions = window2.deepSweep ? DEEP_FIRST_SYNC_MAX_SESSIONS : void 0;
93864
+ await turn.start();
93619
93865
  diagnostic.stage = "claude";
93620
93866
  const claude = await collectClaudeCodeSessions({
93621
93867
  sinceMs,
@@ -93726,12 +93972,14 @@ async function runSyncPassInternal(config2, options, diagnostic) {
93726
93972
  credentials.token,
93727
93973
  items
93728
93974
  );
93975
+ turn.end();
93729
93976
  const foreignKeys = new Set(foreignOwned.map(sessionKey));
93730
93977
  const writable = collected.filter(({ item }) => !foreignKeys.has(sessionKey(item)));
93731
93978
  for (const { harness, harnessSessionId } of foreignOwned) {
93732
93979
  log3(`Skipped ${harness} session ${harnessSessionId}: already synced from another machine.`);
93733
93980
  }
93734
93981
  Object.assign(diagnostic, { upserted, unchanged, outOfPolicy, deferred });
93982
+ if (upserted > 0) options.onRecordsUpserted?.();
93735
93983
  log3(
93736
93984
  `Uploaded: ${upserted} session(s) upserted` + (unchanged > 0 ? `, ${unchanged} unchanged (no writes issued)` : "") + "."
93737
93985
  );
@@ -93863,8 +94111,9 @@ async function runSyncPass(config2, options = {}) {
93863
94111
  }
93864
94112
  };
93865
94113
  notify();
94114
+ const turn = recordTurn(options.recordTurns);
93866
94115
  try {
93867
- const result = await runSyncPassInternal(config2, options, diagnostic);
94116
+ const result = await runSyncPassInternal(config2, options, diagnostic, turn);
93868
94117
  diagnostic.outcome = "complete";
93869
94118
  diagnostic.stage = "complete";
93870
94119
  diagnostic.upserted = result.upserted;
@@ -93877,6 +94126,7 @@ async function runSyncPass(config2, options = {}) {
93877
94126
  if (error63 instanceof Error) failedPasses.set(error63, diagnostic);
93878
94127
  throw error63;
93879
94128
  } finally {
94129
+ turn.end();
93880
94130
  diagnostic.durationMs = Math.max(0, Date.now() - start);
93881
94131
  notify();
93882
94132
  }
@@ -93939,7 +94189,7 @@ function unsupportedVersionNotice(cliVersion2, oldestSupportedVersion) {
93939
94189
  }
93940
94190
 
93941
94191
  // ../../apps/syncservice/src/watch.ts
93942
- import { existsSync as existsSync10, mkdirSync as mkdirSync11, watch as fsWatch2 } from "node:fs";
94192
+ import { existsSync as existsSync10, mkdirSync as mkdirSync11, watch as fsWatch3 } from "node:fs";
93943
94193
  import os19 from "node:os";
93944
94194
  import path21 from "node:path";
93945
94195
 
@@ -93973,7 +94223,7 @@ function defaultStat(file2) {
93973
94223
  }
93974
94224
 
93975
94225
  // ../../apps/syncservice/src/sessionStatusFeed.ts
93976
- import { statSync as statSync5 } from "node:fs";
94226
+ import { closeSync as closeSync7, fstatSync as fstatSync3, openSync as openSync7, readSync as readSync5, statSync as statSync5, watch as fsWatch2 } from "node:fs";
93977
94227
 
93978
94228
  // ../../apps/syncservice/src/sessionStatusModel.ts
93979
94229
  function createSessionStatusModel(now = Date.now) {
@@ -93992,6 +94242,7 @@ function createSessionStatusModel(now = Date.now) {
93992
94242
  return {
93993
94243
  applyLifecycleEntry: (entry) => apply(entry.harness, entry.sessionId, { kind: entry.event, atMs: Date.parse(entry.at) }),
93994
94244
  applyTranscriptWrite: (write) => apply(write.harness, write.sessionId, { kind: "transcript-write", atMs: write.atMs }),
94245
+ applyTranscriptTurnEnd: (turnEnd) => apply(turnEnd.harness, turnEnd.sessionId, { kind: "transcript-turn-end", atMs: turnEnd.atMs }),
93995
94246
  expireDeadlines() {
93996
94247
  const nowMs = now();
93997
94248
  const changes = [];
@@ -94024,11 +94275,14 @@ function createSessionStatusModel(now = Date.now) {
94024
94275
  // ../../apps/syncservice/src/sessionStatusFeed.ts
94025
94276
  var TRANSCRIPT_WRITE_DELAY_MS = 500;
94026
94277
  var HELD_HOOK_ENTRIES_MAX = 500;
94278
+ var TRANSCRIPT_FILE_WATCHES_MAX = 32;
94279
+ var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
94027
94280
  var HOOK_ID_PAIRS_MAX = 1e4;
94028
94281
  var MAX_TIMER_MS = 2 ** 31 - 1;
94029
94282
  function createSessionStatusFeed(deps) {
94030
94283
  const now = deps.now ?? Date.now;
94031
94284
  const adapters = deps.adapters ?? HARNESS_ADAPTERS;
94285
+ const watchFile = deps.watchFile ?? watchFileWrites;
94032
94286
  const model = createSessionStatusModel(now);
94033
94287
  const adapterOf = (harness) => adapters.find((adapter) => adapter.harness === harness);
94034
94288
  const hookIdOf = (harness) => adapterOf(harness)?.hookSessionId;
@@ -94040,8 +94294,35 @@ function createSessionStatusFeed(deps) {
94040
94294
  let readQueued = false;
94041
94295
  let readFailing = false;
94042
94296
  let statFailing = false;
94297
+ let tailFailing = false;
94298
+ let watchFailing = false;
94043
94299
  const writeTimers = /* @__PURE__ */ new Map();
94300
+ const hookFilesRead = /* @__PURE__ */ new Set();
94301
+ const reportedFiles = /* @__PURE__ */ new Map();
94302
+ const fileWatches = /* @__PURE__ */ new Map();
94044
94303
  let stopped = false;
94304
+ function applyJournalEntry(entry) {
94305
+ if (learnFromHookFile(entry.harness, entry.sessionId)) releaseHeld();
94306
+ apply(entry);
94307
+ }
94308
+ function learnFromHookFile(harness, hookId) {
94309
+ const adapter = adapterOf(harness);
94310
+ const hookIdOfStored = adapter?.hookSessionId;
94311
+ const key = pairKey(harness, hookId);
94312
+ if (adapter?.hookTranscriptPath === void 0 || hookIdOfStored === void 0 || hookFilesRead.has(key)) {
94313
+ return false;
94314
+ }
94315
+ hookFilesRead.add(key);
94316
+ const file2 = adapter.hookTranscriptPath(hookId, deps.home);
94317
+ const session = file2 === void 0 ? void 0 : adapter.transcriptSession?.(file2);
94318
+ if (file2 === void 0 || session === void 0) return false;
94319
+ const fileHookId = "externalId" in session ? hookIdOfStored(session.externalId) : session.hookId;
94320
+ if (pairKey(harness, fileHookId) !== key) return false;
94321
+ const externalId = "externalId" in session ? session.externalId : storedIds.get(key);
94322
+ if (externalId === void 0) return false;
94323
+ reportFile(file2, { harness, externalId });
94324
+ return "externalId" in session && learnPair(harness, externalId);
94325
+ }
94045
94326
  function stored(entry) {
94046
94327
  if (hookIdOf(entry.harness) === void 0) return entry;
94047
94328
  const storedId = storedIds.get(pairKey(entry.harness, entry.sessionId));
@@ -94071,8 +94352,20 @@ function createSessionStatusFeed(deps) {
94071
94352
  held = [];
94072
94353
  for (const entry of waiting) apply(entry);
94073
94354
  }
94074
- function applyWrite(harness, sessionId, atMs) {
94075
- if (model.applyTranscriptWrite({ harness, sessionId, atMs }) !== null) changedInBatch = true;
94355
+ function applyWrite(harness, sessionId, atMs, endsWithAbortedTurn) {
94356
+ const before = model.status(harness, sessionId);
94357
+ model.applyTranscriptWrite({ harness, sessionId, atMs });
94358
+ const status = model.status(harness, sessionId);
94359
+ if ((status === "working" || status === "unknown") && endsWithAbortedTurn()) {
94360
+ model.applyTranscriptTurnEnd({ harness, sessionId, atMs });
94361
+ }
94362
+ if (model.status(harness, sessionId) !== before) changedInBatch = true;
94363
+ }
94364
+ function fileEndsWithAbortedTurn(harness, transcriptPath) {
94365
+ const endsWithAbortedTurn = adapterOf(harness)?.endsWithAbortedTurn;
94366
+ if (endsWithAbortedTurn === void 0) return false;
94367
+ const tail = transcriptTail(transcriptPath);
94368
+ return tail !== null && endsWithAbortedTurn(tail);
94076
94369
  }
94077
94370
  function applyWatchedWrite(harness, transcriptPath) {
94078
94371
  const session = adapterOf(harness)?.transcriptSession?.(transcriptPath);
@@ -94082,9 +94375,69 @@ function createSessionStatusFeed(deps) {
94082
94375
  const mtimeMs = modifiedMs(transcriptPath);
94083
94376
  if (mtimeMs === null) return;
94084
94377
  const learned = "externalId" in session && learnPair(harness, externalId);
94085
- applyWrite(harness, externalId, mtimeMs);
94378
+ applyWrite(harness, externalId, mtimeMs, () => fileEndsWithAbortedTurn(harness, transcriptPath));
94379
+ reportFile(transcriptPath, { harness, externalId });
94086
94380
  if (learned) releaseHeld();
94087
94381
  }
94382
+ function reportFile(file2, session) {
94383
+ if (adapterOf(session.harness)?.holdsTranscriptOpen !== true) return;
94384
+ reportedFiles.delete(file2);
94385
+ reportedFiles.set(file2, session);
94386
+ }
94387
+ function needsFileWatch(session) {
94388
+ const status = model.status(session.harness, session.externalId);
94389
+ return status !== void 0 && status !== "ended";
94390
+ }
94391
+ function watchReportedFiles() {
94392
+ for (const [file2, watched] of fileWatches) {
94393
+ if (!needsFileWatch(watched)) closeFileWatch(file2);
94394
+ }
94395
+ const newest = [...reportedFiles].filter(([, session]) => needsFileWatch(session)).slice(-TRANSCRIPT_FILE_WATCHES_MAX);
94396
+ reportedFiles.clear();
94397
+ for (const [file2, session] of newest) {
94398
+ const watched = fileWatches.get(file2);
94399
+ if (watched === void 0) {
94400
+ openFileWatch(file2, session);
94401
+ } else {
94402
+ fileWatches.delete(file2);
94403
+ fileWatches.set(file2, watched);
94404
+ }
94405
+ }
94406
+ while (fileWatches.size > TRANSCRIPT_FILE_WATCHES_MAX) closeFileWatch(fileWatches.keys().next().value);
94407
+ }
94408
+ function openFileWatch(file2, session) {
94409
+ let close2;
94410
+ try {
94411
+ close2 = watchFile(
94412
+ file2,
94413
+ () => noteWrite(session.harness, file2),
94414
+ (error63) => {
94415
+ closeFileWatch(file2);
94416
+ noteWatchFailure(error63);
94417
+ }
94418
+ );
94419
+ } catch (error63) {
94420
+ if (errorCode(error63) !== "ENOENT") noteWatchFailure(error63);
94421
+ return;
94422
+ }
94423
+ if (watchFailing) deps.log("transcript files watched again");
94424
+ watchFailing = false;
94425
+ fileWatches.set(file2, { ...session, close: close2 });
94426
+ const mtimeMs = modifiedMs(file2);
94427
+ if (mtimeMs !== null) {
94428
+ applyWrite(session.harness, session.externalId, mtimeMs, () => fileEndsWithAbortedTurn(session.harness, file2));
94429
+ }
94430
+ }
94431
+ function closeFileWatch(file2) {
94432
+ fileWatches.get(file2)?.close();
94433
+ fileWatches.delete(file2);
94434
+ }
94435
+ function noteWatchFailure(error63) {
94436
+ if (!watchFailing) {
94437
+ deps.log(`\u26A0 transcript file not watched (${errorCode(error63)}), its status waits for hook events and sync passes`);
94438
+ }
94439
+ watchFailing = true;
94440
+ }
94088
94441
  function modifiedMs(transcriptPath) {
94089
94442
  try {
94090
94443
  const mtimeMs = statSync5(transcriptPath).mtimeMs;
@@ -94092,7 +94445,10 @@ function createSessionStatusFeed(deps) {
94092
94445
  statFailing = false;
94093
94446
  return mtimeMs;
94094
94447
  } catch (error63) {
94095
- if (errorCode(error63) === "ENOENT") return null;
94448
+ if (errorCode(error63) === "ENOENT") {
94449
+ closeFileWatch(transcriptPath);
94450
+ return null;
94451
+ }
94096
94452
  if (!statFailing) {
94097
94453
  deps.log(`\u26A0 transcript file not read (${errorCode(error63)}), its status waits for the next sync pass`);
94098
94454
  }
@@ -94100,6 +94456,27 @@ function createSessionStatusFeed(deps) {
94100
94456
  return null;
94101
94457
  }
94102
94458
  }
94459
+ function transcriptTail(transcriptPath) {
94460
+ let fd;
94461
+ try {
94462
+ fd = openSync7(transcriptPath, "r");
94463
+ const size = fstatSync3(fd).size;
94464
+ const buffer = Buffer.alloc(Math.min(size, TRANSCRIPT_TAIL_BYTES));
94465
+ const bytes = readSync5(fd, buffer, 0, buffer.length, size - buffer.length);
94466
+ if (tailFailing) deps.log("transcript ends readable again");
94467
+ tailFailing = false;
94468
+ return buffer.toString("utf8", 0, bytes);
94469
+ } catch (error63) {
94470
+ if (errorCode(error63) === "ENOENT") return null;
94471
+ if (!tailFailing) {
94472
+ deps.log(`\u26A0 transcript end not read (${errorCode(error63)}), a stopped turn shows as working until its deadline`);
94473
+ }
94474
+ tailFailing = true;
94475
+ return null;
94476
+ } finally {
94477
+ if (fd !== void 0) closeSync7(fd);
94478
+ }
94479
+ }
94103
94480
  function noteWrite(harness, transcriptPath) {
94104
94481
  if (writeTimers.has(transcriptPath)) return;
94105
94482
  const timer = setTimeout(() => {
@@ -94111,8 +94488,10 @@ function createSessionStatusFeed(deps) {
94111
94488
  timer.unref?.();
94112
94489
  writeTimers.set(transcriptPath, timer);
94113
94490
  }
94114
- const journal = createLifecycleJournalReader(deps.home, void 0, apply);
94491
+ const journal = createLifecycleJournalReader(deps.home, void 0, applyJournalEntry);
94115
94492
  function settle2() {
94493
+ watchReportedFiles();
94494
+ hookFilesRead.clear();
94116
94495
  armDeadline();
94117
94496
  if (!changedInBatch) return;
94118
94497
  changedInBatch = false;
@@ -94158,9 +94537,9 @@ function createSessionStatusFeed(deps) {
94158
94537
  onEvent(change);
94159
94538
  },
94160
94539
  observeSessions(sessions) {
94161
- for (const { item, mtimeMs } of sessions) {
94540
+ for (const { item, mtimeMs, endsWithAbortedTurn } of sessions) {
94162
94541
  learnPair(item.harness, item.harnessSessionId);
94163
- applyWrite(item.harness, item.harnessSessionId, mtimeMs);
94542
+ applyWrite(item.harness, item.harnessSessionId, mtimeMs, () => endsWithAbortedTurn === true);
94164
94543
  }
94165
94544
  releaseHeld();
94166
94545
  settle2();
@@ -94169,6 +94548,7 @@ function createSessionStatusFeed(deps) {
94169
94548
  const floorMs = lifecycleJournalRetentionFloorMs(now());
94170
94549
  model.forgetBefore(floorMs);
94171
94550
  held = held.filter((entry) => Date.parse(entry.at) >= floorMs);
94551
+ settle2();
94172
94552
  journal.deleteExpired(now());
94173
94553
  },
94174
94554
  status: (harness, externalId) => model.status(harness, externalId),
@@ -94177,9 +94557,17 @@ function createSessionStatusFeed(deps) {
94177
94557
  if (deadlineTimer) clearTimeout(deadlineTimer);
94178
94558
  for (const timer of writeTimers.values()) clearTimeout(timer);
94179
94559
  writeTimers.clear();
94560
+ for (const watched of fileWatches.values()) watched.close();
94561
+ fileWatches.clear();
94180
94562
  }
94181
94563
  };
94182
94564
  }
94565
+ function watchFileWrites(file2, onWrite, onError2) {
94566
+ const watcher = fsWatch2(file2, onWrite);
94567
+ watcher.on("error", onError2);
94568
+ watcher.unref();
94569
+ return () => watcher.close();
94570
+ }
94183
94571
  function errorCode(error63) {
94184
94572
  return error63 instanceof Error && "code" in error63 && typeof error63.code === "string" ? error63.code : "unknown";
94185
94573
  }
@@ -94238,6 +94626,8 @@ function createWatchLoop(deps) {
94238
94626
  const newTranscripts = /* @__PURE__ */ new Map();
94239
94627
  let newSessionTimer = null;
94240
94628
  let newSessionPassDue = false;
94629
+ let newSessionRunning = false;
94630
+ let failedBesidePass = null;
94241
94631
  function onEvent(change) {
94242
94632
  if (stopped) return;
94243
94633
  if (change === "lifecycle") lifecycleChanged = true;
@@ -94269,12 +94659,19 @@ function createWatchLoop(deps) {
94269
94659
  newSessionPassDue = false;
94270
94660
  return;
94271
94661
  }
94272
- if (running || startupOwed || now() < retryNotBefore) return;
94662
+ if (newSessionRunning) return;
94663
+ if (now() < retryNotBefore) {
94664
+ newSessionTimer ??= setTimeout(() => {
94665
+ newSessionTimer = null;
94666
+ void newSessionPass();
94667
+ }, retryNotBefore - now());
94668
+ return;
94669
+ }
94273
94670
  newSessionPassDue = false;
94274
94671
  const transcripts = [...newTranscripts];
94275
94672
  newTranscripts.clear();
94276
94673
  if (transcripts.length === 0) return;
94277
- running = true;
94674
+ newSessionRunning = true;
94278
94675
  deps.log(`${transcripts.length} new transcript file(s), uploading new session records now`);
94279
94676
  const startedAt = now();
94280
94677
  const known = new Set(knownSessions);
@@ -94284,21 +94681,22 @@ function createWatchLoop(deps) {
94284
94681
  isNew: (session) => !known.has(sessionKey(session.item))
94285
94682
  });
94286
94683
  for (const session of summary.sessions) rememberSession(session);
94287
- if (summary.upserted > 0) deps.onNewSessionsUploaded?.(now());
94288
94684
  } catch (error63) {
94289
94685
  const holdMs = Math.max(FAILED_PASS_RETRY_MS, retryAfterMs(error63) ?? 0);
94290
94686
  retryNotBefore = Math.max(retryNotBefore, now() + holdMs);
94291
- deps.onPassResult?.(failedPassResult("new-session", startedAt, error63));
94687
+ const result = failedPassResult("new-session", startedAt, error63);
94688
+ if (running) failedBesidePass = result;
94689
+ else deps.onPassResult?.(result);
94292
94690
  deps.log(
94293
94691
  `new session records not uploaded (${describeError(error63)}), the next pass uploads them in ${describeDelay(holdMs)}`
94294
94692
  );
94295
94693
  }
94296
94694
  for (const [transcript] of transcripts) rememberTranscript(transcript);
94297
- running = false;
94298
- runNext("new-session");
94695
+ newSessionRunning = false;
94696
+ void newSessionPass();
94299
94697
  }
94300
- function runNext(finished) {
94301
- if (queued === null || finished === "regular" && newSessionPassDue) void newSessionPass();
94698
+ function runNext() {
94699
+ void newSessionPass();
94302
94700
  if (queued === null || running) return;
94303
94701
  const next = queued === "steady" ? "net" : "change";
94304
94702
  queued = null;
@@ -94385,6 +94783,7 @@ function createWatchLoop(deps) {
94385
94783
  upserted: summary.upserted,
94386
94784
  ...summary.diagnostic && { diagnostic: summary.diagnostic }
94387
94785
  });
94786
+ if (failedBesidePass !== null && now() < retryNotBefore) deps.onPassResult?.(failedBesidePass);
94388
94787
  } catch (error63) {
94389
94788
  consecutiveFailures += 1;
94390
94789
  const signature = errorSignature(error63);
@@ -94400,7 +94799,7 @@ function createWatchLoop(deps) {
94400
94799
  Math.min(FAILED_PASS_RETRY_MS * 2 ** (streakCount - 1), FAILED_PASS_BACKOFF_CAP_MS),
94401
94800
  retryAfterMs(error63) ?? 0
94402
94801
  );
94403
- retryNotBefore = now() + holdMs;
94802
+ retryNotBefore = Math.max(retryNotBefore, now() + holdMs);
94404
94803
  lifecycleChanged ||= consultLifecycle;
94405
94804
  deps.onPassResult?.(failedPassResult(kind, startedAt, error63));
94406
94805
  const failLine = `${kind} pass failed (${describeError(error63)})`;
@@ -94429,8 +94828,9 @@ function createWatchLoop(deps) {
94429
94828
  }
94430
94829
  }
94431
94830
  running = false;
94831
+ failedBesidePass = null;
94432
94832
  scheduleCompletionCheck();
94433
- runNext("regular");
94833
+ runNext();
94434
94834
  }
94435
94835
  function scheduleCompletionCheck() {
94436
94836
  if (completionTimer) {
@@ -94517,7 +94917,7 @@ function watchEventChange(root, filename) {
94517
94917
  }
94518
94918
  function watchSessionStores(roots, onEvent, onLost) {
94519
94919
  const watchers = roots.map((root) => {
94520
- const watcher = fsWatch2(root.dir, { recursive: root.recursive }, (_type, filename) => {
94920
+ const watcher = fsWatch3(root.dir, { recursive: root.recursive }, (_type, filename) => {
94521
94921
  const change = watchEventChange(root, filename);
94522
94922
  if (change !== "ignore") onEvent(change);
94523
94923
  });
@@ -94531,10 +94931,12 @@ function watchSessionStores(roots, onEvent, onLost) {
94531
94931
  function newSessionSyncOptions(shared, sinceMs, isNew) {
94532
94932
  return {
94533
94933
  lifecycleJournal: shared.lifecycleJournal,
94534
- codexThreadLinks: shared.codexThreadLinks,
94535
- codexThreadReads: shared.codexThreadReads,
94536
94934
  log: shared.log,
94537
94935
  enrollCommand: shared.enrollCommand,
94936
+ onRecordsUpserted: shared.onRecordsUpserted,
94937
+ recordTurns: shared.recordTurns,
94938
+ codexThreadLinks: shared.codexThreadLinks?.copyForSidePass(),
94939
+ codexThreadReads: shared.codexThreadReads?.copyForSidePass(),
94538
94940
  explicitSince: String(STEADY_SYNC_WINDOW_DAYS),
94539
94941
  narrowSinceMs: sinceMs,
94540
94942
  isNewSession: isNew
@@ -94653,6 +95055,7 @@ function startWatch(config2, homeDir, runtime = { version: "checkout" }) {
94653
95055
  });
94654
95056
  const writeStatus = (patch) => statusWriter.write(patch);
94655
95057
  writeStatus({});
95058
+ const recordUpload = () => writeStatus({ lastUploadAt: (/* @__PURE__ */ new Date()).toISOString() });
94656
95059
  const diagnostics = createDiagnosticsReporter(() => loadCredentials(config2.appEnv));
94657
95060
  const checkIn = () => {
94658
95061
  let current;
@@ -94734,6 +95137,7 @@ function startWatch(config2, homeDir, runtime = { version: "checkout" }) {
94734
95137
  const previewRegistrations = /* @__PURE__ */ new Map();
94735
95138
  const codexThreadLinks = new CodexThreadLinks();
94736
95139
  const codexThreadReads = new CodexThreadReads();
95140
+ const recordTurns = new RecordTurns();
94737
95141
  const loop = createWatchLoop({
94738
95142
  runPass: async (options) => {
94739
95143
  let uploaded = 0;
@@ -94753,7 +95157,9 @@ function startWatch(config2, homeDir, runtime = { version: "checkout" }) {
94753
95157
  uploaded += 1;
94754
95158
  if (uploaded === 1 || uploaded % 10 === 0)
94755
95159
  log3(`Transcript progress: ${uploaded} uploaded this pass.`);
94756
- }
95160
+ },
95161
+ onRecordsUpserted: recordUpload,
95162
+ recordTurns
94757
95163
  };
94758
95164
  const catchUp = createRecentCatchup(async (narrowSinceMs) => {
94759
95165
  if (readCollectorControl(config2.appEnv, home).paused) return false;
@@ -94798,10 +95204,15 @@ function startWatch(config2, homeDir, runtime = { version: "checkout" }) {
94798
95204
  }
94799
95205
  return summary;
94800
95206
  },
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() }),
95207
+ runNewSessionPass: ({ sinceMs, isNew }) => runSyncPass(config2, newSessionSyncOptions({
95208
+ lifecycleJournal: journal,
95209
+ log: log3,
95210
+ enrollCommand: runtime.enrollCommand,
95211
+ onRecordsUpserted: recordUpload,
95212
+ recordTurns,
95213
+ codexThreadLinks,
95214
+ codexThreadReads
95215
+ }, sinceMs, isNew)),
94805
95216
  paused: () => readCollectorControl(config2.appEnv, home).paused,
94806
95217
  onPassResult: (result) => writeStatus({
94807
95218
  lastPass: {
@@ -94814,7 +95225,6 @@ function startWatch(config2, homeDir, runtime = { version: "checkout" }) {
94814
95225
  ...result.diagnostic && { diagnostic: result.diagnostic }
94815
95226
  },
94816
95227
  ...result.ok && { lastSyncAt: new Date(result.at).toISOString() },
94817
- ...result.ok && result.upserted > 0 && { lastUploadAt: new Date(result.at).toISOString() },
94818
95228
  consecutiveFailures: result.ok ? 0 : statusWriter.current.consecutiveFailures + 1
94819
95229
  }),
94820
95230
  reportError: reportCollectorError,
@@ -94897,7 +95307,7 @@ import os20 from "node:os";
94897
95307
  import path23 from "node:path";
94898
95308
 
94899
95309
  // ../../apps/syncservice/src/codexReviewFlags.ts
94900
- import { closeSync as closeSync7, lstatSync as lstatSync5, openSync as openSync7, readSync as readSync5, realpathSync as realpathSync4 } from "node:fs";
95310
+ import { closeSync as closeSync8, lstatSync as lstatSync5, openSync as openSync8, readSync as readSync6, realpathSync as realpathSync4 } from "node:fs";
94901
95311
  import path22 from "node:path";
94902
95312
  var pointerSchema = external_exports.object({
94903
95313
  source: external_exports.unknown().optional(),
@@ -94935,14 +95345,14 @@ function readCodexReviewFlags(row, id, home) {
94935
95345
  }
94936
95346
  }
94937
95347
  function readPrefix(file2) {
94938
- const fd = openSync7(file2, "r");
95348
+ const fd = openSync8(file2, "r");
94939
95349
  const buffer = Buffer.alloc(MAX_PREFIX_BYTES);
94940
95350
  let used = 0;
94941
95351
  let first = -1;
94942
95352
  let second = -1;
94943
95353
  try {
94944
95354
  while (used < buffer.length && second < 0) {
94945
- const bytes = readSync5(fd, buffer, used, Math.min(4096, buffer.length - used), null);
95355
+ const bytes = readSync6(fd, buffer, used, Math.min(4096, buffer.length - used), null);
94946
95356
  if (bytes === 0) break;
94947
95357
  const end = used + bytes;
94948
95358
  for (let i = used; i < end; i++) {
@@ -94956,7 +95366,7 @@ function readPrefix(file2) {
94956
95366
  used = end;
94957
95367
  }
94958
95368
  } finally {
94959
- closeSync7(fd);
95369
+ closeSync8(fd);
94960
95370
  }
94961
95371
  if (first < 0 || second < 0) return null;
94962
95372
  try {
@@ -95231,7 +95641,7 @@ function applyLocalSessionFlags(sessions, machineId, localFlags, localKey, claud
95231
95641
  };
95232
95642
  });
95233
95643
  }
95234
- async function fetchMemberSessions(credentials, fetchImpl = fetch, options = { windowHours: RECENT_SESSION_WINDOW_MS / 36e5, limit: RECENT_SESSION_VISIBLE_LIMIT }) {
95644
+ async function fetchMemberSessions(credentials, fetchImpl = fetch, options = { windowHours: RECENT_SESSION_WINDOW_MS / 36e5, limit: RECENT_SESSION_PAGE_MAX }) {
95235
95645
  const { windowHours, limit } = recentSessionOptionsSchema.parse(options);
95236
95646
  try {
95237
95647
  const query = new URLSearchParams({ view: "recent", windowHours: String(windowHours), limit: String(limit) });
@@ -95457,7 +95867,7 @@ async function probeClaudeDesktopSessions(wanted, installed, reportFault, root =
95457
95867
 
95458
95868
  // src/menuModel.ts
95459
95869
  import { randomUUID as randomUUID5 } from "node:crypto";
95460
- import { mkdirSync as mkdirSync17, rmSync as rmSync10, watch as fsWatch3 } from "node:fs";
95870
+ import { mkdirSync as mkdirSync17, rmSync as rmSync10, watch as fsWatch4 } from "node:fs";
95461
95871
  import path34 from "node:path";
95462
95872
 
95463
95873
  // src/menu.ts
@@ -95487,7 +95897,7 @@ import { accessSync, constants as constants2, readFileSync as readFileSync15 } f
95487
95897
  import path28 from "node:path";
95488
95898
 
95489
95899
  // src/version.ts
95490
- var CLI_VERSION = true ? "0.4.20" : "0.0.0-checkout";
95900
+ var CLI_VERSION = true ? "0.4.22" : "0.0.0-checkout";
95491
95901
  var CLI_RELEASE_BUILD = true ? true : false;
95492
95902
  var CLI_PACKAGE_NAME = "@usepowerplant/cli";
95493
95903
 
@@ -96177,8 +96587,8 @@ function syncHeader(i) {
96177
96587
  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
96588
  details.push(describeStoredCaptureSummary(i.credentials));
96179
96589
  }
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)) {
96590
+ const uploadedAt = lastUploadTime(i);
96591
+ if (!i.control.paused && (uploadedAt === void 0 || i.now - uploadedAt > 36e5)) {
96182
96592
  details.push(lastUploadLine(i));
96183
96593
  }
96184
96594
  return { type: "header", title, ...details.length > 0 && { subtitle: details.join(" \xB7 ") } };
@@ -96186,7 +96596,7 @@ function syncHeader(i) {
96186
96596
  var AGENT_TITLE_MAX = 48;
96187
96597
  function recentAgentItems(i) {
96188
96598
  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));
96599
+ 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").sort((a, b2) => Number(b2.agent?.status === "working") - Number(a.agent?.status === "working"));
96190
96600
  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
96601
  if (i.memberSessionsIncomplete) rows.push({
96192
96602
  type: "status",
@@ -96214,7 +96624,7 @@ function agentRow(session, i) {
96214
96624
  const harness = isLegacySessionSource(session.source) ? harnessFromLegacySessionSource(session.source) : null;
96215
96625
  const harnessName = harness?.kind === "harness" ? harness.harness : session.source;
96216
96626
  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];
96627
+ const statusLabel = status === "waiting" ? LOCAL_SESSION_STATUS_LABELS.waiting : null;
96218
96628
  const desktopSessionId = localMachine && session.source === "claude-code" ? i.claudeDesktopSessions?.get(session.externalId)?.sessionId : void 0;
96219
96629
  const app = desktopSessionId !== void 0 ? "Claude desktop" : isLegacySessionSource(session.source) ? describeSessionSource(session.source) : session.source;
96220
96630
  const links = sessionLinks(session, i.config.webUrl, {
@@ -96298,10 +96708,16 @@ function menuContentKey(payload) {
96298
96708
  });
96299
96709
  return JSON.stringify({ ...shown, desktopDiagnostic: void 0 });
96300
96710
  }
96711
+ function lastUploadTime(i) {
96712
+ if (!("status" in i.daemon)) return void 0;
96713
+ const { lastSyncAt, lastUploadAt } = i.daemon.status;
96714
+ const times = [lastSyncAt, lastUploadAt].map((at) => Date.parse(at ?? "")).filter((at) => !Number.isNaN(at));
96715
+ return times.length === 0 ? void 0 : Math.max(...times);
96716
+ }
96301
96717
  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))}`;
96718
+ const uploadedAt = lastUploadTime(i);
96719
+ if (uploadedAt === void 0) return "No successful upload yet";
96720
+ return `Last upload ${ago(i.now - uploadedAt)}`;
96305
96721
  }
96306
96722
  function connectionRow(connection, envArgs, now) {
96307
96723
  const name = CLIENT_NAMES[connection.client];
@@ -97649,14 +98065,14 @@ function createMenuModel(deps) {
97649
98065
  renewDue ||= renew;
97650
98066
  if (stopped || sources === null || awaitingRefresh || renderPending) return;
97651
98067
  renderPending = true;
97652
- const run = () => {
98068
+ const run2 = () => {
97653
98069
  renderPending = false;
97654
98070
  renderTimer = null;
97655
98071
  if (!stopped) renderNow();
97656
98072
  };
97657
98073
  const wait = lastContentWriteAt + MENU_WRITE_INTERVAL_MS - now();
97658
- if (wait <= 0) queueMicrotask(run);
97659
- else renderTimer = setTimeout(run, wait);
98074
+ if (wait <= 0) queueMicrotask(run2);
98075
+ else renderTimer = setTimeout(run2, wait);
97660
98076
  }
97661
98077
  function renderNow() {
97662
98078
  if (sources === null || awaitingRefresh) return;
@@ -97894,7 +98310,7 @@ function startMenuModel(sources) {
97894
98310
  const dir = path34.dirname(menuRefreshRequestPath(appEnv, host.home));
97895
98311
  mkdirSync17(dir, { recursive: true, mode: 448 });
97896
98312
  const openFile = path34.basename(menuOpenRequestPath(appEnv, host.home));
97897
- const watcher = fsWatch3(dir, (_event, filename) => onRequest(filename === openFile ? "menu-open" : "command"));
98313
+ const watcher = fsWatch4(dir, (_event, filename) => onRequest(filename === openFile ? "menu-open" : "command"));
97898
98314
  watcher.on("error", onLost);
97899
98315
  return () => watcher.close();
97900
98316
  },
@@ -97906,12 +98322,12 @@ function startMenuModel(sources) {
97906
98322
  // src/cli.ts
97907
98323
  import { spawn as spawn4 } from "node:child_process";
97908
98324
  import { randomUUID as randomUUID6 } from "node:crypto";
97909
- import { mkdirSync as mkdirSync20, openSync as openSync10, realpathSync as realpathSync5 } from "node:fs";
98325
+ import { mkdirSync as mkdirSync20, openSync as openSync11, realpathSync as realpathSync5 } from "node:fs";
97910
98326
  import path37 from "node:path";
97911
98327
  import { parseArgs } from "node:util";
97912
98328
 
97913
98329
  // src/doctor.ts
97914
- import { closeSync as closeSync8, fstatSync as fstatSync3, openSync as openSync8, readSync as readSync6 } from "node:fs";
98330
+ import { closeSync as closeSync9, fstatSync as fstatSync4, openSync as openSync9, readSync as readSync7 } from "node:fs";
97915
98331
  var DOCTOR_LOG_TAIL_LINES = 50;
97916
98332
  var LOG_TAIL_READ_BYTES = 256 * 1024;
97917
98333
  function installLine(install) {
@@ -97927,23 +98343,23 @@ function redactedLogTail(raw) {
97927
98343
  function readDoctorLogTail(path38) {
97928
98344
  let fd;
97929
98345
  try {
97930
- fd = openSync8(path38, "r");
98346
+ fd = openSync9(path38, "r");
97931
98347
  } catch (error63) {
97932
98348
  if (error63.code === "ENOENT") return { path: path38, lines: null };
97933
98349
  return { path: path38, lines: null, readError: error63 instanceof Error ? error63.message : String(error63) };
97934
98350
  }
97935
98351
  try {
97936
- const size = fstatSync3(fd).size;
98352
+ const size = fstatSync4(fd).size;
97937
98353
  const offset = Math.max(0, size - LOG_TAIL_READ_BYTES);
97938
98354
  const buffer = Buffer.alloc(Math.min(size, LOG_TAIL_READ_BYTES));
97939
- readSync6(fd, buffer, 0, buffer.length, offset);
98355
+ readSync7(fd, buffer, 0, buffer.length, offset);
97940
98356
  let text = buffer.toString("utf8");
97941
98357
  if (offset > 0) text = text.slice(text.indexOf("\n") + 1);
97942
98358
  return { path: path38, lines: redactedLogTail(text) };
97943
98359
  } catch (error63) {
97944
98360
  return { path: path38, lines: null, readError: error63 instanceof Error ? error63.message : String(error63) };
97945
98361
  } finally {
97946
- closeSync8(fd);
98362
+ closeSync9(fd);
97947
98363
  }
97948
98364
  }
97949
98365
  function renderDoctorReport(i) {
@@ -97960,7 +98376,7 @@ function renderDoctorReport(i) {
97960
98376
  );
97961
98377
  const unsupported = unsupportedVersionNotice(i.cliVersion, i.oldestSupportedVersion);
97962
98378
  if (unsupported !== null) lines.push(unsupported);
97963
- lines.push(`Environment: ${i.config.appEnv} (API ${i.config.apiUrl})`);
98379
+ lines.push(`Environment: ${i.config.appEnv} (API ${i.credentials?.apiUrl ?? i.config.apiUrl})`);
97964
98380
  lines.push(`Node: ${i.nodeVersion} (${i.platform})`);
97965
98381
  lines.push(installLine(i.install));
97966
98382
  lines.push(enrollmentLine(i));
@@ -98198,7 +98614,7 @@ function pausedSince(control, now) {
98198
98614
  }
98199
98615
  function pausedNotice(control, resumeCommand2, now) {
98200
98616
  if (!control.paused) return null;
98201
- return `! ${pausedSince(control, now)}. Nothing uploads until you run \`${resumeCommand2}\`.`;
98617
+ return `! ${pausedSince(control, now)}. To resume sync, run \`${resumeCommand2}\`.`;
98202
98618
  }
98203
98619
  function pausedSyncNotice(control, resumeCommand2, now, dryRun) {
98204
98620
  if (!control.paused || dryRun) return pausedNotice(control, resumeCommand2, now);
@@ -98218,7 +98634,7 @@ async function offerResume(deps, options) {
98218
98634
  );
98219
98635
  return "resumed";
98220
98636
  }
98221
- output2.log(`Nothing uploads until you run \`${deps.resumeCommand}\``);
98637
+ output2.log(`To resume sync, run \`${deps.resumeCommand}\``);
98222
98638
  return "still-paused";
98223
98639
  }
98224
98640
  async function confirmResume(deps, output2) {
@@ -98403,7 +98819,7 @@ async function runInit(deps) {
98403
98819
  if (pauseOffer === "still-paused") {
98404
98820
  command.log("! Syncing is paused");
98405
98821
  command.log("");
98406
- command.detail(`Nothing uploads until you run \`${deps.pause.resumeCommand}\``);
98822
+ command.detail(`To resume sync, run \`${deps.pause.resumeCommand}\``);
98407
98823
  if (connectIncomplete) command.detail(`Finish MCP setup: \`${deps.connectCommand}\``);
98408
98824
  return "paused";
98409
98825
  }
@@ -98558,113 +98974,187 @@ function runResumeSession(source, sessionId, deps = {}) {
98558
98974
  }
98559
98975
 
98560
98976
  // src/statusReport.ts
98561
- function automaticUpdatesLine(control) {
98562
- switch (automaticUpdatesChoice(control)) {
98563
- case "on":
98564
- return "Automatic updates: on, the background sync installs new releases within an hour.";
98565
- case "off":
98566
- return "Automatic updates: off, turn on with: powerplant update --automatic on";
98567
- case "not-chosen":
98568
- return "Automatic updates: not turned on yet, keep this CLI current with: powerplant update --automatic on";
98569
- }
98977
+ var block = (line, ...details) => ({ line, details });
98978
+ function run(i, args) {
98979
+ return `\`powerplant ${args}${i.config.appEnv === "production" ? "" : ` --env ${i.config.appEnv}`}\``;
98980
+ }
98981
+ function renderStatus(i, out) {
98982
+ const stopping = syncProblems(i);
98983
+ const sections = [
98984
+ ...stopping.map((problem) => [problem]),
98985
+ machineBlock(i, stopping.length === 0),
98986
+ ...warnings(i).map((warning2) => [warning2]),
98987
+ i.connections.map((connection) => mcpBlock(i, connection)),
98988
+ [cliBlock(i, stopping.length === 0)]
98989
+ ];
98990
+ sections.filter((section) => section.length > 0).forEach((section, index) => {
98991
+ if (index > 0) out.log("");
98992
+ for (const { line, details } of section) {
98993
+ out.log(line);
98994
+ details.forEach(out.detail);
98995
+ }
98996
+ });
98570
98997
  }
98571
- function renderStatus(i) {
98572
- const lines = [];
98573
- if (i.config.appEnv !== "production") lines.push(`Environment: ${i.config.appEnv} (API ${i.config.apiUrl})`);
98998
+ function daemonStatus(i) {
98999
+ return "status" in i.daemon ? i.daemon.status : null;
99000
+ }
99001
+ function syncProblems(i) {
98574
99002
  if (i.credentialsError !== void 0) {
98575
- lines.push(`\u2717 ${i.credentialsError}`);
98576
- } else if (!i.credentials) {
98577
- lines.push("Not enrolled, run: powerplant init");
98578
- } else {
98579
- const orgLine = i.credentials.orgName ? ` into org "${i.credentials.orgName}"` : " (org not recorded, re-enroll to see it)";
98580
- lines.push(
98581
- `Enrolled as "${i.credentials.machineName}" (${i.credentials.machineId})${orgLine} on ${i.credentials.enrolledAt}.`
99003
+ return [block(`\u2717 ${i.credentialsError}`, `Run ${run(i, "init")} to enroll again.`)];
99004
+ }
99005
+ if (i.credentials === null) return [block("! Not enrolled", `Run ${run(i, "init")}.`)];
99006
+ const problems = [];
99007
+ if (i.control.paused) {
99008
+ problems.push(block(`! ${pausedSince(i.control, i.now)}`, `To resume sync, run ${run(i, "resume")}.`));
99009
+ }
99010
+ const service = serviceProblem(i);
99011
+ if (service !== null) problems.push(service);
99012
+ const status = i.daemon.state === "running" && !i.control.paused ? i.daemon.status : null;
99013
+ const lastPass = status?.lastPass;
99014
+ if (status && lastPass && !lastPass.ok) {
99015
+ const failures = status.consecutiveFailures;
99016
+ problems.push(
99017
+ block(
99018
+ `\u2717 Last sync failed, ${lastPass.error ?? "unknown error"}`,
99019
+ `${failures} failure${failures === 1 ? "" : "s"} in a row. Run ${run(i, "doctor")} for diagnostics.`
99020
+ )
98582
99021
  );
98583
- lines.push(`${describeStoredCaptureSummary(i.credentials)}.`);
98584
- for (const entry of i.protectedSessions ?? []) {
98585
- const sessions = `${entry.sessionCount} recent agent session${entry.sessionCount === 1 ? "" : "s"}`;
98586
- lines.push(
98587
- protectedRootReview(i.credentials, entry.root) === "blocked" ? `! macOS is blocking Powerplant from reading ${entry.displayRoot} (${sessions}). Those sessions are staying local. ${MACOS_BLOCKED_FOLDER_REMEDY}` : `! Repository matching is not enabled for ${sessions} under ${entry.displayRoot}. Sessions outside folders you explicitly added are staying local, run \`powerplant init\` to review Git repository scanning.`
98588
- );
98589
- }
98590
99022
  }
98591
- const paused = pausedNotice(i.control, i.resumeCommand, i.now);
98592
- if (paused !== null) lines.push(paused);
98593
- lines.push(automaticUpdatesLine(i.control));
99023
+ return problems;
99024
+ }
99025
+ function unavailableProbe(i) {
99026
+ return block(
99027
+ `! Background sync status unavailable${i.serviceError ? `, ${i.serviceError}` : ""}`,
99028
+ `Run ${run(i, "doctor")} for diagnostics.`
99029
+ );
99030
+ }
99031
+ function serviceProblem(i) {
98594
99032
  switch (i.service) {
98595
99033
  case "unavailable":
98596
- lines.push(`Background sync: status unavailable${i.serviceError ? `, ${i.serviceError}` : ""}. Run powerplant doctor${i.config.appEnv === "production" ? "" : ` --env ${i.config.appEnv}`} for diagnostics.`);
98597
99034
  break;
98598
99035
  case "unsupported":
98599
- lines.push(
98600
- "Background sync: unsupported on this platform (macOS-only for now), run `powerplant sync` on a schedule instead."
99036
+ return block(
99037
+ "! Background sync is not supported on this platform yet",
99038
+ `Run ${run(i, "sync")} on a schedule instead.`
98601
99039
  );
98602
- break;
98603
99040
  case "not-installed":
98604
- lines.push("Background sync: not installed, run: powerplant init");
98605
- break;
99041
+ return block("! Background sync is not installed", `Run ${run(i, "init")}.`);
98606
99042
  case "not-loaded":
98607
- lines.push("Background sync: installed but not loaded, run: powerplant restart");
98608
- break;
99043
+ return block("! Background sync is installed but not loaded", `Run ${run(i, "restart")}.`);
98609
99044
  case "loaded":
98610
- lines.push(...renderDaemon(i));
98611
99045
  break;
98612
99046
  }
98613
- lines.push(
98614
- `CLI: powerplant ${i.cliVersion}` + (isUpdateAvailable(i.cliVersion, i.latestVersion) ? `, ${i.latestVersion} is available: powerplant update` : "")
98615
- );
98616
- const unsupported = unsupportedVersionNotice(i.cliVersion, i.oldestSupportedVersion);
98617
- if (unsupported !== null) lines.push(unsupported);
98618
- return lines;
98619
- }
98620
- function renderDaemon(i) {
98621
- if (i.daemon.state === "unreadable") {
98622
- return [
98623
- "Background sync: loaded, but its status file is unreadable (written by a different version?), run: powerplant restart"
98624
- ];
98625
- }
98626
- if (i.daemon.state === "not-running") {
98627
- return [
98628
- `Background sync: loaded, but no background sync status is available, starting up, or running a version from before status reporting. If this persists: powerplant restart. Log: ${serviceLogPath(i.config.appEnv)}`
98629
- ];
99047
+ switch (i.daemon.state) {
99048
+ case "unreadable":
99049
+ return block(
99050
+ "! Background sync status file is unreadable",
99051
+ `A different version may have written it. Run ${run(i, "restart")}.`
99052
+ );
99053
+ case "not-running":
99054
+ if (i.service === "unavailable") return unavailableProbe(i);
99055
+ return block(
99056
+ "! Background sync has not reported its status",
99057
+ `It may still be starting. If this persists, run ${run(i, "restart")}.`,
99058
+ `The log is at ${serviceLogPath(i.config.appEnv)}.`
99059
+ );
99060
+ case "stalled":
99061
+ return block(`\u2717 Background sync stalled, no heartbeat for ${duration3(i.daemon.staleMs)}`, `Run ${run(i, "restart")}.`);
99062
+ case "running":
99063
+ return null;
98630
99064
  }
98631
- const status = i.daemon.status;
98632
- const lines = [];
98633
- if (i.daemon.state === "stalled") {
98634
- lines.push(
98635
- `\u2717 Background sync stalled, no heartbeat for ${duration3(i.daemon.staleMs)} (pid ${status.pid}). Run: powerplant restart`
98636
- );
98637
- } else {
98638
- lines.push(
98639
- `Background sync: running${i.control.paused ? ", syncing paused" : ""} (version ${status.version}, pid ${status.pid}, started ${status.startedAt}).`
99065
+ }
99066
+ function machineBlock(i, syncing) {
99067
+ const credentials = i.credentials;
99068
+ if (credentials === null) return [];
99069
+ const org = credentials.orgName;
99070
+ const line = syncing ? `\u2713 Syncing ${credentials.machineName}${org ? ` to ${org}` : ""}` : `${credentials.machineName}${org ? ` in ${org}` : ""}`;
99071
+ const details = [`${describeStoredCaptureSummary(credentials)}.`];
99072
+ const status = daemonStatus(i);
99073
+ if (status !== null) {
99074
+ const signals = describeSyncSignals(
99075
+ {
99076
+ lastSessionActivityAt: status.lastUploadAt ?? null,
99077
+ lastCheckedInAt: status.lastPass?.at ?? null,
99078
+ syncPaused: null,
99079
+ lastSyncErrorAt: status.lastPass && !status.lastPass.ok ? status.lastPass.at : null
99080
+ },
99081
+ new Date(i.now),
99082
+ "last upload unmeasured"
98640
99083
  );
99084
+ details.push(signals ?? "No sync has run since background sync started.");
98641
99085
  }
98642
- if (status.version !== i.cliVersion) {
98643
- lines.push(
98644
- ` The daemon is on ${status.version}, this CLI is ${i.cliVersion}, it restarts onto the installed version within minutes, or now via: powerplant restart`
99086
+ return [block(line, ...details)];
99087
+ }
99088
+ function warnings(i) {
99089
+ const found = [];
99090
+ if (isUnsupportedVersion(i.cliVersion, i.oldestSupportedVersion)) {
99091
+ found.push(
99092
+ block(
99093
+ `! powerplant ${i.cliVersion} is no longer supported, the oldest supported is ${i.oldestSupportedVersion}`,
99094
+ `Run ${run(i, "update")} (or npm install -g @usepowerplant/cli). Unsupported versions can stop working without notice.`
99095
+ )
98645
99096
  );
98646
99097
  }
98647
- const signals = describeSyncSignals(
98648
- {
98649
- lastSessionActivityAt: status.lastUploadAt ?? null,
98650
- lastCheckedInAt: status.lastPass?.at ?? null,
98651
- syncPaused: null,
98652
- lastSyncErrorAt: status.lastPass && !status.lastPass.ok ? status.lastPass.at : null
98653
- },
98654
- new Date(i.now),
98655
- "last upload unmeasured"
98656
- );
98657
- lines.push(signals === null ? " No pass has run yet in this run." : ` ${signals}`);
98658
- if (status.lastPass && !status.lastPass.ok) {
98659
- lines.push(
98660
- ` \u2717 Last pass failed: ${status.lastPass.error ?? "unknown error"} (${status.consecutiveFailures} consecutive failure${status.consecutiveFailures === 1 ? "" : "s"}).`
99098
+ for (const entry of i.protectedSessions ?? []) {
99099
+ const sessions = `${entry.sessionCount} recent agent session${entry.sessionCount === 1 ? "" : "s"}`;
99100
+ const staying = entry.sessionCount === 1 ? "is staying local" : "are staying local";
99101
+ found.push(
99102
+ i.credentials !== null && protectedRootReview(i.credentials, entry.root) === "blocked" ? block(
99103
+ `! macOS is blocking Powerplant from reading ${entry.displayRoot}`,
99104
+ `${sessions} ${staying}. ${MACOS_BLOCKED_FOLDER_REMEDY}`
99105
+ ) : block(
99106
+ `! ${sessions} under ${entry.displayRoot} ${staying}`,
99107
+ `Run ${run(i, "init")} to review Git repository scanning.`
99108
+ )
98661
99109
  );
98662
99110
  }
98663
- for (const hook of status.hooks ?? []) {
99111
+ if (i.service === "unavailable" && i.daemon.state !== "not-running") found.push(unavailableProbe(i));
99112
+ if (i.mcpPreferencesError !== void 0) found.push(block("! MCP choices can't be read", i.mcpPreferencesError));
99113
+ for (const hook of daemonStatus(i)?.hooks ?? []) {
98664
99114
  const silence = hookWarning(hook, i.now);
98665
- if (silence) lines.push(` ! ${silence.problem}. ${silence.fix}`);
99115
+ if (silence) found.push(block(`! ${silence.problem}`, silence.fix));
98666
99116
  }
98667
- return lines;
99117
+ return found;
99118
+ }
99119
+ function mcpBlock(i, connection) {
99120
+ const name = `${AGENT_DISPLAY_NAMES[connection.client]}${connection.target ? ` (${connection.target})` : ""}`;
99121
+ const fix = `Run ${run(i, `connect --client ${connection.client}`)}.`;
99122
+ const current = connectionIsCurrent(connection, i.now);
99123
+ const checked = current ? "" : `, checked ${ago(i.now - Date.parse(connection.checkedAt))}`;
99124
+ switch (connection.state) {
99125
+ case "signed-in":
99126
+ return block(`${current ? "\u2713 " : ""}${name}: MCP connected${checked}`);
99127
+ case "sign-in-needed":
99128
+ return block(`! ${name}: MCP sign-in needed${checked}`, fix);
99129
+ case "different-url":
99130
+ return block(
99131
+ `! ${name}: MCP points at a different Powerplant${connection.registeredUrl ? `, ${connection.registeredUrl}` : ""}`,
99132
+ fix
99133
+ );
99134
+ case "not-connected":
99135
+ return i.mcpPreferences?.clients[connection.client]?.optedIn === true ? block(`! ${name}: MCP not connected`, fix) : block(`${name}: MCP not connected`);
99136
+ case "configured":
99137
+ return block(`${name}: MCP configured, sign-in unknown`);
99138
+ case "check-failed":
99139
+ return block(`${name}: MCP check failed${connection.detail ? `, ${connection.detail}` : ""}`);
99140
+ }
99141
+ }
99142
+ function cliBlock(i, syncing) {
99143
+ const details = [];
99144
+ if (i.daemon.state === "running" && i.daemon.status.version !== i.cliVersion) {
99145
+ details.push(
99146
+ `Background sync is on ${i.daemon.status.version}. It restarts onto the installed version within minutes, or now with ${run(i, "restart")}.`
99147
+ );
99148
+ }
99149
+ const automatic = automaticUpdatesChoice(i.control);
99150
+ const version2 = `powerplant ${i.cliVersion}`;
99151
+ if (isUpdateAvailable(i.cliVersion, i.latestVersion) && !isUnsupportedVersion(i.cliVersion, i.oldestSupportedVersion)) {
99152
+ return block(`${version2}, ${i.latestVersion} is available`, ...details, `Run ${run(i, "update")}.`);
99153
+ }
99154
+ if (automatic === "not-chosen" && syncing) {
99155
+ details.push(`Turn them on with ${run(i, "update --automatic on")}.`);
99156
+ }
99157
+ return block(`${version2}, automatic updates ${automatic === "on" ? "on" : "off"}`, ...details);
98668
99158
  }
98669
99159
  function statusJson(i) {
98670
99160
  return {
@@ -98702,7 +99192,9 @@ function statusJson(i) {
98702
99192
  daemon: "status" in i.daemon ? { state: i.daemon.state, ...i.daemon.status } : { state: i.daemon.state },
98703
99193
  paused: i.control.paused,
98704
99194
  automaticUpdates: automaticUpdatesChoice(i.control),
98705
- pausedAt: i.control.pausedAt ?? null
99195
+ pausedAt: i.control.pausedAt ?? null,
99196
+ // The same saved checks the human rendering reads, each with when it ran.
99197
+ mcpConnections: i.connections
98706
99198
  };
98707
99199
  }
98708
99200
  function supportedFlag(cliVersion2, oldestSupportedVersion) {
@@ -98864,7 +99356,7 @@ function createTerminalOutput(options) {
98864
99356
 
98865
99357
  // src/credentialUpgrade.ts
98866
99358
  import { spawn as spawn3 } from "node:child_process";
98867
- import { closeSync as closeSync9, mkdirSync as mkdirSync18, openSync as openSync9 } from "node:fs";
99359
+ import { closeSync as closeSync10, mkdirSync as mkdirSync18, openSync as openSync10 } from "node:fs";
98868
99360
  import path35 from "node:path";
98869
99361
  async function prepareCredentialUpgrade(deps) {
98870
99362
  if (!deps.needsMigration() || !deps.serviceExists() || deps.ready()) return "ready";
@@ -98899,7 +99391,7 @@ async function prepareCredentialUpgrade(deps) {
98899
99391
  }
98900
99392
  async function tryCredentialUpgradeLock(file2) {
98901
99393
  mkdirSync18(path35.dirname(file2), { recursive: true, mode: 448 });
98902
- closeSync9(openSync9(file2, "a", 384));
99394
+ closeSync10(openSync10(file2, "a", 384));
98903
99395
  const child = spawn3("/usr/bin/lockf", ["-k", "-s", "-t", "0", file2, "/bin/sh", "-c", "printf 'ready\\n'; exec /bin/cat >/dev/null"], {
98904
99396
  detached: true,
98905
99397
  stdio: ["pipe", "pipe", "ignore"]
@@ -99639,12 +100131,12 @@ The access you granted these agents stays on your Powerplant account until you r
99639
100131
  latestVersion,
99640
100132
  oldestSupportedVersion,
99641
100133
  protectedSessions,
99642
- resumeCommand: pause.resumeCommand,
100134
+ connections: readHarnessConnections(config2.appEnv, realReadTextFile),
100135
+ ...readMcpPreferencesForMenu(config2.appEnv),
99643
100136
  now: Date.now()
99644
100137
  };
99645
- console.log(
99646
- values.json ? JSON.stringify(statusJson(inputs), null, 2) : renderStatus(inputs).map(styledFor(process.stdout)).join("\n")
99647
- );
100138
+ if (values.json) console.log(JSON.stringify(statusJson(inputs), null, 2));
100139
+ else renderStatus(inputs, terminal.command);
99648
100140
  return;
99649
100141
  }
99650
100142
  case "doctor": {
@@ -99979,7 +100471,7 @@ function startBackgroundUpdate(config2, target) {
99979
100471
  writeProgress: updateProgress.write,
99980
100472
  spawnChild: () => {
99981
100473
  mkdirSync20(path37.dirname(updateLogPath()), { recursive: true });
99982
- const log3 = openSync10(updateLogPath(), "a");
100474
+ const log3 = openSync11(updateLogPath(), "a");
99983
100475
  const child = spawn4(process.execPath, [entryPath(), "update", ...envArgsFor(config2)], {
99984
100476
  detached: true,
99985
100477
  stdio: ["ignore", log3, log3],
@@ -100093,4 +100585,4 @@ undici/lib/web/websocket/frame.js:
100093
100585
  (*! ws. MIT License. Einar Otto Stangvik <einaros@gmail.com> *)
100094
100586
  */
100095
100587
 
100096
- //# debugId=9f447c4d-0ccb-5f45-8960-6853e28aadfb
100588
+ //# debugId=91b4b5e6-fdd5-5127-9372-913ad2bc7af5