slopwaresystems 0.4.13 → 0.4.15
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 +93 -17
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -26460,7 +26460,7 @@ var TaskEventRecord = external_exports.object({
|
|
|
26460
26460
|
});
|
|
26461
26461
|
|
|
26462
26462
|
// ../contract/src/work/sessions.ts
|
|
26463
|
-
var AgentHarness = external_exports.enum(["claude", "codex", "other"]);
|
|
26463
|
+
var AgentHarness = external_exports.enum(["claude", "codex", "cursor", "other"]);
|
|
26464
26464
|
var WORKERS_PER_SESSION = 8;
|
|
26465
26465
|
var WORKER_LABEL_MAX = 80;
|
|
26466
26466
|
var DOING_MAX = 120;
|
|
@@ -27332,6 +27332,10 @@ function fetchCodexHomes(root = homedir3(), inUse = findHomesInUse) {
|
|
|
27332
27332
|
const named = process.env.CODEX_HOME ? [process.env.CODEX_HOME] : [];
|
|
27333
27333
|
return deduplicateStrings([join5(root, ".codex"), ...named, ...inUse("CODEX_HOME"), ...findDirectoriesNamed(root, ".codex").filter((dir) => hasAnyEntry(dir, ["config.toml", "auth.json", "sessions"]))]);
|
|
27334
27334
|
}
|
|
27335
|
+
function fetchCursorHomes(root = homedir3(), _inUse = findHomesInUse) {
|
|
27336
|
+
if (process.env.SLOP_CURSOR_HOME) return [process.env.SLOP_CURSOR_HOME];
|
|
27337
|
+
return deduplicateStrings([join5(root, ".cursor"), ...findDirectoriesNamed(root, ".cursor").filter((dir) => hasAnyEntry(dir, ["cli-config.json", "hooks.json", "mcp.json", "projects"]))]);
|
|
27338
|
+
}
|
|
27335
27339
|
function resolveOwnHarnessHome(variable) {
|
|
27336
27340
|
if (process.platform === "win32") return null;
|
|
27337
27341
|
try {
|
|
@@ -27495,6 +27499,7 @@ function wakeCodexOverWs(text, threadId, endpoint) {
|
|
|
27495
27499
|
}
|
|
27496
27500
|
|
|
27497
27501
|
// src/platform/harness.ts
|
|
27502
|
+
var AgentHarnessValues = AgentHarness.options;
|
|
27498
27503
|
var HEAD_BYTES = 64 * 1024;
|
|
27499
27504
|
var parent = null;
|
|
27500
27505
|
function fetchParentCommand() {
|
|
@@ -27508,9 +27513,11 @@ function fetchParentCommand() {
|
|
|
27508
27513
|
}
|
|
27509
27514
|
function parseHarnessFromCommand(command) {
|
|
27510
27515
|
const first = basename2(command.trim().split(/\s+/)[0] ?? "");
|
|
27516
|
+
if (/^cursor-agent(\b|$)/.test(first)) return "cursor";
|
|
27511
27517
|
if (/^claude(\b|$)/.test(first)) return "claude";
|
|
27512
27518
|
if (/^codex(\b|$)/.test(first)) return "codex";
|
|
27513
27519
|
if (/(^|\/)codex(\S*)?\s/.test(command)) return "codex";
|
|
27520
|
+
if (/(^|\/)cursor-agent(\s|$)/.test(command)) return "cursor";
|
|
27514
27521
|
return null;
|
|
27515
27522
|
}
|
|
27516
27523
|
function isNonInteractiveCommand(command) {
|
|
@@ -27643,7 +27650,7 @@ function resolveCodexBinary() {
|
|
|
27643
27650
|
}
|
|
27644
27651
|
function fetchHarnessConversation(cwd) {
|
|
27645
27652
|
const explicit = process.env.SLOP_CONVERSATION_ID?.trim();
|
|
27646
|
-
if (explicit) return { id: explicit, harness: process.env.SLOP_HARNESS
|
|
27653
|
+
if (explicit) return { id: explicit, harness: AgentHarnessValues.includes(process.env.SLOP_HARNESS) ? process.env.SLOP_HARNESS : "other" };
|
|
27647
27654
|
if (process.env.CODEX_THREAD_ID?.trim()) return { id: process.env.CODEX_THREAD_ID.trim(), harness: "codex" };
|
|
27648
27655
|
if (process.env.CLAUDE_CODE_SESSION_ID?.trim()) return { id: process.env.CLAUDE_CODE_SESSION_ID.trim(), harness: "claude" };
|
|
27649
27656
|
const harness = parseHarnessFromCommand(fetchParentCommand());
|
|
@@ -28800,6 +28807,7 @@ var HeldSession = class {
|
|
|
28800
28807
|
import { closeSync as closeSync2, existsSync as existsSync10, openSync as openSync2, readdirSync as readdirSync7, readFileSync as readFileSync12, readSync as readSync2, statSync as statSync4 } from "fs";
|
|
28801
28808
|
import { basename as basename4, dirname as dirname7, join as join14 } from "path";
|
|
28802
28809
|
var WORKER_FRESH_MS = 12e4;
|
|
28810
|
+
var WORKER_GONE_MS = 3e5;
|
|
28803
28811
|
var READ_CAP = 256 * 1024;
|
|
28804
28812
|
var CARRY_CAP = 64 * 1024;
|
|
28805
28813
|
var CHILD_SCAN_CAP = 40;
|
|
@@ -28937,6 +28945,21 @@ function findClaudeTranscriptPath(homes, conversation) {
|
|
|
28937
28945
|
}
|
|
28938
28946
|
return null;
|
|
28939
28947
|
}
|
|
28948
|
+
function findCursorTranscriptPath(homes, conversation) {
|
|
28949
|
+
for (const home of homes) {
|
|
28950
|
+
let projects;
|
|
28951
|
+
try {
|
|
28952
|
+
projects = readdirSync7(join14(home, "projects"));
|
|
28953
|
+
} catch {
|
|
28954
|
+
continue;
|
|
28955
|
+
}
|
|
28956
|
+
for (const project of projects) {
|
|
28957
|
+
const path2 = join14(home, "projects", project, "agent-transcripts", conversation, `${conversation}.jsonl`);
|
|
28958
|
+
if (existsSync10(path2)) return path2;
|
|
28959
|
+
}
|
|
28960
|
+
}
|
|
28961
|
+
return null;
|
|
28962
|
+
}
|
|
28940
28963
|
function parseClaudeWorkerLabel(text) {
|
|
28941
28964
|
let meta3;
|
|
28942
28965
|
try {
|
|
@@ -28983,6 +29006,12 @@ function parseWhereFromJson(line) {
|
|
|
28983
29006
|
}
|
|
28984
29007
|
}
|
|
28985
29008
|
var isCodexHelperRollout = (head) => /"other"\s*:\s*"guardian"|"thread_source"\s*:\s*"[^"]*guardian/.test(head);
|
|
29009
|
+
function parseCodexWorkerLabel(head) {
|
|
29010
|
+
const nickname = /"agent_nickname"\s*:\s*"([^"]+)"/.exec(head)?.[1];
|
|
29011
|
+
const role = /"agent_role"\s*:\s*"([^"]+)"/.exec(head)?.[1];
|
|
29012
|
+
const parts = [nickname, role].filter((part) => Boolean(part));
|
|
29013
|
+
return parts.length ? truncate(parts.join(" \u2014 "), WORKER_LABEL_MAX) : null;
|
|
29014
|
+
}
|
|
28986
29015
|
function findCodexChildRollouts(roots, parentThreadId, changedAfter) {
|
|
28987
29016
|
const rollouts = [];
|
|
28988
29017
|
const findRolloutsInDirectory = (directory, depth) => {
|
|
@@ -29012,7 +29041,7 @@ function findCodexChildRollouts(roots, parentThreadId, changedAfter) {
|
|
|
29012
29041
|
if (!head.includes('"session_meta"') || isCodexHelperRollout(head)) continue;
|
|
29013
29042
|
if (!new RegExp(`"parent_thread_id"\\s*:\\s*"${parentThreadId}"`).test(head)) continue;
|
|
29014
29043
|
const where = parseWhereFromJson(head);
|
|
29015
|
-
found.push({ ...rollout, label: "subagent", directory: where.directory, branch: where.branch });
|
|
29044
|
+
found.push({ ...rollout, label: parseCodexWorkerLabel(head) ?? "subagent", directory: where.directory, branch: where.branch });
|
|
29016
29045
|
}
|
|
29017
29046
|
return found;
|
|
29018
29047
|
}
|
|
@@ -29081,7 +29110,7 @@ var HarnessActivity = class {
|
|
|
29081
29110
|
}
|
|
29082
29111
|
/** Which files to read: this chat's own, and one per worker still working. */
|
|
29083
29112
|
discover(now) {
|
|
29084
|
-
const found = this.findWorkerFiles(now -
|
|
29113
|
+
const found = this.findWorkerFiles(now - WORKER_GONE_MS);
|
|
29085
29114
|
for (const file2 of found) {
|
|
29086
29115
|
const known = this.workers.get(file2.ref);
|
|
29087
29116
|
if (known) Object.assign(known, { label: file2.label, directory: file2.directory, branch: file2.branch, changedAt: file2.changedAt });
|
|
@@ -29096,6 +29125,11 @@ var HarnessActivity = class {
|
|
|
29096
29125
|
this.own ??= transcript ? new FileTail(transcript, "end") : null;
|
|
29097
29126
|
return transcript ? findClaudeWorkerFiles(transcript, this.conversation.id, changedAfter) : [];
|
|
29098
29127
|
}
|
|
29128
|
+
if (this.conversation.harness === "cursor") {
|
|
29129
|
+
const transcript = findCursorTranscriptPath(fetchCursorHomes(), this.conversation.id);
|
|
29130
|
+
this.own ??= transcript ? new FileTail(transcript, "end") : null;
|
|
29131
|
+
return [];
|
|
29132
|
+
}
|
|
29099
29133
|
if (this.conversation.harness === "codex") {
|
|
29100
29134
|
const roots = fetchCodexHomes().map((home) => join14(home, "sessions"));
|
|
29101
29135
|
const rollout = fetchCodexRolloutPath(roots, this.conversation.id);
|
|
@@ -29116,7 +29150,8 @@ var HarnessActivity = class {
|
|
|
29116
29150
|
for (const worker of this.workers.values()) worker.doing = findNewestCall(worker.tail) ?? worker.doing;
|
|
29117
29151
|
}
|
|
29118
29152
|
build() {
|
|
29119
|
-
const
|
|
29153
|
+
const now = Date.now();
|
|
29154
|
+
const workers = [...this.workers.values()].sort((a, b) => b.changedAt - a.changedAt).slice(0, WORKERS_PER_SESSION).map((worker) => ({ ref: worker.ref, label: worker.label, directory: worker.directory, branch: worker.branch, doing: this.watched && now - worker.changedAt < WORKER_FRESH_MS ? worker.doing : null, since: worker.since }));
|
|
29120
29155
|
return { doing: this.watched ? this.doing : null, workers };
|
|
29121
29156
|
}
|
|
29122
29157
|
};
|
|
@@ -30161,6 +30196,42 @@ tool_timeout_sec = 21600
|
|
|
30161
30196
|
done.push(`Codex: SessionStart hook written to ${hooksPath}; other slop hooks removed (Codex is woken with codex queue; no hook holds a turn open)`);
|
|
30162
30197
|
return done;
|
|
30163
30198
|
}
|
|
30199
|
+
function installIntoCursorHome(dir) {
|
|
30200
|
+
const done = [];
|
|
30201
|
+
mkdirSync9(dir, { recursive: true });
|
|
30202
|
+
const mcpPath = join16(dir, "mcp.json");
|
|
30203
|
+
const mcp = fetchJsonFile(mcpPath);
|
|
30204
|
+
const servers = mcp.mcpServers ?? {};
|
|
30205
|
+
const had = "slop" in servers;
|
|
30206
|
+
servers.slop = { command: "slop", args: ["mcp"] };
|
|
30207
|
+
mcp.mcpServers = servers;
|
|
30208
|
+
writeFileSync8(mcpPath, `${JSON.stringify(mcp, null, 2)}
|
|
30209
|
+
`);
|
|
30210
|
+
done.push(`Cursor: MCP server "slop" ${had ? "already in" : "added to"} ${mcpPath}`);
|
|
30211
|
+
const hooksPath = join16(dir, "hooks.json");
|
|
30212
|
+
const file2 = fetchJsonFile(hooksPath);
|
|
30213
|
+
file2.version = typeof file2.version === "number" ? file2.version : 1;
|
|
30214
|
+
file2.hooks = mergeCursorHooks(file2.hooks ?? {});
|
|
30215
|
+
writeFileSync8(hooksPath, `${JSON.stringify(file2, null, 2)}
|
|
30216
|
+
`);
|
|
30217
|
+
done.push(`Cursor: sessionStart hook written to ${hooksPath}; nothing wakes an idle chat, so messages wait for its next tool call`);
|
|
30218
|
+
try {
|
|
30219
|
+
execFileSync6("cursor-agent", ["mcp", "enable", "slop"], { stdio: "ignore" });
|
|
30220
|
+
done.push("Cursor: slop is on its approved MCP list");
|
|
30221
|
+
} catch {
|
|
30222
|
+
done.push("Cursor: approve the server once with `cursor-agent mcp enable slop`, or answer its prompt");
|
|
30223
|
+
}
|
|
30224
|
+
return done;
|
|
30225
|
+
}
|
|
30226
|
+
function mergeCursorHooks(hooks) {
|
|
30227
|
+
const isOwnHook = (hook) => typeof hook.command === "string" && /(^|\s)slop hook /.test(hook.command);
|
|
30228
|
+
for (const event of Object.keys(hooks)) {
|
|
30229
|
+
hooks[event] = (hooks[event] ?? []).filter((hook) => !isOwnHook(hook));
|
|
30230
|
+
if (hooks[event].length === 0) delete hooks[event];
|
|
30231
|
+
}
|
|
30232
|
+
hooks.sessionStart = [...hooks.sessionStart ?? [], { command: "slop hook start --harness cursor", timeout: 5 }];
|
|
30233
|
+
return hooks;
|
|
30234
|
+
}
|
|
30164
30235
|
async function trustCodexHooks(codexHome) {
|
|
30165
30236
|
const server = spawn3("codex", ["app-server", "-c", "mcp_servers={}"], { stdio: ["pipe", "pipe", "ignore"], env: { ...process.env, CODEX_HOME: codexHome } });
|
|
30166
30237
|
const where = codexHome === join16(homedir4(), ".codex") ? "" : ` under ${codexHome}`;
|
|
@@ -30198,7 +30269,7 @@ async function trustCodexHooks(codexHome) {
|
|
|
30198
30269
|
const sendNotification = (method, params) => server.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}
|
|
30199
30270
|
`);
|
|
30200
30271
|
try {
|
|
30201
|
-
await callCodexMethod("initialize", { clientInfo: { name: "slop", version: "0.4.
|
|
30272
|
+
await callCodexMethod("initialize", { clientInfo: { name: "slop", version: "0.4.15" }, capabilities: { experimentalApi: true } });
|
|
30202
30273
|
sendNotification("initialized", {});
|
|
30203
30274
|
const listed = await callCodexMethod("hooks/list", { cwds: [process.cwd()] });
|
|
30204
30275
|
const ours = listed.data.flatMap((entry) => entry.hooks).filter((hook) => hook.command?.startsWith("slop hook "));
|
|
@@ -30225,7 +30296,7 @@ async function parseHookInput(harness, remember = true) {
|
|
|
30225
30296
|
let conversation = null;
|
|
30226
30297
|
try {
|
|
30227
30298
|
const parsed = JSON.parse(input2);
|
|
30228
|
-
cwd = parsed.cwd ?? cwd;
|
|
30299
|
+
cwd = parsed.cwd ?? parsed.workspace_roots?.[0] ?? cwd;
|
|
30229
30300
|
conversation = parsed.session_id ?? null;
|
|
30230
30301
|
} catch {
|
|
30231
30302
|
}
|
|
@@ -30243,8 +30314,9 @@ async function runStartHook(harness) {
|
|
|
30243
30314
|
if (isNonInteractiveHarness()) return;
|
|
30244
30315
|
const { conversation } = await parseHookInput(harness);
|
|
30245
30316
|
if (!conversation) return;
|
|
30246
|
-
const
|
|
30247
|
-
|
|
30317
|
+
const carried = harness === "codex" ? " (no wait tool on Codex; a reply becomes your next turn)" : harness === "cursor" ? " (nothing wakes an idle chat here: a message reaches you with your next slop tool call)" : "";
|
|
30318
|
+
const text = `On Slopware Tasks you are ${generateAgentName(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${carried}.`;
|
|
30319
|
+
process.stdout.write(`${JSON.stringify({ hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: text }, additional_context: text })}
|
|
30248
30320
|
`);
|
|
30249
30321
|
}
|
|
30250
30322
|
async function runStopHook(harness) {
|
|
@@ -30377,7 +30449,7 @@ function registerMcpCommands(program3, version2) {
|
|
|
30377
30449
|
mcp.command("serve", { isDefault: true }).description("Serve MCP over stdio (what the harness runs).").action(() => serveMcp(attachUpdateNoticeToTools(buildTools(), version2), { name: "slop", version: version2 }));
|
|
30378
30450
|
mcp.command("install").description("Register the server and the two hooks with Claude Code and Codex on this machine.").action(async () => {
|
|
30379
30451
|
const homes = fetchCodexHomes();
|
|
30380
|
-
const lines = [...fetchClaudeHomes().flatMap((home) => installIntoClaudeHome(home)), ...homes.flatMap((home) => installIntoCodexHome(home))];
|
|
30452
|
+
const lines = [...fetchClaudeHomes().flatMap((home) => installIntoClaudeHome(home)), ...homes.flatMap((home) => installIntoCodexHome(home)), ...fetchCursorHomes().flatMap((home) => installIntoCursorHome(home))];
|
|
30381
30453
|
let codexPresent = true;
|
|
30382
30454
|
try {
|
|
30383
30455
|
execFileSync6("codex", ["--version"], { stdio: "ignore" });
|
|
@@ -30388,11 +30460,15 @@ function registerMcpCommands(program3, version2) {
|
|
|
30388
30460
|
emitOutput({ done: lines }, () => lines.join("\n"));
|
|
30389
30461
|
});
|
|
30390
30462
|
const hook = program3.command("hook").description("Harness hooks (installed by slop mcp install).");
|
|
30391
|
-
const parseHarnessFromOption = (options) =>
|
|
30392
|
-
|
|
30393
|
-
|
|
30394
|
-
|
|
30395
|
-
|
|
30463
|
+
const parseHarnessFromOption = (options) => {
|
|
30464
|
+
if (!options.harness) return null;
|
|
30465
|
+
const known = AgentHarness.safeParse(options.harness);
|
|
30466
|
+
return known.success ? known.data : "other";
|
|
30467
|
+
};
|
|
30468
|
+
hook.command("start").description("SessionStart hook (Claude Code, Codex, Cursor): 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, codex or cursor").action((options) => runStartHook(parseHarnessFromOption(options)));
|
|
30469
|
+
hook.command("tool").description("No longer installed; kept so an older install's hook exits cleanly.").option("--harness <name>", "claude, codex or cursor").action((options) => runToolHook(parseHarnessFromOption(options)));
|
|
30470
|
+
hook.command("prompt").description("No longer installed; kept so an older install's hook exits cleanly.").option("--harness <name>", "claude, codex or cursor").action((options) => runPromptHook(parseHarnessFromOption(options)));
|
|
30471
|
+
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, codex or cursor").action((options) => runStopHook(parseHarnessFromOption(options)));
|
|
30396
30472
|
}
|
|
30397
30473
|
|
|
30398
30474
|
// src/commands/sessions.ts
|
|
@@ -30480,13 +30556,13 @@ ${written.map((path2) => ` ${path2}`).join("\n")}`);
|
|
|
30480
30556
|
}
|
|
30481
30557
|
|
|
30482
30558
|
// src/index.ts
|
|
30483
|
-
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.
|
|
30559
|
+
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.15").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)");
|
|
30484
30560
|
registerAuthCommands(program2);
|
|
30485
30561
|
registerProjectCommands(program2);
|
|
30486
30562
|
var taskCommand = registerTaskCommands(program2);
|
|
30487
30563
|
registerSessionCommands(program2);
|
|
30488
30564
|
registerThreadCommands(program2, taskCommand);
|
|
30489
|
-
registerMcpCommands(program2, "0.4.
|
|
30565
|
+
registerMcpCommands(program2, "0.4.15");
|
|
30490
30566
|
registerGuideCommand(program2);
|
|
30491
30567
|
registerSkillCommands(program2);
|
|
30492
30568
|
program2.parseAsync().catch(exitWithError);
|