slopwaresystems 0.4.16 → 0.4.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +207 -379
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -19054,12 +19054,12 @@ function processSchema(schema, ctx, _params = { path: [], schemaPath: [] }) {
19054
19054
  }
19055
19055
  processor(schema, ctx, _json, params);
19056
19056
  }
19057
- const parent2 = schema._zod.parent;
19058
- if (parent2) {
19057
+ const parent = schema._zod.parent;
19058
+ if (parent) {
19059
19059
  if (!result.ref)
19060
- result.ref = parent2;
19061
- processSchema(parent2, ctx, params);
19062
- ctx.seen.get(parent2).isParent = true;
19060
+ result.ref = parent;
19061
+ processSchema(parent, ctx, params);
19062
+ ctx.seen.get(parent).isParent = true;
19063
19063
  }
19064
19064
  }
19065
19065
  const meta3 = ctx.metadataRegistry.get(schema);
@@ -19322,10 +19322,10 @@ function finalize(ctx, schema) {
19322
19322
  }
19323
19323
  }
19324
19324
  }
19325
- const parent2 = zodSchema._zod.parent;
19326
- if (parent2 && parent2 !== ref) {
19327
- flattenRef(parent2);
19328
- const parentSeen = ctx.seen.get(parent2);
19325
+ const parent = zodSchema._zod.parent;
19326
+ if (parent && parent !== ref) {
19327
+ flattenRef(parent);
19328
+ const parentSeen = ctx.seen.get(parent);
19329
19329
  if (parentSeen?.schema.$ref) {
19330
19330
  schema2.$ref = parentSeen.schema.$ref;
19331
19331
  if (parentSeen.def) {
@@ -26852,16 +26852,16 @@ function generateAgentName(key) {
26852
26852
  return AGENT_NAMES[hashKey(key) % AGENT_NAMES.length] ?? AGENT_NAMES[0];
26853
26853
  }
26854
26854
  var WORKER_TITLES = [
26855
- (parent2) => `${parent2} Jr.`,
26856
- (parent2) => `${parent2} II`,
26857
- (parent2) => `${parent2} III`,
26858
- (parent2) => `${parent2} the Second`,
26859
- (parent2) => `${parent2} the Younger`,
26860
- (parent2) => `${parent2} the Lesser`,
26861
- (parent2) => `${parent2}, Deputised`,
26862
- (parent2) => `Young ${parent2}`,
26863
- (parent2) => `Little ${parent2}`,
26864
- (parent2) => `Second-Best ${parent2}`
26855
+ (parent) => `${parent} Jr.`,
26856
+ (parent) => `${parent} II`,
26857
+ (parent) => `${parent} III`,
26858
+ (parent) => `${parent} the Second`,
26859
+ (parent) => `${parent} the Younger`,
26860
+ (parent) => `${parent} the Lesser`,
26861
+ (parent) => `${parent}, Deputised`,
26862
+ (parent) => `Young ${parent}`,
26863
+ (parent) => `Little ${parent}`,
26864
+ (parent) => `Second-Best ${parent}`
26865
26865
  ];
26866
26866
  var CARRIED_ON = /(,? (?:Jr\.|Sr\.|II|III|IV|Prime|Junior|Minor|Deputised|the (?:Second|Third|Younger|Lesser)))+$/;
26867
26867
  function generateWorkerName(parentName, ref) {
@@ -27079,8 +27079,56 @@ function resolveSession() {
27079
27079
 
27080
27080
  // src/platform/session-file.ts
27081
27081
  import { createHash as createHash2 } from "crypto";
27082
- import { readFileSync as readFileSync3, realpathSync as realpathSync2, rmSync as rmSync3, statSync, readdirSync } from "fs";
27082
+ import { readFileSync as readFileSync4, realpathSync as realpathSync2, rmSync as rmSync3, statSync, readdirSync } from "fs";
27083
27083
  import { dirname as dirname3, join as join3 } from "path";
27084
+
27085
+ // src/platform/process-tree.ts
27086
+ import { execFileSync } from "child_process";
27087
+ import { readFileSync as readFileSync3 } from "fs";
27088
+ var runPs = (args) => execFileSync("ps", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 2e3 });
27089
+ var isPid = (pid) => Number.isSafeInteger(pid) && pid > 0;
27090
+ function fetchProcessCommand(pid) {
27091
+ if (process.platform === "win32" || !isPid(pid)) return "";
27092
+ try {
27093
+ if (process.platform === "linux") return readFileSync3(`/proc/${pid}/cmdline`, "latin1").replace(/\0/g, " ").trim();
27094
+ return runPs(["-o", "command=", "-p", String(pid)]).trim();
27095
+ } catch {
27096
+ return "";
27097
+ }
27098
+ }
27099
+ function fetchParentPid(pid) {
27100
+ if (process.platform === "win32" || !isPid(pid) || pid === 1) return null;
27101
+ try {
27102
+ let text;
27103
+ if (process.platform === "linux") {
27104
+ const stat = readFileSync3(`/proc/${pid}/stat`, "utf8");
27105
+ text = stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1] ?? "";
27106
+ } else text = runPs(["-o", "ppid=", "-p", String(pid)]);
27107
+ const parent = Number(text.trim());
27108
+ return isPid(parent) ? parent : null;
27109
+ } catch {
27110
+ return null;
27111
+ }
27112
+ }
27113
+ function fetchProcessStart(pid) {
27114
+ if (process.platform === "win32" || !isPid(pid)) return null;
27115
+ try {
27116
+ return execFileSync("ps", ["-p", String(pid), "-o", "lstart="], { encoding: "utf8", env: { ...process.env, LC_ALL: "C", TZ: "UTC" }, stdio: ["ignore", "pipe", "ignore"], timeout: 1e3 }).trim() || null;
27117
+ } catch {
27118
+ return null;
27119
+ }
27120
+ }
27121
+ function isProcessAlive(pid) {
27122
+ if (!isPid(pid)) return false;
27123
+ try {
27124
+ process.kill(pid, 0);
27125
+ return true;
27126
+ } catch (error62) {
27127
+ return error62.code === "EPERM";
27128
+ }
27129
+ }
27130
+
27131
+ // src/platform/session-file.ts
27084
27132
  var buildSessionsDir = () => join3(dirname3(buildConfigPath()), "sessions");
27085
27133
  var resolveRealCwd = (cwd) => {
27086
27134
  try {
@@ -27096,21 +27144,13 @@ function persistRememberedSession(cwd, sessionId, conversation) {
27096
27144
  }
27097
27145
  function fetchRememberedSession(cwd, conversation) {
27098
27146
  try {
27099
- return readFileSync3(buildSessionPath(cwd, conversation), "utf8").trim() || null;
27147
+ return readFileSync4(buildSessionPath(cwd, conversation), "utf8").trim() || null;
27100
27148
  } catch (error62) {
27101
27149
  if (error62.code === "ENOENT") return null;
27102
27150
  throw error62;
27103
27151
  }
27104
27152
  }
27105
27153
  var buildHolderPath = (cwd, conversation) => join3(buildSessionsDir(), `${generateLocalStateKey(cwd, conversation)}.holder.json`);
27106
- var isProcessAlive = (pid) => {
27107
- try {
27108
- process.kill(pid, 0);
27109
- return true;
27110
- } catch (error62) {
27111
- return error62.code === "EPERM";
27112
- }
27113
- };
27114
27154
  function claimSessionHolder(cwd, conversation, pid = process.pid) {
27115
27155
  const path2 = buildHolderPath(cwd, conversation);
27116
27156
  for (let attempt = 0; attempt < 2; attempt += 1) {
@@ -27121,7 +27161,7 @@ function claimSessionHolder(cwd, conversation, pid = process.pid) {
27121
27161
  }
27122
27162
  let holder = null;
27123
27163
  try {
27124
- holder = JSON.parse(readFileSync3(path2, "utf8"));
27164
+ holder = JSON.parse(readFileSync4(path2, "utf8"));
27125
27165
  } catch {
27126
27166
  return false;
27127
27167
  }
@@ -27134,7 +27174,7 @@ function claimSessionHolder(cwd, conversation, pid = process.pid) {
27134
27174
  function releaseSessionHolder(cwd, conversation, pid = process.pid) {
27135
27175
  const path2 = buildHolderPath(cwd, conversation);
27136
27176
  try {
27137
- const holder = JSON.parse(readFileSync3(path2, "utf8"));
27177
+ const holder = JSON.parse(readFileSync4(path2, "utf8"));
27138
27178
  if (holder?.pid === pid) rmSync3(path2, { force: true });
27139
27179
  } catch {
27140
27180
  }
@@ -27157,7 +27197,7 @@ function fetchRememberedConversation(cwd, notBefore, harness) {
27157
27197
  if (candidate.endsWith(".writing")) continue;
27158
27198
  try {
27159
27199
  if (statSync(candidate).mtimeMs < notBefore) continue;
27160
- const parsed = JSON.parse(readFileSync3(candidate, "utf8"));
27200
+ const parsed = JSON.parse(readFileSync4(candidate, "utf8"));
27161
27201
  const wrote = AgentHarness.safeParse(parsed.harness ?? "other");
27162
27202
  if (typeof parsed.id === "string" && parsed.id && wrote.success) candidates.push({ id: parsed.id, harness: wrote.data });
27163
27203
  } catch {
@@ -27173,9 +27213,9 @@ import { randomUUID as randomUUID4 } from "crypto";
27173
27213
  import { basename as basename3, join as join8, resolve as resolve4 } from "path";
27174
27214
 
27175
27215
  // src/platform/git.ts
27176
- import { execFileSync } from "child_process";
27216
+ import { execFileSync as execFileSync2 } from "child_process";
27177
27217
  import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
27178
- import { existsSync as existsSync3, readFileSync as readFileSync4, rmSync as rmSync4, writeFileSync as writeFileSync3 } from "fs";
27218
+ import { existsSync as existsSync3, readFileSync as readFileSync5, rmSync as rmSync4, writeFileSync as writeFileSync3 } from "fs";
27179
27219
  import { basename, join as join4, resolve as resolve2 } from "path";
27180
27220
 
27181
27221
  // src/platform/errors.ts
@@ -27194,7 +27234,7 @@ var CliError = class extends Error {
27194
27234
  // src/platform/git.ts
27195
27235
  function runGit(args, cwd = process.cwd()) {
27196
27236
  try {
27197
- return execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
27237
+ return execFileSync2("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
27198
27238
  } catch {
27199
27239
  return null;
27200
27240
  }
@@ -27217,7 +27257,7 @@ function requireRepo() {
27217
27257
  }
27218
27258
  function fetchRepoLink(repo) {
27219
27259
  try {
27220
- return JSON.parse(readFileSync4(repo.linkPath, "utf8"));
27260
+ return JSON.parse(readFileSync5(repo.linkPath, "utf8"));
27221
27261
  } catch {
27222
27262
  return null;
27223
27263
  }
@@ -27253,20 +27293,20 @@ function buildRepoDescription(repo, existing) {
27253
27293
  }
27254
27294
 
27255
27295
  // src/platform/harness.ts
27256
- import { execFileSync as execFileSync3 } from "child_process";
27296
+ import { execFileSync as execFileSync4 } from "child_process";
27257
27297
  import { closeSync, openSync, readdirSync as readdirSync3, readSync, readlinkSync, realpathSync as realpathSync3, statSync as statSync3 } from "fs";
27258
27298
  import { basename as basename2, join as join7 } from "path";
27259
27299
 
27260
27300
  // src/platform/harness-wake.ts
27261
- import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
27301
+ import { existsSync as existsSync5, readFileSync as readFileSync7 } from "fs";
27262
27302
  import { join as join6 } from "path";
27263
27303
  import { connect } from "net";
27264
27304
  import { spawn } from "child_process";
27265
27305
  import { randomUUID as randomUUID3 } from "crypto";
27266
27306
 
27267
27307
  // src/platform/homes.ts
27268
- import { execFileSync as execFileSync2 } from "child_process";
27269
- import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
27308
+ import { execFileSync as execFileSync3 } from "child_process";
27309
+ import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync6, statSync as statSync2 } from "fs";
27270
27310
  import { homedir as homedir3 } from "os";
27271
27311
  import { join as join5, resolve as resolve3 } from "path";
27272
27312
  var deduplicateStrings = (paths) => [...new Set(paths.map((path2) => resolve3(path2)))];
@@ -27277,26 +27317,26 @@ function fetchAncestorEnvironments() {
27277
27317
  if (process.platform === "linux") {
27278
27318
  let pid2 = process.ppid;
27279
27319
  for (let hop = 0; hop < 6 && pid2 > 1; hop += 1) {
27280
- seen.push(readFileSync5(`/proc/${pid2}/environ`, "latin1").replace(/\0/g, " "));
27281
- const stat = readFileSync5(`/proc/${pid2}/stat`, "utf8");
27320
+ seen.push(readFileSync6(`/proc/${pid2}/environ`, "latin1").replace(/\0/g, " "));
27321
+ const stat = readFileSync6(`/proc/${pid2}/stat`, "utf8");
27282
27322
  pid2 = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1]);
27283
27323
  }
27284
27324
  for (const entry of readdirSync2("/proc")) {
27285
27325
  if (!/^\d+$/.test(entry)) continue;
27286
27326
  try {
27287
- seen.push(readFileSync5(`/proc/${entry}/environ`, "latin1").replace(/\0/g, " "));
27327
+ seen.push(readFileSync6(`/proc/${entry}/environ`, "latin1").replace(/\0/g, " "));
27288
27328
  } catch {
27289
27329
  }
27290
27330
  }
27291
27331
  return seen;
27292
27332
  }
27293
- const runPs = (args) => execFileSync2("ps", args, { encoding: "utf8", maxBuffer: 64 << 20, stdio: ["ignore", "pipe", "ignore"] });
27333
+ const runPs2 = (args) => execFileSync3("ps", args, { encoding: "utf8", maxBuffer: 64 << 20, stdio: ["ignore", "pipe", "ignore"] });
27294
27334
  let pid = process.ppid;
27295
27335
  for (let hop = 0; hop < 6 && pid > 1; hop += 1) {
27296
- seen.push(runPs(["eww", "-o", "command=", "-p", String(pid)]));
27297
- pid = Number(runPs(["-o", "ppid=", "-p", String(pid)]).trim());
27336
+ seen.push(runPs2(["eww", "-o", "command=", "-p", String(pid)]));
27337
+ pid = Number(runPs2(["-o", "ppid=", "-p", String(pid)]).trim());
27298
27338
  }
27299
- seen.push(runPs(["axeww", "-o", "command="]));
27339
+ seen.push(runPs2(["axeww", "-o", "command="]));
27300
27340
  } catch {
27301
27341
  }
27302
27342
  return seen;
@@ -27333,22 +27373,18 @@ function fetchCodexHomes(root = homedir3(), inUse = findHomesInUse) {
27333
27373
  const named = process.env.CODEX_HOME ? [process.env.CODEX_HOME] : [];
27334
27374
  return deduplicateStrings([join5(root, ".codex"), ...named, ...inUse("CODEX_HOME"), ...findDirectoriesNamed(root, ".codex").filter((dir) => hasAnyEntry(dir, ["config.toml", "auth.json", "sessions"]))]);
27335
27375
  }
27336
- function fetchCursorHomes(root = homedir3(), _inUse = findHomesInUse) {
27337
- if (process.env.SLOP_CURSOR_HOME) return [process.env.SLOP_CURSOR_HOME];
27338
- return deduplicateStrings([join5(root, ".cursor"), ...findDirectoriesNamed(root, ".cursor").filter((dir) => hasAnyEntry(dir, ["cli-config.json", "hooks.json", "mcp.json", "projects"]))]);
27339
- }
27340
27376
  function resolveOwnHarnessHome(variable) {
27341
27377
  if (process.platform === "win32") return null;
27342
27378
  try {
27343
27379
  let pid = process.ppid;
27344
27380
  for (let hop = 0; hop < 6 && pid > 1; hop += 1) {
27345
- const text = process.platform === "linux" ? readFileSync5(`/proc/${pid}/environ`, "latin1").replace(/\0/g, " ") : execFileSync2("ps", ["eww", "-o", "command=", "-p", String(pid)], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
27381
+ const text = process.platform === "linux" ? readFileSync6(`/proc/${pid}/environ`, "latin1").replace(/\0/g, " ") : execFileSync3("ps", ["eww", "-o", "command=", "-p", String(pid)], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
27346
27382
  const match = new RegExp(`(?:^|\\s)${variable}=(\\S+)`).exec(text);
27347
27383
  if (match?.[1]) return resolve3(match[1]);
27348
27384
  if (process.platform === "linux") {
27349
- const stat = readFileSync5(`/proc/${pid}/stat`, "utf8");
27385
+ const stat = readFileSync6(`/proc/${pid}/stat`, "utf8");
27350
27386
  pid = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1]);
27351
- } else pid = Number(execFileSync2("ps", ["-o", "ppid=", "-p", String(pid)], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim());
27387
+ } else pid = Number(execFileSync3("ps", ["-o", "ppid=", "-p", String(pid)], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim());
27352
27388
  }
27353
27389
  } catch {
27354
27390
  }
@@ -27361,7 +27397,7 @@ function fetchHarnessRegistration(pid = process.ppid) {
27361
27397
  const path2 = join6(home, "sessions", `${pid}.json`);
27362
27398
  if (!existsSync5(path2)) continue;
27363
27399
  try {
27364
- return JSON.parse(readFileSync6(path2, "utf8"));
27400
+ return JSON.parse(readFileSync7(path2, "utf8"));
27365
27401
  } catch {
27366
27402
  return null;
27367
27403
  }
@@ -27498,137 +27534,45 @@ function wakeCodexOverWs(text, threadId, endpoint) {
27498
27534
  });
27499
27535
  });
27500
27536
  }
27501
- var CURSOR_TURN_CAP_MS = 6e5;
27502
- var CURSOR_LOAD_WITHIN_MS = 3e4;
27503
- function wakeCursor(text, chatId, cwd, options = {}) {
27504
- const binary = options.binary ?? "cursor-agent";
27505
- return wakeCursorOverAcp(text, chatId, cwd, binary).then((woken) => woken || resumeCursorChat(text, chatId, cwd, binary));
27506
- }
27507
- function wakeCursorOverAcp(text, chatId, cwd, binary) {
27508
- return new Promise((resolve6) => {
27509
- let child;
27510
- try {
27511
- child = spawn(binary, ["acp"], { cwd, stdio: ["pipe", "pipe", "ignore"] });
27512
- } catch {
27513
- resolve6(false);
27514
- return;
27515
- }
27516
- let settled = false;
27517
- let nextId = 1;
27518
- let buffer = "";
27519
- const finishWake = (woken, keepRunning = false) => {
27520
- if (settled) return;
27521
- settled = true;
27522
- clearTimeout(loadTimer);
27523
- if (!keepRunning) child.kill();
27524
- else setTimeout(() => child.kill(), CURSOR_TURN_CAP_MS).unref?.();
27525
- resolve6(woken);
27526
- };
27527
- const loadTimer = setTimeout(() => finishWake(false), CURSOR_LOAD_WITHIN_MS);
27528
- const send = (message) => {
27529
- try {
27530
- child.stdin?.write(`${JSON.stringify(message)}
27531
- `);
27532
- } catch {
27533
- finishWake(false);
27534
- }
27535
- };
27536
- const call = (method, params) => {
27537
- const id = nextId++;
27538
- send({ jsonrpc: "2.0", id, method, params });
27539
- return id;
27540
- };
27541
- const initializeId = call("initialize", { protocolVersion: 1, clientCapabilities: { fs: { readTextFile: false, writeTextFile: false }, terminal: false }, clientInfo: { name: "slop", version: "0.4.16" } });
27542
- let loadId = 0;
27543
- let promptId = 0;
27544
- child.on("error", () => finishWake(false));
27545
- child.on("exit", () => finishWake(false));
27546
- child.stdout?.on("data", (chunk) => {
27547
- buffer += chunk.toString("utf8");
27548
- let index;
27549
- while ((index = buffer.indexOf("\n")) >= 0) {
27550
- const line = buffer.slice(0, index);
27551
- buffer = buffer.slice(index + 1);
27552
- if (!line.trim()) continue;
27553
- let message;
27554
- try {
27555
- message = JSON.parse(line);
27556
- } catch {
27557
- continue;
27558
- }
27559
- if (message.id !== void 0 && message.method === "session/request_permission") {
27560
- const options = message.params?.options ?? [];
27561
- const allow = options.find((option) => option.kind === "allow_once") ?? options.find((option) => /allow|accept/i.test(option.optionId ?? "")) ?? options[0];
27562
- send({ jsonrpc: "2.0", id: message.id, result: { outcome: { outcome: "selected", optionId: allow?.optionId ?? "allow" } } });
27563
- continue;
27564
- }
27565
- if (message.id !== void 0 && message.method) {
27566
- send({ jsonrpc: "2.0", id: message.id, result: {} });
27567
- continue;
27568
- }
27569
- if (message.id === initializeId) {
27570
- if (message.error) finishWake(false);
27571
- else loadId = call("session/load", { sessionId: chatId, cwd, mcpServers: [] });
27572
- continue;
27573
- }
27574
- if (message.id === loadId && loadId !== 0) {
27575
- if (message.error) finishWake(false);
27576
- else promptId = call("session/prompt", { sessionId: chatId, prompt: [{ type: "text", text }] });
27577
- continue;
27578
- }
27579
- if (promptId !== 0 && (message.id === promptId || message.method === "session/update")) finishWake(true, true);
27580
- }
27581
- });
27582
- });
27583
- }
27584
- function resumeCursorChat(text, chatId, cwd, binary) {
27585
- return new Promise((resolve6) => {
27586
- let child;
27587
- try {
27588
- child = spawn(binary, ["--resume", chatId, "-p", text, "--output-format", "text"], { cwd, stdio: ["ignore", "ignore", "ignore"] });
27589
- } catch {
27590
- resolve6(false);
27591
- return;
27592
- }
27593
- const timer = setTimeout(() => resolve6(true), 5e3);
27594
- child.on("error", () => {
27595
- clearTimeout(timer);
27596
- resolve6(false);
27597
- });
27598
- child.on("exit", (code) => {
27599
- clearTimeout(timer);
27600
- resolve6(code === 0);
27601
- });
27602
- });
27603
- }
27604
27537
 
27605
27538
  // src/platform/harness.ts
27606
27539
  var AgentHarnessValues = AgentHarness.options;
27607
27540
  var HEAD_BYTES = 64 * 1024;
27608
- var parent = null;
27609
- function fetchParentCommand() {
27610
- if (parent !== null) return parent;
27611
- try {
27612
- parent = process.platform === "win32" ? "" : execFileSync3("ps", ["-o", "command=", "-p", String(process.ppid)], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
27613
- } catch {
27614
- parent = "";
27615
- }
27616
- return parent;
27617
- }
27618
27541
  function parseHarnessFromCommand(command) {
27619
27542
  const first = basename2(command.trim().split(/\s+/)[0] ?? "");
27620
- if (/^cursor-agent(\b|$)/.test(first)) return "cursor";
27621
27543
  if (/^claude(\b|$)/.test(first)) return "claude";
27622
27544
  if (/^codex(\b|$)/.test(first)) return "codex";
27623
27545
  if (/(^|\/)codex(\S*)?\s/.test(command)) return "codex";
27624
- if (/(^|\/)cursor-agent(\s|$)/.test(command)) return "cursor";
27625
27546
  return null;
27626
27547
  }
27548
+ var ANCESTOR_HOPS = 8;
27549
+ function findHarnessAncestor(pid, fetchCommand = fetchProcessCommand, fetchParent = fetchParentPid, hops = ANCESTOR_HOPS) {
27550
+ let current = pid;
27551
+ for (let hop = 0; hop < hops && current !== null && current > 1; hop += 1) {
27552
+ const command = fetchCommand(current);
27553
+ const harness = parseHarnessFromCommand(command);
27554
+ if (harness) return { pid: current, harness, command };
27555
+ current = fetchParent(current);
27556
+ }
27557
+ return null;
27558
+ }
27559
+ var ancestor;
27560
+ function fetchHarnessAncestor() {
27561
+ if (ancestor !== void 0) return ancestor;
27562
+ const named = process.env.SLOP_HARNESS_PID?.trim();
27563
+ const harness = process.env.SLOP_HARNESS;
27564
+ if (named === "0") ancestor = null;
27565
+ else if (named && Number(named) > 0) {
27566
+ const pid = Number(named);
27567
+ ancestor = AgentHarnessValues.includes(harness ?? "") ? { pid, harness, command: fetchProcessCommand(pid) } : findHarnessAncestor(pid);
27568
+ } else ancestor = findHarnessAncestor(process.ppid);
27569
+ return ancestor;
27570
+ }
27627
27571
  function isNonInteractiveCommand(command) {
27628
27572
  return /(^|[\s/])codex(\S*)?\s+exec(\s|$)/.test(command);
27629
27573
  }
27630
27574
  function isNonInteractiveHarness() {
27631
- return isNonInteractiveCommand(fetchParentCommand());
27575
+ return isNonInteractiveCommand(fetchProcessCommand(process.ppid)) || isNonInteractiveCommand(fetchHarnessAncestor()?.command ?? "");
27632
27576
  }
27633
27577
  var isSamePath = (a, b) => {
27634
27578
  const resolveRealPath = (path2) => {
@@ -27739,13 +27683,14 @@ function fetchCodexRolloutPath(roots, threadId) {
27739
27683
  }
27740
27684
  function resolveCodexBinary() {
27741
27685
  try {
27742
- execFileSync3(process.platform === "win32" ? "where" : "which", ["codex"], { stdio: "ignore" });
27686
+ execFileSync4(process.platform === "win32" ? "where" : "which", ["codex"], { stdio: "ignore" });
27743
27687
  return "codex";
27744
27688
  } catch {
27745
27689
  }
27746
- if (parseHarnessFromCommand(fetchParentCommand()) === "codex" && process.platform !== "win32") {
27690
+ const above = fetchHarnessAncestor();
27691
+ if (above?.harness === "codex" && process.platform !== "win32") {
27747
27692
  try {
27748
- const exe = process.platform === "linux" ? readlinkSync(`/proc/${process.ppid}/exe`) : execFileSync3("ps", ["-o", "comm=", "-p", String(process.ppid)], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
27693
+ const exe = process.platform === "linux" ? readlinkSync(`/proc/${above.pid}/exe`) : execFileSync4("ps", ["-o", "comm=", "-p", String(above.pid)], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
27749
27694
  if (exe && basename2(exe).startsWith("codex")) return exe;
27750
27695
  } catch {
27751
27696
  }
@@ -27753,43 +27698,21 @@ function resolveCodexBinary() {
27753
27698
  return "codex";
27754
27699
  }
27755
27700
  function resolveParentHarness() {
27756
- return parseHarnessFromCommand(fetchParentCommand());
27757
- }
27758
- var RETRY_ACP_AFTER_MS = 5e3;
27759
- var acpSession = null;
27760
- function fetchCursorAcpSessionByCwd(cwd, binary = "cursor-agent") {
27761
- if (acpSession && (acpSession.id !== null || Date.now() - acpSession.at < RETRY_ACP_AFTER_MS)) return acpSession.id;
27762
- acpSession = { id: null, at: Date.now() };
27763
- const said = [
27764
- { jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: 1, clientCapabilities: { fs: { readTextFile: false, writeTextFile: false }, terminal: false }, clientInfo: { name: "slop", version: "0.4.16" } } },
27765
- { jsonrpc: "2.0", id: 2, method: "session/list", params: {} }
27766
- ];
27767
- try {
27768
- const answered = execFileSync3(binary, ["acp"], { input: `${said.map((message) => JSON.stringify(message)).join("\n")}
27769
- `, encoding: "utf8", timeout: 15e3, stdio: ["pipe", "pipe", "ignore"], cwd });
27770
- for (const line of answered.split("\n")) {
27771
- if (!line.includes('"sessions"')) continue;
27772
- const listed = JSON.parse(line).result?.sessions ?? [];
27773
- const here = listed.filter((session) => typeof session.sessionId === "string" && typeof session.cwd === "string" && isSamePath(session.cwd, cwd));
27774
- if (here.length === 1) acpSession = { id: here[0].sessionId, at: Date.now() };
27775
- }
27776
- } catch {
27777
- }
27778
- return acpSession.id;
27701
+ return fetchHarnessAncestor()?.harness ?? null;
27779
27702
  }
27780
27703
  function fetchHarnessConversation(cwd) {
27781
27704
  const explicit = process.env.SLOP_CONVERSATION_ID?.trim();
27782
27705
  if (explicit) return { id: explicit, harness: AgentHarnessValues.includes(process.env.SLOP_HARNESS) ? process.env.SLOP_HARNESS : "other" };
27783
27706
  if (process.env.CODEX_THREAD_ID?.trim()) return { id: process.env.CODEX_THREAD_ID.trim(), harness: "codex" };
27784
27707
  if (process.env.CLAUDE_CODE_SESSION_ID?.trim()) return { id: process.env.CLAUDE_CODE_SESSION_ID.trim(), harness: "claude" };
27785
- const harness = parseHarnessFromCommand(fetchParentCommand());
27786
- if (harness === "claude") {
27708
+ const above = fetchHarnessAncestor();
27709
+ if (above?.harness === "claude") {
27787
27710
  const id = resolveClaudeSession()?.sessionId;
27788
- return id ? { id, harness } : null;
27711
+ return id ? { id, harness: "claude" } : null;
27789
27712
  }
27790
- if (harness === "codex" && !isNonInteractiveHarness()) {
27713
+ if (above?.harness === "codex" && !isNonInteractiveHarness()) {
27791
27714
  const id = fetchCodexConversationByCwd(fetchCodexHomes().map((home) => join7(home, "sessions")), cwd);
27792
- return id ? { id, harness } : null;
27715
+ return id ? { id, harness: "codex" } : null;
27793
27716
  }
27794
27717
  return null;
27795
27718
  }
@@ -27802,11 +27725,7 @@ function fetchKnownConversation(cwd) {
27802
27725
  if (own2) return own2;
27803
27726
  const remembered = fetchRememberedConversation(cwd, startedAt - 12e4, resolveParentHarness());
27804
27727
  if (remembered?.harness === "codex" && isMainChatThread(fetchCodexHomes().map((home) => join8(home, "sessions")), remembered.id)) return remembered;
27805
- const known = remembered?.harness === "codex" ? null : remembered;
27806
- if (known) return known;
27807
- if (resolveParentHarness() !== "cursor") return null;
27808
- const acp = fetchCursorAcpSessionByCwd(cwd);
27809
- return acp ? { id: acp, harness: "cursor" } : null;
27728
+ return remembered?.harness === "codex" ? null : remembered;
27810
27729
  }
27811
27730
  function resolveAgentIdentity(cwd, fallback = "process") {
27812
27731
  const conversation = fetchKnownConversation(cwd);
@@ -27907,14 +27826,14 @@ function buildAuthedApiClient() {
27907
27826
  }
27908
27827
 
27909
27828
  // src/platform/machine.ts
27910
- import { execFileSync as execFileSync4 } from "child_process";
27829
+ import { execFileSync as execFileSync5 } from "child_process";
27911
27830
  import { createHash as createHash4, randomUUID as randomUUID5 } from "crypto";
27912
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
27831
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
27913
27832
  import { hostname as hostname3, platform, userInfo } from "os";
27914
27833
  import { dirname as dirname4, join as join9 } from "path";
27915
27834
  function runCommand(command, args) {
27916
27835
  try {
27917
- return execFileSync4(command, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 5e3 });
27836
+ return execFileSync5(command, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 5e3 });
27918
27837
  } catch {
27919
27838
  return null;
27920
27839
  }
@@ -27925,7 +27844,7 @@ function fetchOsMachineId() {
27925
27844
  return /"IOPlatformUUID" = "([^"]+)"/.exec(runCommand("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"]) ?? "")?.[1] ?? null;
27926
27845
  case "linux":
27927
27846
  for (const path2 of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
27928
- if (existsSync6(path2)) return readFileSync7(path2, "utf8").trim() || null;
27847
+ if (existsSync6(path2)) return readFileSync8(path2, "utf8").trim() || null;
27929
27848
  }
27930
27849
  return null;
27931
27850
  case "win32":
@@ -27938,7 +27857,7 @@ function fetchOsMachineId() {
27938
27857
  }
27939
27858
  function resolvePersistedMachineId() {
27940
27859
  const path2 = join9(dirname4(buildConfigPath()), "machine-id");
27941
- if (existsSync6(path2)) return readFileSync7(path2, "utf8").trim();
27860
+ if (existsSync6(path2)) return readFileSync8(path2, "utf8").trim();
27942
27861
  const id = randomUUID5();
27943
27862
  mkdirSync4(dirname4(path2), { recursive: true, mode: 448 });
27944
27863
  writeFileSync4(path2, `${id}
@@ -28223,32 +28142,24 @@ linked to ${link.projectKey} ${link.projectName} (${link.baseUrl})`
28223
28142
 
28224
28143
  // src/commands/mcp.ts
28225
28144
  import { execFileSync as execFileSync6, spawn as spawn3 } from "child_process";
28226
- import { existsSync as existsSync12, mkdirSync as mkdirSync9, readFileSync as readFileSync17, writeFileSync as writeFileSync8 } from "fs";
28145
+ import { existsSync as existsSync12, mkdirSync as mkdirSync9, readFileSync as readFileSync18, writeFileSync as writeFileSync8 } from "fs";
28227
28146
  import { homedir as homedir4 } from "os";
28228
28147
  import { join as join16 } from "path";
28229
28148
 
28230
28149
  // src/platform/session-recovery.ts
28231
- import { readFileSync as readFileSync11 } from "fs";
28150
+ import { readFileSync as readFileSync12 } from "fs";
28232
28151
  import { dirname as dirname6, join as join13 } from "path";
28233
28152
 
28234
28153
  // src/platform/inbox.ts
28235
28154
  import { createHash as createHash6, randomUUID as randomUUID7 } from "crypto";
28236
- import { existsSync as existsSync9, linkSync as linkSync3, mkdirSync as mkdirSync7, readFileSync as readFileSync10, readdirSync as readdirSync6, realpathSync as realpathSync4, renameSync as renameSync2, rmSync as rmSync7, writeFileSync as writeFileSync6 } from "fs";
28155
+ import { existsSync as existsSync9, linkSync as linkSync3, mkdirSync as mkdirSync7, readFileSync as readFileSync11, readdirSync as readdirSync6, realpathSync as realpathSync4, renameSync as renameSync2, rmSync as rmSync7, writeFileSync as writeFileSync6 } from "fs";
28237
28156
  import { dirname as dirname5, join as join12 } from "path";
28238
28157
 
28239
28158
  // src/platform/inbox-claims.ts
28240
- import { execFileSync as execFileSync5 } from "child_process";
28241
28159
  import { randomUUID as randomUUID6 } from "crypto";
28242
- import { existsSync as existsSync7, linkSync as linkSync2, mkdirSync as mkdirSync5, readFileSync as readFileSync8, readdirSync as readdirSync4, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "fs";
28160
+ import { existsSync as existsSync7, linkSync as linkSync2, mkdirSync as mkdirSync5, readFileSync as readFileSync9, readdirSync as readdirSync4, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "fs";
28243
28161
  import { join as join10 } from "path";
28244
28162
  var processOwner = null;
28245
- function fetchProcessStart(pid) {
28246
- try {
28247
- return execFileSync5("ps", ["-p", String(pid), "-o", "lstart="], { encoding: "utf8", env: { ...process.env, LC_ALL: "C", TZ: "UTC" }, stdio: ["ignore", "pipe", "ignore"], timeout: 1e3 }).trim() || null;
28248
- } catch {
28249
- return null;
28250
- }
28251
- }
28252
28163
  function isClaimOwnerRunning(owner) {
28253
28164
  if (!Number.isSafeInteger(owner.pid) || owner.pid <= 0) throw new Error("Invalid inbox claim owner");
28254
28165
  try {
@@ -28276,7 +28187,7 @@ function claimInboxMessage(directory) {
28276
28187
  if (last && !existsSync7(join10(directory, `${last}.released`))) {
28277
28188
  let owner;
28278
28189
  try {
28279
- owner = JSON.parse(readFileSync8(join10(directory, last), "utf8"));
28190
+ owner = JSON.parse(readFileSync9(join10(directory, last), "utf8"));
28280
28191
  } catch (error62) {
28281
28192
  if (error62.code === "ENOENT") return null;
28282
28193
  throw error62;
@@ -28306,7 +28217,7 @@ function claimInboxMessage(directory) {
28306
28217
 
28307
28218
  // src/platform/inbox-receipts.ts
28308
28219
  import { createHash as createHash5 } from "crypto";
28309
- import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync9, readdirSync as readdirSync5, rmSync as rmSync6 } from "fs";
28220
+ import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync10, readdirSync as readdirSync5, rmSync as rmSync6 } from "fs";
28310
28221
  import { join as join11 } from "path";
28311
28222
  var COMPACTION_SIZE = 128;
28312
28223
  var archives = /* @__PURE__ */ new Map();
@@ -28325,7 +28236,7 @@ function fetchArchivedReceipts(directory) {
28325
28236
  }
28326
28237
  for (const name of names) {
28327
28238
  if (!name.endsWith(".json") || cached2.files.has(name)) continue;
28328
- const keys = JSON.parse(readFileSync9(join11(directory, "compacted", name), "utf8"));
28239
+ const keys = JSON.parse(readFileSync10(join11(directory, "compacted", name), "utf8"));
28329
28240
  if (!Array.isArray(keys) || !keys.every((key) => typeof key === "string" && /^[a-f0-9]{64}$/.test(key))) throw new Error("Invalid inbox receipt archive");
28330
28241
  for (const key of keys) cached2.keys.add(key);
28331
28242
  cached2.files.add(name);
@@ -28448,7 +28359,7 @@ function claimInbox(cwd, owner, accept = () => true) {
28448
28359
  const available = [];
28449
28360
  for (const name of names) {
28450
28361
  try {
28451
- available.push({ key: name.slice(0, -5), push: JSON.parse(readFileSync10(join12(directory, name), "utf8")) });
28362
+ available.push({ key: name.slice(0, -5), push: JSON.parse(readFileSync11(join12(directory, name), "utf8")) });
28452
28363
  } catch (error62) {
28453
28364
  if (error62.code !== "ENOENT") throw error62;
28454
28365
  }
@@ -28505,7 +28416,7 @@ function drainInbox(cwd) {
28505
28416
  throw error62;
28506
28417
  }
28507
28418
  try {
28508
- pushes.push(JSON.parse(readFileSync10(taken, "utf8")));
28419
+ pushes.push(JSON.parse(readFileSync11(taken, "utf8")));
28509
28420
  } finally {
28510
28421
  rmSync7(taken, { force: true });
28511
28422
  }
@@ -28521,7 +28432,7 @@ function drainLegacyInbox(cwd) {
28521
28432
  } catch {
28522
28433
  return [];
28523
28434
  }
28524
- const lines = readFileSync10(taken, "utf8").split("\n").filter(Boolean);
28435
+ const lines = readFileSync11(taken, "utf8").split("\n").filter(Boolean);
28525
28436
  rmSync7(taken, { force: true });
28526
28437
  return lines.flatMap((line) => {
28527
28438
  try {
@@ -28558,7 +28469,7 @@ var SessionRecovery = class {
28558
28469
  }
28559
28470
  async fetchCursor() {
28560
28471
  try {
28561
- const value = JSON.parse(readFileSync11(this.path, "utf8"));
28472
+ const value = JSON.parse(readFileSync12(this.path, "utf8"));
28562
28473
  if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) throw new Error("Invalid saved message cursor");
28563
28474
  this.cursor = value;
28564
28475
  } catch (error62) {
@@ -28937,7 +28848,7 @@ var HeldSession = class {
28937
28848
  };
28938
28849
 
28939
28850
  // src/platform/harness-activity.ts
28940
- import { closeSync as closeSync2, existsSync as existsSync10, openSync as openSync2, readdirSync as readdirSync7, readFileSync as readFileSync12, readSync as readSync2, statSync as statSync4 } from "fs";
28851
+ import { closeSync as closeSync2, existsSync as existsSync10, openSync as openSync2, readdirSync as readdirSync7, readFileSync as readFileSync13, readSync as readSync2, statSync as statSync4 } from "fs";
28941
28852
  import { basename as basename4, dirname as dirname7, join as join14 } from "path";
28942
28853
  var WORKER_FRESH_MS = 12e4;
28943
28854
  var WORKER_GONE_MS = 3e5;
@@ -29010,67 +28921,6 @@ function parseToolCallFromCodexLine(line) {
29010
28921
  if (payload.type === "local_shell_call") return formatToolCallLine("shell", payload.action);
29011
28922
  return null;
29012
28923
  }
29013
- function parseCursorTaskFromLine(line) {
29014
- if (!line.includes("CallDynamicTool") || !line.includes('"Task"')) return null;
29015
- let entry;
29016
- try {
29017
- entry = JSON.parse(line);
29018
- } catch {
29019
- return null;
29020
- }
29021
- const content = entry.message?.content;
29022
- if (!Array.isArray(content)) return null;
29023
- for (const block of content) {
29024
- if (!block || typeof block !== "object") continue;
29025
- const { type, name, input: input2 } = block;
29026
- if (type !== "tool_use" || name !== "CallDynamicTool" || input2?.toolName !== "Task") continue;
29027
- const asked = input2.arguments ?? {};
29028
- const prompt = typeof asked.prompt === "string" ? asked.prompt : null;
29029
- if (!prompt) continue;
29030
- const label = [asked.subagent_type, asked.description].filter((part) => typeof part === "string" && part.trim() !== "").join(" \u2014 ");
29031
- return { label: truncate(label || "subagent", WORKER_LABEL_MAX), prompt };
29032
- }
29033
- return null;
29034
- }
29035
- function parseCursorUserQuery(line) {
29036
- let entry;
29037
- try {
29038
- entry = JSON.parse(line);
29039
- } catch {
29040
- return null;
29041
- }
29042
- const content = entry.message?.content;
29043
- if (entry.role !== "user" || !Array.isArray(content)) return null;
29044
- for (const block of content) {
29045
- const text = block?.text;
29046
- if (typeof text !== "string") continue;
29047
- const asked = /<user_query>\s*([\s\S]*?)\s*<\/user_query>/.exec(text);
29048
- if (asked?.[1]) return asked[1];
29049
- }
29050
- return null;
29051
- }
29052
- function findCursorWorkerFiles(transcriptPath, conversation, changedAfter, asked) {
29053
- if (asked.length === 0) return [];
29054
- const directory = dirname7(dirname7(transcriptPath));
29055
- let chats;
29056
- try {
29057
- chats = readdirSync7(directory);
29058
- } catch {
29059
- return [];
29060
- }
29061
- const found = [];
29062
- for (const chat of chats) {
29063
- if (chat === conversation) continue;
29064
- const path2 = join14(directory, chat, `${chat}.jsonl`);
29065
- const changedAt = fetchChangedAt(path2);
29066
- if (changedAt < changedAfter) continue;
29067
- const query = parseCursorUserQuery(fetchFileHead(path2, 16 * 1024).split("\n")[0] ?? "");
29068
- const task = query === null ? void 0 : asked.find((candidate) => candidate.prompt === query);
29069
- if (!task) continue;
29070
- found.push({ ref: chat, label: task.label, path: path2, directory: null, branch: null, changedAt });
29071
- }
29072
- return found.sort((a, b) => b.changedAt - a.changedAt);
29073
- }
29074
28924
  var fetchFileSize = (path2) => {
29075
28925
  try {
29076
28926
  return statSync4(path2).size;
@@ -29139,21 +28989,6 @@ function findClaudeTranscriptPath(homes, conversation) {
29139
28989
  }
29140
28990
  return null;
29141
28991
  }
29142
- function findCursorTranscriptPath(homes, conversation) {
29143
- for (const home of homes) {
29144
- let projects;
29145
- try {
29146
- projects = readdirSync7(join14(home, "projects"));
29147
- } catch {
29148
- continue;
29149
- }
29150
- for (const project of projects) {
29151
- const path2 = join14(home, "projects", project, "agent-transcripts", conversation, `${conversation}.jsonl`);
29152
- if (existsSync10(path2)) return path2;
29153
- }
29154
- }
29155
- return null;
29156
- }
29157
28992
  function parseClaudeWorkerLabel(text) {
29158
28993
  let meta3;
29159
28994
  try {
@@ -29181,7 +29016,7 @@ function findClaudeWorkerFiles(transcriptPath, conversation, changedAfter) {
29181
29016
  if (changedAt < changedAfter) continue;
29182
29017
  let label = null;
29183
29018
  try {
29184
- label = parseClaudeWorkerLabel(readFileSync12(join14(directory, `agent-${ref}.meta.json`), "utf8"));
29019
+ label = parseClaudeWorkerLabel(readFileSync13(join14(directory, `agent-${ref}.meta.json`), "utf8"));
29185
29020
  } catch {
29186
29021
  }
29187
29022
  const first = fetchFileHead(path2, 8 * 1024).split("\n")[0] ?? "";
@@ -29239,7 +29074,6 @@ function findCodexChildRollouts(roots, parentThreadId, changedAfter) {
29239
29074
  }
29240
29075
  return found;
29241
29076
  }
29242
- var ASKED_CAP = 16;
29243
29077
  var HarnessActivity = class {
29244
29078
  constructor(conversation, onReport) {
29245
29079
  this.conversation = conversation;
@@ -29248,8 +29082,6 @@ var HarnessActivity = class {
29248
29082
  conversation;
29249
29083
  onReport;
29250
29084
  workers = /* @__PURE__ */ new Map();
29251
- /** What this chat has asked a subagent to do, which is how a Cursor chat's children are claimed. */
29252
- asked = [];
29253
29085
  own = null;
29254
29086
  doing = null;
29255
29087
  watched = false;
@@ -29317,11 +29149,6 @@ var HarnessActivity = class {
29317
29149
  this.own ??= transcript ? new FileTail(transcript, "end") : null;
29318
29150
  return transcript ? findClaudeWorkerFiles(transcript, this.conversation.id, changedAfter) : [];
29319
29151
  }
29320
- if (this.conversation.harness === "cursor") {
29321
- const transcript = findCursorTranscriptPath(fetchCursorHomes(), this.conversation.id);
29322
- this.own ??= transcript ? new FileTail(transcript, "end") : null;
29323
- return transcript ? findCursorWorkerFiles(transcript, this.conversation.id, changedAfter, this.asked) : [];
29324
- }
29325
29152
  if (this.conversation.harness === "codex") {
29326
29153
  const roots = fetchCodexHomes().map((home) => join14(home, "sessions"));
29327
29154
  const rollout = fetchCodexRolloutPath(roots, this.conversation.id);
@@ -29340,14 +29167,7 @@ var HarnessActivity = class {
29340
29167
  };
29341
29168
  if (this.own) {
29342
29169
  let newest = null;
29343
- for (const line of this.own.fetchNewLines()) {
29344
- newest = parse3(line) ?? newest;
29345
- const task = this.conversation.harness === "cursor" ? parseCursorTaskFromLine(line) : null;
29346
- if (task) {
29347
- this.asked.push(task);
29348
- while (this.asked.length > ASKED_CAP) this.asked.shift();
29349
- }
29350
- }
29170
+ for (const line of this.own.fetchNewLines()) newest = parse3(line) ?? newest;
29351
29171
  this.doing = newest ?? this.doing;
29352
29172
  }
29353
29173
  for (const worker of this.workers.values()) worker.doing = findNewestCall(worker.tail) ?? worker.doing;
@@ -29478,11 +29298,11 @@ function serveMcp(tools, info) {
29478
29298
  }
29479
29299
 
29480
29300
  // src/commands/tasks.ts
29481
- import { readFileSync as readFileSync14 } from "fs";
29301
+ import { readFileSync as readFileSync15 } from "fs";
29482
29302
 
29483
29303
  // src/commands/items.ts
29484
- import { readFileSync as readFileSync13 } from "fs";
29485
- var fetchTextArgument = (value) => value === "-" ? readFileSync13(0, "utf8").trim() : value;
29304
+ import { readFileSync as readFileSync14 } from "fs";
29305
+ var fetchTextArgument = (value) => value === "-" ? readFileSync14(0, "utf8").trim() : value;
29486
29306
  var buildItemsPath = (projectId, key) => `/projects/${projectId}/tasks/${encodeURIComponent(key)}/items`;
29487
29307
  function formatPlanItems(items) {
29488
29308
  if (items.length === 0) return "No plan yet. Write one: slop task plan <KEY> -";
@@ -29509,7 +29329,7 @@ function registerItemCommands(task, registerProjectOption) {
29509
29329
  registerProjectOption(task.command("plan <key> [lines...]")).description('Append items to the plan, one per argument; "-" reads one per line from stdin (list markers are fine).').action(async (key, lines, options) => {
29510
29330
  const api = buildAuthedApiClient();
29511
29331
  const project = await resolveProject(api, options.project);
29512
- const source = lines.length === 1 && lines[0] === "-" ? readFileSync13(0, "utf8") : lines.join("\n");
29332
+ const source = lines.length === 1 && lines[0] === "-" ? readFileSync14(0, "utf8") : lines.join("\n");
29513
29333
  const titles = source.split("\n").map((line) => line.replace(/^\s*(?:[-*+]|\d+[.)])\s*(?:\[[ xX]\]\s*)?/, "").trim()).filter(Boolean);
29514
29334
  if (titles.length === 0) throw new CliError("Nothing to add", 'give items as arguments, or "-" and one per line on stdin');
29515
29335
  const items = await api.post(buildItemsPath(project.id, key), ApiResponses.TaskItems, { items: titles.map((title3) => ({ title: title3 })) });
@@ -29566,7 +29386,7 @@ function parseOneOf(value, allowed, what) {
29566
29386
  if (allowed.includes(normalized)) return normalized;
29567
29387
  throw new CliError(`Unknown ${what} "${value}"`, `use one of: ${allowed.join(", ")}`);
29568
29388
  }
29569
- var fetchTextArgument2 = (value) => value === "-" ? readFileSync14(0, "utf8").trim() : value;
29389
+ var fetchTextArgument2 = (value) => value === "-" ? readFileSync15(0, "utf8").trim() : value;
29570
29390
  var buildTaskUrl = (baseUrl, projectId, task) => `${baseUrl}/projects/${projectId}?task=${task.key}`;
29571
29391
  var fetchProject = (api, id) => api.get(`/projects/${id}`, ApiResponses.Project);
29572
29392
  function resolveStatus(project, ref) {
@@ -29717,14 +29537,14 @@ ${buildTaskUrl(api.baseUrl, project.id, created)}`);
29717
29537
  }
29718
29538
 
29719
29539
  // src/platform/update-check.ts
29720
- import { existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync15, writeFileSync as writeFileSync7 } from "fs";
29540
+ import { existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync16, writeFileSync as writeFileSync7 } from "fs";
29721
29541
  import { dirname as dirname8, join as join15 } from "path";
29722
29542
  var CHECK_EVERY_MS = 60 * 60 * 1e3;
29723
29543
  var REGISTRY = "https://registry.npmjs.org/slopwaresystems/latest";
29724
29544
  var buildUpdateCachePath = () => join15(dirname8(buildConfigPath()), "update-check.json");
29725
29545
  function fetchUpdateCache() {
29726
29546
  try {
29727
- return JSON.parse(readFileSync15(buildUpdateCachePath(), "utf8"));
29547
+ return JSON.parse(readFileSync16(buildUpdateCachePath(), "utf8"));
29728
29548
  } catch {
29729
29549
  return { checkedAt: 0, latest: null };
29730
29550
  }
@@ -29763,8 +29583,8 @@ function buildUpdateNotice(current) {
29763
29583
  var isUpdateCheckEnabled = () => !existsSync11(join15(dirname8(buildConfigPath()), "no-update-check")) && process.env.SLOP_NO_UPDATE_CHECK !== "1";
29764
29584
 
29765
29585
  // src/commands/threads.ts
29766
- import { readFileSync as readFileSync16 } from "fs";
29767
- var fetchTextArgument3 = (value) => value === "-" ? readFileSync16(0, "utf8").trim() : value;
29586
+ import { readFileSync as readFileSync17 } from "fs";
29587
+ var fetchTextArgument3 = (value) => value === "-" ? readFileSync17(0, "utf8").trim() : value;
29768
29588
  var formatMessageTime = (iso) => iso.slice(0, 16).replace("T", " ");
29769
29589
  var formatAuthor = (message) => message.via !== "agent" ? message.author.name : `${message.agent ?? message.author.name} (${message.agent ? `${message.author.name}'s agent` : "agent"}${message.machine ? ` \xB7 ${message.machine}` : ""})`;
29770
29590
  function formatThreadLine(thread) {
@@ -29911,7 +29731,7 @@ function createProjectTools(api, project, identity) {
29911
29731
  if (waking || stopped) return;
29912
29732
  waking = true;
29913
29733
  const text = `Messages are waiting for you on Slopware Tasks in project ${project.key}. Use session check with project ${project.id} to read them, then act or reply only if it changes something. Do not acknowledge.`;
29914
- const wake = identity.harness === "codex" && identity.conversation ? wakeCodex(text, identity.conversation, { binary: resolveCodexBinary(), codexHome: resolveOwnHarnessHome("CODEX_HOME") }) : identity.harness === "claude" ? wakeHarness(text, void 0, identity.conversation) : identity.harness === "cursor" && identity.conversation ? wakeCursor(text, identity.conversation, process.cwd()) : Promise.resolve(false);
29734
+ const wake = identity.harness === "codex" && identity.conversation ? wakeCodex(text, identity.conversation, { binary: resolveCodexBinary(), codexHome: resolveOwnHarnessHome("CODEX_HOME") }) : identity.harness === "claude" ? wakeHarness(text, void 0, identity.conversation) : Promise.resolve(false);
29915
29735
  void wake.catch(() => false).finally(() => {
29916
29736
  waking = false;
29917
29737
  });
@@ -30329,7 +30149,7 @@ ${task.notes}` : ""].filter(Boolean).join("\n");
30329
30149
  }
30330
30150
  function fetchJsonFile(path2) {
30331
30151
  try {
30332
- return JSON.parse(readFileSync17(path2, "utf8"));
30152
+ return JSON.parse(readFileSync18(path2, "utf8"));
30333
30153
  } catch {
30334
30154
  return {};
30335
30155
  }
@@ -30379,7 +30199,7 @@ function installIntoCodexHome(dir) {
30379
30199
  const done = [];
30380
30200
  mkdirSync9(dir, { recursive: true });
30381
30201
  const configTomlPath = join16(dir, "config.toml");
30382
- const current = existsSync12(configTomlPath) ? readFileSync17(configTomlPath, "utf8") : "";
30202
+ const current = existsSync12(configTomlPath) ? readFileSync18(configTomlPath, "utf8") : "";
30383
30203
  if (/^\[mcp_servers\.slop\]/m.test(current)) {
30384
30204
  if (/tool_timeout_sec = 1800\b/.test(current)) writeFileSync8(configTomlPath, current.replace(/tool_timeout_sec = 1800\b/, "tool_timeout_sec = 21600"));
30385
30205
  done.push(`Codex: MCP server already in ${configTomlPath}`);
@@ -30399,41 +30219,43 @@ tool_timeout_sec = 21600
30399
30219
  done.push(`Codex: SessionStart hook written to ${hooksPath}; other slop hooks removed (Codex is woken with codex queue; no hook holds a turn open)`);
30400
30220
  return done;
30401
30221
  }
30402
- function installIntoCursorHome(dir) {
30222
+ function removeFromCursorHome(dir) {
30403
30223
  const done = [];
30404
- mkdirSync9(dir, { recursive: true });
30405
- const mcpPath = join16(dir, "mcp.json");
30406
- const mcp = fetchJsonFile(mcpPath);
30407
- const servers = mcp.mcpServers ?? {};
30408
- const had = "slop" in servers;
30409
- servers.slop = { command: "slop", args: ["mcp"] };
30410
- mcp.mcpServers = servers;
30411
- writeFileSync8(mcpPath, `${JSON.stringify(mcp, null, 2)}
30412
- `);
30413
- done.push(`Cursor: MCP server "slop" ${had ? "already in" : "added to"} ${mcpPath}`);
30414
30224
  const hooksPath = join16(dir, "hooks.json");
30415
- const file2 = fetchJsonFile(hooksPath);
30416
- file2.version = typeof file2.version === "number" ? file2.version : 1;
30417
- file2.hooks = mergeCursorHooks(file2.hooks ?? {});
30418
- writeFileSync8(hooksPath, `${JSON.stringify(file2, null, 2)}
30225
+ if (existsSync12(hooksPath)) {
30226
+ const file2 = fetchJsonFile(hooksPath);
30227
+ const hooks = file2.hooks ?? {};
30228
+ if (deleteSlopHooksFromCursorHooks(hooks)) {
30229
+ file2.hooks = hooks;
30230
+ writeFileSync8(hooksPath, `${JSON.stringify(file2, null, 2)}
30419
30231
  `);
30420
- done.push(`Cursor: sessionStart hook written to ${hooksPath}; nothing wakes an idle chat, so messages wait for its next tool call`);
30421
- try {
30422
- execFileSync6("cursor-agent", ["mcp", "enable", "slop"], { stdio: "ignore" });
30423
- done.push("Cursor: slop is on its approved MCP list");
30424
- } catch {
30425
- done.push("Cursor: approve the server once with `cursor-agent mcp enable slop`, or answer its prompt");
30232
+ done.push(`Cursor: slop hooks removed from ${hooksPath} (Cursor CLI is not supported)`);
30233
+ }
30234
+ }
30235
+ const mcpPath = join16(dir, "mcp.json");
30236
+ if (existsSync12(mcpPath)) {
30237
+ const mcp = fetchJsonFile(mcpPath);
30238
+ const servers = mcp.mcpServers ?? {};
30239
+ if ("slop" in servers) {
30240
+ delete servers.slop;
30241
+ mcp.mcpServers = servers;
30242
+ writeFileSync8(mcpPath, `${JSON.stringify(mcp, null, 2)}
30243
+ `);
30244
+ done.push(`Cursor: MCP server "slop" removed from ${mcpPath}`);
30245
+ }
30426
30246
  }
30427
30247
  return done;
30428
30248
  }
30429
- function mergeCursorHooks(hooks) {
30249
+ function deleteSlopHooksFromCursorHooks(hooks) {
30430
30250
  const isOwnHook = (hook) => typeof hook.command === "string" && /(^|\s)slop hook /.test(hook.command);
30251
+ let removed = false;
30431
30252
  for (const event of Object.keys(hooks)) {
30432
- hooks[event] = (hooks[event] ?? []).filter((hook) => !isOwnHook(hook));
30433
- if (hooks[event].length === 0) delete hooks[event];
30253
+ const kept = (hooks[event] ?? []).filter((hook) => !isOwnHook(hook));
30254
+ if (kept.length !== (hooks[event] ?? []).length) removed = true;
30255
+ if (kept.length === 0) delete hooks[event];
30256
+ else hooks[event] = kept;
30434
30257
  }
30435
- hooks.sessionStart = [...hooks.sessionStart ?? [], { command: "slop hook start --harness cursor", timeout: 5 }];
30436
- return hooks;
30258
+ return removed;
30437
30259
  }
30438
30260
  async function trustCodexHooks(codexHome) {
30439
30261
  const server = spawn3("codex", ["app-server", "-c", "mcp_servers={}"], { stdio: ["pipe", "pipe", "ignore"], env: { ...process.env, CODEX_HOME: codexHome } });
@@ -30472,7 +30294,7 @@ async function trustCodexHooks(codexHome) {
30472
30294
  const sendNotification = (method, params) => server.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}
30473
30295
  `);
30474
30296
  try {
30475
- await callCodexMethod("initialize", { clientInfo: { name: "slop", version: "0.4.16" }, capabilities: { experimentalApi: true } });
30297
+ await callCodexMethod("initialize", { clientInfo: { name: "slop", version: "0.4.18" }, capabilities: { experimentalApi: true } });
30476
30298
  sendNotification("initialized", {});
30477
30299
  const listed = await callCodexMethod("hooks/list", { cwds: [process.cwd()] });
30478
30300
  const ours = listed.data.flatMap((entry) => entry.hooks).filter((hook) => hook.command?.startsWith("slop hook "));
@@ -30488,19 +30310,23 @@ async function trustCodexHooks(codexHome) {
30488
30310
  server.kill();
30489
30311
  }
30490
30312
  }
30491
- async function parseHookInput(installedAs, remember = true) {
30492
- const harness = resolveParentHarness() ?? installedAs;
30493
- const input2 = await new Promise((resolve6) => {
30313
+ var isCursorHook = () => Boolean(process.env.CURSOR_VERSION);
30314
+ function fetchHookPayloadFromStdin() {
30315
+ return new Promise((resolve6) => {
30494
30316
  let data = "";
30495
30317
  process.stdin.on("data", (chunk) => data += chunk);
30496
30318
  process.stdin.on("end", () => resolve6(data));
30497
30319
  setTimeout(() => resolve6(data), 2e3).unref();
30498
30320
  });
30321
+ }
30322
+ async function parseHookInput(installedAs, remember = true) {
30323
+ const harness = fetchHarnessAncestor()?.harness ?? installedAs;
30324
+ const input2 = await fetchHookPayloadFromStdin();
30499
30325
  let cwd = process.cwd();
30500
30326
  let conversation = null;
30501
30327
  try {
30502
30328
  const parsed = JSON.parse(input2);
30503
- cwd = parsed.cwd ?? parsed.workspace_roots?.[0] ?? cwd;
30329
+ cwd = parsed.cwd ?? cwd;
30504
30330
  conversation = parsed.session_id ?? null;
30505
30331
  } catch {
30506
30332
  }
@@ -30509,22 +30335,24 @@ async function parseHookInput(installedAs, remember = true) {
30509
30335
  return { cwd, conversation, harness };
30510
30336
  }
30511
30337
  async function runPromptHook(harness) {
30338
+ if (isCursorHook()) return;
30512
30339
  await parseHookInput(harness);
30513
30340
  }
30514
30341
  async function runToolHook(harness) {
30342
+ if (isCursorHook()) return;
30515
30343
  await parseHookInput(harness, false);
30516
30344
  }
30517
30345
  async function runStartHook(installedAs) {
30518
- if (isNonInteractiveHarness()) return;
30346
+ if (isCursorHook() || isNonInteractiveHarness()) return;
30519
30347
  const { conversation, harness } = await parseHookInput(installedAs);
30520
30348
  if (!conversation) return;
30521
- const carried = harness === "codex" ? " (no wait tool on Codex; a reply becomes your next turn)" : harness === "cursor" ? " (no wait tool on Cursor; a message for you arrives as a new turn in this chat)" : "";
30349
+ const carried = harness === "codex" ? " (no wait tool on Codex; a reply becomes your next turn)" : "";
30522
30350
  const text = `On Slopware Tasks you are ${generateAgentName(conversation)}: that is how people and other agents address you (session who lists everyone). Messages between agents are letters, read when the other side gets to them: write once, reply only when it changes something, never wait for a reply${carried}.`;
30523
30351
  process.stdout.write(`${JSON.stringify({ hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: text }, additional_context: text })}
30524
30352
  `);
30525
30353
  }
30526
30354
  async function runStopHook(harness) {
30527
- if (isNonInteractiveHarness()) return;
30355
+ if (isCursorHook() || isNonInteractiveHarness()) return;
30528
30356
  const { cwd } = await parseHookInput(harness);
30529
30357
  process.chdir(cwd);
30530
30358
  const parts = drainInbox(cwd).map(formatPushBlock);
@@ -30651,9 +30479,9 @@ function attachUpdateNoticeToTools(list, version2) {
30651
30479
  function registerMcpCommands(program3, version2) {
30652
30480
  const mcp = program3.command("mcp").description("The MCP server: the board and its threads as tools for Claude Code, Codex and any MCP client. Holds your live session while it runs.");
30653
30481
  mcp.command("serve", { isDefault: true }).description("Serve MCP over stdio (what the harness runs).").action(() => serveMcp(attachUpdateNoticeToTools(buildTools(), version2), { name: "slop", version: version2 }));
30654
- mcp.command("install").description("Register the server and the two hooks with Claude Code and Codex on this machine.").action(async () => {
30482
+ mcp.command("install").description("Register the server and the hooks with Claude Code and Codex on this machine.").action(async () => {
30655
30483
  const homes = fetchCodexHomes();
30656
- const lines = [...fetchClaudeHomes().flatMap((home) => installIntoClaudeHome(home)), ...homes.flatMap((home) => installIntoCodexHome(home)), ...fetchCursorHomes().flatMap((home) => installIntoCursorHome(home))];
30484
+ const lines = [...fetchClaudeHomes().flatMap((home) => installIntoClaudeHome(home)), ...homes.flatMap((home) => installIntoCodexHome(home)), ...removeFromCursorHome(join16(homedir4(), ".cursor"))];
30657
30485
  let codexPresent = true;
30658
30486
  try {
30659
30487
  execFileSync6("codex", ["--version"], { stdio: "ignore" });
@@ -30669,10 +30497,10 @@ function registerMcpCommands(program3, version2) {
30669
30497
  const known = AgentHarness.safeParse(options.harness);
30670
30498
  return known.success ? known.data : "other";
30671
30499
  };
30672
- hook.command("start").description("SessionStart hook (Claude Code, Codex, Cursor): writes the conversation down so the MCP server is its session, and tells the agent its name and how messages work.").option("--harness <name>", "claude, codex or cursor").action((options) => runStartHook(parseHarnessFromOption(options)));
30673
- hook.command("tool").description("No longer installed; kept so an older install's hook exits cleanly.").option("--harness <name>", "claude, codex or cursor").action((options) => runToolHook(parseHarnessFromOption(options)));
30674
- hook.command("prompt").description("No longer installed; kept so an older install's hook exits cleanly.").option("--harness <name>", "claude, codex or cursor").action((options) => runPromptHook(parseHarnessFromOption(options)));
30675
- hook.command("stop").description("No longer installed (Claude Code is woken over its socket, Codex through codex queue); kept so an older install's hook still hands over unread messages.").option("--harness <name>", "claude, codex or cursor").action((options) => runStopHook(parseHarnessFromOption(options)));
30500
+ hook.command("start").description("SessionStart hook (Claude Code, Codex): writes the conversation down so the MCP server is its session, and tells the agent its name and how messages work.").option("--harness <name>", "claude or codex").action((options) => runStartHook(parseHarnessFromOption(options)));
30501
+ hook.command("tool").description("No longer installed; kept so an older install's hook exits cleanly.").option("--harness <name>", "claude or codex").action((options) => runToolHook(parseHarnessFromOption(options)));
30502
+ hook.command("prompt").description("No longer installed; kept so an older install's hook exits cleanly.").option("--harness <name>", "claude or codex").action((options) => runPromptHook(parseHarnessFromOption(options)));
30503
+ hook.command("stop").description("No longer installed (Claude Code is woken over its socket, Codex through codex queue); kept so an older install's hook still hands over unread messages.").option("--harness <name>", "claude or codex").action((options) => runStopHook(parseHarnessFromOption(options)));
30676
30504
  }
30677
30505
 
30678
30506
  // src/commands/sessions.ts
@@ -30760,13 +30588,13 @@ ${written.map((path2) => ` ${path2}`).join("\n")}`);
30760
30588
  }
30761
30589
 
30762
30590
  // src/index.ts
30763
- var program2 = new Command().name("slop").description("Slopware Tasks from the terminal. Everything a person can do on the board, an agent can do here.").version("0.4.16").option("--json", "machine-readable output: JSON on stdout, errors as JSON on stderr").hook("preAction", (_, action) => updateJsonMode(Boolean(action.optsWithGlobals().json))).showHelpAfterError("(run with --help for usage)");
30591
+ var program2 = new Command().name("slop").description("Slopware Tasks from the terminal. Everything a person can do on the board, an agent can do here.").version("0.4.18").option("--json", "machine-readable output: JSON on stdout, errors as JSON on stderr").hook("preAction", (_, action) => updateJsonMode(Boolean(action.optsWithGlobals().json))).showHelpAfterError("(run with --help for usage)");
30764
30592
  registerAuthCommands(program2);
30765
30593
  registerProjectCommands(program2);
30766
30594
  var taskCommand = registerTaskCommands(program2);
30767
30595
  registerSessionCommands(program2);
30768
30596
  registerThreadCommands(program2, taskCommand);
30769
- registerMcpCommands(program2, "0.4.16");
30597
+ registerMcpCommands(program2, "0.4.18");
30770
30598
  registerGuideCommand(program2);
30771
30599
  registerSkillCommands(program2);
30772
30600
  program2.parseAsync().catch(exitWithError);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slopwaresystems",
3
- "version": "0.4.16",
3
+ "version": "0.4.18",
4
4
  "private": false,
5
5
  "description": "slop: the Slopware Tasks board from the terminal, for people and coding agents.",
6
6
  "type": "module",