slopwaresystems 0.4.9 → 0.4.10
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 +375 -145
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3379,7 +3379,7 @@ 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` (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
|
|
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 ordinary answers are cut at 16k characters and say so; ask for less with `limit`, `offset` or `before`. Incoming messages are kept whole in a separate batch. Use `session check` to read any remaining messages.\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\nWhen a wake says messages are waiting, use `session` `check` with the project named in the wake. Each conversation and project has a separate inbox, including when chats share a checkout. A failed handoff is retried; a crash just after output can repeat a message, so do not repeat an action you already completed.\n\nLaunchers can set `SLOP_CONVERSATION_ID` to the host's stable chat ID and `SLOP_HARNESS` to `claude`, `codex`, or `other`. Host-provided conversation IDs are detected automatically when available. An ambiguous directory is not enough to identify a chat; supply its ID when running several chats there without host-provided identity.\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';
|
|
@@ -24825,7 +24825,7 @@ This repository is linked to a Slopware Tasks board. Use the \`slop\` CLI to kee
|
|
|
24825
24825
|
- Before starting tracked work: \`slop task list\`, then \`slop task start <KEY>\`.
|
|
24826
24826
|
- When it is finished and verified: \`slop task done <KEY>\`.
|
|
24827
24827
|
- 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 -\`.
|
|
24828
|
-
- 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. If a wake says mail is waiting, use \`session check\` with the project it names. Each conversation and project has its own inbox. 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\`.
|
|
24828
|
+
- 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. If a wake says mail is waiting, use \`session check\` with the project it names. Each conversation and project has its own inbox. Messages are kept whole; use \`session check\` for any remaining messages. A crash just after output can repeat a message, so do not repeat an action you already completed. Messages for you reach you on their own: idle, as a turn of yours; while you work, as your next turn (Codex) or with your next tool result (Claude Code). Messages are letters, not chat: the other agent reads yours when it gets to it, its reply may predate your latest, so write once and completely, answer a pile of messages with one reply, and reply only when it changes something (never to acknowledge). Do not wait for replies; end your turn. 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\`.
|
|
24829
24829
|
- 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.
|
|
24830
24830
|
- 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.
|
|
24831
24831
|
- Add \`--json\` to any command for machine-readable output. \`slop task --help\` lists everything.
|
|
@@ -24968,20 +24968,137 @@ linked to ${link.projectKey} ${link.projectName} (${link.baseUrl})`
|
|
|
24968
24968
|
}
|
|
24969
24969
|
|
|
24970
24970
|
// src/commands/mcp.ts
|
|
24971
|
-
import { execFileSync as
|
|
24972
|
-
import { existsSync as
|
|
24971
|
+
import { execFileSync as execFileSync6, spawn as spawn3 } from "child_process";
|
|
24972
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync9, readFileSync as readFileSync16, writeFileSync as writeFileSync9 } from "fs";
|
|
24973
24973
|
import { homedir as homedir4 } from "os";
|
|
24974
|
-
import { join as
|
|
24974
|
+
import { join as join15 } from "path";
|
|
24975
24975
|
|
|
24976
24976
|
// src/platform/session-recovery.ts
|
|
24977
|
-
import { readFileSync as
|
|
24978
|
-
import { dirname as dirname6, join as
|
|
24977
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
24978
|
+
import { dirname as dirname6, join as join13 } from "path";
|
|
24979
|
+
|
|
24980
|
+
// src/platform/inbox.ts
|
|
24981
|
+
import { createHash as createHash6, randomUUID as randomUUID7 } from "crypto";
|
|
24982
|
+
import { existsSync as existsSync9, linkSync as linkSync3, mkdirSync as mkdirSync7, readFileSync as readFileSync10, readdirSync as readdirSync6, realpathSync as realpathSync4, renameSync as renameSync2, rmSync as rmSync7, writeFileSync as writeFileSync7 } from "fs";
|
|
24983
|
+
import { dirname as dirname5, join as join12 } from "path";
|
|
24984
|
+
|
|
24985
|
+
// src/platform/inbox-claims.ts
|
|
24986
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
24987
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
24988
|
+
import { existsSync as existsSync7, linkSync as linkSync2, mkdirSync as mkdirSync5, readFileSync as readFileSync8, readdirSync as readdirSync4, rmSync as rmSync5, writeFileSync as writeFileSync6 } from "fs";
|
|
24989
|
+
import { join as join10 } from "path";
|
|
24990
|
+
var processOwner = null;
|
|
24991
|
+
function fetchProcessStart(pid) {
|
|
24992
|
+
try {
|
|
24993
|
+
return execFileSync5("ps", ["-p", String(pid), "-o", "lstart="], { encoding: "utf8", env: { ...process.env, LC_ALL: "C", TZ: "UTC" }, stdio: ["ignore", "pipe", "ignore"], timeout: 1e3 }).trim() || null;
|
|
24994
|
+
} catch {
|
|
24995
|
+
return null;
|
|
24996
|
+
}
|
|
24997
|
+
}
|
|
24998
|
+
function isClaimOwnerRunning(owner) {
|
|
24999
|
+
if (!Number.isSafeInteger(owner.pid) || owner.pid <= 0) throw new Error("Invalid inbox claim owner");
|
|
25000
|
+
try {
|
|
25001
|
+
process.kill(owner.pid, 0);
|
|
25002
|
+
} catch (error62) {
|
|
25003
|
+
if (error62.code === "ESRCH") return false;
|
|
25004
|
+
return true;
|
|
25005
|
+
}
|
|
25006
|
+
if (owner.pid === process.pid) return !owner.started || !processOwner?.started || owner.started === processOwner.started;
|
|
25007
|
+
const started = owner.started && fetchProcessStart(owner.pid);
|
|
25008
|
+
return !started || started === owner.started;
|
|
25009
|
+
}
|
|
25010
|
+
function claimInboxMessage(directory) {
|
|
25011
|
+
mkdirSync5(directory, { recursive: true });
|
|
25012
|
+
processOwner ??= { pid: process.pid, started: fetchProcessStart(process.pid) };
|
|
25013
|
+
for (; ; ) {
|
|
25014
|
+
let names;
|
|
25015
|
+
try {
|
|
25016
|
+
names = readdirSync4(directory).filter((name) => /^\d+\.json$/.test(name)).sort((a, b) => Number(a.slice(0, -5)) - Number(b.slice(0, -5)));
|
|
25017
|
+
} catch (error62) {
|
|
25018
|
+
if (error62.code === "ENOENT") return null;
|
|
25019
|
+
throw error62;
|
|
25020
|
+
}
|
|
25021
|
+
const last = names.at(-1);
|
|
25022
|
+
if (last && !existsSync7(join10(directory, `${last}.released`))) {
|
|
25023
|
+
let owner;
|
|
25024
|
+
try {
|
|
25025
|
+
owner = JSON.parse(readFileSync8(join10(directory, last), "utf8"));
|
|
25026
|
+
} catch (error62) {
|
|
25027
|
+
if (error62.code === "ENOENT") return null;
|
|
25028
|
+
throw error62;
|
|
25029
|
+
}
|
|
25030
|
+
if (isClaimOwnerRunning(owner)) return null;
|
|
25031
|
+
}
|
|
25032
|
+
const next = join10(directory, `${last ? Number(last.slice(0, -5)) + 1 : 0}.json`);
|
|
25033
|
+
const temporary = join10(directory, `${randomUUID6()}.writing`);
|
|
25034
|
+
try {
|
|
25035
|
+
writeFileSync6(temporary, JSON.stringify(processOwner), { flag: "wx", mode: 384 });
|
|
25036
|
+
linkSync2(temporary, next);
|
|
25037
|
+
return { release: () => {
|
|
25038
|
+
try {
|
|
25039
|
+
writeFileSync6(`${next}.released`, "", { mode: 384 });
|
|
25040
|
+
} catch (error62) {
|
|
25041
|
+
if (error62.code !== "ENOENT") throw error62;
|
|
25042
|
+
}
|
|
25043
|
+
} };
|
|
25044
|
+
} catch (error62) {
|
|
25045
|
+
if (error62.code === "ENOENT") return null;
|
|
25046
|
+
if (error62.code !== "EEXIST") throw error62;
|
|
25047
|
+
} finally {
|
|
25048
|
+
rmSync5(temporary, { force: true });
|
|
25049
|
+
}
|
|
25050
|
+
}
|
|
25051
|
+
}
|
|
25052
|
+
|
|
25053
|
+
// src/platform/inbox-receipts.ts
|
|
25054
|
+
import { createHash as createHash5 } from "crypto";
|
|
25055
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync9, readdirSync as readdirSync5, rmSync as rmSync6 } from "fs";
|
|
25056
|
+
import { join as join11 } from "path";
|
|
25057
|
+
var COMPACTION_SIZE = 128;
|
|
25058
|
+
var archives = /* @__PURE__ */ new Map();
|
|
25059
|
+
function fetchArchivedReceipts(directory) {
|
|
25060
|
+
let cached2 = archives.get(directory);
|
|
25061
|
+
if (!cached2) {
|
|
25062
|
+
cached2 = { files: /* @__PURE__ */ new Set(), keys: /* @__PURE__ */ new Set() };
|
|
25063
|
+
archives.set(directory, cached2);
|
|
25064
|
+
}
|
|
25065
|
+
let names;
|
|
25066
|
+
try {
|
|
25067
|
+
names = readdirSync5(join11(directory, "compacted"));
|
|
25068
|
+
} catch (error62) {
|
|
25069
|
+
if (error62.code === "ENOENT") return cached2.keys;
|
|
25070
|
+
throw error62;
|
|
25071
|
+
}
|
|
25072
|
+
for (const name of names) {
|
|
25073
|
+
if (!name.endsWith(".json") || cached2.files.has(name)) continue;
|
|
25074
|
+
const keys = JSON.parse(readFileSync9(join11(directory, "compacted", name), "utf8"));
|
|
25075
|
+
if (!Array.isArray(keys) || !keys.every((key) => typeof key === "string" && /^[a-f0-9]{64}$/.test(key))) throw new Error("Invalid inbox receipt archive");
|
|
25076
|
+
for (const key of keys) cached2.keys.add(key);
|
|
25077
|
+
cached2.files.add(name);
|
|
25078
|
+
}
|
|
25079
|
+
return cached2.keys;
|
|
25080
|
+
}
|
|
25081
|
+
function hasInboxReceipt(directory, key) {
|
|
25082
|
+
return existsSync8(join11(directory, key)) || fetchArchivedReceipts(directory).has(key);
|
|
25083
|
+
}
|
|
25084
|
+
function compactInboxReceipts(directory) {
|
|
25085
|
+
let keys;
|
|
25086
|
+
try {
|
|
25087
|
+
keys = readdirSync5(directory).filter((name2) => /^[a-f0-9]{64}$/.test(name2)).sort();
|
|
25088
|
+
} catch (error62) {
|
|
25089
|
+
if (error62.code === "ENOENT") return;
|
|
25090
|
+
throw error62;
|
|
25091
|
+
}
|
|
25092
|
+
if (keys.length < COMPACTION_SIZE) return;
|
|
25093
|
+
mkdirSync6(join11(directory, "compacted"), { recursive: true });
|
|
25094
|
+
const text = JSON.stringify(keys);
|
|
25095
|
+
const name = createHash5("sha256").update(text).digest("hex");
|
|
25096
|
+
persistLocalState(join11(directory, "compacted", `${name}.json`), text, false);
|
|
25097
|
+
for (const key of keys) rmSync6(join11(directory, key), { force: true });
|
|
25098
|
+
}
|
|
24979
25099
|
|
|
24980
25100
|
// src/platform/inbox.ts
|
|
24981
|
-
|
|
24982
|
-
import { existsSync as existsSync7, linkSync as linkSync2, mkdirSync as mkdirSync5, readFileSync as readFileSync8, readdirSync as readdirSync4, realpathSync as realpathSync4, renameSync as renameSync2, rmSync as rmSync5, writeFileSync as writeFileSync6 } from "fs";
|
|
24983
|
-
import { dirname as dirname5, join as join10 } from "path";
|
|
24984
|
-
var buildInboxDir = () => join10(dirname5(buildConfigPath()), "inbox");
|
|
25101
|
+
var buildInboxDir = () => join12(dirname5(buildConfigPath()), "inbox");
|
|
24985
25102
|
var resolveRealCwd2 = (cwd) => {
|
|
24986
25103
|
try {
|
|
24987
25104
|
return realpathSync4(cwd);
|
|
@@ -24989,100 +25106,169 @@ var resolveRealCwd2 = (cwd) => {
|
|
|
24989
25106
|
return cwd;
|
|
24990
25107
|
}
|
|
24991
25108
|
};
|
|
24992
|
-
var generateInboxKey = (cwd, owner) => owner ? generateLocalStateKey(cwd, JSON.stringify([owner.baseUrl.replace(/\/$/, ""), owner.projectId, owner.conversation])) :
|
|
24993
|
-
var buildInboxPath = (cwd) =>
|
|
24994
|
-
var buildInboxQueuePath = (cwd, owner) =>
|
|
25109
|
+
var generateInboxKey = (cwd, owner) => owner ? generateLocalStateKey(cwd, JSON.stringify([owner.baseUrl.replace(/\/$/, ""), owner.projectId, owner.conversation])) : createHash6("sha256").update(resolveRealCwd2(cwd)).digest("hex").slice(0, 16);
|
|
25110
|
+
var buildInboxPath = (cwd) => join12(buildInboxDir(), `${generateInboxKey(cwd)}.jsonl`);
|
|
25111
|
+
var buildInboxQueuePath = (cwd, owner) => join12(buildInboxDir(), generateInboxKey(cwd, owner));
|
|
24995
25112
|
var sequence = 0;
|
|
24996
25113
|
function persistInboxPush(cwd, push, owner) {
|
|
24997
25114
|
const directory = buildInboxQueuePath(cwd, owner);
|
|
24998
|
-
|
|
24999
|
-
const name = `${Date.now()}.${process.pid}.${String(sequence++).padStart(12, "0")}.${
|
|
25000
|
-
const temporary =
|
|
25001
|
-
|
|
25115
|
+
mkdirSync7(directory, { recursive: true });
|
|
25116
|
+
const name = `${Date.now()}.${process.pid}.${String(sequence++).padStart(12, "0")}.${randomUUID7()}`;
|
|
25117
|
+
const temporary = join12(directory, `${name}.writing`);
|
|
25118
|
+
writeFileSync7(temporary, JSON.stringify(push), { flag: "wx" });
|
|
25002
25119
|
if (owner) {
|
|
25003
|
-
const key =
|
|
25004
|
-
const published =
|
|
25120
|
+
const key = createHash6("sha256").update(push.message.id).digest("hex");
|
|
25121
|
+
const published = join12(directory, `${key}.json`);
|
|
25005
25122
|
try {
|
|
25006
|
-
if (
|
|
25007
|
-
|
|
25123
|
+
if (hasInboxReceipt(join12(directory, "receipts"), key)) return false;
|
|
25124
|
+
linkSync3(temporary, published);
|
|
25008
25125
|
return true;
|
|
25009
25126
|
} catch (error62) {
|
|
25010
|
-
if (error62.code === "EEXIST") return !
|
|
25127
|
+
if (error62.code === "EEXIST") return !hasInboxReceipt(join12(directory, "receipts"), key);
|
|
25011
25128
|
throw error62;
|
|
25012
25129
|
} finally {
|
|
25013
|
-
|
|
25130
|
+
rmSync7(temporary, { force: true });
|
|
25014
25131
|
}
|
|
25015
25132
|
}
|
|
25016
|
-
renameSync2(temporary,
|
|
25133
|
+
renameSync2(temporary, join12(directory, `${name}.json`));
|
|
25017
25134
|
return true;
|
|
25018
25135
|
}
|
|
25019
|
-
function
|
|
25020
|
-
const
|
|
25021
|
-
const directory = buildInboxQueuePath(cwd, owner);
|
|
25136
|
+
function deleteAcknowledgedInboxClaims(directory) {
|
|
25137
|
+
const claims = join12(directory, "claims");
|
|
25022
25138
|
let names;
|
|
25023
25139
|
try {
|
|
25024
|
-
names =
|
|
25140
|
+
names = readdirSync6(claims);
|
|
25025
25141
|
} catch (error62) {
|
|
25026
|
-
if (error62.code === "ENOENT") return
|
|
25142
|
+
if (error62.code === "ENOENT") return;
|
|
25027
25143
|
throw error62;
|
|
25028
25144
|
}
|
|
25029
|
-
|
|
25030
|
-
|
|
25031
|
-
|
|
25145
|
+
for (const key of names) {
|
|
25146
|
+
if (!/^[a-f0-9]{64}$/.test(key) || !hasInboxReceipt(join12(directory, "receipts"), key)) continue;
|
|
25147
|
+
try {
|
|
25148
|
+
rmSync7(join12(claims, key), { recursive: true, force: true });
|
|
25149
|
+
} catch (error62) {
|
|
25150
|
+
if (error62.code !== "ENOTEMPTY") throw error62;
|
|
25151
|
+
}
|
|
25152
|
+
}
|
|
25153
|
+
}
|
|
25154
|
+
function claimInbox(cwd, owner, accept = () => true) {
|
|
25155
|
+
const directory = buildInboxQueuePath(cwd, owner);
|
|
25156
|
+
const receipts = join12(directory, "receipts");
|
|
25157
|
+
const claims = [];
|
|
25158
|
+
let settled = false;
|
|
25159
|
+
const release = () => {
|
|
25160
|
+
if (settled) return;
|
|
25161
|
+
for (const claim2 of claims) claim2.release();
|
|
25162
|
+
settled = true;
|
|
25163
|
+
};
|
|
25164
|
+
const delivery = {
|
|
25165
|
+
pushes: [],
|
|
25166
|
+
release,
|
|
25167
|
+
acknowledge: () => {
|
|
25168
|
+
if (settled) return;
|
|
25169
|
+
mkdirSync7(receipts, { recursive: true });
|
|
25170
|
+
for (const { key } of claims) {
|
|
25171
|
+
try {
|
|
25172
|
+
writeFileSync7(join12(receipts, key), "", { flag: "wx", mode: 384 });
|
|
25173
|
+
} catch (error62) {
|
|
25174
|
+
if (error62.code !== "EEXIST") throw error62;
|
|
25175
|
+
}
|
|
25176
|
+
}
|
|
25177
|
+
settled = true;
|
|
25178
|
+
try {
|
|
25179
|
+
for (const { key } of claims) rmSync7(join12(directory, `${key}.json`), { force: true });
|
|
25180
|
+
deleteAcknowledgedInboxClaims(directory);
|
|
25181
|
+
compactInboxReceipts(receipts);
|
|
25182
|
+
} catch {
|
|
25183
|
+
}
|
|
25184
|
+
}
|
|
25185
|
+
};
|
|
25186
|
+
try {
|
|
25187
|
+
let names;
|
|
25188
|
+
try {
|
|
25189
|
+
names = readdirSync6(directory).filter((file2) => /^[a-f0-9]{64}\.json$/.test(file2));
|
|
25190
|
+
} catch (error62) {
|
|
25191
|
+
if (error62.code === "ENOENT") return delivery;
|
|
25192
|
+
throw error62;
|
|
25193
|
+
}
|
|
25032
25194
|
const available = [];
|
|
25033
25195
|
for (const name of names) {
|
|
25034
25196
|
try {
|
|
25035
|
-
available.push({ name, push: JSON.parse(
|
|
25197
|
+
available.push({ key: name.slice(0, -5), push: JSON.parse(readFileSync10(join12(directory, name), "utf8")) });
|
|
25036
25198
|
} catch (error62) {
|
|
25037
25199
|
if (error62.code !== "ENOENT") throw error62;
|
|
25038
25200
|
}
|
|
25039
25201
|
}
|
|
25040
25202
|
available.sort((a, b) => a.push.message.createdAt.localeCompare(b.push.message.createdAt) || a.push.message.id.localeCompare(b.push.message.id));
|
|
25041
|
-
for (const {
|
|
25203
|
+
for (const { key, push } of available) {
|
|
25204
|
+
if (hasInboxReceipt(receipts, key)) {
|
|
25205
|
+
rmSync7(join12(directory, `${key}.json`), { force: true });
|
|
25206
|
+
continue;
|
|
25207
|
+
}
|
|
25042
25208
|
if (!accept(push)) continue;
|
|
25043
|
-
|
|
25044
|
-
const receipt = join10(receipts, name.slice(0, -5));
|
|
25209
|
+
let claim2;
|
|
25045
25210
|
try {
|
|
25046
|
-
|
|
25211
|
+
claim2 = claimInboxMessage(join12(directory, "claims", key));
|
|
25047
25212
|
} catch (error62) {
|
|
25048
|
-
if (
|
|
25049
|
-
rmSync5(path2, { force: true });
|
|
25050
|
-
continue;
|
|
25051
|
-
}
|
|
25213
|
+
if (hasInboxReceipt(receipts, key)) continue;
|
|
25052
25214
|
throw error62;
|
|
25053
25215
|
}
|
|
25054
|
-
|
|
25055
|
-
|
|
25216
|
+
if (!claim2) continue;
|
|
25217
|
+
claims.push({ key, ...claim2 });
|
|
25218
|
+
if (!hasInboxReceipt(receipts, key)) delivery.pushes.push(push);
|
|
25056
25219
|
}
|
|
25057
|
-
return
|
|
25220
|
+
return delivery;
|
|
25221
|
+
} catch (error62) {
|
|
25222
|
+
release();
|
|
25223
|
+
throw error62;
|
|
25224
|
+
}
|
|
25225
|
+
}
|
|
25226
|
+
function hasPendingInboxMessages(cwd, owner) {
|
|
25227
|
+
const directory = buildInboxQueuePath(cwd, owner);
|
|
25228
|
+
try {
|
|
25229
|
+
return readdirSync6(directory).some((name) => /^[a-f0-9]{64}\.json$/.test(name) && !hasInboxReceipt(join12(directory, "receipts"), name.slice(0, -5)));
|
|
25230
|
+
} catch (error62) {
|
|
25231
|
+
if (error62.code === "ENOENT") return false;
|
|
25232
|
+
throw error62;
|
|
25233
|
+
}
|
|
25234
|
+
}
|
|
25235
|
+
function drainInbox(cwd) {
|
|
25236
|
+
const pushes = drainLegacyInbox(cwd);
|
|
25237
|
+
const directory = buildInboxQueuePath(cwd);
|
|
25238
|
+
let names;
|
|
25239
|
+
try {
|
|
25240
|
+
names = readdirSync6(directory).filter((file2) => file2.endsWith(".json")).sort();
|
|
25241
|
+
} catch (error62) {
|
|
25242
|
+
if (error62.code === "ENOENT") return pushes;
|
|
25243
|
+
throw error62;
|
|
25058
25244
|
}
|
|
25059
25245
|
for (const name of names) {
|
|
25060
|
-
const taken =
|
|
25246
|
+
const taken = join12(directory, `${name}.${randomUUID7()}.draining`);
|
|
25061
25247
|
try {
|
|
25062
|
-
renameSync2(
|
|
25248
|
+
renameSync2(join12(directory, name), taken);
|
|
25063
25249
|
} catch (error62) {
|
|
25064
25250
|
if (error62.code === "ENOENT") continue;
|
|
25065
25251
|
throw error62;
|
|
25066
25252
|
}
|
|
25067
25253
|
try {
|
|
25068
|
-
pushes.push(JSON.parse(
|
|
25254
|
+
pushes.push(JSON.parse(readFileSync10(taken, "utf8")));
|
|
25069
25255
|
} finally {
|
|
25070
|
-
|
|
25256
|
+
rmSync7(taken, { force: true });
|
|
25071
25257
|
}
|
|
25072
25258
|
}
|
|
25073
25259
|
return pushes;
|
|
25074
25260
|
}
|
|
25075
25261
|
function drainLegacyInbox(cwd) {
|
|
25076
25262
|
const path2 = buildInboxPath(cwd);
|
|
25077
|
-
if (!
|
|
25263
|
+
if (!existsSync9(path2)) return [];
|
|
25078
25264
|
const taken = `${path2}.${process.pid}.${Date.now()}.draining`;
|
|
25079
25265
|
try {
|
|
25080
25266
|
renameSync2(path2, taken);
|
|
25081
25267
|
} catch {
|
|
25082
25268
|
return [];
|
|
25083
25269
|
}
|
|
25084
|
-
const lines =
|
|
25085
|
-
|
|
25270
|
+
const lines = readFileSync10(taken, "utf8").split("\n").filter(Boolean);
|
|
25271
|
+
rmSync7(taken, { force: true });
|
|
25086
25272
|
return lines.flatMap((line) => {
|
|
25087
25273
|
try {
|
|
25088
25274
|
return [JSON.parse(line)];
|
|
@@ -25098,7 +25284,7 @@ var SessionRecovery = class {
|
|
|
25098
25284
|
this.api = api;
|
|
25099
25285
|
this.owner = owner;
|
|
25100
25286
|
this.deliverPush = deliverPush;
|
|
25101
|
-
this.path =
|
|
25287
|
+
this.path = join13(dirname6(buildConfigPath()), "cursors", `${generateInboxKey(cwd, owner)}.json`);
|
|
25102
25288
|
}
|
|
25103
25289
|
api;
|
|
25104
25290
|
owner;
|
|
@@ -25118,7 +25304,7 @@ var SessionRecovery = class {
|
|
|
25118
25304
|
}
|
|
25119
25305
|
async fetchCursor() {
|
|
25120
25306
|
try {
|
|
25121
|
-
const value = JSON.parse(
|
|
25307
|
+
const value = JSON.parse(readFileSync11(this.path, "utf8"));
|
|
25122
25308
|
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) throw new Error("Invalid saved message cursor");
|
|
25123
25309
|
this.cursor = value;
|
|
25124
25310
|
} catch (error62) {
|
|
@@ -25471,63 +25657,98 @@ function truncateToolResult(result) {
|
|
|
25471
25657
|
|
|
25472
25658
|
// src/platform/mcp.ts
|
|
25473
25659
|
import { createInterface } from "readline";
|
|
25660
|
+
var McpOutput = class {
|
|
25661
|
+
deliveries = [];
|
|
25662
|
+
remainingMessageChars = RESULT_CAP;
|
|
25663
|
+
registerDelivery(delivery) {
|
|
25664
|
+
this.deliveries.push(delivery);
|
|
25665
|
+
this.remainingMessageChars -= delivery.text.length;
|
|
25666
|
+
}
|
|
25667
|
+
formatResult(text) {
|
|
25668
|
+
const messages = this.deliveries.map((delivery) => delivery.text).filter(Boolean);
|
|
25669
|
+
return [truncateToolResult(text), ...messages.length ? ["--- Messages for you (read once; do not acknowledge). Use session check for any remaining messages. ---", ...messages] : []].filter(Boolean).join("\n\n");
|
|
25670
|
+
}
|
|
25671
|
+
acknowledgeDeliveries() {
|
|
25672
|
+
for (const delivery of this.deliveries) delivery.acknowledge();
|
|
25673
|
+
}
|
|
25674
|
+
releaseDeliveries() {
|
|
25675
|
+
for (const delivery of this.deliveries) delivery.release();
|
|
25676
|
+
}
|
|
25677
|
+
};
|
|
25474
25678
|
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
25475
25679
|
function buildMcpRequestHandler(tools, info, sendJsonRpcMessage) {
|
|
25476
25680
|
const sendJsonRpcResult = (id, result) => sendJsonRpcMessage({ jsonrpc: "2.0", id, result });
|
|
25477
25681
|
const sendJsonRpcError = (id, code, message) => sendJsonRpcMessage({ jsonrpc: "2.0", id, error: { code, message } });
|
|
25478
25682
|
return async (value) => {
|
|
25479
25683
|
if (!isRecord(value) || value.jsonrpc !== "2.0" || typeof value.method !== "string" || value.id !== void 0 && value.id !== null && typeof value.id !== "string" && typeof value.id !== "number") {
|
|
25480
|
-
sendJsonRpcError(null, -32600, "Invalid request");
|
|
25684
|
+
await sendJsonRpcError(null, -32600, "Invalid request");
|
|
25481
25685
|
return;
|
|
25482
25686
|
}
|
|
25483
25687
|
const request = value;
|
|
25484
25688
|
const { id, method } = request;
|
|
25485
25689
|
if (id === void 0) return;
|
|
25486
25690
|
if (request.params !== void 0 && !isRecord(request.params)) {
|
|
25487
|
-
sendJsonRpcError(id, -32602, "Params must be an object");
|
|
25691
|
+
await sendJsonRpcError(id, -32602, "Params must be an object");
|
|
25488
25692
|
return;
|
|
25489
25693
|
}
|
|
25490
25694
|
const params = request.params ?? {};
|
|
25491
25695
|
switch (method) {
|
|
25492
25696
|
case "initialize":
|
|
25493
|
-
sendJsonRpcResult(id, { protocolVersion: params.protocolVersion ?? "2025-06-18", capabilities: { tools: {} }, serverInfo: info });
|
|
25697
|
+
await sendJsonRpcResult(id, { protocolVersion: params.protocolVersion ?? "2025-06-18", capabilities: { tools: {} }, serverInfo: info });
|
|
25494
25698
|
return;
|
|
25495
25699
|
case "notifications/initialized":
|
|
25496
25700
|
case "notifications/cancelled":
|
|
25497
25701
|
return;
|
|
25498
25702
|
case "ping":
|
|
25499
|
-
sendJsonRpcResult(id, {});
|
|
25703
|
+
await sendJsonRpcResult(id, {});
|
|
25500
25704
|
return;
|
|
25501
25705
|
case "tools/list":
|
|
25502
|
-
sendJsonRpcResult(id, { tools: tools.map(({ name, description: description2, inputSchema }) => ({ name, description: description2, inputSchema })) });
|
|
25706
|
+
await sendJsonRpcResult(id, { tools: tools.map(({ name, description: description2, inputSchema }) => ({ name, description: description2, inputSchema })) });
|
|
25503
25707
|
return;
|
|
25504
25708
|
case "tools/call": {
|
|
25505
25709
|
const name = params.name;
|
|
25506
25710
|
if (typeof name !== "string" || params.arguments !== void 0 && !isRecord(params.arguments)) {
|
|
25507
|
-
sendJsonRpcError(id, -32602, "Tool name and arguments are invalid");
|
|
25711
|
+
await sendJsonRpcError(id, -32602, "Tool name and arguments are invalid");
|
|
25508
25712
|
return;
|
|
25509
25713
|
}
|
|
25510
25714
|
const tool = tools.find((candidate) => candidate.name === name);
|
|
25511
25715
|
if (!tool) {
|
|
25512
|
-
sendJsonRpcError(id, -32602, `No tool named ${name}`);
|
|
25716
|
+
await sendJsonRpcError(id, -32602, `No tool named ${name}`);
|
|
25513
25717
|
return;
|
|
25514
25718
|
}
|
|
25719
|
+
const output2 = new McpOutput();
|
|
25720
|
+
let text;
|
|
25515
25721
|
try {
|
|
25516
|
-
|
|
25517
|
-
sendJsonRpcResult(id, { content: [{ type: "text", text }] });
|
|
25722
|
+
text = output2.formatResult(await tool.run(params.arguments ?? {}, output2));
|
|
25518
25723
|
} catch (error62) {
|
|
25519
|
-
|
|
25724
|
+
output2.releaseDeliveries();
|
|
25725
|
+
await sendJsonRpcResult(id, { content: [{ type: "text", text: error62 instanceof Error ? error62.message : String(error62) }], isError: true });
|
|
25726
|
+
return;
|
|
25727
|
+
}
|
|
25728
|
+
try {
|
|
25729
|
+
await sendJsonRpcResult(id, { content: [{ type: "text", text }] });
|
|
25730
|
+
output2.acknowledgeDeliveries();
|
|
25731
|
+
} catch (error62) {
|
|
25732
|
+
output2.releaseDeliveries();
|
|
25733
|
+
throw error62;
|
|
25520
25734
|
}
|
|
25521
25735
|
return;
|
|
25522
25736
|
}
|
|
25523
25737
|
default:
|
|
25524
|
-
if (id !== void 0) sendJsonRpcError(id, -32601, `Unknown method ${method}`);
|
|
25738
|
+
if (id !== void 0) await sendJsonRpcError(id, -32601, `Unknown method ${method}`);
|
|
25525
25739
|
}
|
|
25526
25740
|
};
|
|
25527
25741
|
}
|
|
25528
25742
|
function serveMcp(tools, info) {
|
|
25529
|
-
const sendJsonRpcMessage = (message) =>
|
|
25530
|
-
|
|
25743
|
+
const sendJsonRpcMessage = (message) => new Promise((resolve6, reject) => {
|
|
25744
|
+
process.stdout.write(`${JSON.stringify(message)}
|
|
25745
|
+
`, (error62) => error62 ? reject(error62) : resolve6());
|
|
25746
|
+
});
|
|
25747
|
+
const endFailedOutput = () => {
|
|
25748
|
+
process.exitCode = 1;
|
|
25749
|
+
process.stdin.destroy();
|
|
25750
|
+
};
|
|
25751
|
+
process.stdout.on("error", endFailedOutput);
|
|
25531
25752
|
const handleJsonRpcRequest = buildMcpRequestHandler(tools, info, sendJsonRpcMessage);
|
|
25532
25753
|
const lines = createInterface({ input: process.stdin });
|
|
25533
25754
|
lines.on("line", (line) => {
|
|
@@ -25536,19 +25757,19 @@ function serveMcp(tools, info) {
|
|
|
25536
25757
|
try {
|
|
25537
25758
|
request = JSON.parse(line);
|
|
25538
25759
|
} catch {
|
|
25539
|
-
sendJsonRpcMessage({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error" } });
|
|
25760
|
+
void sendJsonRpcMessage({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error" } }).catch(endFailedOutput);
|
|
25540
25761
|
return;
|
|
25541
25762
|
}
|
|
25542
|
-
void handleJsonRpcRequest(request);
|
|
25763
|
+
void handleJsonRpcRequest(request).catch(endFailedOutput);
|
|
25543
25764
|
});
|
|
25544
25765
|
}
|
|
25545
25766
|
|
|
25546
25767
|
// src/commands/tasks.ts
|
|
25547
|
-
import { readFileSync as
|
|
25768
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
25548
25769
|
|
|
25549
25770
|
// src/commands/items.ts
|
|
25550
|
-
import { readFileSync as
|
|
25551
|
-
var fetchTextArgument = (value) => value === "-" ?
|
|
25771
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
25772
|
+
var fetchTextArgument = (value) => value === "-" ? readFileSync12(0, "utf8").trim() : value;
|
|
25552
25773
|
var buildItemsPath = (projectId, key) => `/projects/${projectId}/tasks/${encodeURIComponent(key)}/items`;
|
|
25553
25774
|
function formatPlanItems(items) {
|
|
25554
25775
|
if (items.length === 0) return "No plan yet. Write one: slop task plan <KEY> -";
|
|
@@ -25575,7 +25796,7 @@ function registerItemCommands(task, registerProjectOption) {
|
|
|
25575
25796
|
registerProjectOption(task.command("plan <key> [lines...]")).description('Append items to the plan, one per argument; "-" reads one per line from stdin (list markers are fine).').action(async (key, lines, options) => {
|
|
25576
25797
|
const api = buildAuthedApiClient();
|
|
25577
25798
|
const project = await resolveProject(api, options.project);
|
|
25578
|
-
const source = lines.length === 1 && lines[0] === "-" ?
|
|
25799
|
+
const source = lines.length === 1 && lines[0] === "-" ? readFileSync12(0, "utf8") : lines.join("\n");
|
|
25579
25800
|
const titles = source.split("\n").map((line) => line.replace(/^\s*(?:[-*+]|\d+[.)])\s*(?:\[[ xX]\]\s*)?/, "").trim()).filter(Boolean);
|
|
25580
25801
|
if (titles.length === 0) throw new CliError("Nothing to add", 'give items as arguments, or "-" and one per line on stdin');
|
|
25581
25802
|
const items = await api.post(buildItemsPath(project.id, key), ApiResponses.TaskItems, { items: titles.map((title3) => ({ title: title3 })) });
|
|
@@ -25632,7 +25853,7 @@ function parseOneOf(value, allowed, what) {
|
|
|
25632
25853
|
if (allowed.includes(normalized)) return normalized;
|
|
25633
25854
|
throw new CliError(`Unknown ${what} "${value}"`, `use one of: ${allowed.join(", ")}`);
|
|
25634
25855
|
}
|
|
25635
|
-
var fetchTextArgument2 = (value) => value === "-" ?
|
|
25856
|
+
var fetchTextArgument2 = (value) => value === "-" ? readFileSync13(0, "utf8").trim() : value;
|
|
25636
25857
|
var buildTaskUrl = (baseUrl, projectId, task) => `${baseUrl}/projects/${projectId}?task=${task.key}`;
|
|
25637
25858
|
var fetchProject = (api, id) => api.get(`/projects/${id}`, ApiResponses.Project);
|
|
25638
25859
|
function resolveStatus(project, ref) {
|
|
@@ -25783,21 +26004,21 @@ ${buildTaskUrl(api.baseUrl, project.id, created)}`);
|
|
|
25783
26004
|
}
|
|
25784
26005
|
|
|
25785
26006
|
// src/platform/update-check.ts
|
|
25786
|
-
import { existsSync as
|
|
25787
|
-
import { dirname as dirname7, join as
|
|
26007
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync14, writeFileSync as writeFileSync8 } from "fs";
|
|
26008
|
+
import { dirname as dirname7, join as join14 } from "path";
|
|
25788
26009
|
var CHECK_EVERY_MS = 60 * 60 * 1e3;
|
|
25789
26010
|
var REGISTRY = "https://registry.npmjs.org/slopwaresystems/latest";
|
|
25790
|
-
var buildUpdateCachePath = () =>
|
|
26011
|
+
var buildUpdateCachePath = () => join14(dirname7(buildConfigPath()), "update-check.json");
|
|
25791
26012
|
function fetchUpdateCache() {
|
|
25792
26013
|
try {
|
|
25793
|
-
return JSON.parse(
|
|
26014
|
+
return JSON.parse(readFileSync14(buildUpdateCachePath(), "utf8"));
|
|
25794
26015
|
} catch {
|
|
25795
26016
|
return { checkedAt: 0, latest: null };
|
|
25796
26017
|
}
|
|
25797
26018
|
}
|
|
25798
26019
|
function persistUpdateCache(cache) {
|
|
25799
|
-
|
|
25800
|
-
|
|
26020
|
+
mkdirSync8(dirname7(buildUpdateCachePath()), { recursive: true });
|
|
26021
|
+
writeFileSync8(buildUpdateCachePath(), JSON.stringify(cache));
|
|
25801
26022
|
}
|
|
25802
26023
|
function isNewerVersion(a, b) {
|
|
25803
26024
|
const parseVersionParts = (version2) => version2.split(".").map((part) => Number.parseInt(part, 10));
|
|
@@ -25826,11 +26047,11 @@ function buildUpdateNotice(current) {
|
|
|
25826
26047
|
if (!latest || !isNewerVersion(latest, current)) return null;
|
|
25827
26048
|
return `Update available: slopwaresystems ${latest} (this is ${current}). Ask your person to run: npm i -g slopwaresystems && slop mcp install`;
|
|
25828
26049
|
}
|
|
25829
|
-
var isUpdateCheckEnabled = () => !
|
|
26050
|
+
var isUpdateCheckEnabled = () => !existsSync10(join14(dirname7(buildConfigPath()), "no-update-check")) && process.env.SLOP_NO_UPDATE_CHECK !== "1";
|
|
25830
26051
|
|
|
25831
26052
|
// src/commands/threads.ts
|
|
25832
|
-
import { readFileSync as
|
|
25833
|
-
var fetchTextArgument3 = (value) => value === "-" ?
|
|
26053
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
26054
|
+
var fetchTextArgument3 = (value) => value === "-" ? readFileSync15(0, "utf8").trim() : value;
|
|
25834
26055
|
var formatMessageTime = (iso) => iso.slice(0, 16).replace("T", " ");
|
|
25835
26056
|
var formatAuthor = (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}` : ""})`;
|
|
25836
26057
|
function formatThreadLine(thread) {
|
|
@@ -25941,12 +26162,32 @@ function createProjectTools(api, project, identity) {
|
|
|
25941
26162
|
api.sessionId = null;
|
|
25942
26163
|
const resolveProjectFromArgs = async (_args) => project;
|
|
25943
26164
|
const owner = { baseUrl: api.baseUrl, projectId: project.id, conversation: identity.conversation };
|
|
25944
|
-
const
|
|
26165
|
+
const claimOwnInbox = (output2, accept = () => true) => {
|
|
26166
|
+
let remaining = output2.remainingMessageChars;
|
|
26167
|
+
const delivery = claimInbox(process.cwd(), owner, (push) => {
|
|
26168
|
+
if (!accept(push)) return false;
|
|
26169
|
+
const length = formatPushBlock(push).length + 2;
|
|
26170
|
+
if (length > remaining && remaining !== RESULT_CAP) return false;
|
|
26171
|
+
remaining -= length;
|
|
26172
|
+
return true;
|
|
26173
|
+
});
|
|
26174
|
+
output2.registerDelivery({ ...delivery, text: delivery.pushes.map(formatPushBlock).join("\n\n") });
|
|
26175
|
+
return delivery.pushes.length > 0;
|
|
26176
|
+
};
|
|
25945
26177
|
const persistOwnInboxPush = (push) => persistInboxPush(process.cwd(), push, owner);
|
|
25946
26178
|
let held = null;
|
|
25947
26179
|
let stopped = false;
|
|
25948
26180
|
const recovery = new SessionRecovery(api, owner, process.cwd(), (push) => held?.deliverPush(push));
|
|
25949
26181
|
let waking = false;
|
|
26182
|
+
const wakeOwnHarness = () => {
|
|
26183
|
+
if (waking || stopped) return;
|
|
26184
|
+
waking = true;
|
|
26185
|
+
const text = `Messages are waiting for you on Slopware Tasks in project ${project.key}. Use session check with project ${project.id} to read them, then act or reply only if it changes something. Do not acknowledge.`;
|
|
26186
|
+
const wake = identity.harness === "codex" && identity.conversation ? wakeCodex(text, identity.conversation, { binary: resolveCodexBinary(), codexHome: resolveOwnHarnessHome("CODEX_HOME") }) : identity.harness === "claude" ? wakeHarness(text, void 0, identity.conversation) : Promise.resolve(false);
|
|
26187
|
+
void wake.catch(() => false).finally(() => {
|
|
26188
|
+
waking = false;
|
|
26189
|
+
});
|
|
26190
|
+
};
|
|
25950
26191
|
const resolveOwnAgentIdentity = () => identity;
|
|
25951
26192
|
const holdSession = async (projectId, taskKey) => {
|
|
25952
26193
|
if (stopped) throw new Error("The project session is stopping");
|
|
@@ -25963,16 +26204,7 @@ function createProjectTools(api, project, identity) {
|
|
|
25963
26204
|
held?.stop();
|
|
25964
26205
|
held = new HeldSession(api, projectId, taskKey, {
|
|
25965
26206
|
onPush: persistOwnInboxPush,
|
|
25966
|
-
onMessage:
|
|
25967
|
-
if (waking) return;
|
|
25968
|
-
waking = true;
|
|
25969
|
-
const text = `Messages are waiting for you on Slopware Tasks in project ${project.key}. Use session check with project ${project.id} to read them, then act or reply only if it changes something. Do not acknowledge.`;
|
|
25970
|
-
const me = resolveOwnAgentIdentity();
|
|
25971
|
-
const wake = me.harness === "codex" && me.conversation ? wakeCodex(text, me.conversation, { binary: resolveCodexBinary(), codexHome: resolveOwnHarnessHome("CODEX_HOME") }) : me.harness === "claude" ? wakeHarness(text, void 0, me.conversation) : Promise.resolve(false);
|
|
25972
|
-
void wake.catch(() => false).finally(() => {
|
|
25973
|
-
waking = false;
|
|
25974
|
-
});
|
|
25975
|
-
},
|
|
26207
|
+
onMessage: wakeOwnHarness,
|
|
25976
26208
|
claim: resolveOwnAgentIdentity,
|
|
25977
26209
|
onReconnect: () => recovery.sync()
|
|
25978
26210
|
});
|
|
@@ -25980,6 +26212,7 @@ function createProjectTools(api, project, identity) {
|
|
|
25980
26212
|
await current.start();
|
|
25981
26213
|
await current.requireSession();
|
|
25982
26214
|
await recovery.sync();
|
|
26215
|
+
if (held === current && !current.isStopped && hasPendingInboxMessages(process.cwd(), owner)) wakeOwnHarness();
|
|
25983
26216
|
};
|
|
25984
26217
|
const resolveProjectWithPresence = async (args) => {
|
|
25985
26218
|
const target = await resolveProjectFromArgs(args);
|
|
@@ -25990,29 +26223,28 @@ function createProjectTools(api, project, identity) {
|
|
|
25990
26223
|
const releaseTaskSession = async () => {
|
|
25991
26224
|
if (held?.taskKey) await holdSession(held.projectId, null);
|
|
25992
26225
|
};
|
|
25993
|
-
const waitForNextMessage = async (thread, minutes) => {
|
|
25994
|
-
|
|
25995
|
-
if (wanted.length) return { pushes: wanted, released: false };
|
|
26226
|
+
const waitForNextMessage = async (output2, thread, minutes) => {
|
|
26227
|
+
if (claimOwnInbox(output2, (push) => !thread || push.thread.id === thread)) return { received: true, released: false };
|
|
25996
26228
|
const waitingOn = held;
|
|
25997
|
-
if (!waitingOn || waitingOn.isStopped || !waitingOn.session?.reachable) return {
|
|
26229
|
+
if (!waitingOn || waitingOn.isStopped || !waitingOn.session?.reachable) return { received: false, released: true };
|
|
25998
26230
|
return new Promise((resolve6) => {
|
|
25999
26231
|
const acceptDeliveredPush = (delivered) => {
|
|
26000
26232
|
if (delivered && thread && delivered.thread.id !== thread) return false;
|
|
26001
|
-
if (delivered &&
|
|
26233
|
+
if (delivered && !claimOwnInbox(output2, (push) => push.message.id === delivered.message.id)) return false;
|
|
26002
26234
|
waitingOn.waiters.delete(acceptDeliveredPush);
|
|
26003
26235
|
clearTimeout(timer);
|
|
26004
|
-
resolve6({
|
|
26236
|
+
resolve6({ received: delivered !== null, released: delivered === null });
|
|
26005
26237
|
return true;
|
|
26006
26238
|
};
|
|
26007
26239
|
waitingOn.waiters.add(acceptDeliveredPush);
|
|
26008
26240
|
const timer = setTimeout(() => {
|
|
26009
26241
|
waitingOn.waiters.delete(acceptDeliveredPush);
|
|
26010
|
-
resolve6({
|
|
26242
|
+
resolve6({ received: false, released: false });
|
|
26011
26243
|
}, minutes * 6e4);
|
|
26012
26244
|
});
|
|
26013
26245
|
};
|
|
26014
26246
|
const formatWaitOutcome = (outcome, minutes, thread) => {
|
|
26015
|
-
if (outcome.
|
|
26247
|
+
if (outcome.received) return "";
|
|
26016
26248
|
if (outcome.released) return "This wait ended because the session was released, replaced, or made unreachable.";
|
|
26017
26249
|
return thread ? `No reply in ${thread} in ${minutes} minutes.` : `No message in ${minutes} minutes.`;
|
|
26018
26250
|
};
|
|
@@ -26021,30 +26253,29 @@ function createProjectTools(api, project, identity) {
|
|
|
26021
26253
|
name: "wait_for_message",
|
|
26022
26254
|
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.",
|
|
26023
26255
|
inputSchema: buildToolSchema({ task: buildStringSchema("Task key to go live on first (optional)"), thread: buildStringSchema("Only a message in this thread"), minutes: buildIntegerSchema("Give up after (default 300; 20 with thread)"), project: PROJECT }),
|
|
26024
|
-
run: async (args) => {
|
|
26256
|
+
run: async (args, output2) => {
|
|
26025
26257
|
const target = await resolveProjectWithPresence(args);
|
|
26026
26258
|
if (args.task) await holdSession(target.id, String(args.task));
|
|
26027
26259
|
const thread = args.thread ? String(args.thread) : null;
|
|
26028
26260
|
const minutes = typeof args.minutes === "number" ? args.minutes : thread ? 20 : 300;
|
|
26029
|
-
return formatWaitOutcome(await waitForNextMessage(thread, minutes), minutes, thread);
|
|
26261
|
+
return formatWaitOutcome(await waitForNextMessage(output2, thread, minutes), minutes, thread);
|
|
26030
26262
|
}
|
|
26031
26263
|
},
|
|
26032
26264
|
{
|
|
26033
26265
|
name: "check_messages",
|
|
26034
26266
|
description: "What is unread right now: anything the held session received since you last looked, threads about a task (if given), and general threads. Does not wait. Marks them read.",
|
|
26035
26267
|
inputSchema: buildToolSchema({ task: buildStringSchema("Task key"), project: PROJECT }),
|
|
26036
|
-
run: async (args) => {
|
|
26268
|
+
run: async (args, output2) => {
|
|
26037
26269
|
const target = await resolveProjectWithPresence(args);
|
|
26038
|
-
|
|
26039
|
-
|
|
26040
|
-
return [...queued, rest].join("\n\n");
|
|
26270
|
+
claimOwnInbox(output2);
|
|
26271
|
+
return buildUnreadDigest(api, target.id, args.task ?? null);
|
|
26041
26272
|
}
|
|
26042
26273
|
},
|
|
26043
26274
|
{
|
|
26044
26275
|
name: "reply",
|
|
26045
26276
|
description: "Reply in a thread. In a thread about a task, it reaches whoever is live on that task now, or waits for them.",
|
|
26046
26277
|
inputSchema: buildToolSchema({ thread: buildStringSchema("Thread id"), body: buildStringSchema("The message (Markdown)"), wait: buildIntegerSchema("Claude Code only, rarely: then wait this many minutes for a reply in the thread"), project: PROJECT }, ["thread", "body"]),
|
|
26047
|
-
run: async (args) => {
|
|
26278
|
+
run: async (args, output2) => {
|
|
26048
26279
|
const target = await resolveProjectWithPresence(args);
|
|
26049
26280
|
const thread = String(args.thread);
|
|
26050
26281
|
const result = await api.post(`/projects/${target.id}/threads/${thread}/messages`, ApiResponses.CreatedThreadMessage, { body: String(args.body) });
|
|
@@ -26052,14 +26283,14 @@ function createProjectTools(api, project, identity) {
|
|
|
26052
26283
|
if (typeof args.wait !== "number" || args.wait <= 0) return replied;
|
|
26053
26284
|
return `${replied}
|
|
26054
26285
|
|
|
26055
|
-
${formatWaitOutcome(await waitForNextMessage(thread, args.wait), args.wait, thread)}`;
|
|
26286
|
+
${formatWaitOutcome(await waitForNextMessage(output2, thread, args.wait), args.wait, thread)}`;
|
|
26056
26287
|
}
|
|
26057
26288
|
},
|
|
26058
26289
|
{
|
|
26059
26290
|
name: "post",
|
|
26060
26291
|
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.",
|
|
26061
26292
|
inputSchema: buildToolSchema({ subject: buildStringSchema("Subject (default: the first line of the body)"), body: buildStringSchema("First message (Markdown)"), task: buildStringSchema("Task key, to make it about that task"), wait: buildIntegerSchema("Claude Code only, rarely: then wait this many minutes for a reply in the thread"), project: PROJECT }, ["body"]),
|
|
26062
|
-
run: async (args) => {
|
|
26293
|
+
run: async (args, output2) => {
|
|
26063
26294
|
const target = await resolveProjectWithPresence(args);
|
|
26064
26295
|
const result = await api.post(`/projects/${target.id}/threads`, ApiResponses.CreatedThread, { subject: args.subject ? String(args.subject) : void 0, body: String(args.body), task: args.task, to: args.to });
|
|
26065
26296
|
const names = result.thread.to.map((recipient) => recipient.agent).join(", ");
|
|
@@ -26067,7 +26298,7 @@ ${formatWaitOutcome(await waitForNextMessage(thread, args.wait), args.wait, thre
|
|
|
26067
26298
|
if (typeof args.wait !== "number" || args.wait <= 0) return started;
|
|
26068
26299
|
return `${started}
|
|
26069
26300
|
|
|
26070
|
-
${formatWaitOutcome(await waitForNextMessage(result.thread.id, args.wait), args.wait, result.thread.id)}`;
|
|
26301
|
+
${formatWaitOutcome(await waitForNextMessage(output2, result.thread.id, args.wait), args.wait, result.thread.id)}`;
|
|
26071
26302
|
}
|
|
26072
26303
|
},
|
|
26073
26304
|
{
|
|
@@ -26343,44 +26574,41 @@ ${task.notes}` : ""].filter(Boolean).join("\n");
|
|
|
26343
26574
|
recovery.stop();
|
|
26344
26575
|
held?.stop();
|
|
26345
26576
|
},
|
|
26346
|
-
tools: tools.map((tool) => ({ ...tool, run: async (args) => {
|
|
26347
|
-
const result =
|
|
26348
|
-
|
|
26349
|
-
return
|
|
26350
|
-
|
|
26351
|
-
--- Messages arrived while you worked (answer what is still open, once; do not acknowledge) ---
|
|
26352
|
-
${arrived.map(formatPushBlock).join("\n\n")}` : result;
|
|
26577
|
+
tools: tools.map((tool) => ({ ...tool, run: async (args, output2) => {
|
|
26578
|
+
const result = await tool.run(args, output2);
|
|
26579
|
+
if (tool.name !== "check_messages" && tool.name !== "wait_for_message") claimOwnInbox(output2);
|
|
26580
|
+
return result;
|
|
26353
26581
|
} }))
|
|
26354
26582
|
};
|
|
26355
26583
|
}
|
|
26356
26584
|
function fetchJsonFile(path2) {
|
|
26357
26585
|
try {
|
|
26358
|
-
return JSON.parse(
|
|
26586
|
+
return JSON.parse(readFileSync16(path2, "utf8"));
|
|
26359
26587
|
} catch {
|
|
26360
26588
|
return {};
|
|
26361
26589
|
}
|
|
26362
26590
|
}
|
|
26363
26591
|
function installIntoClaudeHome(dir) {
|
|
26364
26592
|
const done = [];
|
|
26365
|
-
const primary = dir ===
|
|
26593
|
+
const primary = dir === join15(homedir4(), ".claude");
|
|
26366
26594
|
const env = { ...process.env };
|
|
26367
26595
|
if (primary) delete env.CLAUDE_CONFIG_DIR;
|
|
26368
26596
|
else env.CLAUDE_CONFIG_DIR = dir;
|
|
26369
26597
|
const where = primary ? "" : ` for ${dir}`;
|
|
26370
26598
|
try {
|
|
26371
|
-
|
|
26599
|
+
execFileSync6("claude", ["mcp", "add", "--scope", "user", "slop", "--", "slop", "mcp"], { stdio: ["ignore", "ignore", "pipe"], env });
|
|
26372
26600
|
done.push(`Claude Code: MCP server "slop" registered (user scope${where})`);
|
|
26373
26601
|
} catch (error62) {
|
|
26374
26602
|
const said = String(error62.stderr ?? "") + String(error62.stdout ?? "");
|
|
26375
26603
|
if (/already exists/i.test(said)) done.push(`Claude Code: MCP server "slop" already registered (user scope${where})`);
|
|
26376
26604
|
else done.push(`Claude Code: run \`${primary ? "" : `CLAUDE_CONFIG_DIR=${dir} `}claude mcp add --scope user slop -- slop mcp\` (${said.trim() || "the claude command was not found here"})`);
|
|
26377
26605
|
}
|
|
26378
|
-
const path2 =
|
|
26379
|
-
|
|
26606
|
+
const path2 = join15(dir, "settings.json");
|
|
26607
|
+
mkdirSync9(dir, { recursive: true });
|
|
26380
26608
|
const settings = fetchJsonFile(path2);
|
|
26381
26609
|
settings.hooks = mergeHooks(settings.hooks ?? {}, "claude");
|
|
26382
26610
|
settings.env = { ...settings.env ?? {}, MCP_TOOL_TIMEOUT: "21600000" };
|
|
26383
|
-
|
|
26611
|
+
writeFileSync9(path2, `${JSON.stringify(settings, null, 2)}
|
|
26384
26612
|
`);
|
|
26385
26613
|
done.push(`Claude Code: SessionStart hook and MCP_TOOL_TIMEOUT written to ${path2}; other slop hooks removed (its session socket wakes it)`);
|
|
26386
26614
|
return done;
|
|
@@ -26403,31 +26631,31 @@ function mergeHooks(hooks, harness) {
|
|
|
26403
26631
|
}
|
|
26404
26632
|
function installIntoCodexHome(dir) {
|
|
26405
26633
|
const done = [];
|
|
26406
|
-
|
|
26407
|
-
const configTomlPath =
|
|
26408
|
-
const current =
|
|
26634
|
+
mkdirSync9(dir, { recursive: true });
|
|
26635
|
+
const configTomlPath = join15(dir, "config.toml");
|
|
26636
|
+
const current = existsSync11(configTomlPath) ? readFileSync16(configTomlPath, "utf8") : "";
|
|
26409
26637
|
if (/^\[mcp_servers\.slop\]/m.test(current)) {
|
|
26410
|
-
if (/tool_timeout_sec = 1800\b/.test(current))
|
|
26638
|
+
if (/tool_timeout_sec = 1800\b/.test(current)) writeFileSync9(configTomlPath, current.replace(/tool_timeout_sec = 1800\b/, "tool_timeout_sec = 21600"));
|
|
26411
26639
|
done.push(`Codex: MCP server already in ${configTomlPath}`);
|
|
26412
26640
|
} else {
|
|
26413
|
-
|
|
26641
|
+
writeFileSync9(configTomlPath, `${current.trimEnd()}${current.trim() ? "\n\n" : ""}[mcp_servers.slop]
|
|
26414
26642
|
command = "slop"
|
|
26415
26643
|
args = ["mcp"]
|
|
26416
26644
|
tool_timeout_sec = 21600
|
|
26417
26645
|
`);
|
|
26418
26646
|
done.push(`Codex: MCP server "slop" added to ${configTomlPath}`);
|
|
26419
26647
|
}
|
|
26420
|
-
const hooksPath =
|
|
26648
|
+
const hooksPath = join15(dir, "hooks.json");
|
|
26421
26649
|
const file2 = fetchJsonFile(hooksPath);
|
|
26422
26650
|
file2.hooks = mergeHooks(file2.hooks ?? {}, "codex");
|
|
26423
|
-
|
|
26651
|
+
writeFileSync9(hooksPath, `${JSON.stringify(file2, null, 2)}
|
|
26424
26652
|
`);
|
|
26425
26653
|
done.push(`Codex: SessionStart hook written to ${hooksPath}; other slop hooks removed (Codex is woken with codex queue; no hook holds a turn open)`);
|
|
26426
26654
|
return done;
|
|
26427
26655
|
}
|
|
26428
26656
|
async function trustCodexHooks(codexHome) {
|
|
26429
26657
|
const server = spawn3("codex", ["app-server", "-c", "mcp_servers={}"], { stdio: ["pipe", "pipe", "ignore"], env: { ...process.env, CODEX_HOME: codexHome } });
|
|
26430
|
-
const where = codexHome ===
|
|
26658
|
+
const where = codexHome === join15(homedir4(), ".codex") ? "" : ` under ${codexHome}`;
|
|
26431
26659
|
let buffer = "";
|
|
26432
26660
|
const pending = /* @__PURE__ */ new Map();
|
|
26433
26661
|
server.stdout.on("data", (chunk) => {
|
|
@@ -26462,7 +26690,7 @@ async function trustCodexHooks(codexHome) {
|
|
|
26462
26690
|
const sendNotification = (method, params) => server.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}
|
|
26463
26691
|
`);
|
|
26464
26692
|
try {
|
|
26465
|
-
await callCodexMethod("initialize", { clientInfo: { name: "slop", version: "0.4.
|
|
26693
|
+
await callCodexMethod("initialize", { clientInfo: { name: "slop", version: "0.4.10" }, capabilities: { experimentalApi: true } });
|
|
26466
26694
|
sendNotification("initialized", {});
|
|
26467
26695
|
const listed = await callCodexMethod("hooks/list", { cwds: [process.cwd()] });
|
|
26468
26696
|
const ours = listed.data.flatMap((entry) => entry.hooks).filter((hook) => hook.command?.startsWith("slop hook "));
|
|
@@ -26493,7 +26721,7 @@ async function parseHookInput(harness, remember = true) {
|
|
|
26493
26721
|
conversation = parsed.session_id ?? null;
|
|
26494
26722
|
} catch {
|
|
26495
26723
|
}
|
|
26496
|
-
const ownThread = harness !== "codex" || !conversation || isMainChatThread(fetchCodexHomes().map((home) =>
|
|
26724
|
+
const ownThread = harness !== "codex" || !conversation || isMainChatThread(fetchCodexHomes().map((home) => join15(home, "sessions")), conversation);
|
|
26497
26725
|
if (remember && conversation && ownThread && !isNonInteractiveHarness()) persistRememberedConversation(cwd, { id: conversation, harness: harness ?? "other" });
|
|
26498
26726
|
return { cwd, conversation };
|
|
26499
26727
|
}
|
|
@@ -26559,12 +26787,12 @@ function buildTools() {
|
|
|
26559
26787
|
}
|
|
26560
26788
|
return context;
|
|
26561
26789
|
};
|
|
26562
|
-
const runHandlerByName = async (name, args) => {
|
|
26790
|
+
const runHandlerByName = async (name, args, output2) => {
|
|
26563
26791
|
if (name === "projects") return fetchProjectsOverview(api);
|
|
26564
26792
|
const project = await resolveProject(api, args.project);
|
|
26565
26793
|
const handler = requireProjectTools(project).tools.find((candidate) => candidate.name === name);
|
|
26566
26794
|
if (!handler) throw new Error("No such action");
|
|
26567
|
-
return handler.run(args);
|
|
26795
|
+
return handler.run(args, output2);
|
|
26568
26796
|
};
|
|
26569
26797
|
void (async () => {
|
|
26570
26798
|
for (let waited = 0; waited < 1e4 && !fetchKnownConversation(process.cwd()) && !stopped; waited += 250) await new Promise((resolve6) => setTimeout(resolve6, 250));
|
|
@@ -26572,20 +26800,22 @@ function buildTools() {
|
|
|
26572
26800
|
await requireProjectTools(await resolveProject(api)).start();
|
|
26573
26801
|
})().catch(() => void 0);
|
|
26574
26802
|
const endSessionAndExit = () => {
|
|
26803
|
+
if (stopped) return;
|
|
26575
26804
|
stopped = true;
|
|
26576
26805
|
for (const context of projects.values()) context.stop();
|
|
26577
|
-
setTimeout(() => process.exit(0), 300).unref();
|
|
26806
|
+
setTimeout(() => process.exit(process.exitCode ?? 0), 300).unref();
|
|
26578
26807
|
};
|
|
26579
26808
|
process.stdin.once("end", endSessionAndExit);
|
|
26809
|
+
process.stdin.once("close", endSessionAndExit);
|
|
26580
26810
|
for (const signal of ["SIGTERM", "SIGHUP", "SIGINT"]) process.once(signal, endSessionAndExit);
|
|
26581
26811
|
const buildGroupedTool = (name, description2, actions, properties) => ({
|
|
26582
26812
|
name,
|
|
26583
26813
|
description: description2,
|
|
26584
26814
|
inputSchema: buildToolSchema({ action: { type: "string", enum: Object.keys(actions) }, ...properties, project: PROJECT }, ["action"]),
|
|
26585
|
-
run: (args) => {
|
|
26815
|
+
run: (args, output2) => {
|
|
26586
26816
|
const target = actions[String(args.action)];
|
|
26587
26817
|
if (!target) throw new Error(`action must be one of: ${Object.keys(actions).join(", ")}`);
|
|
26588
|
-
return runHandlerByName(target, args);
|
|
26818
|
+
return runHandlerByName(target, args, output2);
|
|
26589
26819
|
}
|
|
26590
26820
|
});
|
|
26591
26821
|
return [
|
|
@@ -26625,8 +26855,8 @@ function buildTools() {
|
|
|
26625
26855
|
function attachUpdateNoticeToTools(list, version2) {
|
|
26626
26856
|
return list.map((tool) => ({
|
|
26627
26857
|
...tool,
|
|
26628
|
-
run: async (args) => {
|
|
26629
|
-
const result =
|
|
26858
|
+
run: async (args, output2) => {
|
|
26859
|
+
const result = await tool.run(args, output2);
|
|
26630
26860
|
const parts = [result];
|
|
26631
26861
|
const notice = isUpdateCheckEnabled() ? buildUpdateNotice(version2) : null;
|
|
26632
26862
|
if (notice) parts.push(notice);
|
|
@@ -26642,7 +26872,7 @@ function registerMcpCommands(program3, version2) {
|
|
|
26642
26872
|
const lines = [...fetchClaudeHomes().flatMap((home) => installIntoClaudeHome(home)), ...homes.flatMap((home) => installIntoCodexHome(home))];
|
|
26643
26873
|
let codexPresent = true;
|
|
26644
26874
|
try {
|
|
26645
|
-
|
|
26875
|
+
execFileSync6("codex", ["--version"], { stdio: "ignore" });
|
|
26646
26876
|
} catch {
|
|
26647
26877
|
codexPresent = false;
|
|
26648
26878
|
}
|
|
@@ -26742,13 +26972,13 @@ ${written.map((path2) => ` ${path2}`).join("\n")}`);
|
|
|
26742
26972
|
}
|
|
26743
26973
|
|
|
26744
26974
|
// src/index.ts
|
|
26745
|
-
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.
|
|
26975
|
+
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.10").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)");
|
|
26746
26976
|
registerAuthCommands(program2);
|
|
26747
26977
|
registerProjectCommands(program2);
|
|
26748
26978
|
var taskCommand = registerTaskCommands(program2);
|
|
26749
26979
|
registerSessionCommands(program2);
|
|
26750
26980
|
registerThreadCommands(program2, taskCommand);
|
|
26751
|
-
registerMcpCommands(program2, "0.4.
|
|
26981
|
+
registerMcpCommands(program2, "0.4.10");
|
|
26752
26982
|
registerGuideCommand(program2);
|
|
26753
26983
|
registerSkillCommands(program2);
|
|
26754
26984
|
program2.parseAsync().catch(exitWithError);
|