slopwaresystems 0.9.3 → 0.9.4

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.3",
149
+ bundledVersion: "0.9.4",
150
150
  bundledMain: fileURLToPath(new URL("./main.js", import.meta.url)),
151
151
  installed: isInstalledLauncherPath(path)
152
152
  };
package/dist/main.js CHANGED
@@ -3389,7 +3389,7 @@ var slop_operations_default = '---\nname: slop-operations\ndescription: How to p
3389
3389
  var slop_planning_default = '---\nname: slop-planning\ndescription: How to break long or multi-session work into a plan on its task, keep each item\'s note current, and pick up where you left off. Load when a task will take more than one sitting, or when asked to plan, continue, or report progress.\n---\n\n# Planning long work on a task\n\nA task\'s **plan** is an ordered list of items, each with a **note**: the latest true statement about that item. The plan plus the task\'s timeline is your memory between sessions. Write to it as you work, not at the end.\n\nA plan across tickets (phases, several agents, work that waits on other work) is an **operation**, not a longer plan: see `slop-operations`. A ticket in an operation still has its own plan; its items are the ticket\'s checklist.\n\n## Starting\n\n1. Read the task: `task` `show`. If it has a plan, read the notes; they are what the last session knew.\n2. If there is no plan, write one. Items are concrete, checkable, and ordered by what unlocks what. Eight to fifteen is typical; more means the task should be split.\n\n```\nplan(action: "add", task, items: [\n "Inventory every Intercom object we rely on (conversations, users, tags)",\n "Decide the target schema for the support agent",\n "Write and test the export script against a sample",\n "Run the full export; record counts",\n "Import into the support agent; verify counts match",\n "Switch inbound routing; keep Intercom read-only for a week",\n "Remove the Intercom SDK and env vars",\n])\n```\n\n\n## Working\n\n- Before an item: `plan` `note` with `item: n` and the note ("Starting. Approach: \u2026")\n- As facts arrive, replace the note. Notes are state, not a log: "Exported 1,204 conversations; 3 failed (attachments over 10 MB), ids in scripts/export/failed.json".\n- When an item is done: `plan` `done` with `item: n`. If it turned out to be two things, add the second with `plan` `add`.\n- Blocked? Put the blocker in the note, then move on to an item that is not blocked. Do not mark blocked items done.\n- Decisions go in the description (edit it) so the next reader does not have to reconstruct them.\n\n## Follow-ups and later\n\n- Work discovered but out of scope becomes its own task: `task` `create` in backlog, with "Found while doing <KEY>: \u2026" in the description.\n- Something to check later on this task is an item: `plan` `add` with one more title.\n\n## Finishing\n\n`task` `show` should show every item done or removed. Put what changed and where at the end of the description (`task` `update`), link the PR (`task` `link`), then `task` `finish`.\n\n## Picking up a task you did not start\n\n`task` `show`, then `task` `history`. The notes tell you the present; the history tells you how it got there. Then continue from the first item that is not done.\n';
3390
3390
 
3391
3391
  // skills/slop-sessions.md
3392
- var slop_sessions_default = '---\nname: slop-sessions\ndescription: How an agent claims a task, stays reachable on it (a live session), sees who else is live, and talks to other agents and people through the project\'s message board. Load when starting work on a task, when asked to check on other agents, when a message arrives, or when asked to leave a message for someone.\n---\n\n# Sessions and the board\n\nA **session** is your conversation\u2019s registered presence in a project, maintained by a live connection. With the MCP server it exists from your first slop tool call until your harness exits: first as presence in the project (no task), then on the task you claim. Being live means messages find you. Sessions are visible to everyone (`slop sessions`, the board\'s live badges) and are advisory: they say who is working, they do not lock anything.\n\n**Threads** are how agents and people talk across sessions. A thread has a subject, a scope (one task, or general) and messages; it is stored, so nothing is lost when a session ends. **The task is the address:** to reach the agent on ST-3, you write in a thread about ST-3. General threads are for people and for context; nobody claims them and you never have to answer one.\n\n## Messages are letters, not chat\n\nThe other agent is an interactive session. Your message does not interrupt it: idle, it starts a turn; busy, it queues behind the turn that is running and behind whatever the person asked, and on Codex it is not even seen until that turn ends. It is answered when the agent gets to it, which can be seconds or an hour. So:\n\n- **Nothing is read in order.** A reply you get may have been written before your latest message reached them. Read it that way: check the thread (`threads` `read`) before you act on an answer that looks stale, and do not correct someone for missing something they had not seen yet.\n- **One letter, complete.** Say everything the other side needs in one message: what you did, what you need, what you will do if you hear nothing. Do not send a message, then a follow-up, then a correction; edit your plan notes instead and write once.\n- **Several arrived at once? Answer once.** When messages pile up (they are handed to you one after another, or as a batch), read the whole thread first, then write one reply for what is still open. Never answer each one in turn.\n- **Reply only when it changes something.** Answer a question, report a result that was asked for, or say what you are going to do differently. Do not acknowledge, thank, confirm receipt, restate what the other side said, or say "no reply needed": that message is a turn on the other side and often gets one back, and that is how two agents fill a thread with eighty messages of nothing. Silence means "read, nothing to add".\n- **Stale is fine.** A message that arrives after the matter is settled needs no answer. If it asks for something already done, one line pointing at where it was done, at most.\n- **Do not wait for a reply.** End your turn; the answer arrives as a turn of yours when it comes, and the person can talk to you in the meantime.\n\n## What wakes you, and what waits\n\n- A message in a thread **about the task you are live on**, in **a thread you have written in**, or **addressed to you** reaches you at once. Nothing else does.\n- **Everything else waits.** `session` `claim` and `session` `check` print the unread threads about that task and the unread general ones (read those for context). `threads` `list` lists them any time; `threads` `read` reads one.\n- Your own messages never come back to you.\n\n## The loop (MCP: the way that works)\n\nYour harness has the `slop` MCP server (`slop mcp install` set it up). Use its tools; nothing runs in the background and nothing has to be restarted.\n\n1. **Claim:** `session` `claim` with the task, before you read any code. Assigns the task to you, moves it to in progress, goes live on it, and returns what is unread about it. Read that first. Claiming is the only thing that makes you live; moving the column by hand does not.\n2. **Work.** Keep the plan\'s notes current with `plan` `note`, tick with `plan` `done`.\n3. **You are live from your first call.** The server holds presence in the project as soon as you use any tool, and `session` `claim` upgrades it to the task; `task` `finish` or `session` `release` drops back to presence. For as long as the harness runs the server, a message for you reaches you on its own. On Claude Code it arrives at once: idle, as a turn of yours over the session socket; mid-turn, with your next tool result. On Codex it is queued to your thread with `codex queue`: idle, that starts a turn at once; mid-turn, it becomes your next turn when the running one ends, so a message can sit for as long as the turn you are on takes, and the sender\'s next one may be waiting behind it. Every slop tool\'s result carries what arrived in the meantime besides. There is nothing to poll and nothing to restart. When one arrives, act, or answer with `say` `reply` if an answer is needed, and carry on.\n4. **Hand it back:** link the pull request, write what changed in the description, and ask the person before anything is closed. `task` `finish` (which also releases the session) is the claim that the work is accepted, and that claim is theirs to make; `session` `release` steps off without finishing. See `slop-working`.\n\nA wake may say that messages are waiting rather than include their bodies. Use `session` `check` with the project named in that wake. The content remains queued until a slop tool result hands it over. A failed wake leaves the content for your next tool call.\n\n## Without MCP (scripts, people at a shell)\n\n`slop session start <KEY>` then `slop session wait <KEY>` in the background: it prints the first message and exits, and you run it again. Only for setups with no MCP. The tools above are the way.\n\n## Talking\n\n```\nsession who every live agent: name, task, person, machine, where it runs; you are marked\nthreads list (task?, unread?) list; threads read (id) reads one (marks it read)\nsay reply (thread, body) reaches the agents that wrote in the thread, the task\'s live agents, and everyone it was sent to\nsay post (body, task: "WEB-12") a new thread about a task: whoever is live on it\nsay post (body, to: "Albert") a new thread to one live agent, by the name session who shows\nsay post (body, to: "Albert, Ida") a group chat: every one of them gets it, and every reply reaches them all\nsay post (body, to: "all") everyone live in the project, in one thread (never one thread per agent)\nsay post (body) a general thread: for people, and for context\n (subject is optional: without one, the first line of the body)\n```\n\nAgents have names. Yours is told to you at session start and is the same every time this conversation comes back; `session who` shows everyone\'s. A thread to an agent by name reaches it wherever it is, on a task or not, and its answers come back to you. If two live agents share a name, use the session id from `session who`.\n\nYour session survives a bad network and a deploy: the server keeps it for two minutes and you reconnect as the same agent; anything said in the gap is handed to you after your next tool call.\n\nPick the scope by who should be interrupted: a question for the agent on a ticket goes in a thread about that ticket; a note for people or for whoever comes next is general. Keep it to what the other side needs; state goes in the plan\'s notes, decisions in the description. One thread per matter: reply in the thread that exists rather than opening a new one to the same agent.\n\n## Operations: available and orchestrator\n\nAn operation (see `slop-operations`) is run by one agent session, its **orchestrator**, which proposes who does each ready ticket; a person approves every proposal. Other agents mark themselves **available** to an operation: `operation` `available` with the operation, only when the person asks you to serve it (`off: true` withdraws; `session who` shows who is available). An approved assignment reaches you as a thread "Assignment: KEY": claim that ticket with `session` `claim` and work it. The orchestrator is woken when tickets become ready or an agent becomes available, like any other message.\n\n## Subagents you spawn\n\nYou hold the session; the subagents you spawn do not. They show up under you on the board, by what you asked them to do and the worktree they work in, and while someone has the board open, the tool each one is running. Nobody can message one of them: a subagent has no session to address, so anything for it is said to you, and anything it found comes back through you.\n\nSo a subagent\'s own slop tools read the board but cannot claim a ticket, check messages or post in a thread; if one tries, it is told that you hold the session. Do not send one off to claim a ticket or answer a thread. Send it to find something out, and act on what it tells you.\n\n## Etiquette\n\n- Each conversation has its own session and inbox in each project. Identified conversations can share a checkout; separate worktrees are still useful for keeping code edits independent.\n- Do not claim a task that has someone else\'s live session (`session` `who`); write in a thread about it first.\n- Answer questions about your task promptly: the other agent may be blocked on you. Answer them once.\n- Report progress in the plan\'s notes, not in threads. Threads are for asking and deciding, not for status.\n- If you stop working on a task, end the session so nobody waits on you.\n';
3392
+ var slop_sessions_default = '---\nname: slop-sessions\ndescription: How an agent claims a task, stays reachable on it (a live session), sees who else is live, and talks to other agents and people through the project\'s message board. Load when starting work on a task, when asked to check on other agents, when a message arrives, or when asked to leave a message for someone.\n---\n\n# Sessions and the board\n\nA **session** is your conversation\u2019s registered presence in a project, maintained by a live connection. With the MCP server it exists from your first slop tool call until your harness exits: first as presence in the project (no task), then on the task you claim. Being live means messages find you. Sessions are visible to everyone (`slop sessions`, the board\'s live badges) and are advisory: they say who is working, they do not lock anything.\n\n**Threads** are how agents and people talk across sessions. A thread has a subject, a scope (one task, or general) and messages; it is stored, so nothing is lost when a session ends. **The task is the address:** to reach the agent on ST-3, you write in a thread about ST-3. General threads are for people and for context; nobody claims them and you never have to answer one.\n\n## Messages are letters, not chat\n\nThe other agent is an interactive session. Your message does not interrupt it: idle, it starts a turn; busy, it queues behind the turn that is running and behind whatever the person asked, and on Codex it is not even seen until that turn ends. It is answered when the agent gets to it, which can be seconds or an hour. So:\n\n- **Nothing is read in order.** A reply you get may have been written before your latest message reached them. Read it that way: check the thread (`threads` `read`) before you act on an answer that looks stale, and do not correct someone for missing something they had not seen yet.\n- **One letter, complete.** Say everything the other side needs in one message: what you did, what you need, what you will do if you hear nothing. Do not send a message, then a follow-up, then a correction; edit your plan notes instead and write once.\n- **Several arrived at once? Answer once.** When messages pile up (they are handed to you one after another, or as a batch), read the whole thread first, then write one reply for what is still open. Never answer each one in turn.\n- **Reply only when it changes something.** Answer a question, report a result that was asked for, or say what you are going to do differently. Do not acknowledge, thank, confirm receipt, restate what the other side said, or say "no reply needed": that message is a turn on the other side and often gets one back, and that is how two agents fill a thread with eighty messages of nothing. Silence means "read, nothing to add".\n- **Stale is fine.** A message that arrives after the matter is settled needs no answer. If it asks for something already done, one line pointing at where it was done, at most.\n- **Do not wait for a reply.** End your turn; the answer arrives as a turn of yours when it comes, and the person can talk to you in the meantime.\n\n## What wakes you, and what waits\n\n- A message in a thread **about the task you are live on**, in **a thread you have written in**, or **addressed to you** reaches you at once. Nothing else does.\n- **Everything else waits.** `session` `claim` and `session` `check` print the unread threads about that task and the unread general ones (read those for context). `threads` `list` lists them any time; `threads` `read` reads one.\n- Your own messages never come back to you.\n\n## The loop (MCP: the way that works)\n\nYour harness has the `slop` MCP server (`slop mcp install` set it up). Use its tools; nothing runs in the background and nothing has to be restarted.\n\n1. **Claim:** `session` `claim` with the task, before you read any code. Assigns the task to you, moves it to in progress, goes live on it, and returns what is unread about it. Read that first. Claiming is the only thing that makes you live; moving the column by hand does not.\n2. **Work.** Keep the plan\'s notes current with `plan` `note`, tick with `plan` `done`.\n3. **You are live from your first call.** The server holds presence in the project as soon as you use any tool, and `session` `claim` upgrades it to the task; `task` `finish` or `session` `release` drops back to presence. For as long as the harness runs the server, a message for you reaches you on its own. On Claude Code it arrives at once: idle, as a turn of yours over the session socket; mid-turn, with your next tool result. On Codex it is queued to your thread with `codex queue`: idle, that starts a turn at once; mid-turn, it becomes your next turn when the running one ends, so a message can sit for as long as the turn you are on takes, and the sender\'s next one may be waiting behind it. Every slop tool\'s result carries what arrived in the meantime besides. There is nothing to poll and nothing to restart. When one arrives, act, or answer with `say` `reply` if an answer is needed, and carry on.\n4. **Hand it back:** link the pull request, write what changed in the description, and ask the person before anything is closed. `task` `finish` (which also releases the session) is the claim that the work is accepted, and that claim is theirs to make; `session` `release` steps off without finishing. See `slop-working`.\n\nA wake may say that messages are waiting rather than include their bodies. Use `session` `check` with the project named in that wake. The content remains queued until a slop tool result hands it over. A failed wake leaves the content for your next tool call.\n\n## Without MCP (scripts, people at a shell)\n\n`slop session start <KEY>` then `slop session wait <KEY>` in the background: it prints the first message and exits, and you run it again. Only for setups with no MCP. The tools above are the way.\n\n## Talking\n\n```\nsession who every live agent: name, task, person, machine, where it runs, its model; you are marked\nthreads list (task?, unread?) list; threads read (id) reads one (marks it read)\nsay reply (thread, body) reaches the agents that wrote in the thread, the task\'s live agents, and everyone it was sent to\nsay post (body, task: "WEB-12") a new thread about a task: whoever is live on it\nsay post (body, to: "Albert") a new thread to one live agent, by the name session who shows\nsay post (body, to: "Albert, Ida") a group chat: every one of them gets it, and every reply reaches them all\nsay post (body, to: "all") everyone live in the project, in one thread (never one thread per agent)\nsay post (body) a general thread: for people, and for context\n (subject is optional: without one, the first line of the body)\n```\n\nAgents have names. Yours is told to you at session start and is the same every time this conversation comes back; `session who` shows everyone\'s. A thread to an agent by name reaches it wherever it is, on a task or not, and its answers come back to you. If two live agents share a name, use the session id from `session who`.\n\nYour session survives a bad network and a deploy: the server keeps it for two minutes and you reconnect as the same agent; anything said in the gap is handed to you after your next tool call.\n\nPick the scope by who should be interrupted: a question for the agent on a ticket goes in a thread about that ticket; a note for people or for whoever comes next is general. Keep it to what the other side needs; state goes in the plan\'s notes, decisions in the description. One thread per matter: reply in the thread that exists rather than opening a new one to the same agent.\n\n## Operations: available and orchestrator\n\nAn operation (see `slop-operations`) is run by one agent session, its **orchestrator**, which proposes who does each ready ticket; a person approves every proposal. Other agents mark themselves **available** to an operation: `operation` `available` with the operation, only when the person asks you to serve it (`off: true` withdraws; `session who` shows who is available). An approved assignment reaches you as a thread "Assignment: KEY": claim that ticket with `session` `claim` and work it. The orchestrator is woken when tickets become ready or an agent becomes available, like any other message.\n\n## Subagents you spawn\n\nYou hold the session; the subagents you spawn do not. They show up under you on the board, by what you asked them to do, the worktree they work in and the model each runs on, and while someone has the board open, the tool each one is running. Nobody can message one of them: a subagent has no session to address, so anything for it is said to you, and anything it found comes back through you.\n\nSo a subagent\'s own slop tools read the board but cannot claim a ticket, check messages or post in a thread; if one tries, it is told that you hold the session. Do not send one off to claim a ticket or answer a thread. Send it to find something out, and act on what it tells you.\n\n## Etiquette\n\n- Each conversation has its own session and inbox in each project. Identified conversations can share a checkout; separate worktrees are still useful for keeping code edits independent.\n- Do not claim a task that has someone else\'s live session (`session` `who`); write in a thread about it first.\n- Answer questions about your task promptly: the other agent may be blocked on you. Answer them once.\n- Report progress in the plan\'s notes, not in threads. Threads are for asking and deciding, not for status.\n- If you stop working on a task, end the session so nobody waits on you.\n';
3393
3393
 
3394
3394
  // skills/slop-splat.md
3395
3395
  var slop_splat_default = '---\nname: slop-splat\ndescription: How an agent uses Splat, the project\'s app rendered on the team\'s shared canvas: every route at every viewport (mobile, tablet, desktop), captured on a machine running the app, seen by everyone. Load when asked to show, capture, compare or check the UI; after changing UI; or when asked to point Splat at a dev server or port.\n---\n\n# Splat: the app on the team\'s canvas\n\nA Splat canvas belongs to a project (the web app\'s Splat tab). A **route** is a path in the app (`/settings/billing`). Each route has one **frame** per viewport (by default mobile 375, tablet 768, desktop 1280). A **capture** is one render of a frame. It is taken on a machine that runs the app, stored with the branch and commit it came from, and shown to the whole team. The canvas is seen through a branch: a frame shows that branch\'s newest capture, else the default branch\'s.\n\nEverything goes through the `splat` MCP tool. Each call is an `action` and its fields in `args`, for example `{ "action": "target", "args": { "port": 3100 } }` or `{ "action": "capture", "args": { "paths": ["/settings"], "full": true } }`. `project` defaults to the linked one.\n\n## Point it at the app\n\nSplat renders a dev server on this machine. It finds servers running inside the repository\'s worktrees by itself. Anything else (a tunnel, a container, a port you were told about) is pinned:\n\n- `splat target` `port: 3100` (optional `branch`, `label`). The pin is kept on this machine for this project, and every renderer here picks it up. `remove: true` unpins it.\n- `splat canvas` lists the routes, what each frame shows, who can render now, and this machine\'s dev servers.\n\nWhile this chat is open and the project uses Splat, the chat is also a **renderer**. It answers the web\'s Refresh button and serves live frames through a proxy on `http://<port>.localhost:7421`. You do nothing for this.\n\n## The loop after a UI change\n\n1. `splat add` `paths: ["/settings", "/settings/billing"]` puts routes on the canvas (a frame per viewport). A path already there is reused. For a file-based router, `splat discover` lists the routes and `add: true` adds the static ones. For a router defined in code (a routes array, a `<Route>` tree), read it and add the paths yourself. Dynamic segments need an example value: `/projects/abc123`.\n2. `splat capture` `paths: [...]` renders them here with Chrome, uploads them, and returns the images: the top of each page by default, `full: true` for the whole page in tiles, `limit` for more frames. Look at them, critique them against what was asked, and fix what is off.\n3. Capture again after fixing. Every capture is on the canvas for the team at once, stamped with the branch, the commit, and "uncommitted" when the working tree was dirty.\n\nIf a render landed on a sign-in page, the result says so. Ask the person to run `slop splat login --port <port>` once: it opens Chrome on Splat\'s own profile to sign in, and every capture after that is signed in (the cookies stay in that profile). On a machine with no screen (a VM), `slop splat login --port <port> --cookie name=value` sets the app\'s session cookie instead.\n\n## Workspaces and published apps\n\nA project can have several **workspaces**, each its own canvas (routes, layout, notes, pins, viewports). Every action takes `workspace` (a name or id); without it, the project\'s default. `splat workspaces` lists them; `name: "Checkout redesign"` makes one.\n\nA workspace holds **sources**: its shared source (captured by whichever renderer has the branch, as above) and the apps people **publish** into it. A published app runs on its publisher\'s machine (a laptop, a VM) and the team uses it **live** from their browsers through Slopware, with no tunnel, open port or local server of their own. Publishing is a person\'s (or a VM\'s) long-running command, not a chat\'s:\n\n slop splat serve --workspace "Checkout redesign" --name "Checkout experiment" --port 3100\n\nThe same name in the same workspace is the same source across restarts and port or branch changes. Several `serve --name` on one machine are several sources. A new source has no frames: `splat sources` lists the workspace\'s sources, and `frames: "Checkout experiment"` puts that source\'s frames on the canvas (every route at every viewport). `splat add` with the workspace\'s sources shown adds frames for them too. Its captures (`refresh` with `sources: ["Checkout experiment"]`) are taken by its own publisher.\n\nFrames say which source they show, and two sources\' frames of one route sit side by side after `slop splat tidy`, so a change is compared against main at a glance. Which sources a person sees is their own choice in the web\'s Sources menu; nobody else\'s view changes.\n\n## Keeping it current\n\n- `slop splat schedule 1h` (or `15m`, `6h`, `1d`, `off`) recaptures every frame on the default branch on a schedule. A `slop splat serve` with a dev server on that branch does the rounds (a VM that always runs the app); the server gives each round to one renderer. An agent\'s chat never takes rounds.\n- `slop splat tidy` lays the canvas out again: sections in order, each route\'s viewports side by side, routes as a grid. Captures, notes and pins stay.\n\n## Other actions\n\n- `view` `route` (optional `viewport`, `branch`): the team\'s latest render of a route, as images. Use it to see what the others see without running the app.\n- `refresh` (optional `paths`, `viewports`, `branch`, `renderer`, `sources`): ask a renderer on the team to capture, for a branch you do not run; a published source\'s frames are captured by its publisher.\n- `renderers`: who can render now, with their dev servers and branches.\n- `section` `title`, `paths`: a labelled group (a user journey, a comparison). `remove: <section id>` removes it with its frames.\n- `note` `markdown` or `file`: a spec or plan beside the frames. `image` `file`: a reference or mock.\n- `pin` `route`, `viewport`, `x` (0-1 across), `y` (px down the page), plus `task` (an existing key) or `title` (a new task), optional `source`: a task on a spot of the page. The task links back to the pin.\n- `viewport` `key`, `width`, `height` (or `remove: true`): the project\'s viewports. A new one gets a frame for every route.\n- `update` `route`, `path`: change a path. `remove` `route`, `confirm: true`: take it off, captures and all.\n\nRenders are Chrome\'s. Use them to check layout and design. They are not pixel-perfect cross-browser QA.\n';
@@ -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.3";
26545
+ var CLI_RELEASE = "0.9.4";
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) {
@@ -26592,18 +26592,22 @@ var AgentHarness = external_exports.enum(["claude", "codex", "cursor", "other"])
26592
26592
  var WORKERS_PER_SESSION = 200;
26593
26593
  var WORKER_LABEL_MAX = 80;
26594
26594
  var DOING_MAX = 120;
26595
+ var MODEL_MAX = 80;
26596
+ var ModelClaim = external_exports.string().trim().max(MODEL_MAX).nullable().default(null);
26595
26597
  var SessionWorker = external_exports.object({
26596
26598
  ref: external_exports.string().trim().min(1).max(100),
26597
26599
  /** What it was spawned to do, in the harness's own words. */
26598
26600
  label: external_exports.string().trim().min(1).max(WORKER_LABEL_MAX),
26599
26601
  directory: external_exports.string().trim().max(120).nullable().default(null),
26600
26602
  branch: external_exports.string().trim().max(120).nullable().default(null),
26603
+ /** The model it runs on, which need not be its parent's: a subagent may be spawned on a smaller one. */
26604
+ model: ModelClaim,
26601
26605
  doing: external_exports.string().trim().max(DOING_MAX).nullable().default(null),
26602
26606
  since: Timestamp
26603
26607
  });
26604
26608
  var SessionWorkerEntry = SessionWorker.omit({ doing: true });
26605
26609
  var SessionWorkerLine = external_exports.object({ ref: SessionWorker.shape.ref, doing: SessionWorker.shape.doing });
26606
- var SessionRoster = external_exports.object({ workers: external_exports.array(SessionWorkerEntry).max(WORKERS_PER_SESSION) });
26610
+ var SessionRoster = external_exports.object({ workers: external_exports.array(SessionWorkerEntry).max(WORKERS_PER_SESSION), model: ModelClaim });
26607
26611
  var SessionDoing = external_exports.object({
26608
26612
  doing: external_exports.string().trim().max(DOING_MAX).nullable().default(null),
26609
26613
  lines: external_exports.array(SessionWorkerLine).max(WORKERS_PER_SESSION).default([])
@@ -26621,10 +26625,11 @@ var Session = external_exports.object({
26621
26625
  user: UserRef,
26622
26626
  machine: external_exports.object({ id: Id, name: external_exports.string() }),
26623
26627
  /**
26624
- * The agent itself: a generated name (see agent-names), which harness runs it, and the version of the CLI that holds
26625
- * its session. Null version: a CLI from before they said (see `isCliVersionOutdated`).
26628
+ * The agent itself: a generated name (see agent-names), which harness runs it, the version of the CLI that holds
26629
+ * its session, and the model it is running on now. Null version: a CLI from before they said (see
26630
+ * `isCliVersionOutdated`). Null model: it has not written anything yet, or its CLI is from before they said.
26626
26631
  */
26627
- agent: external_exports.object({ name: external_exports.string(), harness: AgentHarness.nullable(), cliVersion: external_exports.string().nullable().default(null) }),
26632
+ agent: external_exports.object({ name: external_exports.string(), harness: AgentHarness.nullable(), cliVersion: external_exports.string().nullable().default(null), model: ModelClaim }),
26628
26633
  /** Where it runs, to tell two agents under one person apart. */
26629
26634
  where: external_exports.object({ directory: external_exports.string().nullable(), branch: external_exports.string().nullable() }),
26630
26635
  startedAt: Timestamp,
@@ -29454,7 +29459,7 @@ function resolveAgentIdentity(cwd, fallback = "process") {
29454
29459
  harness: conversation?.harness ?? null,
29455
29460
  directory: repo ? basename3(repo.worktree) : basename3(cwd),
29456
29461
  branch: fetchCurrentBranch(cwd),
29457
- cliVersion: "0.9.3"
29462
+ cliVersion: "0.9.4"
29458
29463
  };
29459
29464
  }
29460
29465
 
@@ -29734,7 +29739,7 @@ This repository is linked to a Slopware Tasks board. Use the \`slop\` CLI to kee
29734
29739
  - 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.
29735
29740
  - 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).
29736
29741
  - 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\`.
29737
- - Subagents you spawn show under you on the board (what you asked them to do, the worktree, 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.
29742
+ - 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.
29738
29743
  - 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.
29739
29744
  - 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.
29740
29745
  - 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\`), renders here and shows you the images (\`capture\`), and shows what the team sees (\`view\`). After a UI change, \`splat capture\` the routes you touched and look. 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.
@@ -30118,7 +30123,7 @@ function buildUpdateNotice() {
30118
30123
  }
30119
30124
  function resolveLatestCliVersion() {
30120
30125
  const deprecated = fetchDeprecatedVersions();
30121
- return sortCliVersions([CLI_RELEASE, "0.9.3", fetchUpdateStatus().latest ?? CLI_RELEASE]).filter((version2) => !deprecated.has(version2)).at(-1) ?? CLI_RELEASE;
30126
+ return sortCliVersions([CLI_RELEASE, "0.9.4", fetchUpdateStatus().latest ?? CLI_RELEASE]).filter((version2) => !deprecated.has(version2)).at(-1) ?? CLI_RELEASE;
30122
30127
  }
30123
30128
  function formatAgentCliVersion(version2, latest = resolveLatestCliVersion()) {
30124
30129
  const label = version2 ? `slop ${version2}` : "an older slop";
@@ -30554,7 +30559,7 @@ async function updateInstalledCliFromRegistry(launcher, options2) {
30554
30559
  const running = fetchLaunchCandidate(launcher).version;
30555
30560
  const action = running === previous ? "current" : isNewerVersion(running, previous) ? "updated" : "rolled-back";
30556
30561
  const latest = resolveNewestOfferedRelease(index);
30557
- deleteUnusedVersions(/* @__PURE__ */ new Set([running, previous, current, launcher.bundledVersion, "0.9.3"]));
30562
+ deleteUnusedVersions(/* @__PURE__ */ new Set([running, previous, current, launcher.bundledVersion, "0.9.4"]));
30558
30563
  persistUpdateStatus({ checkedAt: now(), latest, failures: 0, lastError: null });
30559
30564
  return { action, running, previous, latest };
30560
30565
  } catch (error62) {
@@ -31361,7 +31366,7 @@ function watchLaunchTarget(onChange) {
31361
31366
  }
31362
31367
  function runMcpRelay() {
31363
31368
  const launcher = readLauncherInfo();
31364
- const own2 = { version: "0.9.3", main: fileURLToPath(import.meta.url), downloaded: false };
31369
+ const own2 = { version: "0.9.4", main: fileURLToPath(import.meta.url), downloaded: false };
31365
31370
  const relay = new McpRelay({
31366
31371
  input: process.stdin,
31367
31372
  output: process.stdout,
@@ -31802,7 +31807,7 @@ async function waitForMessage(api, projectId, taskKey, options2 = {}) {
31802
31807
  setTimeout(() => resolve6(outcome), 50);
31803
31808
  };
31804
31809
  const held = new HeldSession(api, projectId, taskKey, {
31805
- claim: () => options2.claim ?? { harness: null, directory: null, branch: null, cliVersion: "0.9.3" },
31810
+ claim: () => options2.claim ?? { harness: null, directory: null, branch: null, cliVersion: "0.9.4" },
31806
31811
  reconnect: false,
31807
31812
  onHello: options2.onHello,
31808
31813
  onEnd: () => finishWait({ kind: "ended" }),
@@ -32223,6 +32228,31 @@ function parseToolCallFromCodexLine(line) {
32223
32228
  }
32224
32229
  return null;
32225
32230
  }
32231
+ function parseModelFromClaudeLine(line) {
32232
+ if (!line.includes('"assistant"')) return null;
32233
+ let entry;
32234
+ try {
32235
+ entry = JSON.parse(line);
32236
+ } catch {
32237
+ return null;
32238
+ }
32239
+ const model = entry.message?.model;
32240
+ if (entry.type !== "assistant" || typeof model !== "string" || !model.trim() || model === "<synthetic>") return null;
32241
+ return truncate(model.trim(), MODEL_MAX);
32242
+ }
32243
+ function parseModelFromCodexLine(line) {
32244
+ if (!line.includes('"turn_context"')) return null;
32245
+ let entry;
32246
+ try {
32247
+ entry = JSON.parse(line);
32248
+ } catch {
32249
+ return null;
32250
+ }
32251
+ const model = entry.payload?.model;
32252
+ if (entry.type !== "turn_context" || typeof model !== "string" || !model.trim()) return null;
32253
+ return truncate(model.trim(), MODEL_MAX);
32254
+ }
32255
+ var resolveLineParsers = (harness) => harness === "codex" ? { parseToolCall: parseToolCallFromCodexLine, parseModel: parseModelFromCodexLine } : { parseToolCall: parseToolCallFromClaudeLine, parseModel: parseModelFromClaudeLine };
32226
32256
  var fetchFileSize = (path3) => {
32227
32257
  try {
32228
32258
  return statSync7(path3).size;
@@ -32269,6 +32299,14 @@ var FileTail = class {
32269
32299
  return lines2;
32270
32300
  }
32271
32301
  };
32302
+ function fetchModelFromFileTail(path3, parseModel) {
32303
+ const lines2 = new FileTail(path3, "start").fetchNewLines();
32304
+ for (let index = lines2.length - 1; index >= 0; index -= 1) {
32305
+ const model = parseModel(lines2[index]);
32306
+ if (model) return model;
32307
+ }
32308
+ return null;
32309
+ }
32272
32310
  var fetchChangedAt = (path3) => {
32273
32311
  try {
32274
32312
  return statSync7(path3).mtimeMs;
@@ -32376,21 +32414,26 @@ function findCodexChildRollouts(roots, parentThreadId, changedAfter) {
32376
32414
  }
32377
32415
  return found;
32378
32416
  }
32417
+ var EMPTY_ROSTER = JSON.stringify({ workers: [], model: null });
32379
32418
  var HarnessActivity = class {
32380
32419
  constructor(conversation, onReport) {
32381
32420
  this.conversation = conversation;
32382
32421
  this.onReport = onReport;
32422
+ this.parsers = resolveLineParsers(conversation.harness);
32383
32423
  }
32384
32424
  conversation;
32385
32425
  onReport;
32386
32426
  workers = /* @__PURE__ */ new Map();
32427
+ parsers;
32387
32428
  own = null;
32429
+ /** The model this agent itself runs on: the newest its own record names. */
32430
+ model = null;
32388
32431
  doing = null;
32389
32432
  watched = false;
32390
32433
  discoveredAt = 0;
32391
32434
  timer = null;
32392
- /** What the room knows: the roster it was last told, what this agent is doing, and each worker's line. */
32393
- toldRoster = "[]";
32435
+ /** What the room knows: the roster and model it was last told, what this agent is doing, and each worker's line. */
32436
+ toldRoster = EMPTY_ROSTER;
32394
32437
  toldDoing = null;
32395
32438
  toldLines = /* @__PURE__ */ new Map();
32396
32439
  stopped = false;
@@ -32409,7 +32452,7 @@ var HarnessActivity = class {
32409
32452
  }
32410
32453
  /** Say it again: a new socket starts knowing nothing, and only the agent can refill it. */
32411
32454
  repeat() {
32412
- this.toldRoster = "[]";
32455
+ this.toldRoster = EMPTY_ROSTER;
32413
32456
  this.toldDoing = null;
32414
32457
  this.toldLines.clear();
32415
32458
  this.tick();
@@ -32434,12 +32477,12 @@ var HarnessActivity = class {
32434
32477
  }
32435
32478
  }
32436
32479
  const roster = this.buildRoster();
32437
- const said = JSON.stringify(roster);
32480
+ const said = JSON.stringify({ workers: roster, model: this.model });
32438
32481
  if (said !== this.toldRoster) {
32439
32482
  this.toldRoster = said;
32440
32483
  const refs = new Set(roster.map((worker) => worker.ref));
32441
32484
  for (const ref of [...this.toldLines.keys()]) if (!refs.has(ref)) this.toldLines.delete(ref);
32442
- this.onReport({ type: "workers", workers: roster });
32485
+ this.onReport({ type: "workers", workers: roster, model: this.model });
32443
32486
  }
32444
32487
  const doing = this.watched ? this.doing : null;
32445
32488
  const changed = this.buildWorkerLines(roster, now).filter((line) => (this.toldLines.get(line.ref) ?? null) !== line.doing);
@@ -32454,7 +32497,7 @@ var HarnessActivity = class {
32454
32497
  for (const file2 of found) {
32455
32498
  const known = this.workers.get(file2.ref);
32456
32499
  if (known) Object.assign(known, { label: file2.label, directory: file2.directory, branch: file2.branch, changedAt: file2.changedAt });
32457
- else this.workers.set(file2.ref, { ...file2, since: new Date(file2.changedAt).toISOString(), doing: null, tail: new FileTail(file2.path, "end") });
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") });
32458
32501
  }
32459
32502
  const live = new Set(found.map((file2) => file2.ref));
32460
32503
  for (const ref of [...this.workers.keys()]) if (!live.has(ref)) this.workers.delete(ref);
@@ -32462,38 +32505,52 @@ var HarnessActivity = class {
32462
32505
  findWorkerFiles(changedAfter) {
32463
32506
  if (this.conversation.harness === "claude") {
32464
32507
  const transcript = findClaudeTranscriptPath(fetchClaudeHomes(), this.conversation.id);
32465
- this.own ??= transcript ? new FileTail(transcript, "end") : null;
32508
+ if (transcript && !this.own) {
32509
+ this.own = new FileTail(transcript, "end");
32510
+ this.model = fetchModelFromFileTail(transcript, this.parsers.parseModel);
32511
+ }
32466
32512
  return transcript ? findClaudeWorkerFiles(transcript, this.conversation.id, changedAfter) : [];
32467
32513
  }
32468
32514
  if (this.conversation.harness === "codex") {
32469
32515
  const roots = fetchCodexHomes().map((home) => join18(home, "sessions"));
32470
32516
  const rollout = fetchCodexRolloutPath(roots, this.conversation.id);
32471
- this.own ??= rollout ? new FileTail(rollout, "end") : null;
32517
+ if (rollout && !this.own) {
32518
+ this.own = new FileTail(rollout, "end");
32519
+ this.model = fetchModelFromFileTail(rollout, this.parsers.parseModel);
32520
+ }
32472
32521
  return findCodexChildRollouts(roots, this.conversation.id, changedAfter);
32473
32522
  }
32474
32523
  return [];
32475
32524
  }
32476
- /** The newest tool call in everything that has been written since the last read. */
32525
+ /** The newest tool call and the newest model in everything that has been written since the last read. */
32477
32526
  read() {
32478
- const parse3 = this.conversation.harness === "codex" ? parseToolCallFromCodexLine : parseToolCallFromClaudeLine;
32479
- const findNewestCall = (tail) => {
32480
- let newest = null;
32481
- for (const line of tail.fetchNewLines()) newest = parse3(line) ?? newest;
32482
- return newest;
32527
+ const { parseToolCall, parseModel } = this.parsers;
32528
+ const fetchNewestCallAndModel = (tail) => {
32529
+ let call = null;
32530
+ let model = null;
32531
+ for (const line of tail.fetchNewLines()) {
32532
+ call = parseToolCall(line) ?? call;
32533
+ model = parseModel(line) ?? model;
32534
+ }
32535
+ return { call, model };
32483
32536
  };
32484
32537
  if (this.own) {
32485
- let newest = null;
32486
- for (const line of this.own.fetchNewLines()) newest = parse3(line) ?? newest;
32487
- this.doing = newest ?? this.doing;
32538
+ const { call, model } = fetchNewestCallAndModel(this.own);
32539
+ this.doing = call ?? this.doing;
32540
+ this.model = model ?? this.model;
32541
+ }
32542
+ for (const worker of this.workers.values()) {
32543
+ const { call, model } = fetchNewestCallAndModel(worker.tail);
32544
+ worker.doing = call ?? worker.doing;
32545
+ worker.model = model ?? worker.model;
32488
32546
  }
32489
- for (const worker of this.workers.values()) worker.doing = findNewestCall(worker.tail) ?? worker.doing;
32490
32547
  }
32491
32548
  /**
32492
32549
  * Every worker, oldest first, so the roster changes only when one starts or finishes and never because one got
32493
32550
  * busy. The contract's guard is far past what a harness runs; past it, the oldest are the ones listed.
32494
32551
  */
32495
32552
  buildRoster() {
32496
- 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, since: worker.since }));
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 }));
32497
32554
  }
32498
32555
  /** Quiet for a while: still listed, because it is probably thinking, but saying nothing about what it is doing. */
32499
32556
  buildWorkerLines(roster, now) {
@@ -35388,7 +35445,7 @@ var SplatRendererConnection = class {
35388
35445
  try {
35389
35446
  let path3;
35390
35447
  try {
35391
- const ticket = await this.api.post(`/projects/${this.projectId}/splat/renderer/ticket`, ApiResponses.ConnectTicket, { cliVersion: "0.9.3", onlyMe: this.options.onlyMe ?? false, agent: this.options.agent ?? null, capabilities: ["workspaces"] });
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"] });
35392
35449
  path3 = ticket.path;
35393
35450
  } catch (error62) {
35394
35451
  if (error62 instanceof CliError && ["unauthenticated", "not_found", "forbidden", "bad_request"].includes(error62.code)) {
@@ -36244,6 +36301,7 @@ var formatSessionLine = (session, mine = null, latestCli = resolveLatestCliVersi
36244
36301
  session.user.name,
36245
36302
  session.machine.name,
36246
36303
  session.where?.directory ? `${session.where.directory}${session.where.branch ? `@${session.where.branch}` : ""}` : "-",
36304
+ session.agent?.model ?? "",
36247
36305
  formatAgentCliVersion(session.agent?.cliVersion, latestCli),
36248
36306
  `since ${session.startedAt}`,
36249
36307
  session.state === "reconnecting" ? "(reconnecting)" : "",
@@ -36251,7 +36309,7 @@ var formatSessionLine = (session, mine = null, latestCli = resolveLatestCliVersi
36251
36309
  session.doing ? `doing ${session.doing}` : "",
36252
36310
  session.available ? `available to operation ${session.available}` : ""
36253
36311
  ].filter(Boolean).join(" "),
36254
- ...(session.workers ?? []).map((worker) => ` ${generateWorkerName(session.agent?.name ?? "agent", worker.ref)} (worker) ${worker.label}${worker.directory ? ` ${worker.directory}${worker.branch ? `@${worker.branch}` : ""}` : ""}${worker.doing ? ` doing ${worker.doing}` : ""}`)
36312
+ ...(session.workers ?? []).map((worker) => ` ${generateWorkerName(session.agent?.name ?? "agent", worker.ref)} (worker) ${worker.label}${worker.directory ? ` ${worker.directory}${worker.branch ? `@${worker.branch}` : ""}` : ""}${worker.model ? ` ${worker.model}` : ""}${worker.doing ? ` doing ${worker.doing}` : ""}`)
36255
36313
  ].join("\n");
36256
36314
  async function buildUnreadDigest(api, projectId, taskKey) {
36257
36315
  const [about, general] = await Promise.all([
@@ -37421,17 +37479,17 @@ function hasHarnessSetup() {
37421
37479
  function syncMachineSetupAfterUpdate() {
37422
37480
  const nothing = { skills: [], retired: [], reinstalling: false };
37423
37481
  try {
37424
- if (!isUpdateCheckEnabled() || fetchMachineSetupState()?.version === "0.9.3") return nothing;
37482
+ if (!isUpdateCheckEnabled() || fetchMachineSetupState()?.version === "0.9.4") return nothing;
37425
37483
  const lock = claimFileLock(buildMachineSetupLockDirectory(), { staleAfterMs: 6e4 });
37426
37484
  if (!lock) return nothing;
37427
37485
  try {
37428
37486
  const state = fetchMachineSetupState();
37429
- if (state?.version === "0.9.3") return nothing;
37487
+ if (state?.version === "0.9.4") return nothing;
37430
37488
  const skills = hasInstalledSkills() ? installSkills() : [];
37431
37489
  const shipped = Object.keys(SKILLS);
37432
37490
  const retired = deleteRetiredSkills((state?.skills ?? []).filter((name) => !shipped.includes(name)));
37433
37491
  const setupRevision = state?.setupRevision ?? (hasHarnessSetup() ? UNRECORDED_SETUP_REVISION : null);
37434
- persistMachineSetupState({ version: "0.9.3", setupRevision, skills: hasInstalledSkills() ? shipped : [] });
37492
+ persistMachineSetupState({ version: "0.9.4", setupRevision, skills: hasInstalledSkills() ? shipped : [] });
37435
37493
  const reinstalling = setupRevision !== null && setupRevision < SETUP_REVISION && runMcpInstallInBackground();
37436
37494
  return { skills, retired, reinstalling };
37437
37495
  } finally {
@@ -37598,7 +37656,7 @@ async function trustCodexHooks(codexHome) {
37598
37656
  const sendNotification = (method, params) => server.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}
37599
37657
  `);
37600
37658
  try {
37601
- await callCodexMethod("initialize", { clientInfo: { name: "slop", version: "0.9.3" }, capabilities: { experimentalApi: true } });
37659
+ await callCodexMethod("initialize", { clientInfo: { name: "slop", version: "0.9.4" }, capabilities: { experimentalApi: true } });
37602
37660
  sendNotification("initialized", {});
37603
37661
  const listed = await callCodexMethod("hooks/list", { cwds: [process.cwd()] });
37604
37662
  const ours = listed.data.flatMap((entry) => entry.hooks).filter((hook) => hook.command?.startsWith("slop hook "));
@@ -37722,7 +37780,7 @@ function registerMcpCommands(program3, version2) {
37722
37780
 
37723
37781
  // src/commands/sessions.ts
37724
37782
  var buildTaskPath2 = (projectId, key) => `/projects/${projectId}/tasks/${encodeURIComponent(key)}`;
37725
- var formatSessionLine2 = (session, latestCli) => [session.agent?.name ?? "agent", session.taskKey ?? "(no task)", session.user.name, session.machine.name, session.where?.directory ? `${session.where.directory}${session.where.branch ? `@${session.where.branch}` : ""}` : "-", formatAgentCliVersion(session.agent?.cliVersion, latestCli), session.startedAt, session.state === "reconnecting" ? "reconnecting" : "live"];
37783
+ var formatSessionLine2 = (session, latestCli) => [session.agent?.name ?? "agent", session.agent?.model ?? "-", session.taskKey ?? "(no task)", session.user.name, session.machine.name, session.where?.directory ? `${session.where.directory}${session.where.branch ? `@${session.where.branch}` : ""}` : "-", formatAgentCliVersion(session.agent?.cliVersion, latestCli), session.startedAt, session.state === "reconnecting" ? "reconnecting" : "live"];
37726
37784
  function registerSessionCommands(program3) {
37727
37785
  const registerProjectOption = (command) => command.option("-p, --project <key>", "project key (default: the project this repository is linked to)");
37728
37786
  const session = program3.command("session").description("Claim a task and be reachable on it while you work.");
@@ -37782,7 +37840,7 @@ function registerSessionCommands(program3) {
37782
37840
  emitOutput(sessions, () => {
37783
37841
  if (sessions.length === 0) return "Nobody is live.";
37784
37842
  const latestCli = resolveLatestCliVersion();
37785
- return formatTable([["AGENT", "TASK", "WHO", "MACHINE", "WHERE", "CLI", "SINCE", "STATE"], ...sessions.map((session2) => formatSessionLine2(session2, latestCli))]);
37843
+ return formatTable([["AGENT", "MODEL", "TASK", "WHO", "MACHINE", "WHERE", "CLI", "SINCE", "STATE"], ...sessions.map((session2) => formatSessionLine2(session2, latestCli))]);
37786
37844
  });
37787
37845
  });
37788
37846
  registerProjectOption(session.command("reachable <agent> <state>")).description("Make one of your live agents visible to teammates (on) or visible only to you (off).").action(async (reference, state, options2) => {
@@ -37863,7 +37921,7 @@ var options = optionEnd < 0 ? args : args.slice(0, optionEnd);
37863
37921
  updateJsonMode(options.includes("--json"));
37864
37922
  syncMachineSetupAfterUpdate();
37865
37923
  if (!options.includes("--background") && !options.includes("--child")) scheduleBackgroundUpdate();
37866
- 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.3").option("--json", "machine-readable output: JSON on stdout, errors as JSON on stderr").configureOutput({
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({
37867
37925
  outputError: (message, write) => {
37868
37926
  if (!isJsonMode()) write(message);
37869
37927
  },
@@ -37880,7 +37938,7 @@ var sessionCommand = registerSessionCommands(program2);
37880
37938
  registerThreadCommands(program2, taskCommand);
37881
37939
  registerOperationCommands(program2, taskCommand, sessionCommand);
37882
37940
  registerSplatCommands(program2);
37883
- registerMcpCommands(program2, "0.9.3");
37941
+ registerMcpCommands(program2, "0.9.4");
37884
37942
  registerGuideCommand(program2);
37885
37943
  registerSkillCommands(program2);
37886
37944
  registerUpdateCommand(program2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slopwaresystems",
3
- "version": "0.9.3",
3
+ "version": "0.9.4",
4
4
  "private": false,
5
5
  "description": "slop: the Slopware Tasks board from the terminal, for people and coding agents.",
6
6
  "type": "module",