slopwaresystems 0.9.4 → 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.4",
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.4";
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) {
@@ -28760,6 +28760,25 @@ function persistRememberedConversation(cwd, conversation) {
28760
28760
  const directory = `${buildConversationPath(cwd)}.d`;
28761
28761
  persistLocalState(join3(directory, `${generateLocalStateKey(cwd, conversation.id)}.json`), JSON.stringify(conversation));
28762
28762
  }
28763
+ var buildRelayConversationPath = (relay) => join3(buildSessionsDir(), `relay-${relay.relayPid}-${relay.harnessPid}.conversation.json`);
28764
+ function persistRelayConversation(relay, conversation) {
28765
+ try {
28766
+ persistLocalState(buildRelayConversationPath(relay), JSON.stringify(conversation));
28767
+ } catch {
28768
+ }
28769
+ }
28770
+ function fetchRelayConversation(relay) {
28771
+ try {
28772
+ const parsed = JSON.parse(readFileSync4(buildRelayConversationPath(relay), "utf8"));
28773
+ const harness = AgentHarness.safeParse(parsed.harness);
28774
+ return typeof parsed.id === "string" && parsed.id && harness.success ? { id: parsed.id, harness: harness.data } : null;
28775
+ } catch {
28776
+ return null;
28777
+ }
28778
+ }
28779
+ function deleteRelayConversation(relay) {
28780
+ rmSync4(buildRelayConversationPath(relay), { force: true });
28781
+ }
28763
28782
  function fetchRememberedConversation(cwd, notBefore, harness) {
28764
28783
  const path3 = buildConversationPath(cwd);
28765
28784
  const candidates = [];
@@ -29378,6 +29397,24 @@ function isMainChatThread(roots, threadId) {
29378
29397
  const head = fetchFileHead(path3, HEAD_BYTES);
29379
29398
  return head === "" ? true : isMainChatRollout(head.split("\n")[0] ?? "");
29380
29399
  }
29400
+ function parseCodexThreadFromToolCallMeta(meta3) {
29401
+ const turn = meta3["x-codex-turn-metadata"];
29402
+ if (typeof turn !== "object" || turn === null || Array.isArray(turn)) return null;
29403
+ const thread = turn.thread_id ?? meta3.threadId;
29404
+ return typeof thread === "string" && thread.trim() ? thread.trim() : null;
29405
+ }
29406
+ var PARENT_THREAD_HOPS = 8;
29407
+ function fetchCodexChatThreadId(roots, threadId) {
29408
+ let current = threadId;
29409
+ for (let hop = 0; hop < PARENT_THREAD_HOPS; hop += 1) {
29410
+ const path3 = fetchCodexRolloutPath(roots, current);
29411
+ const line = path3 ? fetchFileHead(path3, HEAD_BYTES).split("\n")[0] ?? "" : "";
29412
+ const parent = /"parent_thread_id"\s*:\s*"([^"]+)"/.exec(line)?.[1];
29413
+ if (!parent || parent === current) return current;
29414
+ current = parent;
29415
+ }
29416
+ return current;
29417
+ }
29381
29418
  function fetchCodexRolloutPath(roots, threadId) {
29382
29419
  const suffix = `-${threadId}.jsonl`;
29383
29420
  let best = null;
@@ -29451,15 +29488,14 @@ function fetchKnownConversation(cwd) {
29451
29488
  if (remembered?.harness === "codex" && isMainChatThread(fetchCodexHomes().map((home) => join9(home, "sessions")), remembered.id)) return remembered;
29452
29489
  return remembered?.harness === "codex" ? null : remembered;
29453
29490
  }
29454
- function resolveAgentIdentity(cwd, fallback = "process") {
29455
- const conversation = fetchKnownConversation(cwd);
29491
+ function resolveAgentIdentity(cwd, fallback = "process", conversation = fetchKnownConversation(cwd)) {
29456
29492
  const repo = fetchRepo();
29457
29493
  return {
29458
29494
  conversation: conversation?.id ?? (fallback === "process" ? processConversation : `dir:${resolve4(cwd)}`),
29459
29495
  harness: conversation?.harness ?? null,
29460
29496
  directory: repo ? basename3(repo.worktree) : basename3(cwd),
29461
29497
  branch: fetchCurrentBranch(cwd),
29462
- cliVersion: "0.9.4"
29498
+ cliVersion: "0.9.6"
29463
29499
  };
29464
29500
  }
29465
29501
 
@@ -29924,6 +29960,9 @@ linked to ${link.projectKey} ${link.projectName} (${link.baseUrl})`
29924
29960
  });
29925
29961
  }
29926
29962
 
29963
+ // src/commands/mcp.ts
29964
+ import { join as join26 } from "path";
29965
+
29927
29966
  // src/platform/mcp.ts
29928
29967
  import { createInterface } from "readline";
29929
29968
 
@@ -30008,6 +30047,7 @@ function buildMcpRequestHandler(tools, info, sendJsonRpcMessage, options2 = {})
30008
30047
  const output2 = new McpOutput();
30009
30048
  let text;
30010
30049
  try {
30050
+ options2.onToolCall?.(isRecord(params._meta) ? params._meta : {});
30011
30051
  const suppliedArguments = params.arguments ?? {};
30012
30052
  const args2 = tool.parseInput ? tool.parseInput(suppliedArguments) : suppliedArguments;
30013
30053
  text = output2.formatResult(await tool.run(args2, output2));
@@ -30123,7 +30163,7 @@ function buildUpdateNotice() {
30123
30163
  }
30124
30164
  function resolveLatestCliVersion() {
30125
30165
  const deprecated = fetchDeprecatedVersions();
30126
- return sortCliVersions([CLI_RELEASE, "0.9.4", 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;
30127
30167
  }
30128
30168
  function formatAgentCliVersion(version2, latest = resolveLatestCliVersion()) {
30129
30169
  const label = version2 ? `slop ${version2}` : "an older slop";
@@ -30559,7 +30599,7 @@ async function updateInstalledCliFromRegistry(launcher, options2) {
30559
30599
  const running = fetchLaunchCandidate(launcher).version;
30560
30600
  const action = running === previous ? "current" : isNewerVersion(running, previous) ? "updated" : "rolled-back";
30561
30601
  const latest = resolveNewestOfferedRelease(index);
30562
- deleteUnusedVersions(/* @__PURE__ */ new Set([running, previous, current, launcher.bundledVersion, "0.9.4"]));
30602
+ deleteUnusedVersions(/* @__PURE__ */ new Set([running, previous, current, launcher.bundledVersion, "0.9.6"]));
30563
30603
  persistUpdateStatus({ checkedAt: now(), latest, failures: 0, lastError: null });
30564
30604
  return { action, running, previous, latest };
30565
30605
  } catch (error62) {
@@ -31366,7 +31406,7 @@ function watchLaunchTarget(onChange) {
31366
31406
  }
31367
31407
  function runMcpRelay() {
31368
31408
  const launcher = readLauncherInfo();
31369
- const own2 = { version: "0.9.4", main: fileURLToPath(import.meta.url), downloaded: false };
31409
+ const own2 = { version: "0.9.6", main: fileURLToPath(import.meta.url), downloaded: false };
31370
31410
  const relay = new McpRelay({
31371
31411
  input: process.stdin,
31372
31412
  output: process.stdout,
@@ -31807,7 +31847,7 @@ async function waitForMessage(api, projectId, taskKey, options2 = {}) {
31807
31847
  setTimeout(() => resolve6(outcome), 50);
31808
31848
  };
31809
31849
  const held = new HeldSession(api, projectId, taskKey, {
31810
- claim: () => options2.claim ?? { harness: null, directory: null, branch: null, cliVersion: "0.9.4" },
31850
+ claim: () => options2.claim ?? { harness: null, directory: null, branch: null, cliVersion: "0.9.6" },
31811
31851
  reconnect: false,
31812
31852
  onHello: options2.onHello,
31813
31853
  onEnd: () => finishWait({ kind: "ended" }),
@@ -32118,7 +32158,6 @@ var WORKER_FRESH_MS = 12e4;
32118
32158
  var WORKER_GONE_MS = 3e5;
32119
32159
  var READ_CAP = 256 * 1024;
32120
32160
  var CARRY_CAP = 64 * 1024;
32121
- var CHILD_SCAN_CAP = 40;
32122
32161
  var DISCOVER_EVERY_MS = 1e4;
32123
32162
  var TAIL_EVERY_MS = 1e3;
32124
32163
  var truncate = (text, cap) => text.length <= cap ? text : `${text.slice(0, cap - 1)}\u2026`;
@@ -32252,7 +32291,33 @@ function parseModelFromCodexLine(line) {
32252
32291
  if (entry.type !== "turn_context" || typeof model !== "string" || !model.trim()) return null;
32253
32292
  return truncate(model.trim(), MODEL_MAX);
32254
32293
  }
32255
- 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 };
32256
32321
  var fetchFileSize = (path3) => {
32257
32322
  try {
32258
32323
  return statSync7(path3).size;
@@ -32307,6 +32372,14 @@ function fetchModelFromFileTail(path3, parseModel) {
32307
32372
  }
32308
32373
  return null;
32309
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
+ }
32310
32383
  var fetchChangedAt = (path3) => {
32311
32384
  try {
32312
32385
  return statSync7(path3).mtimeMs;
@@ -32382,7 +32455,7 @@ function parseCodexWorkerLabel(head) {
32382
32455
  return parts.length ? truncate(parts.join(" \u2014 "), WORKER_LABEL_MAX) : null;
32383
32456
  }
32384
32457
  function findCodexChildRollouts(roots, parentThreadId, changedAfter) {
32385
- const rollouts = [];
32458
+ const rollouts = /* @__PURE__ */ new Map();
32386
32459
  const findRolloutsInDirectory = (directory, depth) => {
32387
32460
  let entries;
32388
32461
  try {
@@ -32400,12 +32473,13 @@ function findCodexChildRollouts(roots, parentThreadId, changedAfter) {
32400
32473
  if (!ref || ref === parentThreadId) continue;
32401
32474
  const changedAt = fetchChangedAt(path3);
32402
32475
  if (changedAt < changedAfter) continue;
32403
- rollouts.push({ path: path3, ref, changedAt });
32476
+ if (!rollouts.has(ref)) rollouts.set(ref, { path: path3, ref, changedAt });
32404
32477
  }
32405
32478
  };
32406
32479
  for (const root of roots) findRolloutsInDirectory(root, 0);
32407
32480
  const found = [];
32408
- 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;
32409
32483
  const head = fetchFileHead(rollout.path, HEAD_BYTES).split("\n")[0] ?? "";
32410
32484
  if (!head.includes('"session_meta"') || isCodexHelperRollout(head)) continue;
32411
32485
  if (!new RegExp(`"parent_thread_id"\\s*:\\s*"${parentThreadId}"`).test(head)) continue;
@@ -32497,7 +32571,10 @@ var HarnessActivity = class {
32497
32571
  for (const file2 of found) {
32498
32572
  const known = this.workers.get(file2.ref);
32499
32573
  if (known) Object.assign(known, { label: file2.label, directory: file2.directory, branch: file2.branch, changedAt: file2.changedAt });
32500
- 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
+ }
32501
32578
  }
32502
32579
  const live = new Set(found.map((file2) => file2.ref));
32503
32580
  for (const ref of [...this.workers.keys()]) if (!live.has(ref)) this.workers.delete(ref);
@@ -32522,35 +32599,39 @@ var HarnessActivity = class {
32522
32599
  }
32523
32600
  return [];
32524
32601
  }
32525
- /** 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. */
32526
32603
  read() {
32527
- const { parseToolCall, parseModel } = this.parsers;
32528
- const fetchNewestCallAndModel = (tail) => {
32604
+ const { parseToolCall, parseModel, parseTurnState } = this.parsers;
32605
+ const fetchNewestCallModelAndTurnState = (tail) => {
32529
32606
  let call = null;
32530
32607
  let model = null;
32608
+ let turn = null;
32531
32609
  for (const line of tail.fetchNewLines()) {
32532
32610
  call = parseToolCall(line) ?? call;
32533
32611
  model = parseModel(line) ?? model;
32612
+ turn = parseTurnState(line) ?? turn;
32534
32613
  }
32535
- return { call, model };
32614
+ return { call, model, turn };
32536
32615
  };
32537
32616
  if (this.own) {
32538
- const { call, model } = fetchNewestCallAndModel(this.own);
32617
+ const { call, model } = fetchNewestCallModelAndTurnState(this.own);
32539
32618
  this.doing = call ?? this.doing;
32540
32619
  this.model = model ?? this.model;
32541
32620
  }
32542
32621
  for (const worker of this.workers.values()) {
32543
- const { call, model } = fetchNewestCallAndModel(worker.tail);
32622
+ const { call, model, turn } = fetchNewestCallModelAndTurnState(worker.tail);
32544
32623
  worker.doing = call ?? worker.doing;
32545
32624
  worker.model = model ?? worker.model;
32625
+ if (turn) worker.finished = turn === "finished";
32546
32626
  }
32547
32627
  }
32548
32628
  /**
32549
- * Every worker, oldest first, so the roster changes only when one starts or finishes and never because one got
32550
- * 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.
32551
32632
  */
32552
32633
  buildRoster() {
32553
- 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 }));
32554
32635
  }
32555
32636
  /** Quiet for a while: still listed, because it is probably thinking, but saying nothing about what it is doing. */
32556
32637
  buildWorkerLines(roster, now) {
@@ -35445,7 +35526,7 @@ var SplatRendererConnection = class {
35445
35526
  try {
35446
35527
  let path3;
35447
35528
  try {
35448
- const ticket = await this.api.post(`/projects/${this.projectId}/splat/renderer/ticket`, ApiResponses.ConnectTicket, { cliVersion: "0.9.4", 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"] });
35449
35530
  path3 = ticket.path;
35450
35531
  } catch (error62) {
35451
35532
  if (error62 instanceof CliError && ["unauthenticated", "not_found", "forbidden", "bad_request"].includes(error62.code)) {
@@ -37479,17 +37560,17 @@ function hasHarnessSetup() {
37479
37560
  function syncMachineSetupAfterUpdate() {
37480
37561
  const nothing = { skills: [], retired: [], reinstalling: false };
37481
37562
  try {
37482
- if (!isUpdateCheckEnabled() || fetchMachineSetupState()?.version === "0.9.4") return nothing;
37563
+ if (!isUpdateCheckEnabled() || fetchMachineSetupState()?.version === "0.9.6") return nothing;
37483
37564
  const lock = claimFileLock(buildMachineSetupLockDirectory(), { staleAfterMs: 6e4 });
37484
37565
  if (!lock) return nothing;
37485
37566
  try {
37486
37567
  const state = fetchMachineSetupState();
37487
- if (state?.version === "0.9.4") return nothing;
37568
+ if (state?.version === "0.9.6") return nothing;
37488
37569
  const skills = hasInstalledSkills() ? installSkills() : [];
37489
37570
  const shipped = Object.keys(SKILLS);
37490
37571
  const retired = deleteRetiredSkills((state?.skills ?? []).filter((name) => !shipped.includes(name)));
37491
37572
  const setupRevision = state?.setupRevision ?? (hasHarnessSetup() ? UNRECORDED_SETUP_REVISION : null);
37492
- persistMachineSetupState({ version: "0.9.4", setupRevision, skills: hasInstalledSkills() ? shipped : [] });
37573
+ persistMachineSetupState({ version: "0.9.6", setupRevision, skills: hasInstalledSkills() ? shipped : [] });
37493
37574
  const reinstalling = setupRevision !== null && setupRevision < SETUP_REVISION && runMcpInstallInBackground();
37494
37575
  return { skills, retired, reinstalling };
37495
37576
  } finally {
@@ -37656,7 +37737,7 @@ async function trustCodexHooks(codexHome) {
37656
37737
  const sendNotification = (method, params) => server.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}
37657
37738
  `);
37658
37739
  try {
37659
- await callCodexMethod("initialize", { clientInfo: { name: "slop", version: "0.9.4" }, capabilities: { experimentalApi: true } });
37740
+ await callCodexMethod("initialize", { clientInfo: { name: "slop", version: "0.9.6" }, capabilities: { experimentalApi: true } });
37660
37741
  sendNotification("initialized", {});
37661
37742
  const listed = await callCodexMethod("hooks/list", { cwds: [process.cwd()] });
37662
37743
  const ours = listed.data.flatMap((entry) => entry.hooks).filter((hook) => hook.command?.startsWith("slop hook "));
@@ -37689,18 +37770,34 @@ function registerMcpInstallCommand(mcp) {
37689
37770
  }
37690
37771
 
37691
37772
  // src/commands/mcp.ts
37692
- function buildTools(lifecycle) {
37693
- if (isNonInteractiveHarness()) return { tools: [], startPresence: () => void 0, releaseSessions: () => void 0 };
37773
+ function buildTools(lifecycle, relay) {
37774
+ if (isNonInteractiveHarness()) return { tools: [], startPresence: () => void 0, releaseSessions: () => void 0, observeToolCall: () => void 0 };
37694
37775
  let api = null;
37695
37776
  const requireApi = () => api ??= buildAuthedApiClient();
37696
37777
  let identity = null;
37778
+ let called = relay ? fetchRelayConversation(relay) : null;
37779
+ const observeToolCall = (meta3) => {
37780
+ if (called) return;
37781
+ const thread = parseCodexThreadFromToolCallMeta(meta3);
37782
+ if (!thread) return;
37783
+ called = { id: fetchCodexChatThreadId(fetchCodexHomes().map((home) => join26(home, "sessions")), thread), harness: "codex" };
37784
+ if (relay) persistRelayConversation(relay, called);
37785
+ };
37786
+ const fetchOwnConversation = () => called ?? fetchKnownConversation(process.cwd());
37697
37787
  let stopped = false;
37698
37788
  const projects = /* @__PURE__ */ new Map();
37699
37789
  const requireProjectTools = (project) => {
37700
37790
  if (stopped) throw new Error("The MCP server is stopping");
37701
37791
  let context2 = projects.get(project.id);
37702
37792
  if (!context2) {
37703
- identity ??= resolveAgentIdentity(process.cwd());
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
+ }
37704
37801
  context2 = createProjectTools(buildAuthedApiClient(), project, identity);
37705
37802
  projects.set(project.id, context2);
37706
37803
  }
@@ -37719,8 +37816,9 @@ function buildTools(lifecycle) {
37719
37816
  if (presenceStarted) return;
37720
37817
  presenceStarted = true;
37721
37818
  void (async () => {
37722
- for (let waited = 0; waited < 1e4 && !fetchKnownConversation(process.cwd()) && !stopped; waited += 250) await new Promise((resolve6) => setTimeout(resolve6, 250));
37819
+ for (let waited = 0; waited < 1e4 && !fetchOwnConversation() && !stopped; waited += 250) await new Promise((resolve6) => setTimeout(resolve6, 250));
37723
37820
  if (stopped) return;
37821
+ if (!identity && !fetchOwnConversation() && resolveParentHarness() === "codex") return;
37724
37822
  await requireProjectTools(await resolveProject(requireApi())).start();
37725
37823
  })().catch(() => void 0);
37726
37824
  };
@@ -37728,6 +37826,7 @@ function buildTools(lifecycle) {
37728
37826
  if (stopped) return;
37729
37827
  stopped = true;
37730
37828
  for (const context2 of projects.values()) context2.stop(reason);
37829
+ if (relay && reason === "released") deleteRelayConversation(relay);
37731
37830
  };
37732
37831
  let exiting = false;
37733
37832
  const endSessionAndExit = (reason) => {
@@ -37739,7 +37838,7 @@ function buildTools(lifecycle) {
37739
37838
  process.stdin.once("end", () => endSessionAndExit(lifecycle.onInputEnd));
37740
37839
  process.stdin.once("close", () => endSessionAndExit(lifecycle.onInputEnd));
37741
37840
  for (const signal of ["SIGTERM", "SIGHUP", "SIGINT"]) process.once(signal, () => endSessionAndExit("released"));
37742
- return { tools: createProjectGroupedTools(runHandlerByName), startPresence, releaseSessions };
37841
+ return { tools: createProjectGroupedTools(runHandlerByName), startPresence, releaseSessions, observeToolCall };
37743
37842
  }
37744
37843
  function attachUpdateNoticeToTools(list) {
37745
37844
  return list.map((tool) => ({
@@ -37754,10 +37853,12 @@ ${notice}` : result;
37754
37853
  }));
37755
37854
  }
37756
37855
  function serveBoardTools(version2) {
37757
- const underRelay = drainRelayChildContextFromEnvironment() !== null;
37856
+ const relay = drainRelayChildContextFromEnvironment();
37857
+ const underRelay = relay !== null;
37758
37858
  const lifecycle = { onInputEnd: underRelay ? "orphaned" : "released" };
37759
- const { tools, startPresence, releaseSessions } = buildTools(lifecycle);
37859
+ const { tools, startPresence, releaseSessions, observeToolCall } = buildTools(lifecycle, relay);
37760
37860
  serveMcp(attachUpdateNoticeToTools(tools), { name: "slop", version: version2 }, {
37861
+ onToolCall: observeToolCall,
37761
37862
  onNotification: (method) => {
37762
37863
  if (method === RELAY_START_NOTIFICATION) startPresence();
37763
37864
  else if (method === RELAY_HANDOVER_NOTIFICATION) {
@@ -37921,7 +38022,7 @@ var options = optionEnd < 0 ? args : args.slice(0, optionEnd);
37921
38022
  updateJsonMode(options.includes("--json"));
37922
38023
  syncMachineSetupAfterUpdate();
37923
38024
  if (!options.includes("--background") && !options.includes("--child")) scheduleBackgroundUpdate();
37924
- 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.4").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({
37925
38026
  outputError: (message, write) => {
37926
38027
  if (!isJsonMode()) write(message);
37927
38028
  },
@@ -37938,7 +38039,7 @@ var sessionCommand = registerSessionCommands(program2);
37938
38039
  registerThreadCommands(program2, taskCommand);
37939
38040
  registerOperationCommands(program2, taskCommand, sessionCommand);
37940
38041
  registerSplatCommands(program2);
37941
- registerMcpCommands(program2, "0.9.4");
38042
+ registerMcpCommands(program2, "0.9.6");
37942
38043
  registerGuideCommand(program2);
37943
38044
  registerSkillCommands(program2);
37944
38045
  registerUpdateCommand(program2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slopwaresystems",
3
- "version": "0.9.4",
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",