slopwaresystems 0.10.0 → 0.10.1

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.10.0",
149
+ bundledVersion: "0.10.1",
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.10.0";
26545
+ var CLI_RELEASE = "0.10.1";
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) {
@@ -29503,7 +29503,7 @@ function resolveAgentIdentity(cwd, fallback = "process", conversation = fetchKno
29503
29503
  harness: conversation?.harness ?? null,
29504
29504
  directory: repo ? basename3(repo.worktree) : basename3(cwd),
29505
29505
  branch: fetchCurrentBranch(cwd),
29506
- cliVersion: "0.10.0"
29506
+ cliVersion: "0.10.1"
29507
29507
  };
29508
29508
  }
29509
29509
 
@@ -29783,7 +29783,7 @@ This repository is linked to a Slopware Tasks board. Use the \`slop\` CLI to kee
29783
29783
  - Shell project commands for people and scripts: \`slop project bindings --project <KEY>\` lists repository bindings, including other checkouts; \`slop project binding delete <ID> --project <KEY>\` removes one if you created it or are an org admin. \`slop session reachable <AGENT|SESSION_ID> on|off\` changes visibility for one of your live sessions. \`slop task move <KEY> <COLUMN> --before <NEIGHBOR>\` places the task immediately before that neighbor; \`--after <NEIGHBOR>\` places it immediately after. Use both only for adjacent neighbors: \`--after <PREVIOUS> --before <NEXT>\`. With neither, the task goes to the end of the column. \`slop task delete <KEY...>\` accepts explicit task keys; delete only tasks you created. Each deletion is separate, so a later server rejection can follow earlier successful deletions.
29784
29784
  - Other shell project actions for people and scripts: \`slop task unlink <KEY> <LINK>\` removes one task link by ID, title or URL; \`slop task archive-done\` archives every task in a done column; \`slop project delete <KEY|ID>\` permanently deletes a project and its tasks (admin only, no undo).
29785
29785
  - Agents use the eight slop MCP tools for everything on the board (\`slop mcp install\` once per machine): \`board\`, \`task\`, \`plan\`, \`session\`, \`threads\`, \`say\`, \`operation\`, \`splat\`, each with an \`action\`. You have a name (session start tells you; \`session who\` lists everyone's); \`say post\` with \`to\` reaches one agent by name. If a wake says mail is waiting, use \`session check\` with the project it names. Each conversation and project has its own inbox. Messages are kept whole; use \`session check\` for any remaining messages. A crash just after output can repeat a message, so do not repeat an action you already completed. Messages for you reach you on their own: idle, as a turn of yours; while you work, as your next turn (Codex) or with your next tool result (Claude Code). Messages are letters, not chat: the other agent reads yours when it gets to it, its reply may predate your latest, so write once and completely, answer a pile of messages with one reply, and reply only when it changes something (never to acknowledge). Do not wait for replies; end your turn. slop updates itself (the CLI, the MCP server and the skills) and open chats switch to a new version on their own: never update or restart for it. The \`slop\` command is for people and scripts. Talk through threads: \`slop task ask <KEY> "\u2026"\` reaches whoever is live on that task, \`slop thread reply <id> "\u2026"\` answers, \`slop threads\` lists them; general threads are for people and for context. Method: \`slop skill slop-sessions\`.
29786
- - Subagents you spawn show under you on the board (what you asked them to do, the worktree, the model, and while someone has the board open, the tool each is running). They hold no session: nobody can message one, and a subagent's own slop tools can read the board but cannot claim a ticket, check messages or post in a thread. Send one to find something out, not to work a ticket or answer a thread.
29786
+ - Subagents you spawn show under you on the board, a workflow's agents too (what you asked them to do, or what the workflow calls them, the worktree, the model, and while someone has the board open, the tool each is running). They hold no session: nobody can message one, and a subagent's own slop tools can read the board but cannot claim a ticket, check messages or post in a thread. Send one to find something out, not to work a ticket or answer a thread.
29787
29787
  - Work that outlasts one session gets a plan on its task: \`slop task plan <KEY> -\` (one item per line), then \`slop task item note|done <KEY> <n>\` as you go; \`slop task items <KEY>\` shows where you are. \`slop skill slop-planning\` is the method; \`slop skills install\` puts the skills in this repository.
29788
29788
  - An operation is a phased plan across tickets: requirements, a spec per ticket, typed edges between tickets, files owned. The \`operation\` tool plans and reads one; \`slop op show <op>\` from a shell; \`slop skill slop-operations\` is the method.
29789
29789
  - Splat is the project's app on a shared canvas: every route at every viewport, rendered on a machine running the app, seen by the whole team. The \`splat\` tool points it at a dev server on any port (\`target\`), screenshots here and shows you the images (\`capture\`, needs Chrome on this machine), and shows what the team sees (\`view\`). Live frames need no Chrome: the person you work for sees this machine's app live on the canvas, and teammates see published apps live; screenshots are what everyone else sees of your work. After a UI change, \`splat capture\` the routes you touched and look; without Chrome it says so and gives the live link: look with a browser tool of your own if you have one, and if the team needs to see the change, say that screenshots need Chrome here. A project has workspaces (separate canvases; \`workspace\` on any action), and people publish running apps into them (\`slop splat serve --name <app> --port <port>\`, from any machine or VM) that the team then uses live in their browsers. From a shell: \`slop splat\`; \`slop skill slop-splat\` is the method.
@@ -30171,7 +30171,7 @@ function buildUpdateNotice() {
30171
30171
  }
30172
30172
  function resolveLatestCliVersion() {
30173
30173
  const deprecated = fetchDeprecatedVersions();
30174
- return sortCliVersions([CLI_RELEASE, "0.10.0", fetchUpdateStatus().latest ?? CLI_RELEASE]).filter((version2) => !deprecated.has(version2)).at(-1) ?? CLI_RELEASE;
30174
+ return sortCliVersions([CLI_RELEASE, "0.10.1", fetchUpdateStatus().latest ?? CLI_RELEASE]).filter((version2) => !deprecated.has(version2)).at(-1) ?? CLI_RELEASE;
30175
30175
  }
30176
30176
  function formatAgentCliVersion(version2, latest = resolveLatestCliVersion()) {
30177
30177
  const label = version2 ? `slop ${version2}` : "an older slop";
@@ -30607,7 +30607,7 @@ async function updateInstalledCliFromRegistry(launcher, options2) {
30607
30607
  const running = fetchLaunchCandidate(launcher).version;
30608
30608
  const action = running === previous ? "current" : isNewerVersion(running, previous) ? "updated" : "rolled-back";
30609
30609
  const latest = resolveNewestOfferedRelease(index);
30610
- deleteUnusedVersions(/* @__PURE__ */ new Set([running, previous, current, launcher.bundledVersion, "0.10.0"]));
30610
+ deleteUnusedVersions(/* @__PURE__ */ new Set([running, previous, current, launcher.bundledVersion, "0.10.1"]));
30611
30611
  persistUpdateStatus({ checkedAt: now(), latest, failures: 0, lastError: null });
30612
30612
  return { action, running, previous, latest };
30613
30613
  } catch (error62) {
@@ -31414,7 +31414,7 @@ function watchLaunchTarget(onChange) {
31414
31414
  }
31415
31415
  function runMcpRelay() {
31416
31416
  const launcher = readLauncherInfo();
31417
- const own2 = { version: "0.10.0", main: fileURLToPath(import.meta.url), downloaded: false };
31417
+ const own2 = { version: "0.10.1", main: fileURLToPath(import.meta.url), downloaded: false };
31418
31418
  const relay = new McpRelay({
31419
31419
  input: process.stdin,
31420
31420
  output: process.stdout,
@@ -31855,7 +31855,7 @@ async function waitForMessage(api, projectId, taskKey, options2 = {}) {
31855
31855
  setTimeout(() => resolve6(outcome), 50);
31856
31856
  };
31857
31857
  const held = new HeldSession(api, projectId, taskKey, {
31858
- claim: () => options2.claim ?? { harness: null, directory: null, branch: null, cliVersion: "0.10.0" },
31858
+ claim: () => options2.claim ?? { harness: null, directory: null, branch: null, cliVersion: "0.10.1" },
31859
31859
  reconnect: false,
31860
31860
  onHello: options2.onHello,
31861
31861
  onEnd: () => finishWait({ kind: "ended" }),
@@ -32307,7 +32307,7 @@ function parseTurnStateFromClaudeLine(line) {
32307
32307
  } catch {
32308
32308
  return null;
32309
32309
  }
32310
- if (entry.type === "user") return "working";
32310
+ if (entry.type === "user") return entry.toolEndsTurn === true ? "finished" : "working";
32311
32311
  if (entry.type !== "assistant") return null;
32312
32312
  const stop = entry.message?.stop_reason;
32313
32313
  return stop === "end_turn" || stop === "stop_sequence" ? "finished" : "working";
@@ -32421,7 +32421,44 @@ function parseClaudeWorkerLabel(text) {
32421
32421
  return parts.length ? truncate(parts.join(" \u2014 "), WORKER_LABEL_MAX) : null;
32422
32422
  }
32423
32423
  function findClaudeWorkerFiles(transcriptPath, conversation, changedAfter) {
32424
- const directory = join18(dirname11(transcriptPath), conversation, "subagents");
32424
+ const chat = join18(dirname11(transcriptPath), conversation);
32425
+ const directory = join18(chat, "subagents");
32426
+ const found = findClaudeWorkerFilesInDirectory(directory, changedAfter, (ref) => {
32427
+ try {
32428
+ return parseClaudeWorkerLabel(readFileSync17(join18(directory, `agent-${ref}.meta.json`), "utf8"));
32429
+ } catch {
32430
+ return null;
32431
+ }
32432
+ });
32433
+ let runs = [];
32434
+ try {
32435
+ runs = readdirSync10(join18(directory, "workflows"));
32436
+ } catch {
32437
+ }
32438
+ for (const runId of runs) {
32439
+ const runDirectory = join18(directory, "workflows", runId);
32440
+ const agents = findClaudeWorkerFilesInDirectory(runDirectory, changedAfter, () => null);
32441
+ if (agents.length === 0) continue;
32442
+ let journal = "";
32443
+ try {
32444
+ journal = readFileSync17(join18(runDirectory, "journal.jsonl"), "utf8");
32445
+ } catch {
32446
+ }
32447
+ let record2 = "";
32448
+ try {
32449
+ record2 = readFileSync17(join18(chat, "workflows", `${runId}.json`), "utf8");
32450
+ } catch {
32451
+ }
32452
+ const journaled = parseClaudeWorkflowFinishedAgents(journal);
32453
+ const run = parseClaudeWorkflowRecord(record2);
32454
+ for (const agent of agents) {
32455
+ const label = run.labels.get(agent.ref) ?? parseClaudeWorkerPromptLabel(fetchFileHead(agent.path, 16 * 1024).split("\n")[0] ?? "");
32456
+ found.push({ ...agent, label: label ? truncate(label, WORKER_LABEL_MAX) : "workflow agent", finished: run.over || journaled.has(agent.ref) || run.finished.has(agent.ref) });
32457
+ }
32458
+ }
32459
+ return found.sort((a, b) => b.changedAt - a.changedAt);
32460
+ }
32461
+ function findClaudeWorkerFilesInDirectory(directory, changedAfter, labelFor) {
32425
32462
  let names;
32426
32463
  try {
32427
32464
  names = readdirSync10(directory);
@@ -32435,16 +32472,56 @@ function findClaudeWorkerFiles(transcriptPath, conversation, changedAfter) {
32435
32472
  const path3 = join18(directory, name);
32436
32473
  const changedAt = fetchChangedAt(path3);
32437
32474
  if (changedAt < changedAfter) continue;
32438
- let label = null;
32475
+ const first = fetchFileHead(path3, 8 * 1024).split("\n")[0] ?? "";
32476
+ const where = parseWhereFromJson(first);
32477
+ found.push({ ref, label: labelFor(ref) ?? "subagent", path: path3, directory: where.directory, branch: where.branch, changedAt });
32478
+ }
32479
+ return found;
32480
+ }
32481
+ function parseClaudeWorkflowFinishedAgents(journal) {
32482
+ const finished = /* @__PURE__ */ new Set();
32483
+ for (const line of journal.split("\n")) {
32484
+ if (!line.includes('"result"')) continue;
32439
32485
  try {
32440
- label = parseClaudeWorkerLabel(readFileSync17(join18(directory, `agent-${ref}.meta.json`), "utf8"));
32486
+ const entry = JSON.parse(line);
32487
+ if (entry.type === "result" && typeof entry.agentId === "string") finished.add(entry.agentId);
32441
32488
  } catch {
32442
32489
  }
32443
- const first = fetchFileHead(path3, 8 * 1024).split("\n")[0] ?? "";
32444
- const where = parseWhereFromJson(first);
32445
- found.push({ ref, label: label ?? "subagent", path: path3, directory: where.directory, branch: where.branch, changedAt });
32446
32490
  }
32447
- return found.sort((a, b) => b.changedAt - a.changedAt);
32491
+ return finished;
32492
+ }
32493
+ var WORKFLOW_RUN_LIVE = /* @__PURE__ */ new Set(["running", "pending", "queued", "starting"]);
32494
+ var WORKFLOW_AGENT_DONE = /* @__PURE__ */ new Set(["done", "error", "failed", "cancelled", "skipped"]);
32495
+ function parseClaudeWorkflowRecord(record2) {
32496
+ const parsed = { labels: /* @__PURE__ */ new Map(), finished: /* @__PURE__ */ new Set(), over: false };
32497
+ let run;
32498
+ try {
32499
+ run = JSON.parse(record2);
32500
+ } catch {
32501
+ return parsed;
32502
+ }
32503
+ parsed.over = typeof run.status === "string" && !WORKFLOW_RUN_LIVE.has(run.status);
32504
+ if (!Array.isArray(run.workflowProgress)) return parsed;
32505
+ const workflow = typeof run.workflowName === "string" && run.workflowName.trim() ? run.workflowName.trim() : null;
32506
+ for (const step of run.workflowProgress) {
32507
+ if (step?.type !== "workflow_agent" || typeof step.agentId !== "string") continue;
32508
+ if (typeof step.state === "string" && WORKFLOW_AGENT_DONE.has(step.state)) parsed.finished.add(step.agentId);
32509
+ if (typeof step.label === "string" && step.label.trim()) parsed.labels.set(step.agentId, workflow ? `${workflow} \u2014 ${step.label.trim()}` : step.label.trim());
32510
+ }
32511
+ return parsed;
32512
+ }
32513
+ function parseClaudeWorkerPromptLabel(line) {
32514
+ let entry;
32515
+ try {
32516
+ entry = JSON.parse(line);
32517
+ } catch {
32518
+ return null;
32519
+ }
32520
+ if (entry.type !== "user") return null;
32521
+ const content = entry.message?.content;
32522
+ const text = typeof content === "string" ? content : Array.isArray(content) ? content.map((part) => typeof part?.text === "string" ? part.text : "").join(" ") : "";
32523
+ const first = text.split("\n").map((one) => one.trim()).find((one) => one !== "");
32524
+ return first ?? null;
32448
32525
  }
32449
32526
  function parseWhereFromJson(line) {
32450
32527
  try {
@@ -32578,10 +32655,12 @@ var HarnessActivity = class {
32578
32655
  const found = this.findWorkerFiles(now - WORKER_GONE_MS);
32579
32656
  for (const file2 of found) {
32580
32657
  const known = this.workers.get(file2.ref);
32581
- if (known) Object.assign(known, { label: file2.label, directory: file2.directory, branch: file2.branch, changedAt: file2.changedAt });
32582
- else {
32658
+ if (known) {
32659
+ Object.assign(known, { label: file2.label, directory: file2.directory, branch: file2.branch, changedAt: file2.changedAt });
32660
+ if (file2.finished) known.finished = true;
32661
+ } else {
32583
32662
  const tail = new FileTail(file2.path, "end");
32584
- 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 });
32663
+ this.workers.set(file2.ref, { ...file2, since: new Date(file2.changedAt).toISOString(), model: fetchModelFromFileTail(file2.path, this.parsers.parseModel), doing: null, finished: file2.finished === true || fetchTurnStateFromFileTail(file2.path, this.parsers.parseTurnState) === "finished", tail });
32585
32664
  }
32586
32665
  }
32587
32666
  const live = new Set(found.map((file2) => file2.ref));
@@ -35569,7 +35648,7 @@ var SplatRendererConnection = class {
35569
35648
  try {
35570
35649
  let path3;
35571
35650
  try {
35572
- const ticket = await this.api.post(`/projects/${this.projectId}/splat/renderer/ticket`, ApiResponses.ConnectTicket, { cliVersion: "0.10.0", onlyMe: this.options.onlyMe ?? false, agent: this.options.agent ?? null, capabilities: ["workspaces", ...this.options.liveRelay === false ? [] : [SPLAT_RENDERER_LIVE_RELAY]] });
35651
+ const ticket = await this.api.post(`/projects/${this.projectId}/splat/renderer/ticket`, ApiResponses.ConnectTicket, { cliVersion: "0.10.1", onlyMe: this.options.onlyMe ?? false, agent: this.options.agent ?? null, capabilities: ["workspaces", ...this.options.liveRelay === false ? [] : [SPLAT_RENDERER_LIVE_RELAY]] });
35573
35652
  path3 = ticket.path;
35574
35653
  } catch (error62) {
35575
35654
  if (error62 instanceof CliError && ["unauthenticated", "not_found", "forbidden", "bad_request"].includes(error62.code)) {
@@ -37651,17 +37730,17 @@ function hasHarnessSetup() {
37651
37730
  function syncMachineSetupAfterUpdate() {
37652
37731
  const nothing = { skills: [], retired: [], reinstalling: false };
37653
37732
  try {
37654
- if (!isUpdateCheckEnabled() || fetchMachineSetupState()?.version === "0.10.0") return nothing;
37733
+ if (!isUpdateCheckEnabled() || fetchMachineSetupState()?.version === "0.10.1") return nothing;
37655
37734
  const lock = claimFileLock(buildMachineSetupLockDirectory(), { staleAfterMs: 6e4 });
37656
37735
  if (!lock) return nothing;
37657
37736
  try {
37658
37737
  const state = fetchMachineSetupState();
37659
- if (state?.version === "0.10.0") return nothing;
37738
+ if (state?.version === "0.10.1") return nothing;
37660
37739
  const skills = hasInstalledSkills() ? installSkills() : [];
37661
37740
  const shipped = Object.keys(SKILLS);
37662
37741
  const retired = deleteRetiredSkills((state?.skills ?? []).filter((name) => !shipped.includes(name)));
37663
37742
  const setupRevision = state?.setupRevision ?? (hasHarnessSetup() ? UNRECORDED_SETUP_REVISION : null);
37664
- persistMachineSetupState({ version: "0.10.0", setupRevision, skills: hasInstalledSkills() ? shipped : [] });
37743
+ persistMachineSetupState({ version: "0.10.1", setupRevision, skills: hasInstalledSkills() ? shipped : [] });
37665
37744
  const reinstalling = setupRevision !== null && setupRevision < SETUP_REVISION && runMcpInstallInBackground();
37666
37745
  return { skills, retired, reinstalling };
37667
37746
  } finally {
@@ -37828,7 +37907,7 @@ async function trustCodexHooks(codexHome) {
37828
37907
  const sendNotification = (method, params) => server.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}
37829
37908
  `);
37830
37909
  try {
37831
- await callCodexMethod("initialize", { clientInfo: { name: "slop", version: "0.10.0" }, capabilities: { experimentalApi: true } });
37910
+ await callCodexMethod("initialize", { clientInfo: { name: "slop", version: "0.10.1" }, capabilities: { experimentalApi: true } });
37832
37911
  sendNotification("initialized", {});
37833
37912
  const listed = await callCodexMethod("hooks/list", { cwds: [process.cwd()] });
37834
37913
  const ours = listed.data.flatMap((entry) => entry.hooks).filter((hook) => hook.command?.startsWith("slop hook "));
@@ -38113,7 +38192,7 @@ var options = optionEnd < 0 ? args : args.slice(0, optionEnd);
38113
38192
  updateJsonMode(options.includes("--json"));
38114
38193
  syncMachineSetupAfterUpdate();
38115
38194
  if (!options.includes("--background") && !options.includes("--child")) scheduleBackgroundUpdate();
38116
- 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.10.0").option("--json", "machine-readable output: JSON on stdout, errors as JSON on stderr").configureOutput({
38195
+ 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.10.1").option("--json", "machine-readable output: JSON on stdout, errors as JSON on stderr").configureOutput({
38117
38196
  outputError: (message, write) => {
38118
38197
  if (!isJsonMode()) write(message);
38119
38198
  },
@@ -38130,7 +38209,7 @@ var sessionCommand = registerSessionCommands(program2);
38130
38209
  registerThreadCommands(program2, taskCommand);
38131
38210
  registerOperationCommands(program2, taskCommand, sessionCommand);
38132
38211
  registerSplatCommands(program2);
38133
- registerMcpCommands(program2, "0.10.0");
38212
+ registerMcpCommands(program2, "0.10.1");
38134
38213
  registerGuideCommand(program2);
38135
38214
  registerSkillCommands(program2);
38136
38215
  registerUpdateCommand(program2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slopwaresystems",
3
- "version": "0.10.0",
3
+ "version": "0.10.1",
4
4
  "private": false,
5
5
  "description": "slop: the Slopware Tasks board from the terminal, for people and coding agents.",
6
6
  "type": "module",