slopwaresystems 0.4.7 → 0.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +23 -99
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3385,7 +3385,7 @@ var slop_cli_default = "---\nname: slop-cli\ndescription: How an agent works the
3385
3385
  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\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';
3386
3386
 
3387
3387
  // skills/slop-sessions.md
3388
- 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 you, live. 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: it is handed over at the agent\'s next tool call or, if the agent is idle, as a turn, behind whatever the person asked. 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** (Codex: never; Claude Code: only when you truly cannot proceed). 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. Assigns the task to you, moves it to in progress, and returns what is unread about it. Read that first.\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: idle, it becomes a turn of yours (Claude Code over its session socket, Codex through `codex queue` to your thread); mid-turn, it comes with your next tool result (a hook hands it over, on both). 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. **Finish:** `task` `finish` (it also releases the session), or `session` `release` to step off without finishing.\n\nNothing is lost at the end of a turn: what arrives while you are idle starts a turn; what arrives while you work is handed to you with your next tool result.\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\n**Claude Code only:** when you cannot go on without the answer, `say` `reply` (or `post`) with `wait: 20` returns the reply in the same call, or `session` `wait` with `thread` waits for the next message there (20 minutes at most). This is rare; the other agent may not read your letter for a long time. **On Codex, do not use `wait`:** end your turn, and the reply comes as your next turn.\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## Etiquette\n\n- One live session per agent, and one agent per working directory: run each agent in its own checkout or git worktree, so its inbox is its own.\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';
3388
+ 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 you, live. 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** (Codex: never; Claude Code: only when you truly cannot proceed). 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. Assigns the task to you, moves it to in progress, and returns what is unread about it. Read that first.\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. **Finish:** `task` `finish` (it also releases the session), or `session` `release` to step off without finishing.\n\nNothing is lost at the end of a turn: what arrives while you are idle starts a turn; what arrives while you work is your next turn (Codex) or rides your next tool result (Claude Code).\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\n**Claude Code only:** when you cannot go on without the answer, `say` `reply` (or `post`) with `wait: 20` returns the reply in the same call, or `session` `wait` with `thread` waits for the next message there (20 minutes at most). This is rare; the other agent may not read your letter for a long time. **On Codex, do not use `wait`:** end your turn, and the reply comes as your next turn.\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## Etiquette\n\n- One live session per agent, and one agent per working directory: run each agent in its own checkout or git worktree, so its inbox is its own.\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';
3389
3389
 
3390
3390
  // skills/slop-working.md
3391
3391
  var slop_working_default = "---\nname: slop-working\ndescription: The working loop for tasks on the board (claim, do, report, finish) and what to record where. Load at the start of any coding session in a repository linked with slop.\n---\n\n# The loop\n\n1. **What is there:** `board` `list` with `mine`, or plain `list`. If nothing is assigned to you, take the top item of the first \"to do\" column.\n2. **Claim it:** `session` `claim`. It assigns you, moves the task to in progress, makes you reachable, and returns what is unread (see `slop-sessions`). Then `task` `show` and read all of it, plan and notes included.\n3. **Do it.** Long work: follow `slop-planning`. Short work: just do it.\n4. **Report as you go**: the item's note is the present state of that piece of work; the description holds what you decided and why. Write in sentences a teammate can act on.\n5. **Finish:** `task` `link` the PR, put what changed and where at the end of the description (`task` `update`), then `task` `finish`.\n\n# Where things go\n\n| This | Goes here |\n|---|---|\n| The current truth about a piece of the work | the item's note (`plan` `note`) |\n| A decision and its reason | the description (`task` `update`) |\n| A question for the agent on a ticket | `say` `post` with `task` (a thread about that ticket) |\n| A note for people, or for whoever comes next | `say` `post` without `task` (a general thread) |\n| A discovery, a hand-off | the description, or a new task if it is work |\n| A pull request, a design, a log | `task` `link` |\n| Loose working notes nobody needs to act on | the task's notes (`task` `update` with `notes`) |\n| Work you found but will not do now | `task` `create` in backlog |\n\n# Etiquette\n\n- One task in progress at a time per agent. `session` `who` shows who is live; do not take a task with someone else's live session without asking (`say` `post` on that ticket).\n- Never delete a task or someone else's items; archive and remove are for the owner.\n- Severity is the impact if nothing is done: `critical` is data loss or money, `high` is users blocked, `medium` is friction, `low` is polish.\n";
@@ -24257,7 +24257,7 @@ This repository is linked to a Slopware Tasks board. Use the \`slop\` CLI to kee
24257
24257
  - Before starting tracked work: \`slop task list\`, then \`slop task start <KEY>\`.
24258
24258
  - When it is finished and verified: \`slop task done <KEY>\`.
24259
24259
  - Descriptions are Markdown and are shown in full on the board. Write real ones: context, file paths, what done means. Pipe long ones in with \`-d -\`.
24260
- - Agents use the six slop MCP tools for everything on the board (\`slop mcp install\` once per machine): \`board\`, \`task\`, \`plan\`, \`session\`, \`threads\`, \`say\`, 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. Messages for you reach you on their own: idle, as a turn of yours; mid-turn, with your next tool result. 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. 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\`.
24260
+ - Agents use the six slop MCP tools for everything on the board (\`slop mcp install\` once per machine): \`board\`, \`task\`, \`plan\`, \`session\`, \`threads\`, \`say\`, 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. 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. 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\`.
24261
24261
  - 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.
24262
24262
  - Everything is editable: \`slop task update <KEY> --title \u2026 --severity \u2026 --status \u2026 --assignee \u2026\`. Assign with \`slop task assign <KEY> <email|me|none>\`; see who exists with \`slop members\`. \`slop task list --mine\` shows your user's tasks.
24263
24263
  - Add \`--json\` to any command for machine-readable output. \`slop task --help\` lists everything.
@@ -24785,18 +24785,24 @@ function fetchHarnessRegistration(pid = process.ppid) {
24785
24785
  }
24786
24786
  return null;
24787
24787
  }
24788
- function wakeHarness(text, pid = Number(process.env.SLOP_HARNESS_PID) || process.ppid) {
24788
+ function resolveClaudeSession(pid = Number(process.env.SLOP_HARNESS_PID) || process.ppid) {
24789
+ const sessionId = process.env.CLAUDE_CODE_SESSION_ID;
24790
+ if (sessionId) return { sessionId, socketPath: process.env.CLAUDE_CODE_MESSAGING_SOCKET || fetchHarnessRegistration(pid)?.messagingSocketPath || null };
24789
24791
  const registration = fetchHarnessRegistration(pid);
24790
- if (!registration?.sessionId || !registration.messagingSocketPath) return Promise.resolve(false);
24792
+ return registration?.sessionId ? { sessionId: registration.sessionId, socketPath: registration.messagingSocketPath ?? null } : null;
24793
+ }
24794
+ function wakeHarness(text, pid = Number(process.env.SLOP_HARNESS_PID) || process.ppid) {
24795
+ const session = resolveClaudeSession(pid);
24796
+ if (!session?.socketPath) return Promise.resolve(false);
24791
24797
  const frame = {
24792
24798
  uuid: randomUUID3(),
24793
- session_id: registration.sessionId,
24799
+ session_id: session.sessionId,
24794
24800
  type: "user",
24795
24801
  parent_tool_use_id: null,
24796
24802
  message: { role: "user", content: text }
24797
24803
  };
24798
24804
  return new Promise((resolve5) => {
24799
- const socket = connect({ path: registration.messagingSocketPath });
24805
+ const socket = connect({ path: session.socketPath });
24800
24806
  const finishWake = (ok) => {
24801
24807
  clearTimeout(timer);
24802
24808
  socket.destroy();
@@ -25029,36 +25035,6 @@ function fetchCodexRolloutPath(roots, threadId) {
25029
25035
  for (const root of roots) findRolloutInDirectory(root, 0);
25030
25036
  return best ? best.path : null;
25031
25037
  }
25032
- function fetchCodexTurnState(roots, threadId) {
25033
- const path2 = fetchCodexRolloutPath(roots, threadId);
25034
- if (!path2) return "unknown";
25035
- let tail = "";
25036
- let mtime = 0;
25037
- try {
25038
- const stats = statSync3(path2);
25039
- mtime = stats.mtimeMs;
25040
- const size = stats.size;
25041
- const length = Math.min(size, 256 * 1024);
25042
- const fd = openSync(path2, "r");
25043
- try {
25044
- const buffer = Buffer.alloc(length);
25045
- tail = buffer.subarray(0, readSync(fd, buffer, 0, length, size - length)).toString("utf8");
25046
- } finally {
25047
- closeSync(fd);
25048
- }
25049
- } catch {
25050
- return "unknown";
25051
- }
25052
- let state = "unknown";
25053
- for (const line of tail.split("\n")) {
25054
- if (!line.includes('"event_msg"')) continue;
25055
- if (line.includes('"task_started"')) state = "busy";
25056
- else if (line.includes('"task_complete"') || line.includes('"turn_aborted"')) state = "idle";
25057
- }
25058
- if (state === "busy" && Date.now() - mtime > STALE_TURN_MS) return "idle";
25059
- return state;
25060
- }
25061
- var STALE_TURN_MS = 15 * 60 * 1e3;
25062
25038
  function resolveCodexBinary() {
25063
25039
  try {
25064
25040
  execFileSync4(process.platform === "win32" ? "where" : "which", ["codex"], { stdio: "ignore" });
@@ -25077,7 +25053,7 @@ function resolveCodexBinary() {
25077
25053
  function fetchHarnessConversation(cwd) {
25078
25054
  const harness = parseHarnessFromCommand(fetchParentCommand());
25079
25055
  if (harness === "claude") {
25080
- const id = fetchHarnessRegistration()?.sessionId;
25056
+ const id = resolveClaudeSession()?.sessionId;
25081
25057
  return id ? { id, harness } : null;
25082
25058
  }
25083
25059
  if (harness === "codex" && !isNonInteractiveHarness()) {
@@ -25553,15 +25529,6 @@ function persistInboxPush(cwd, push) {
25553
25529
  writeFileSync7(temporary, JSON.stringify(push), { flag: "wx" });
25554
25530
  renameSync(temporary, join11(directory, `${name}.json`));
25555
25531
  }
25556
- function hasInboxPushes(cwd) {
25557
- if (existsSync9(buildInboxPath(cwd))) return true;
25558
- try {
25559
- return readdirSync3(buildInboxQueuePath(cwd)).some((file2) => file2.endsWith(".json"));
25560
- } catch (error62) {
25561
- if (error62.code === "ENOENT") return false;
25562
- throw error62;
25563
- }
25564
- }
25565
25532
  function drainInbox(cwd) {
25566
25533
  const pushes = drainLegacyInbox(cwd);
25567
25534
  const directory = buildInboxQueuePath(cwd);
@@ -25660,49 +25627,17 @@ function buildToolHandlers() {
25660
25627
  const formatWakeMessage = (pushes) => `${pushes.length > 1 ? `${pushes.length} messages` : "A message"} for you on Slopware Tasks. ${pushes.length > 1 ? "They" : "It"} waited for your turn and others may have followed: read the thread (threads read) if it may be stale, act, and answer once (say reply) only if something is asked that you have not given yet. Do not acknowledge. Then carry on.
25661
25628
 
25662
25629
  ${pushes.map(formatPushBlock).join("\n\n")}`;
25663
- const resolveCodexSessionRoots = () => {
25664
- const own2 = resolveOwnHarnessHome("CODEX_HOME");
25665
- return (own2 ? [own2, ...fetchCodexHomes()] : fetchCodexHomes()).map((home) => join12(home, "sessions"));
25666
- };
25667
- let flushing = false;
25668
- const wakeCodexWithInboxWhenIdle = (conversation) => {
25669
- if (flushing) return;
25670
- flushing = true;
25671
- const wakeWhenTurnIdle = () => {
25672
- if (!hasInboxPushes(process.cwd())) {
25673
- flushing = false;
25674
- return;
25675
- }
25676
- if (fetchCodexTurnState(resolveCodexSessionRoots(), conversation) === "busy") {
25677
- setTimeout(wakeWhenTurnIdle, 2e3).unref();
25678
- return;
25679
- }
25680
- flushing = false;
25681
- const pushes = drainInbox(process.cwd());
25682
- if (pushes.length === 0) return;
25683
- void wakeCodex(formatWakeMessage(pushes), conversation, { binary: resolveCodexBinary(), codexHome: resolveOwnHarnessHome("CODEX_HOME") }).then((woken) => {
25684
- if (!woken) for (const push of pushes) persistInboxPush(process.cwd(), push);
25685
- });
25686
- };
25687
- setTimeout(wakeWhenTurnIdle, 2e3).unref();
25688
- };
25689
25630
  const holdSession = async (projectId, taskKey) => {
25690
25631
  if (held && held.projectId === projectId && held.taskKey === taskKey) return;
25691
25632
  held?.stop();
25692
25633
  held = new HeldSession(api, projectId, taskKey, {
25693
25634
  // Claude Code is woken over its session socket: the message arrives as a turn, now, idle or not (mid-turn it
25694
- // rides the next tool result). Codex idle: `codex queue` to its thread starts a turn there. Codex mid-turn (its
25695
- // rollout says so): the queue would only be read when the turn ends, however long that is, so the message goes
25696
- // to the inbox and the PostToolUse hook hands it over after the next tool call; if the turn ends first, the
25697
- // inbox is queued then, as one message. When nothing can be done the inbox rides the chat's next slop tool
25698
- // result. Nothing holds a turn open.
25635
+ // rides the next tool result). Codex is handed the message with `codex queue` to its thread: idle, that starts
25636
+ // a turn at once; mid-turn, it becomes the next turn when the running one ends, which is why the skills tell
25637
+ // agents to treat messages as letters that may be read late. When neither can be done the message waits in
25638
+ // the inbox and rides the chat's next slop tool result. Nothing holds a turn open.
25699
25639
  onMessage: (push) => {
25700
25640
  const me = resolveOwnAgentIdentity();
25701
- if (me.harness === "codex" && me.conversation && fetchCodexTurnState(resolveCodexSessionRoots(), me.conversation) === "busy") {
25702
- persistInboxPush(process.cwd(), push);
25703
- wakeCodexWithInboxWhenIdle(me.conversation);
25704
- return;
25705
- }
25706
25641
  const wake = me.harness === "codex" && me.conversation ? wakeCodex(formatWakeMessage([push]), me.conversation, { binary: resolveCodexBinary(), codexHome: resolveOwnHarnessHome("CODEX_HOME") }) : wakeHarness(formatWakeMessage([push]));
25707
25642
  void wake.then((woken) => {
25708
25643
  if (!woken) persistInboxPush(process.cwd(), push);
@@ -26144,7 +26079,6 @@ function mergeHooks(hooks, harness) {
26144
26079
  hooks[event] = entries;
26145
26080
  };
26146
26081
  installHookForEvent("SessionStart", "start", 5, "Slopware Tasks");
26147
- if (harness === "codex") installHookForEvent("PostToolUse", "tool", 10, "Slopware Tasks");
26148
26082
  return hooks;
26149
26083
  }
26150
26084
  function installIntoCodexHome(dir) {
@@ -26168,7 +26102,7 @@ tool_timeout_sec = 21600
26168
26102
  file2.hooks = mergeHooks(file2.hooks ?? {}, "codex");
26169
26103
  writeFileSync8(hooksPath, `${JSON.stringify(file2, null, 2)}
26170
26104
  `);
26171
- done.push(`Codex: SessionStart and PostToolUse hooks written to ${hooksPath}; other slop hooks removed (no Codex hook holds a turn open)`);
26105
+ done.push(`Codex: SessionStart hook written to ${hooksPath}; other slop hooks removed (Codex is woken with codex queue; no hook holds a turn open)`);
26172
26106
  return done;
26173
26107
  }
26174
26108
  async function trustCodexHooks(codexHome) {
@@ -26208,7 +26142,7 @@ async function trustCodexHooks(codexHome) {
26208
26142
  const sendNotification = (method, params) => server.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}
26209
26143
  `);
26210
26144
  try {
26211
- await callCodexMethod("initialize", { clientInfo: { name: "slop", version: "0.4.7" }, capabilities: { experimentalApi: true } });
26145
+ await callCodexMethod("initialize", { clientInfo: { name: "slop", version: "0.4.8" }, capabilities: { experimentalApi: true } });
26212
26146
  sendNotification("initialized", {});
26213
26147
  const listed = await callCodexMethod("hooks/list", { cwds: [process.cwd()] });
26214
26148
  const ours = listed.data.flatMap((entry) => entry.hooks).filter((hook) => hook.command?.startsWith("slop hook "));
@@ -26257,17 +26191,7 @@ async function runPromptHook(harness) {
26257
26191
  await parseHookInput(harness);
26258
26192
  }
26259
26193
  async function runToolHook(harness) {
26260
- if (isNonInteractiveHarness()) return;
26261
- const { cwd, conversation } = await parseHookInput(harness, false);
26262
- const known = fetchKnownConversation(cwd);
26263
- if (known && conversation && known.id !== conversation) return;
26264
- const arrived = drainInbox(cwd);
26265
- if (arrived.length === 0) return;
26266
- const text = `Messages arrived on Slopware Tasks while you worked (answer what is still open, once, with say reply; do not acknowledge):
26267
-
26268
- ${arrived.map(formatPushBlock).join("\n\n")}`;
26269
- process.stdout.write(`${JSON.stringify({ hookSpecificOutput: { hookEventName: "PostToolUse", additionalContext: text } })}
26270
- `);
26194
+ await parseHookInput(harness, false);
26271
26195
  }
26272
26196
  async function runStartHook(harness) {
26273
26197
  if (isNonInteractiveHarness()) return;
@@ -26396,7 +26320,7 @@ function registerMcpCommands(program3, version2) {
26396
26320
  const hook = program3.command("hook").description("Harness hooks (installed by slop mcp install).");
26397
26321
  const parseHarnessFromOption = (options) => options.harness === "claude" || options.harness === "codex" ? options.harness : options.harness ? "other" : null;
26398
26322
  hook.command("start").description("SessionStart hook (Claude Code and Codex): writes the conversation down so the MCP server is its session, and tells the agent its name and how messages work.").option("--harness <name>", "claude or codex").action((options) => runStartHook(parseHarnessFromOption(options)));
26399
- hook.command("tool").description("PostToolUse hook (Codex): hands the model what reached the inbox while its turn ran, with the tool's result.").option("--harness <name>", "claude or codex").action((options) => runToolHook(parseHarnessFromOption(options)));
26323
+ hook.command("tool").description("No longer installed; kept so an older install's hook exits cleanly.").option("--harness <name>", "claude or codex").action((options) => runToolHook(parseHarnessFromOption(options)));
26400
26324
  hook.command("prompt").description("No longer installed; kept so an older install's hook exits cleanly.").option("--harness <name>", "claude or codex").action((options) => runPromptHook(parseHarnessFromOption(options)));
26401
26325
  hook.command("stop").description("No longer installed (Claude Code is woken over its socket, Codex through codex queue); kept so an older install's hook still hands over unread messages.").option("--harness <name>", "claude or codex").action((options) => runStopHook(parseHarnessFromOption(options)));
26402
26326
  }
@@ -26487,13 +26411,13 @@ ${written.map((path2) => ` ${path2}`).join("\n")}`);
26487
26411
  }
26488
26412
 
26489
26413
  // src/index.ts
26490
- var program2 = new Command().name("slop").description("Slopware Tasks from the terminal. Everything a person can do on the board, an agent can do here.").version("0.4.7").option("--json", "machine-readable output: JSON on stdout, errors as JSON on stderr").hook("preAction", (_, action) => updateJsonMode(Boolean(action.optsWithGlobals().json))).showHelpAfterError("(run with --help for usage)");
26414
+ var program2 = new Command().name("slop").description("Slopware Tasks from the terminal. Everything a person can do on the board, an agent can do here.").version("0.4.8").option("--json", "machine-readable output: JSON on stdout, errors as JSON on stderr").hook("preAction", (_, action) => updateJsonMode(Boolean(action.optsWithGlobals().json))).showHelpAfterError("(run with --help for usage)");
26491
26415
  registerAuthCommands(program2);
26492
26416
  registerProjectCommands(program2);
26493
26417
  var taskCommand = registerTaskCommands(program2);
26494
26418
  registerSessionCommands(program2, taskCommand);
26495
26419
  registerThreadCommands(program2, taskCommand);
26496
- registerMcpCommands(program2, "0.4.7");
26420
+ registerMcpCommands(program2, "0.4.8");
26497
26421
  registerGuideCommand(program2);
26498
26422
  registerSkillCommands(program2);
26499
26423
  program2.parseAsync().catch(exitWithError);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slopwaresystems",
3
- "version": "0.4.7",
3
+ "version": "0.4.8",
4
4
  "private": false,
5
5
  "description": "slop: the Slopware Tasks board from the terminal, for people and coding agents.",
6
6
  "type": "module",