slopwaresystems 0.4.5 → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +344 -40
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3379,13 +3379,13 @@ import { homedir } from "os";
|
|
|
3379
3379
|
import { join } from "path";
|
|
3380
3380
|
|
|
3381
3381
|
// skills/slop-cli.md
|
|
3382
|
-
var slop_cli_default = "---\nname: slop-cli\ndescription: How an agent works the Slopware Tasks board through the slop MCP tools (board, task, plan, session, threads, say), and what the slop command is for (people and machines: login, link, install). Load when asked to create, update, plan, discuss or finish tasks.\n---\n\n# The board, as six tools\n\nYour harness has the `slop` MCP server. Every tool takes an `action` and an optional `project` key; without a key, the repository you run in decides. Descriptions and notes are Markdown. Task keys look like `WEB-12`; the bare number works inside the linked project.\n\n| Tool | Actions |\n|---|---|\n| `board` | `list` (`mine`, `status`, `all`, `archived`; `limit`/`offset` to page), `columns`, `members`, `projects` |\n| `task` | `show`, `create`, `update` (only given fields change), `move`, `link`, `archive`, `restore`, `history` (`limit`/`before` to page back), `finish` |\n| `plan` | `add` (`items`), `note`, `done`, `reopen`, `rename`, `remove` (by `item` number) |\n| `session` | `claim`, `release`, `who` (every live agent by name, you marked), `check`, `wait` (
|
|
3382
|
+
var slop_cli_default = "---\nname: slop-cli\ndescription: How an agent works the Slopware Tasks board through the slop MCP tools (board, task, plan, session, threads, say), and what the slop command is for (people and machines: login, link, install). Load when asked to create, update, plan, discuss or finish tasks.\n---\n\n# The board, as six tools\n\nYour harness has the `slop` MCP server. Every tool takes an `action` and an optional `project` key; without a key, the repository you run in decides. Descriptions and notes are Markdown. Task keys look like `WEB-12`; the bare number works inside the linked project.\n\n| Tool | Actions |\n|---|---|\n| `board` | `list` (`mine`, `status`, `all`, `archived`; `limit`/`offset` to page), `columns`, `members`, `projects` |\n| `task` | `show`, `create`, `update` (only given fields change), `move`, `link`, `archive`, `restore`, `history` (`limit`/`before` to page back), `finish` |\n| `plan` | `add` (`items`), `note`, `done`, `reopen`, `rename`, `remove` (by `item` number) |\n| `session` | `claim`, `release`, `who` (every live agent by name, you marked), `check`, `wait` (Claude Code only, and rarely; messages reach you on their own; never on Codex; see `slop-sessions`) |\n| `threads` | `list`, `read` (newest 40; `limit`/`before` to page back) |\n| `say` | `post` (new thread; with `task` it reaches whoever is live on it; with `to: <name>` it reaches that one agent; with neither it is general), `reply`. Messages are letters: read when the other agent gets to them, not at once; write once, reply only when it changes something (see `slop-sessions`) |\n\nYou have a name, given at session start (the `SessionStart` hook says it) and shown by `session who`. People and other agents address you by it. Long answers are cut at 16k characters and say so; ask for less with `limit`, `offset` or `before`.\n\nDo not shell out to `slop` for board work; the tools are the way.\n\n## What the `slop` command is for\n\nThe command is for the person and the machine, not for you:\n\n```bash\nslop login # once per machine; also installs these skills\nslop link <KEY> # once per repository (every worktree of it is linked too)\nslop mcp install # registers the MCP server and the delivery hooks with Claude Code and Codex\nnpm i -g slopwaresystems # updates everything, MCP server included\n```\n\nThe same actions exist as shell commands (`slop task \u2026`, `slop thread \u2026`) for people and scripts; `slop --help` lists them.\n\n## Rules of the road\n\n- Do not delete tasks you did not create. Archive finished work instead.\n- Keep titles short and imperative; put the reasoning in the description or the plan's notes.\n- When you open a pull request for a task, link it: `task` with `action: link`.\n";
|
|
3383
3383
|
|
|
3384
3384
|
// skills/slop-planning.md
|
|
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 =
|
|
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';
|
|
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";
|
|
@@ -23325,6 +23325,7 @@ var ThreadMessage = external_exports.object({
|
|
|
23325
23325
|
body: external_exports.string(),
|
|
23326
23326
|
createdAt: Timestamp
|
|
23327
23327
|
});
|
|
23328
|
+
var ThreadRecipient = external_exports.object({ sessionId: external_exports.string(), agent: external_exports.string() });
|
|
23328
23329
|
var Thread = external_exports.object({
|
|
23329
23330
|
id: Id,
|
|
23330
23331
|
projectId: Id,
|
|
@@ -23332,8 +23333,8 @@ var Thread = external_exports.object({
|
|
|
23332
23333
|
taskId: Id.nullable(),
|
|
23333
23334
|
taskKey: external_exports.string().nullable(),
|
|
23334
23335
|
subject: external_exports.string(),
|
|
23335
|
-
/**
|
|
23336
|
-
to: external_exports.
|
|
23336
|
+
/** The agents it is for (by session, named at the time); replies reach each wherever it is. Empty: the task's agents, or general. */
|
|
23337
|
+
to: external_exports.array(ThreadRecipient),
|
|
23337
23338
|
createdBy: UserRef,
|
|
23338
23339
|
createdAt: Timestamp,
|
|
23339
23340
|
lastMessageAt: Timestamp,
|
|
@@ -23349,8 +23350,8 @@ var CreateThreadInput = external_exports.object({
|
|
|
23349
23350
|
subject: external_exports.string().trim().min(1, "Give it a subject").max(200).optional(),
|
|
23350
23351
|
/** A task key or number; absent means general. */
|
|
23351
23352
|
task: external_exports.string().trim().min(1).optional(),
|
|
23352
|
-
/**
|
|
23353
|
-
to: external_exports.string().trim().min(1).max(200).optional(),
|
|
23353
|
+
/** Live agents, by session id or name (as `session who` lists them): a list, or one string with commas; `all` is every live agent. */
|
|
23354
|
+
to: external_exports.union([external_exports.string().trim().min(1).max(2e3), external_exports.array(external_exports.string().trim().min(1).max(200)).max(50)]).optional(),
|
|
23354
23355
|
body
|
|
23355
23356
|
});
|
|
23356
23357
|
var ReplyInput = external_exports.object({ body });
|
|
@@ -23599,7 +23600,207 @@ var AGENT_NAMES = [
|
|
|
23599
23600
|
"Reginald Sandwich",
|
|
23600
23601
|
"Cliff Hanger",
|
|
23601
23602
|
"Elmo Deodorant",
|
|
23602
|
-
"Skip Intro"
|
|
23603
|
+
"Skip Intro",
|
|
23604
|
+
"Barnaby Crunch",
|
|
23605
|
+
"Petra Fied",
|
|
23606
|
+
"Sir Wigglesworth",
|
|
23607
|
+
"Glenda Quench",
|
|
23608
|
+
"Rusty Shackleford Jr.",
|
|
23609
|
+
"Ophelia Blip",
|
|
23610
|
+
"Duke Nuke'em Lite",
|
|
23611
|
+
"Bertha Von Snooze",
|
|
23612
|
+
"Tobias Flibbert",
|
|
23613
|
+
"Nan Bread",
|
|
23614
|
+
"Reverend Toasty",
|
|
23615
|
+
"Muffin Dunbar",
|
|
23616
|
+
"Percival Wetsock",
|
|
23617
|
+
"Agnes Thunderpants",
|
|
23618
|
+
"Colin Firthday",
|
|
23619
|
+
"Lettuce Prey",
|
|
23620
|
+
"Jimbo Sequitur",
|
|
23621
|
+
"Doris Skywalker",
|
|
23622
|
+
"Brock Lee",
|
|
23623
|
+
"Fig Newton-John",
|
|
23624
|
+
"Hal Ape\xF1o",
|
|
23625
|
+
"Ivana Tinkle",
|
|
23626
|
+
"Kenny Loggins Off",
|
|
23627
|
+
"Lola Granola",
|
|
23628
|
+
"Max Volume",
|
|
23629
|
+
"Nacho Average Agent",
|
|
23630
|
+
"Olaf Lundgren",
|
|
23631
|
+
"Pat Downe",
|
|
23632
|
+
"Quincy Meh",
|
|
23633
|
+
"Ramona Flowerpot",
|
|
23634
|
+
"Salami Davis Jr.",
|
|
23635
|
+
"Terry Aki",
|
|
23636
|
+
"Ursula Mildew",
|
|
23637
|
+
"Vinnie Vidi Vici",
|
|
23638
|
+
"Wendy Windbreaker",
|
|
23639
|
+
"Xander Cage-Free",
|
|
23640
|
+
"Yvonne Yawn",
|
|
23641
|
+
"Zeke Squeak",
|
|
23642
|
+
"Ambrose Mumble",
|
|
23643
|
+
"Bunny Rebellion",
|
|
23644
|
+
"Clancy Wiggum Lite",
|
|
23645
|
+
"Delilah Dumpling",
|
|
23646
|
+
"Egbert Sideways",
|
|
23647
|
+
"Fiona Fondue",
|
|
23648
|
+
"Gunther Gumshoe",
|
|
23649
|
+
"Hortense Haggle",
|
|
23650
|
+
"Igor Beaver",
|
|
23651
|
+
"Jolene Jolene",
|
|
23652
|
+
"Kirby Vacuum",
|
|
23653
|
+
"Lars Farquaad",
|
|
23654
|
+
"Mavis Meander",
|
|
23655
|
+
"Ned Flanders Field",
|
|
23656
|
+
"Octavia Oops",
|
|
23657
|
+
"Pippa Puddlejump",
|
|
23658
|
+
"Quigley Quagmire",
|
|
23659
|
+
"Roland Butter",
|
|
23660
|
+
"Sophie Sofa",
|
|
23661
|
+
"Thaddeus Thicc",
|
|
23662
|
+
"Ulysses Grunt",
|
|
23663
|
+
"Valerie Vibe",
|
|
23664
|
+
"Waldo Found",
|
|
23665
|
+
"Xenia Warrior Princess",
|
|
23666
|
+
"Yusuf Yikes",
|
|
23667
|
+
"Zsa Zsa Gabortion",
|
|
23668
|
+
"Archibald Shrimp",
|
|
23669
|
+
"Bianca Bellows",
|
|
23670
|
+
"Cornbread Stanley",
|
|
23671
|
+
"Dot Matrix",
|
|
23672
|
+
"Eugene Levy Jr.",
|
|
23673
|
+
"Fanny Pack",
|
|
23674
|
+
"Gordon Ramsgate",
|
|
23675
|
+
"Hilda Honk",
|
|
23676
|
+
"Inigo Montoya II",
|
|
23677
|
+
"Jarvis Cocker Spaniel",
|
|
23678
|
+
"Kermit Crumb",
|
|
23679
|
+
"Lupita Lint",
|
|
23680
|
+
"Morty Fied",
|
|
23681
|
+
"Nelly Furtado Jr.",
|
|
23682
|
+
"Ozzy Oatmeal",
|
|
23683
|
+
"Prudence Prune",
|
|
23684
|
+
"Quaid Damage",
|
|
23685
|
+
"Randy Marsh Gas",
|
|
23686
|
+
"Sigmund Freud Slip",
|
|
23687
|
+
"Tallulah Tumble",
|
|
23688
|
+
"Umberto Echo",
|
|
23689
|
+
"Velma Dinkley Jr.",
|
|
23690
|
+
"Wilbur Wobbleton",
|
|
23691
|
+
"Ximena Xylophone",
|
|
23692
|
+
"Yannick Yodel",
|
|
23693
|
+
"Zebedee Zilch",
|
|
23694
|
+
"Anchovy Pete",
|
|
23695
|
+
"Beulah Bumble",
|
|
23696
|
+
"Chauncey Chowder",
|
|
23697
|
+
"Darla Drizzle",
|
|
23698
|
+
"Enid Blight",
|
|
23699
|
+
"Fergus Fumble",
|
|
23700
|
+
"Gretchen Grumble",
|
|
23701
|
+
"Horace Hush",
|
|
23702
|
+
"Ivy Overgrown",
|
|
23703
|
+
"Jethro Tulle",
|
|
23704
|
+
"Kiki Kibble",
|
|
23705
|
+
"Leonard Nimoy Jr.",
|
|
23706
|
+
"Maude Flanders Field",
|
|
23707
|
+
"Norbert Nibble",
|
|
23708
|
+
"Orville Reddenbacher",
|
|
23709
|
+
"Phyllis Phlegm",
|
|
23710
|
+
"Quentin Tarantino Jr.",
|
|
23711
|
+
"Roxie Rasp",
|
|
23712
|
+
"Stanley Kubrick Jr.",
|
|
23713
|
+
"Tiffany Aching",
|
|
23714
|
+
"Ulrich Unwell",
|
|
23715
|
+
"Vera Wang Chung",
|
|
23716
|
+
"Wallace Gromit Jr.",
|
|
23717
|
+
"Xavier Xerox",
|
|
23718
|
+
"Yolo Swaggins",
|
|
23719
|
+
"Zelda Fitzgerald II",
|
|
23720
|
+
"Alfie Wafflehouse",
|
|
23721
|
+
"Bea Arthur Fonzarelli",
|
|
23722
|
+
"Cyril Sneer",
|
|
23723
|
+
"Dudley Doo-Wrong",
|
|
23724
|
+
"Esther Bunny",
|
|
23725
|
+
"Flossie Floss",
|
|
23726
|
+
"Gustav Gravlax",
|
|
23727
|
+
"Henrietta Hiccup",
|
|
23728
|
+
"Isaac Newton Jr.",
|
|
23729
|
+
"Judy Jetsonstream",
|
|
23730
|
+
"Kelvin Degree",
|
|
23731
|
+
"Lionel Ritchie Rich",
|
|
23732
|
+
"Mortimer Snerd",
|
|
23733
|
+
"Nigella Lawsuit",
|
|
23734
|
+
"Oprah Winfreeze",
|
|
23735
|
+
"Pablo Escobar Jr.",
|
|
23736
|
+
"Queenie Quibble",
|
|
23737
|
+
"Rodney Dangerfield Jr.",
|
|
23738
|
+
"Sabrina Teenage Witch",
|
|
23739
|
+
"Toby Ornot Toby",
|
|
23740
|
+
"Ulla Bora",
|
|
23741
|
+
"Vince Vaughn Jr.",
|
|
23742
|
+
"Winona Rider Jr.",
|
|
23743
|
+
"Xerxes Xanthum",
|
|
23744
|
+
"Yoda Lay-He-Hoo",
|
|
23745
|
+
"Zachary Binx",
|
|
23746
|
+
"Aloysius Snuffleupagus",
|
|
23747
|
+
"Barbara Streisandwich",
|
|
23748
|
+
"Chester Copperpot",
|
|
23749
|
+
"Deirdre Dither",
|
|
23750
|
+
"Ebenezer Screwed",
|
|
23751
|
+
"Fitzgerald Fizzle",
|
|
23752
|
+
"Gwendolyn Gnash",
|
|
23753
|
+
"Humbert Humbug",
|
|
23754
|
+
"Isadora Drizzle",
|
|
23755
|
+
"Jebediah Springfield",
|
|
23756
|
+
"Kunta Kinte Jr.",
|
|
23757
|
+
"Lucinda Lozenge",
|
|
23758
|
+
"Montgomery Burns Unit",
|
|
23759
|
+
"Nadia Comaneci Jr.",
|
|
23760
|
+
"Oswald Cobblepot",
|
|
23761
|
+
"Persephone Pickle",
|
|
23762
|
+
"Quasimodo Junior",
|
|
23763
|
+
"Rosalind Rumple",
|
|
23764
|
+
"Sylvester Stallion",
|
|
23765
|
+
"Theodora Thimble",
|
|
23766
|
+
"Umberto Unpaid",
|
|
23767
|
+
"Vivienne Vacuum",
|
|
23768
|
+
"Wolfgang Puck Off",
|
|
23769
|
+
"Xena Xtra",
|
|
23770
|
+
"Yakov Smirnoff Jr.",
|
|
23771
|
+
"Zinnia Zipper",
|
|
23772
|
+
"Alistair Cookie",
|
|
23773
|
+
"Belinda Bilge",
|
|
23774
|
+
"Cuthbert Custard",
|
|
23775
|
+
"Dorcas Doorknob",
|
|
23776
|
+
"Ezekiel Zucchini",
|
|
23777
|
+
"Florence Nightingale Jr.",
|
|
23778
|
+
"Gideon Gizzard",
|
|
23779
|
+
"Harriet Hairball",
|
|
23780
|
+
"Irving Berlin Wall",
|
|
23781
|
+
"Jocasta Jam",
|
|
23782
|
+
"Kingsley Kipper",
|
|
23783
|
+
"Leopold Loophole",
|
|
23784
|
+
"Marjorie Marmalade",
|
|
23785
|
+
"Nikolai Nosebleed",
|
|
23786
|
+
"Oberon Obtuse",
|
|
23787
|
+
"Priscilla Pretzel",
|
|
23788
|
+
"Quintus Quicksand",
|
|
23789
|
+
"Reuben Sandwich",
|
|
23790
|
+
"Saoirse Sneeze",
|
|
23791
|
+
"Tobias Tofu",
|
|
23792
|
+
"Ulysses S. Grunt",
|
|
23793
|
+
"Veronica Vex",
|
|
23794
|
+
"Wilhelmina Wombat",
|
|
23795
|
+
"Xiomara Xmas",
|
|
23796
|
+
"Yorick Alas",
|
|
23797
|
+
"Zebulon Zest",
|
|
23798
|
+
"Ambrosia Salad",
|
|
23799
|
+
"Balthazar Bidet",
|
|
23800
|
+
"Clementine Clatter",
|
|
23801
|
+
"Dmitri Dumpling",
|
|
23802
|
+
"Eleanor Rigby Jr.",
|
|
23803
|
+
"Ferdinand Fedora"
|
|
23603
23804
|
];
|
|
23604
23805
|
function agentNameFor(key) {
|
|
23605
23806
|
let hash2 = 2166136261;
|
|
@@ -23938,7 +24139,7 @@ This repository is linked to a Slopware Tasks board. Use the \`slop\` CLI to kee
|
|
|
23938
24139
|
- Before starting tracked work: \`slop task list\`, then \`slop task start <KEY>\`.
|
|
23939
24140
|
- When it is finished and verified: \`slop task done <KEY>\`.
|
|
23940
24141
|
- 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 -\`.
|
|
23941
|
-
- 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
|
|
24142
|
+
- 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\`.
|
|
23942
24143
|
- 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.
|
|
23943
24144
|
- 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.
|
|
23944
24145
|
- Add \`--json\` to any command for machine-readable output. \`slop task --help\` lists everything.
|
|
@@ -24645,6 +24846,58 @@ function codexConversation(roots, cwd) {
|
|
|
24645
24846
|
}
|
|
24646
24847
|
return null;
|
|
24647
24848
|
}
|
|
24849
|
+
function codexRollout(roots, threadId) {
|
|
24850
|
+
const suffix = `-${threadId}.jsonl`;
|
|
24851
|
+
let best = null;
|
|
24852
|
+
const walk = (dir, depth) => {
|
|
24853
|
+
let entries;
|
|
24854
|
+
try {
|
|
24855
|
+
entries = readdirSync2(dir, { withFileTypes: true });
|
|
24856
|
+
} catch {
|
|
24857
|
+
return;
|
|
24858
|
+
}
|
|
24859
|
+
for (const entry of entries) {
|
|
24860
|
+
const path3 = join8(dir, entry.name);
|
|
24861
|
+
if (entry.isDirectory()) {
|
|
24862
|
+
if (depth < 3) walk(path3, depth + 1);
|
|
24863
|
+
continue;
|
|
24864
|
+
}
|
|
24865
|
+
if (!entry.name.startsWith("rollout-") || !entry.name.endsWith(suffix)) continue;
|
|
24866
|
+
try {
|
|
24867
|
+
const mtime = statSync3(path3).mtimeMs;
|
|
24868
|
+
if (!best || mtime > best.mtime) best = { path: path3, mtime };
|
|
24869
|
+
} catch {
|
|
24870
|
+
}
|
|
24871
|
+
}
|
|
24872
|
+
};
|
|
24873
|
+
for (const root of roots) walk(root, 0);
|
|
24874
|
+
return best ? best.path : null;
|
|
24875
|
+
}
|
|
24876
|
+
function codexTurnState(roots, threadId) {
|
|
24877
|
+
const path3 = codexRollout(roots, threadId);
|
|
24878
|
+
if (!path3) return "unknown";
|
|
24879
|
+
let tail = "";
|
|
24880
|
+
try {
|
|
24881
|
+
const size = statSync3(path3).size;
|
|
24882
|
+
const length = Math.min(size, 256 * 1024);
|
|
24883
|
+
const fd = openSync(path3, "r");
|
|
24884
|
+
try {
|
|
24885
|
+
const buffer = Buffer.alloc(length);
|
|
24886
|
+
tail = buffer.subarray(0, readSync(fd, buffer, 0, length, size - length)).toString("utf8");
|
|
24887
|
+
} finally {
|
|
24888
|
+
closeSync(fd);
|
|
24889
|
+
}
|
|
24890
|
+
} catch {
|
|
24891
|
+
return "unknown";
|
|
24892
|
+
}
|
|
24893
|
+
let state = "unknown";
|
|
24894
|
+
for (const line of tail.split("\n")) {
|
|
24895
|
+
if (!line.includes('"event_msg"')) continue;
|
|
24896
|
+
if (line.includes('"task_started"')) state = "busy";
|
|
24897
|
+
else if (line.includes('"task_complete"') || line.includes('"turn_aborted"')) state = "idle";
|
|
24898
|
+
}
|
|
24899
|
+
return state;
|
|
24900
|
+
}
|
|
24648
24901
|
function codexBinary() {
|
|
24649
24902
|
try {
|
|
24650
24903
|
execFileSync4(process.platform === "win32" ? "where" : "which", ["codex"], { stdio: "ignore" });
|
|
@@ -25046,7 +25299,7 @@ var readText2 = (value) => value === "-" ? readFileSync11(0, "utf8").trim() : va
|
|
|
25046
25299
|
var when = (iso) => iso.slice(0, 16).replace("T", " ");
|
|
25047
25300
|
var who = (message) => message.via !== "agent" ? message.author.name : `${message.agent ?? message.author.name} (${message.agent ? `${message.author.name}'s agent` : "agent"}${message.machine ? ` \xB7 ${message.machine}` : ""})`;
|
|
25048
25301
|
function threadLine(thread) {
|
|
25049
|
-
const scope = [thread.taskKey ?? (thread.to ? null : "general"), thread.to ? `to ${thread.to.agent}` : null].filter(Boolean).join(", ");
|
|
25302
|
+
const scope = [thread.taskKey ?? (thread.to.length ? null : "general"), thread.to.length ? `to ${thread.to.map((recipient) => recipient.agent).join(", ")}` : null].filter(Boolean).join(", ");
|
|
25050
25303
|
const unread = thread.unread > 0 ? ` ${thread.unread} unread` : "";
|
|
25051
25304
|
const last = thread.last ? `
|
|
25052
25305
|
${who(thread.last)}: ${thread.last.body.split("\n")[0]}` : "";
|
|
@@ -25078,7 +25331,7 @@ function registerThreadCommands(program3, task) {
|
|
|
25078
25331
|
emit(threads, () => threads.length === 0 ? "No threads." : threads.map(threadLine).join("\n"));
|
|
25079
25332
|
});
|
|
25080
25333
|
const thread = program3.command("thread").description("One thread: start, read, reply.");
|
|
25081
|
-
withProject(thread.command("new <subject>")).description('Start a thread. General by default; --task makes it about a task (and reaches whoever is live on it). The first message is -m, or "-" for stdin.').requiredOption("-m, --message <text>", 'the first message; "-" reads stdin').option("-t, --task <key>", "about this task").option("--to <
|
|
25334
|
+
withProject(thread.command("new <subject>")).description('Start a thread. General by default; --task makes it about a task (and reaches whoever is live on it). The first message is -m, or "-" for stdin.').requiredOption("-m, --message <text>", 'the first message; "-" reads stdin').option("-t, --task <key>", "about this task").option("--to <agents>", "to live agents, by name or session id, commas between (slop sessions); all = everyone live").action(async (subject, options) => {
|
|
25082
25335
|
const api = authedApi();
|
|
25083
25336
|
const project = await resolveProject(api, options.project);
|
|
25084
25337
|
const result = await api.post(`/projects/${project.id}/threads`, { subject, task: options.task, to: options.to, body: readText2(options.message) });
|
|
@@ -25111,7 +25364,7 @@ function registerThreadCommands(program3, task) {
|
|
|
25111
25364
|
emit(result, () => started(result));
|
|
25112
25365
|
});
|
|
25113
25366
|
}
|
|
25114
|
-
var started = (result) => result.thread.to ? `Started ${result.thread.id} to ${result.thread.to.agent}${result.delivered ? "
|
|
25367
|
+
var started = (result) => result.thread.to.length ? `Started ${result.thread.id} to ${result.thread.to.map((recipient) => recipient.agent).join(", ")}${result.delivered ? `; ${result.delivered} of them ha${result.delivered === 1 ? "s" : "ve"} it now.` : "; they read it when they are back."}` : result.thread.taskKey ? result.delivered ? `Started ${result.thread.id} on ${result.thread.taskKey}; reached ${result.delivered} live session${result.delivered === 1 ? "" : "s"}.` : `Started ${result.thread.id} on ${result.thread.taskKey}. Nobody is live on it; they see it when they start.` : `Started ${result.thread.id} (general). People see it on the web; agents at their next session start.`;
|
|
25115
25368
|
|
|
25116
25369
|
// src/platform/inbox.ts
|
|
25117
25370
|
import { createHash as createHash4 } from "crypto";
|
|
@@ -25132,6 +25385,9 @@ function inboxWrite(cwd, push) {
|
|
|
25132
25385
|
appendFileSync(inboxFile(cwd), `${JSON.stringify(push)}
|
|
25133
25386
|
`);
|
|
25134
25387
|
}
|
|
25388
|
+
function inboxHas(cwd) {
|
|
25389
|
+
return existsSync9(inboxFile(cwd));
|
|
25390
|
+
}
|
|
25135
25391
|
function inboxDrain(cwd) {
|
|
25136
25392
|
const path3 = inboxFile(cwd);
|
|
25137
25393
|
if (!existsSync9(path3)) return [];
|
|
@@ -25192,7 +25448,7 @@ async function catchUp(api, projectId, taskKey, cwd) {
|
|
|
25192
25448
|
if (seen.has(thread.id) || thread.unread === 0) continue;
|
|
25193
25449
|
seen.add(thread.id);
|
|
25194
25450
|
const detail = await api.get(`/projects/${projectId}/threads/${thread.id}?read=0`);
|
|
25195
|
-
for (const message of detail.messages.slice(-thread.unread)) inboxWrite(cwd, { thread: { id: detail.id, subject: detail.subject, taskKey: detail.taskKey, to: detail.to
|
|
25451
|
+
for (const message of detail.messages.slice(-thread.unread)) inboxWrite(cwd, { thread: { id: detail.id, subject: detail.subject, taskKey: detail.taskKey, to: detail.to.length ? detail.to.map((recipient) => recipient.agent).join(", ") : null }, message });
|
|
25196
25452
|
}
|
|
25197
25453
|
}
|
|
25198
25454
|
function handlers() {
|
|
@@ -25201,21 +25457,53 @@ function handlers() {
|
|
|
25201
25457
|
let held = null;
|
|
25202
25458
|
let identity = null;
|
|
25203
25459
|
const whoAmI = () => identity ??= agentIdentity(process.cwd());
|
|
25460
|
+
const wakeText = (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.
|
|
25461
|
+
|
|
25462
|
+
${pushes.map(pushBlock).join("\n\n")}`;
|
|
25463
|
+
const codexRoots = () => {
|
|
25464
|
+
const own2 = ownHarnessHome("CODEX_HOME");
|
|
25465
|
+
return (own2 ? [own2, ...codexHomes()] : codexHomes()).map((home) => join11(home, "sessions"));
|
|
25466
|
+
};
|
|
25467
|
+
let flushing = false;
|
|
25468
|
+
const flushInboxWhenIdle = (conversation) => {
|
|
25469
|
+
if (flushing) return;
|
|
25470
|
+
flushing = true;
|
|
25471
|
+
const tick = () => {
|
|
25472
|
+
if (!inboxHas(process.cwd())) {
|
|
25473
|
+
flushing = false;
|
|
25474
|
+
return;
|
|
25475
|
+
}
|
|
25476
|
+
if (codexTurnState(codexRoots(), conversation) === "busy") {
|
|
25477
|
+
setTimeout(tick, 2e3).unref();
|
|
25478
|
+
return;
|
|
25479
|
+
}
|
|
25480
|
+
flushing = false;
|
|
25481
|
+
const pushes = inboxDrain(process.cwd());
|
|
25482
|
+
if (pushes.length === 0) return;
|
|
25483
|
+
void wakeCodex(wakeText(pushes), conversation, { binary: codexBinary(), codexHome: ownHarnessHome("CODEX_HOME") }).then((woken) => {
|
|
25484
|
+
if (!woken) for (const push of pushes) inboxWrite(process.cwd(), push);
|
|
25485
|
+
});
|
|
25486
|
+
};
|
|
25487
|
+
setTimeout(tick, 2e3).unref();
|
|
25488
|
+
};
|
|
25204
25489
|
const hold = async (projectId, taskKey) => {
|
|
25205
25490
|
if (held && held.projectId === projectId && held.taskKey === taskKey) return;
|
|
25206
25491
|
held?.stop();
|
|
25207
25492
|
held = new HeldSession(api, projectId, taskKey, {
|
|
25208
|
-
//
|
|
25209
|
-
//
|
|
25210
|
-
//
|
|
25211
|
-
//
|
|
25212
|
-
//
|
|
25493
|
+
// Claude Code is woken over its session socket: the message arrives as a turn, now, idle or not (mid-turn it
|
|
25494
|
+
// rides the next tool result). Codex idle: `codex queue` to its thread starts a turn there. Codex mid-turn (its
|
|
25495
|
+
// rollout says so): the queue would only be read when the turn ends, however long that is, so the message goes
|
|
25496
|
+
// to the inbox and the PostToolUse hook hands it over after the next tool call; if the turn ends first, the
|
|
25497
|
+
// inbox is queued then, as one message. When nothing can be done the inbox rides the chat's next slop tool
|
|
25498
|
+
// result. Nothing holds a turn open.
|
|
25213
25499
|
onMessage: (push) => {
|
|
25214
|
-
const text = `A message for you on Slopware Tasks. Act or answer it (say reply), then carry on.
|
|
25215
|
-
|
|
25216
|
-
${pushBlock(push)}`;
|
|
25217
25500
|
const me = whoAmI();
|
|
25218
|
-
|
|
25501
|
+
if (me.harness === "codex" && me.conversation && codexTurnState(codexRoots(), me.conversation) === "busy") {
|
|
25502
|
+
inboxWrite(process.cwd(), push);
|
|
25503
|
+
flushInboxWhenIdle(me.conversation);
|
|
25504
|
+
return;
|
|
25505
|
+
}
|
|
25506
|
+
const wake = me.harness === "codex" && me.conversation ? wakeCodex(wakeText([push]), me.conversation, { binary: codexBinary(), codexHome: ownHarnessHome("CODEX_HOME") }) : wakeHarness(wakeText([push]));
|
|
25219
25507
|
void wake.then((woken) => {
|
|
25220
25508
|
if (!woken) inboxWrite(process.cwd(), push);
|
|
25221
25509
|
});
|
|
@@ -25277,7 +25565,7 @@ ${pushBlock(push)}`;
|
|
|
25277
25565
|
return [
|
|
25278
25566
|
{
|
|
25279
25567
|
name: "wait_for_message",
|
|
25280
|
-
description: "
|
|
25568
|
+
description: "Claude Code only, and rarely: block until the next message for you (on your task, in a thread you are in, or addressed to you), or, with `thread`, the next one in that thread; or until `minutes` pass. Messages reach you on their own; use this only when you cannot go on without the answer. Never on Codex: end your turn and the reply is your next turn.",
|
|
25281
25569
|
inputSchema: schema({ task: str("Task key to go live on first (optional)"), thread: str("Only a message in this thread"), minutes: int2("Give up after (default 300; 20 with thread)"), project: PROJECT }),
|
|
25282
25570
|
run: async (args) => {
|
|
25283
25571
|
const target = await project(args);
|
|
@@ -25301,7 +25589,7 @@ ${pushBlock(push)}`;
|
|
|
25301
25589
|
{
|
|
25302
25590
|
name: "reply",
|
|
25303
25591
|
description: "Reply in a thread. In a thread about a task, it reaches whoever is live on that task now, or waits for them.",
|
|
25304
|
-
inputSchema: schema({ thread: str("Thread id"), body: str("The message (Markdown)"), wait: int2("
|
|
25592
|
+
inputSchema: schema({ thread: str("Thread id"), body: str("The message (Markdown)"), wait: int2("Claude Code only, rarely: then wait this many minutes for a reply in the thread"), project: PROJECT }, ["thread", "body"]),
|
|
25305
25593
|
run: async (args) => {
|
|
25306
25594
|
const target = await project(args);
|
|
25307
25595
|
const thread = String(args.thread);
|
|
@@ -25316,11 +25604,12 @@ ${waited(await waitFor(thread, args.wait), args.wait, thread)}`;
|
|
|
25316
25604
|
{
|
|
25317
25605
|
name: "post",
|
|
25318
25606
|
description: "Start a thread. With a task: about that task, to whoever is live on it (or whoever picks it up). Without: general, for people and for context; nobody has to answer.",
|
|
25319
|
-
inputSchema: schema({ subject: str("Subject (default: the first line of the body)"), body: str("First message (Markdown)"), task: str("Task key, to make it about that task"), wait: int2("
|
|
25607
|
+
inputSchema: schema({ subject: str("Subject (default: the first line of the body)"), body: str("First message (Markdown)"), task: str("Task key, to make it about that task"), wait: int2("Claude Code only, rarely: then wait this many minutes for a reply in the thread"), project: PROJECT }, ["body"]),
|
|
25320
25608
|
run: async (args) => {
|
|
25321
25609
|
const target = await project(args);
|
|
25322
25610
|
const result = await api.post(`/projects/${target.id}/threads`, { subject: args.subject ? String(args.subject) : void 0, body: String(args.body), task: args.task, to: args.to });
|
|
25323
|
-
const
|
|
25611
|
+
const names = result.thread.to.map((recipient) => recipient.agent).join(", ");
|
|
25612
|
+
const started2 = `Started ${result.thread.id}${names ? ` to ${names}` : ""}${result.thread.taskKey ? ` on ${result.thread.taskKey}` : names ? "" : " (general)"}${result.delivered ? `; reached ${result.delivered} live session(s)` : ""}.`;
|
|
25324
25613
|
if (typeof args.wait !== "number" || args.wait <= 0) return started2;
|
|
25325
25614
|
return `${started2}
|
|
25326
25615
|
|
|
@@ -25655,6 +25944,7 @@ function mergeHooks(hooks, harness) {
|
|
|
25655
25944
|
hooks[event] = entries;
|
|
25656
25945
|
};
|
|
25657
25946
|
add("SessionStart", "start", 5, "Slopware Tasks");
|
|
25947
|
+
if (harness === "codex") add("PostToolUse", "tool", 10, "Slopware Tasks");
|
|
25658
25948
|
return hooks;
|
|
25659
25949
|
}
|
|
25660
25950
|
function installCodex(dir) {
|
|
@@ -25678,7 +25968,7 @@ tool_timeout_sec = 21600
|
|
|
25678
25968
|
file2.hooks = mergeHooks(file2.hooks ?? {}, "codex");
|
|
25679
25969
|
writeFileSync7(hooksPath, `${JSON.stringify(file2, null, 2)}
|
|
25680
25970
|
`);
|
|
25681
|
-
done.push(`Codex: SessionStart
|
|
25971
|
+
done.push(`Codex: SessionStart and PostToolUse hooks written to ${hooksPath}; other slop hooks removed (no Codex hook holds a turn open)`);
|
|
25682
25972
|
return done;
|
|
25683
25973
|
}
|
|
25684
25974
|
async function trustCodexHooks(codexHome) {
|
|
@@ -25718,7 +26008,7 @@ async function trustCodexHooks(codexHome) {
|
|
|
25718
26008
|
const notify = (method, params) => server.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}
|
|
25719
26009
|
`);
|
|
25720
26010
|
try {
|
|
25721
|
-
await call("initialize", { clientInfo: { name: "slop", version: "0.4.
|
|
26011
|
+
await call("initialize", { clientInfo: { name: "slop", version: "0.4.6" }, capabilities: { experimentalApi: true } });
|
|
25722
26012
|
notify("initialized", {});
|
|
25723
26013
|
const listed = await call("hooks/list", { cwds: [process.cwd()] });
|
|
25724
26014
|
const ours = listed.data.flatMap((entry) => entry.hooks).filter((hook) => hook.command?.startsWith("slop hook "));
|
|
@@ -25744,12 +26034,12 @@ async function healCodexTrust() {
|
|
|
25744
26034
|
if (readFileSync13(configPath3, "utf8").includes(`hooks.state."${hooksPath}:stop:`)) return;
|
|
25745
26035
|
await trustCodexHooks(home);
|
|
25746
26036
|
}
|
|
25747
|
-
async function hookInput(harness) {
|
|
26037
|
+
async function hookInput(harness, remember = true) {
|
|
25748
26038
|
const input2 = await new Promise((resolve5) => {
|
|
25749
26039
|
let data = "";
|
|
25750
26040
|
process.stdin.on("data", (chunk) => data += chunk);
|
|
25751
26041
|
process.stdin.on("end", () => resolve5(data));
|
|
25752
|
-
setTimeout(() => resolve5(data), 2e3);
|
|
26042
|
+
setTimeout(() => resolve5(data), 2e3).unref();
|
|
25753
26043
|
});
|
|
25754
26044
|
let cwd = process.cwd();
|
|
25755
26045
|
let conversation = null;
|
|
@@ -25759,17 +26049,30 @@ async function hookInput(harness) {
|
|
|
25759
26049
|
conversation = parsed.session_id ?? null;
|
|
25760
26050
|
} catch {
|
|
25761
26051
|
}
|
|
25762
|
-
if (conversation && !nonInteractiveHarness()) rememberConversation(cwd, { id: conversation, harness: harness ?? "other" });
|
|
26052
|
+
if (remember && conversation && !nonInteractiveHarness()) rememberConversation(cwd, { id: conversation, harness: harness ?? "other" });
|
|
25763
26053
|
return { cwd, conversation };
|
|
25764
26054
|
}
|
|
25765
26055
|
async function promptHook(harness) {
|
|
25766
26056
|
await hookInput(harness);
|
|
25767
26057
|
}
|
|
26058
|
+
async function toolHook(harness) {
|
|
26059
|
+
if (nonInteractiveHarness()) return;
|
|
26060
|
+
const { cwd, conversation } = await hookInput(harness, false);
|
|
26061
|
+
const known = knownConversation(cwd);
|
|
26062
|
+
if (known && conversation && known.id !== conversation) return;
|
|
26063
|
+
const arrived = inboxDrain(cwd);
|
|
26064
|
+
if (arrived.length === 0) return;
|
|
26065
|
+
const text = `Messages arrived on Slopware Tasks while you worked (answer what is still open, once, with say reply; do not acknowledge):
|
|
26066
|
+
|
|
26067
|
+
${arrived.map(pushBlock).join("\n\n")}`;
|
|
26068
|
+
process.stdout.write(`${JSON.stringify({ hookSpecificOutput: { hookEventName: "PostToolUse", additionalContext: text } })}
|
|
26069
|
+
`);
|
|
26070
|
+
}
|
|
25768
26071
|
async function startHook(harness) {
|
|
25769
26072
|
if (nonInteractiveHarness()) return;
|
|
25770
26073
|
const { conversation } = await hookInput(harness);
|
|
25771
26074
|
if (!conversation) return;
|
|
25772
|
-
const text = `On Slopware Tasks you are ${agentNameFor(conversation)}: that is how people and other agents address you (session who lists everyone).`;
|
|
26075
|
+
const text = `On Slopware Tasks you are ${agentNameFor(conversation)}: that is how people and other agents address you (session who lists everyone). Messages between agents are letters, read when the other side gets to them: write once, reply only when it changes something, never wait for a reply${harness === "codex" ? " (no wait tool on Codex; a reply becomes your next turn)" : ""}.`;
|
|
25773
26076
|
process.stdout.write(`${JSON.stringify({ hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: text } })}
|
|
25774
26077
|
`);
|
|
25775
26078
|
}
|
|
@@ -25844,16 +26147,16 @@ function tools() {
|
|
|
25844
26147
|
),
|
|
25845
26148
|
group(
|
|
25846
26149
|
"session",
|
|
25847
|
-
"Being live. claim: take a task, go live on it, get what is unread (messages then reach you on their own). release; finish is on task. who: live agents by name (you included). check: unread now. wait: block for the next message, or the next one in a thread.",
|
|
26150
|
+
"Being live. claim: take a task, go live on it, get what is unread (messages then reach you on their own). release; finish is on task. who: live agents by name (you included). check: unread now. wait (Claude Code only, rarely): block for the next message, or the next one in a thread. Never wait on Codex.",
|
|
25848
26151
|
{ claim: "claim_task", release: "release_task", who: "sessions", check: "check_messages", wait: "wait_for_message" },
|
|
25849
26152
|
{ task: str("Task key"), thread: str("wait: only a message in this thread"), minutes: int2("wait: give up after (default 300; 20 with thread)") }
|
|
25850
26153
|
),
|
|
25851
26154
|
group("threads", "Talk between agents and people. list threads; read one (marks it read; newest 40 unless limit/before).", { list: "threads", read: "thread" }, { task: str("list: about this task"), unread: { type: "boolean" }, id: str("read: thread id"), limit: int2("read: newest N"), before: str("read: page back from this time") }),
|
|
25852
26155
|
group(
|
|
25853
26156
|
"say",
|
|
25854
|
-
"post: start a thread (with task: reaches whoever is live on it; with to:
|
|
26157
|
+
"post: start a thread (with task: reaches whoever is live on it; with to: live agents by name, one or several, or all; without either: general, for people). reply: in a thread; reaches its task's live sessions, everyone who wrote, and everyone it was sent to. Messages are letters: read when the other agent gets to them, so write once and completely, and reply only when it changes something (never to acknowledge). Claude Code only, when you cannot go on without the answer: wait (minutes) returns the reply in the same call. Never wait on Codex.",
|
|
25855
26158
|
{ post: "post", reply: "reply" },
|
|
25856
|
-
{ subject: str("post: optional, else the first line"), body: str("Markdown"), task: str("post: about this task"), to: str("post:
|
|
26159
|
+
{ subject: str("post: optional, else the first line"), body: str("Markdown"), task: str("post: about this task"), to: str("post: agents' names (session who), commas between; all = everyone live. Several make a group chat"), thread: str("reply: thread id"), wait: int2("Claude Code only, rarely: then wait this many minutes for a reply in the thread") }
|
|
25857
26160
|
)
|
|
25858
26161
|
];
|
|
25859
26162
|
}
|
|
@@ -25865,7 +26168,7 @@ function withInbox(list, version2) {
|
|
|
25865
26168
|
const parts = [result];
|
|
25866
26169
|
if (!(tool.name === "session" && (args.action === "check" || args.action === "wait"))) {
|
|
25867
26170
|
const arrived = inboxDrain(process.cwd());
|
|
25868
|
-
if (arrived.length) parts.push(`--- Messages arrived while you worked ---
|
|
26171
|
+
if (arrived.length) parts.push(`--- Messages arrived while you worked (answer what is still open, once; do not acknowledge) ---
|
|
25869
26172
|
${arrived.map(pushBlock).join("\n\n")}`);
|
|
25870
26173
|
}
|
|
25871
26174
|
const notice = updateChecksEnabled() ? updateNotice(version2) : null;
|
|
@@ -25891,9 +26194,10 @@ function registerMcpCommands(program3, version2) {
|
|
|
25891
26194
|
});
|
|
25892
26195
|
const hook = program3.command("hook").description("Harness hooks (installed by slop mcp install).");
|
|
25893
26196
|
const harnessOf2 = (options) => options.harness === "claude" || options.harness === "codex" ? options.harness : options.harness ? "other" : null;
|
|
25894
|
-
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.").option("--harness <name>", "claude or codex").action((options) => startHook(harnessOf2(options)));
|
|
25895
|
-
hook.command("
|
|
25896
|
-
hook.command("
|
|
26197
|
+
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) => startHook(harnessOf2(options)));
|
|
26198
|
+
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) => toolHook(harnessOf2(options)));
|
|
26199
|
+
hook.command("prompt").description("No longer installed; kept so an older install's hook exits cleanly.").option("--harness <name>", "claude or codex").action((options) => promptHook(harnessOf2(options)));
|
|
26200
|
+
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) => stopHook(harnessOf2(options)));
|
|
25897
26201
|
}
|
|
25898
26202
|
|
|
25899
26203
|
// src/commands/sessions.ts
|
|
@@ -25982,13 +26286,13 @@ ${written.map((path3) => ` ${path3}`).join("\n")}`);
|
|
|
25982
26286
|
}
|
|
25983
26287
|
|
|
25984
26288
|
// src/index.ts
|
|
25985
|
-
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.
|
|
26289
|
+
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.6").option("--json", "machine-readable output: JSON on stdout, errors as JSON on stderr").hook("preAction", (_, action) => setJsonMode(Boolean(action.optsWithGlobals().json))).showHelpAfterError("(run with --help for usage)");
|
|
25986
26290
|
registerAuthCommands(program2);
|
|
25987
26291
|
registerProjectCommands(program2);
|
|
25988
26292
|
var taskCommand = registerTaskCommands(program2);
|
|
25989
26293
|
registerSessionCommands(program2, taskCommand);
|
|
25990
26294
|
registerThreadCommands(program2, taskCommand);
|
|
25991
|
-
registerMcpCommands(program2, "0.4.
|
|
26295
|
+
registerMcpCommands(program2, "0.4.6");
|
|
25992
26296
|
registerGuideCommand(program2);
|
|
25993
26297
|
registerSkillCommands(program2);
|
|
25994
26298
|
program2.parseAsync().catch(fail);
|