slopwaresystems 0.9.5 → 0.9.6

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/index.js CHANGED
@@ -146,7 +146,7 @@ function fetchLaunchCandidate(launcher2) {
146
146
  var path = fileURLToPath(import.meta.url);
147
147
  var launcher = {
148
148
  path,
149
- bundledVersion: "0.9.5",
149
+ bundledVersion: "0.9.6",
150
150
  bundledMain: fileURLToPath(new URL("./main.js", import.meta.url)),
151
151
  installed: isInstalledLauncherPath(path)
152
152
  };
package/dist/main.js CHANGED
@@ -26542,7 +26542,7 @@ var TaskEventRecord = external_exports.object({
26542
26542
  });
26543
26543
 
26544
26544
  // ../contract/src/work/cli-release.ts
26545
- var CLI_RELEASE = "0.9.5";
26545
+ var CLI_RELEASE = "0.9.6";
26546
26546
  var CLI_VERSION_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
26547
26547
  var CLI_VERSION_MAX = 64;
26548
26548
  function parseCliVersion(version2) {
@@ -29495,7 +29495,7 @@ function resolveAgentIdentity(cwd, fallback = "process", conversation = fetchKno
29495
29495
  harness: conversation?.harness ?? null,
29496
29496
  directory: repo ? basename3(repo.worktree) : basename3(cwd),
29497
29497
  branch: fetchCurrentBranch(cwd),
29498
- cliVersion: "0.9.5"
29498
+ cliVersion: "0.9.6"
29499
29499
  };
29500
29500
  }
29501
29501
 
@@ -30163,7 +30163,7 @@ function buildUpdateNotice() {
30163
30163
  }
30164
30164
  function resolveLatestCliVersion() {
30165
30165
  const deprecated = fetchDeprecatedVersions();
30166
- return sortCliVersions([CLI_RELEASE, "0.9.5", fetchUpdateStatus().latest ?? CLI_RELEASE]).filter((version2) => !deprecated.has(version2)).at(-1) ?? CLI_RELEASE;
30166
+ return sortCliVersions([CLI_RELEASE, "0.9.6", fetchUpdateStatus().latest ?? CLI_RELEASE]).filter((version2) => !deprecated.has(version2)).at(-1) ?? CLI_RELEASE;
30167
30167
  }
30168
30168
  function formatAgentCliVersion(version2, latest = resolveLatestCliVersion()) {
30169
30169
  const label = version2 ? `slop ${version2}` : "an older slop";
@@ -30599,7 +30599,7 @@ async function updateInstalledCliFromRegistry(launcher, options2) {
30599
30599
  const running = fetchLaunchCandidate(launcher).version;
30600
30600
  const action = running === previous ? "current" : isNewerVersion(running, previous) ? "updated" : "rolled-back";
30601
30601
  const latest = resolveNewestOfferedRelease(index);
30602
- deleteUnusedVersions(/* @__PURE__ */ new Set([running, previous, current, launcher.bundledVersion, "0.9.5"]));
30602
+ deleteUnusedVersions(/* @__PURE__ */ new Set([running, previous, current, launcher.bundledVersion, "0.9.6"]));
30603
30603
  persistUpdateStatus({ checkedAt: now(), latest, failures: 0, lastError: null });
30604
30604
  return { action, running, previous, latest };
30605
30605
  } catch (error62) {
@@ -31406,7 +31406,7 @@ function watchLaunchTarget(onChange) {
31406
31406
  }
31407
31407
  function runMcpRelay() {
31408
31408
  const launcher = readLauncherInfo();
31409
- const own2 = { version: "0.9.5", main: fileURLToPath(import.meta.url), downloaded: false };
31409
+ const own2 = { version: "0.9.6", main: fileURLToPath(import.meta.url), downloaded: false };
31410
31410
  const relay = new McpRelay({
31411
31411
  input: process.stdin,
31412
31412
  output: process.stdout,
@@ -31847,7 +31847,7 @@ async function waitForMessage(api, projectId, taskKey, options2 = {}) {
31847
31847
  setTimeout(() => resolve6(outcome), 50);
31848
31848
  };
31849
31849
  const held = new HeldSession(api, projectId, taskKey, {
31850
- claim: () => options2.claim ?? { harness: null, directory: null, branch: null, cliVersion: "0.9.5" },
31850
+ claim: () => options2.claim ?? { harness: null, directory: null, branch: null, cliVersion: "0.9.6" },
31851
31851
  reconnect: false,
31852
31852
  onHello: options2.onHello,
31853
31853
  onEnd: () => finishWait({ kind: "ended" }),
@@ -32158,7 +32158,6 @@ var WORKER_FRESH_MS = 12e4;
32158
32158
  var WORKER_GONE_MS = 3e5;
32159
32159
  var READ_CAP = 256 * 1024;
32160
32160
  var CARRY_CAP = 64 * 1024;
32161
- var CHILD_SCAN_CAP = 40;
32162
32161
  var DISCOVER_EVERY_MS = 1e4;
32163
32162
  var TAIL_EVERY_MS = 1e3;
32164
32163
  var truncate = (text, cap) => text.length <= cap ? text : `${text.slice(0, cap - 1)}\u2026`;
@@ -32292,7 +32291,33 @@ function parseModelFromCodexLine(line) {
32292
32291
  if (entry.type !== "turn_context" || typeof model !== "string" || !model.trim()) return null;
32293
32292
  return truncate(model.trim(), MODEL_MAX);
32294
32293
  }
32295
- var resolveLineParsers = (harness) => harness === "codex" ? { parseToolCall: parseToolCallFromCodexLine, parseModel: parseModelFromCodexLine } : { parseToolCall: parseToolCallFromClaudeLine, parseModel: parseModelFromClaudeLine };
32294
+ function parseTurnStateFromClaudeLine(line) {
32295
+ if (!line.includes('"assistant"') && !line.includes('"user"')) return null;
32296
+ let entry;
32297
+ try {
32298
+ entry = JSON.parse(line);
32299
+ } catch {
32300
+ return null;
32301
+ }
32302
+ if (entry.type === "user") return "working";
32303
+ if (entry.type !== "assistant") return null;
32304
+ const stop = entry.message?.stop_reason;
32305
+ return stop === "end_turn" || stop === "stop_sequence" ? "finished" : "working";
32306
+ }
32307
+ function parseTurnStateFromCodexLine(line) {
32308
+ if (!line.includes('"event_msg"') && !line.includes('"response_item"') && !line.includes('"turn_context"')) return null;
32309
+ let entry;
32310
+ try {
32311
+ entry = JSON.parse(line);
32312
+ } catch {
32313
+ return null;
32314
+ }
32315
+ if (entry.type === "response_item" || entry.type === "turn_context") return "working";
32316
+ if (entry.type !== "event_msg") return null;
32317
+ if (entry.payload?.type === "task_complete" || entry.payload?.type === "turn_aborted") return "finished";
32318
+ return entry.payload?.type === "task_started" ? "working" : null;
32319
+ }
32320
+ var resolveLineParsers = (harness) => harness === "codex" ? { parseToolCall: parseToolCallFromCodexLine, parseModel: parseModelFromCodexLine, parseTurnState: parseTurnStateFromCodexLine } : { parseToolCall: parseToolCallFromClaudeLine, parseModel: parseModelFromClaudeLine, parseTurnState: parseTurnStateFromClaudeLine };
32296
32321
  var fetchFileSize = (path3) => {
32297
32322
  try {
32298
32323
  return statSync7(path3).size;
@@ -32347,6 +32372,14 @@ function fetchModelFromFileTail(path3, parseModel) {
32347
32372
  }
32348
32373
  return null;
32349
32374
  }
32375
+ function fetchTurnStateFromFileTail(path3, parseTurnState) {
32376
+ const lines2 = new FileTail(path3, "start").fetchNewLines();
32377
+ for (let index = lines2.length - 1; index >= 0; index -= 1) {
32378
+ const state = parseTurnState(lines2[index]);
32379
+ if (state) return state;
32380
+ }
32381
+ return "working";
32382
+ }
32350
32383
  var fetchChangedAt = (path3) => {
32351
32384
  try {
32352
32385
  return statSync7(path3).mtimeMs;
@@ -32422,7 +32455,7 @@ function parseCodexWorkerLabel(head) {
32422
32455
  return parts.length ? truncate(parts.join(" \u2014 "), WORKER_LABEL_MAX) : null;
32423
32456
  }
32424
32457
  function findCodexChildRollouts(roots, parentThreadId, changedAfter) {
32425
- const rollouts = [];
32458
+ const rollouts = /* @__PURE__ */ new Map();
32426
32459
  const findRolloutsInDirectory = (directory, depth) => {
32427
32460
  let entries;
32428
32461
  try {
@@ -32440,12 +32473,13 @@ function findCodexChildRollouts(roots, parentThreadId, changedAfter) {
32440
32473
  if (!ref || ref === parentThreadId) continue;
32441
32474
  const changedAt = fetchChangedAt(path3);
32442
32475
  if (changedAt < changedAfter) continue;
32443
- rollouts.push({ path: path3, ref, changedAt });
32476
+ if (!rollouts.has(ref)) rollouts.set(ref, { path: path3, ref, changedAt });
32444
32477
  }
32445
32478
  };
32446
32479
  for (const root of roots) findRolloutsInDirectory(root, 0);
32447
32480
  const found = [];
32448
- for (const rollout of rollouts.sort((a, b) => b.changedAt - a.changedAt).slice(0, CHILD_SCAN_CAP)) {
32481
+ for (const rollout of [...rollouts.values()].sort((a, b) => b.changedAt - a.changedAt)) {
32482
+ if (found.length >= WORKERS_PER_SESSION) break;
32449
32483
  const head = fetchFileHead(rollout.path, HEAD_BYTES).split("\n")[0] ?? "";
32450
32484
  if (!head.includes('"session_meta"') || isCodexHelperRollout(head)) continue;
32451
32485
  if (!new RegExp(`"parent_thread_id"\\s*:\\s*"${parentThreadId}"`).test(head)) continue;
@@ -32537,7 +32571,10 @@ var HarnessActivity = class {
32537
32571
  for (const file2 of found) {
32538
32572
  const known = this.workers.get(file2.ref);
32539
32573
  if (known) Object.assign(known, { label: file2.label, directory: file2.directory, branch: file2.branch, changedAt: file2.changedAt });
32540
- else this.workers.set(file2.ref, { ...file2, since: new Date(file2.changedAt).toISOString(), model: fetchModelFromFileTail(file2.path, this.parsers.parseModel), doing: null, tail: new FileTail(file2.path, "end") });
32574
+ else {
32575
+ const tail = new FileTail(file2.path, "end");
32576
+ this.workers.set(file2.ref, { ...file2, since: new Date(file2.changedAt).toISOString(), model: fetchModelFromFileTail(file2.path, this.parsers.parseModel), doing: null, finished: fetchTurnStateFromFileTail(file2.path, this.parsers.parseTurnState) === "finished", tail });
32577
+ }
32541
32578
  }
32542
32579
  const live = new Set(found.map((file2) => file2.ref));
32543
32580
  for (const ref of [...this.workers.keys()]) if (!live.has(ref)) this.workers.delete(ref);
@@ -32562,35 +32599,39 @@ var HarnessActivity = class {
32562
32599
  }
32563
32600
  return [];
32564
32601
  }
32565
- /** The newest tool call and the newest model in everything that has been written since the last read. */
32602
+ /** The newest tool call, model and turn state in everything that has been written since the last read. */
32566
32603
  read() {
32567
- const { parseToolCall, parseModel } = this.parsers;
32568
- const fetchNewestCallAndModel = (tail) => {
32604
+ const { parseToolCall, parseModel, parseTurnState } = this.parsers;
32605
+ const fetchNewestCallModelAndTurnState = (tail) => {
32569
32606
  let call = null;
32570
32607
  let model = null;
32608
+ let turn = null;
32571
32609
  for (const line of tail.fetchNewLines()) {
32572
32610
  call = parseToolCall(line) ?? call;
32573
32611
  model = parseModel(line) ?? model;
32612
+ turn = parseTurnState(line) ?? turn;
32574
32613
  }
32575
- return { call, model };
32614
+ return { call, model, turn };
32576
32615
  };
32577
32616
  if (this.own) {
32578
- const { call, model } = fetchNewestCallAndModel(this.own);
32617
+ const { call, model } = fetchNewestCallModelAndTurnState(this.own);
32579
32618
  this.doing = call ?? this.doing;
32580
32619
  this.model = model ?? this.model;
32581
32620
  }
32582
32621
  for (const worker of this.workers.values()) {
32583
- const { call, model } = fetchNewestCallAndModel(worker.tail);
32622
+ const { call, model, turn } = fetchNewestCallModelAndTurnState(worker.tail);
32584
32623
  worker.doing = call ?? worker.doing;
32585
32624
  worker.model = model ?? worker.model;
32625
+ if (turn) worker.finished = turn === "finished";
32586
32626
  }
32587
32627
  }
32588
32628
  /**
32589
- * Every worker, oldest first, so the roster changes only when one starts or finishes and never because one got
32590
- * busy. The contract's guard is far past what a harness runs; past it, the oldest are the ones listed.
32629
+ * Every worker still working, oldest first, so the roster changes only when one starts or finishes and never
32630
+ * because one got busy. One whose turn has ended leaves at once, not when its records have gone quiet. The
32631
+ * contract's guard is far past what a harness runs; past it, the oldest are the ones listed.
32591
32632
  */
32592
32633
  buildRoster() {
32593
- return [...this.workers.values()].sort((a, b) => a.since.localeCompare(b.since) || a.ref.localeCompare(b.ref)).slice(0, WORKERS_PER_SESSION).map((worker) => ({ ref: worker.ref, label: worker.label, directory: worker.directory, branch: worker.branch, model: worker.model, since: worker.since }));
32634
+ return [...this.workers.values()].filter((worker) => !worker.finished).sort((a, b) => a.since.localeCompare(b.since) || a.ref.localeCompare(b.ref)).slice(0, WORKERS_PER_SESSION).map((worker) => ({ ref: worker.ref, label: worker.label, directory: worker.directory, branch: worker.branch, model: worker.model, since: worker.since }));
32594
32635
  }
32595
32636
  /** Quiet for a while: still listed, because it is probably thinking, but saying nothing about what it is doing. */
32596
32637
  buildWorkerLines(roster, now) {
@@ -35485,7 +35526,7 @@ var SplatRendererConnection = class {
35485
35526
  try {
35486
35527
  let path3;
35487
35528
  try {
35488
- const ticket = await this.api.post(`/projects/${this.projectId}/splat/renderer/ticket`, ApiResponses.ConnectTicket, { cliVersion: "0.9.5", onlyMe: this.options.onlyMe ?? false, agent: this.options.agent ?? null, capabilities: ["workspaces"] });
35529
+ const ticket = await this.api.post(`/projects/${this.projectId}/splat/renderer/ticket`, ApiResponses.ConnectTicket, { cliVersion: "0.9.6", onlyMe: this.options.onlyMe ?? false, agent: this.options.agent ?? null, capabilities: ["workspaces"] });
35489
35530
  path3 = ticket.path;
35490
35531
  } catch (error62) {
35491
35532
  if (error62 instanceof CliError && ["unauthenticated", "not_found", "forbidden", "bad_request"].includes(error62.code)) {
@@ -37519,17 +37560,17 @@ function hasHarnessSetup() {
37519
37560
  function syncMachineSetupAfterUpdate() {
37520
37561
  const nothing = { skills: [], retired: [], reinstalling: false };
37521
37562
  try {
37522
- if (!isUpdateCheckEnabled() || fetchMachineSetupState()?.version === "0.9.5") return nothing;
37563
+ if (!isUpdateCheckEnabled() || fetchMachineSetupState()?.version === "0.9.6") return nothing;
37523
37564
  const lock = claimFileLock(buildMachineSetupLockDirectory(), { staleAfterMs: 6e4 });
37524
37565
  if (!lock) return nothing;
37525
37566
  try {
37526
37567
  const state = fetchMachineSetupState();
37527
- if (state?.version === "0.9.5") return nothing;
37568
+ if (state?.version === "0.9.6") return nothing;
37528
37569
  const skills = hasInstalledSkills() ? installSkills() : [];
37529
37570
  const shipped = Object.keys(SKILLS);
37530
37571
  const retired = deleteRetiredSkills((state?.skills ?? []).filter((name) => !shipped.includes(name)));
37531
37572
  const setupRevision = state?.setupRevision ?? (hasHarnessSetup() ? UNRECORDED_SETUP_REVISION : null);
37532
- persistMachineSetupState({ version: "0.9.5", setupRevision, skills: hasInstalledSkills() ? shipped : [] });
37573
+ persistMachineSetupState({ version: "0.9.6", setupRevision, skills: hasInstalledSkills() ? shipped : [] });
37533
37574
  const reinstalling = setupRevision !== null && setupRevision < SETUP_REVISION && runMcpInstallInBackground();
37534
37575
  return { skills, retired, reinstalling };
37535
37576
  } finally {
@@ -37696,7 +37737,7 @@ async function trustCodexHooks(codexHome) {
37696
37737
  const sendNotification = (method, params) => server.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}
37697
37738
  `);
37698
37739
  try {
37699
- await callCodexMethod("initialize", { clientInfo: { name: "slop", version: "0.9.5" }, capabilities: { experimentalApi: true } });
37740
+ await callCodexMethod("initialize", { clientInfo: { name: "slop", version: "0.9.6" }, capabilities: { experimentalApi: true } });
37700
37741
  sendNotification("initialized", {});
37701
37742
  const listed = await callCodexMethod("hooks/list", { cwds: [process.cwd()] });
37702
37743
  const ours = listed.data.flatMap((entry) => entry.hooks).filter((hook) => hook.command?.startsWith("slop hook "));
@@ -37749,7 +37790,14 @@ function buildTools(lifecycle, relay) {
37749
37790
  if (stopped) throw new Error("The MCP server is stopping");
37750
37791
  let context2 = projects.get(project.id);
37751
37792
  if (!context2) {
37752
- identity ??= resolveAgentIdentity(process.cwd(), "process", fetchOwnConversation());
37793
+ if (!identity) {
37794
+ const conversation = fetchOwnConversation();
37795
+ identity = resolveAgentIdentity(process.cwd(), "process", conversation);
37796
+ if (relay && conversation && !called) {
37797
+ called = conversation;
37798
+ persistRelayConversation(relay, conversation);
37799
+ }
37800
+ }
37753
37801
  context2 = createProjectTools(buildAuthedApiClient(), project, identity);
37754
37802
  projects.set(project.id, context2);
37755
37803
  }
@@ -37974,7 +38022,7 @@ var options = optionEnd < 0 ? args : args.slice(0, optionEnd);
37974
38022
  updateJsonMode(options.includes("--json"));
37975
38023
  syncMachineSetupAfterUpdate();
37976
38024
  if (!options.includes("--background") && !options.includes("--child")) scheduleBackgroundUpdate();
37977
- 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.9.5").option("--json", "machine-readable output: JSON on stdout, errors as JSON on stderr").configureOutput({
38025
+ 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.9.6").option("--json", "machine-readable output: JSON on stdout, errors as JSON on stderr").configureOutput({
37978
38026
  outputError: (message, write) => {
37979
38027
  if (!isJsonMode()) write(message);
37980
38028
  },
@@ -37991,7 +38039,7 @@ var sessionCommand = registerSessionCommands(program2);
37991
38039
  registerThreadCommands(program2, taskCommand);
37992
38040
  registerOperationCommands(program2, taskCommand, sessionCommand);
37993
38041
  registerSplatCommands(program2);
37994
- registerMcpCommands(program2, "0.9.5");
38042
+ registerMcpCommands(program2, "0.9.6");
37995
38043
  registerGuideCommand(program2);
37996
38044
  registerSkillCommands(program2);
37997
38045
  registerUpdateCommand(program2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slopwaresystems",
3
- "version": "0.9.5",
3
+ "version": "0.9.6",
4
4
  "private": false,
5
5
  "description": "slop: the Slopware Tasks board from the terminal, for people and coding agents.",
6
6
  "type": "module",