@cmgmyr/hive 1.2.1 → 1.3.0
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/README.md +1 -1
- package/claude-plugin/README.md +3 -0
- package/claude-plugin/kickoff.mjs +2 -1
- package/claude-plugin/skills/profile/SKILL.md +78 -0
- package/claude-plugin/skills/profile/references/interview.md +139 -0
- package/claude-plugin/skills/profile/references/recipes/index.md +12 -0
- package/claude-plugin/skills/profile/references/recipes/review-decision.md +41 -0
- package/claude-plugin/skills/profile/references/recipes/session-continuity.md +37 -0
- package/claude-plugin/skills/profile/references/recipes/shared-test-resource.md +45 -0
- package/claude-plugin/skills/profile/references/recipes/verification.md +50 -0
- package/claude-plugin/skills/profile/references/recipes/worker-context-checkpoint.md +51 -0
- package/dist/build-info.json +1 -1
- package/dist/cli.js +78 -59
- package/dist/codexHome.js +23 -8
- package/dist/config.js +8 -61
- package/dist/db.js +20 -3
- package/dist/globalConfig.js +247 -0
- package/dist/harnesses.js +8 -0
- package/dist/help.js +2 -2
- package/dist/hooks.js +2 -1
- package/dist/kickoff.js +3 -4
- package/dist/projectYml.js +224 -179
- package/dist/result.js +19 -2
- package/dist/scheduler.js +12 -6
- package/dist/spawn.js +17 -4
- package/dist/strictInput.js +24 -3
- package/dist/tools/agents.js +149 -15
- package/dist/tools/kv.js +24 -0
- package/dist/tools/leases.js +12 -0
- package/dist/tools/meta.js +42 -0
- package/dist/tools/pads.js +42 -0
- package/dist/tools/todos.js +54 -0
- package/dist/tools/wakes.js +36 -0
- package/package.json +2 -2
package/dist/tools/agents.js
CHANGED
|
@@ -3,15 +3,15 @@ import { randomUUID } from "node:crypto";
|
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { db } from "../db.js";
|
|
5
5
|
import { agentBriefPath, mergedBriefVars, readAgentBrief, workerBrief, workerCommandString, writeAgentBrief, } from "../brief.js";
|
|
6
|
-
import { codexHomeDir, codexInstructionsPhrase, codexLaunchArgs, ensureCodexHome, ensureCodexHooksFile } from "../codexHome.js";
|
|
6
|
+
import { codexHomeDir, codexInstructionsPhrase, codexLaunchArgs, ensureCodexHome, ensureCodexHooksFile, refreshCodexMcpEnv } from "../codexHome.js";
|
|
7
7
|
import { currentActor, findProjectForDir, getProject, linkedWorktreePrimaryRoot, resolveProject } from "../context.js";
|
|
8
|
-
import { commandHead, harnessFor, harnessNames, paneClassifierFor, resolvedCommandPrefix, screenClassifiable, } from "../harnesses.js";
|
|
8
|
+
import { commandHead, harnessFor, harnessNames, readOnlyExtraArgsAllowed, paneClassifierFor, resolvedCommandPrefix, screenClassifiable, } from "../harnesses.js";
|
|
9
9
|
import { ensureWorkerHooksFile } from "../hooks.js";
|
|
10
10
|
import { activeProfile, allowedAgents, loadProjectYml } from "../projectYml.js";
|
|
11
11
|
import { classifyMiss, LEAD_MESSAGE_THRESHOLD, leadPointerMarker, missMessage, readLeadMessage, renderLeadPointer, senderTag, shortenedSendFailureClause, shortenedSendNote, storeLeadMessage, } from "../leadMessage.js";
|
|
12
12
|
import { run } from "../result.js";
|
|
13
13
|
import { markGoneReported } from "../scheduler.js";
|
|
14
|
-
import { branchAt, closeAgentRow, discardOrphanedPane, isReservedAgentName, isRunningLeadActor, killAgentPane, launchAgent, LEAD_KIND, parkAgentRow, reapCodexHomeForClosedAgent, releaseParkRow, renameAgent, resumeAgent, } from "../spawn.js";
|
|
14
|
+
import { agentIdentityEnv, branchAt, closeAgentRow, discardOrphanedPane, isReservedAgentName, isRunningLeadActor, killAgentPane, launchAgent, LEAD_KIND, parkAgentRow, reapCodexHomeForClosedAgent, releaseParkRow, renameAgent, resumeAgent, storedLaunchOptions, } from "../spawn.js";
|
|
15
15
|
import { COMMAND_KIND, runningCommandRow, stopLine, stopProcess, STOP_REASONS } from "../processes.js";
|
|
16
16
|
import { readContextTokens, readContextFill, resolveTranscriptDir } from "../transcript.js";
|
|
17
17
|
import { capturePane, captureFinalScreen, DEFAULT_LAYOUT, cancelCopyMode, describePaneChoice, ensureAttached, findUnsafeControlChar, holdsHumanInput, liveTargets, paneCurrentCommand, paneInCopyMode, paneWindow, pollPaneReadiness, rowAlive, rowLive, sendText, sessionName, setRemainOnExit, sleep, TEXT_ALLOWED_CONTROL_CHARS, tmux, TmuxTimeoutError, WINDOW_LAYOUTS, } from "../tmux.js";
|
|
@@ -161,17 +161,67 @@ function requireNameFree(projectId, name, exceptAgentId) {
|
|
|
161
161
|
throw new Error(`A running agent named "${taken.name}" already exists. Pick another name.`);
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
164
|
+
function commandTokens(command) {
|
|
165
|
+
const tokens = [];
|
|
166
|
+
let token = "";
|
|
167
|
+
let quote = null;
|
|
168
|
+
let escaped = false;
|
|
169
|
+
for (const char of command) {
|
|
170
|
+
if (escaped) {
|
|
171
|
+
token += char;
|
|
172
|
+
escaped = false;
|
|
173
|
+
}
|
|
174
|
+
else if (char === "\\" && quote !== "'") {
|
|
175
|
+
escaped = true;
|
|
176
|
+
}
|
|
177
|
+
else if (quote) {
|
|
178
|
+
if (char === quote)
|
|
179
|
+
quote = null;
|
|
180
|
+
else
|
|
181
|
+
token += char;
|
|
182
|
+
}
|
|
183
|
+
else if (char === "'" || char === '"') {
|
|
184
|
+
quote = char;
|
|
185
|
+
}
|
|
186
|
+
else if (/\s/.test(char)) {
|
|
187
|
+
if (token) {
|
|
188
|
+
tokens.push(token);
|
|
189
|
+
token = "";
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
token += char;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (escaped)
|
|
197
|
+
token += "\\";
|
|
198
|
+
if (token)
|
|
199
|
+
tokens.push(token);
|
|
200
|
+
return tokens;
|
|
201
|
+
}
|
|
168
202
|
function extractCommandFlag(command, flag) {
|
|
169
|
-
const tokens = command
|
|
203
|
+
const tokens = commandTokens(command);
|
|
170
204
|
const i = tokens.indexOf(flag);
|
|
171
205
|
if (i === -1 || i + 1 >= tokens.length)
|
|
172
206
|
return undefined;
|
|
173
207
|
return tokens[i + 1];
|
|
174
208
|
}
|
|
209
|
+
function stripSessionArgs(args) {
|
|
210
|
+
const stripped = [];
|
|
211
|
+
for (let i = 0; i < args.length; i++) {
|
|
212
|
+
const arg = args[i];
|
|
213
|
+
if (arg === "--resume" || arg === "--session-id" || arg === "-r") {
|
|
214
|
+
i++;
|
|
215
|
+
}
|
|
216
|
+
else if (arg === "--last" || arg === "--fork-session" || arg.startsWith("--resume=") || arg.startsWith("--session-id=")) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
stripped.push(arg);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return stripped;
|
|
224
|
+
}
|
|
175
225
|
function requestsExistingSession(extraArgs) {
|
|
176
226
|
return (extraArgs ?? []).some((arg) => arg === "--resume" ||
|
|
177
227
|
arg.startsWith("--resume=") ||
|
|
@@ -261,6 +311,7 @@ function agentSummary(row, snapshot) {
|
|
|
261
311
|
provenance,
|
|
262
312
|
...lastLogEventField(row),
|
|
263
313
|
...permissionModeField(row),
|
|
314
|
+
read_only: storedLaunchOptions(row.extra_args).readOnly,
|
|
264
315
|
...contextFillField(row),
|
|
265
316
|
...(row.parked_at ? { parked_at: row.parked_at, parked_branch: row.parked_branch || null } : {}),
|
|
266
317
|
tmux_target: row.tmux_target,
|
|
@@ -285,7 +336,13 @@ export function worktreeInstallNotice(cwd, projectPath, config) {
|
|
|
285
336
|
}
|
|
286
337
|
export function registerAgents(server) {
|
|
287
338
|
server.registerTool("agent_spawn", {
|
|
288
|
-
description: "Spawn a worker agent (default: claude, or the project's hive.yml agents: default). A claude worker is briefed automatically: the full brief is appended to its system prompt, so send it its assignment directly. A command or harness that resolves to a known harness (claude, codex) not listed in the project's hive.yml agents: is refused; absent agents: means claude only. A command hive cannot classify the screen of (claude and codex both do; a harness with no entry does not) can be spawned but NOT typed into: the receipt carries brief_path and says so, and agent_send's text path and wakes both refuse that pane. The worker is locked to this project. Humans can watch with: tmux attach -t hive-main.",
|
|
339
|
+
description: "Spawn a worker agent (default: claude, or the project's hive.yml agents: default). A claude worker is briefed automatically: the full brief is appended to its system prompt, so send it its assignment directly. A command or harness that resolves to a known harness (claude, codex) not listed in the project's hive.yml agents: is refused; absent agents: means claude only. A command hive cannot classify the screen of (claude and codex both do; a harness with no entry does not) can be spawned but NOT typed into: the receipt carries brief_path and says so, and agent_send's text path and wakes both refuse that pane. Set read_only: true to block local file writes and mutating shell while keeping Hive MCP available; agent_resume preserves this mode and agent_status reports it. Only bare claude/codex executables are supported in this mode, with only effort-setting extra_args. The worker is locked to this project. Humans can watch with: tmux attach -t hive-main.",
|
|
340
|
+
annotations: {
|
|
341
|
+
readOnlyHint: false,
|
|
342
|
+
destructiveHint: false,
|
|
343
|
+
idempotentHint: false,
|
|
344
|
+
openWorldHint: false,
|
|
345
|
+
},
|
|
289
346
|
inputSchema: {
|
|
290
347
|
name: z
|
|
291
348
|
.string()
|
|
@@ -301,6 +358,7 @@ export function registerAgents(server) {
|
|
|
301
358
|
.optional()
|
|
302
359
|
.describe("Spawn a known harness by name (e.g. \"codex\") instead of a raw command. Ignored when command is also given. Must be in the project's hive.yml agents: list (default: claude only)."),
|
|
303
360
|
extra_args: z.array(z.string()).optional().describe("Extra CLI arguments."),
|
|
361
|
+
read_only: z.boolean().optional().describe("Prevent local file writes and mutating shell commands while allowing Hive MCP tools. Only bare claude/codex executables. Extra args allow codex -c model_reasoning_effort=low|medium|high|xhigh, or claude --effort low|medium|high|xhigh|max; all other arguments are refused."),
|
|
304
362
|
cwd: z
|
|
305
363
|
.string()
|
|
306
364
|
.optional()
|
|
@@ -326,6 +384,7 @@ export function registerAgents(server) {
|
|
|
326
384
|
worktree_install: z.string().optional(),
|
|
327
385
|
brief_path: z.string().optional(),
|
|
328
386
|
codex_home: z.string().optional(),
|
|
387
|
+
read_only: z.boolean().optional(),
|
|
329
388
|
codex_instructions: z.string().optional(),
|
|
330
389
|
ready: z.boolean().optional(),
|
|
331
390
|
exited: z.boolean().optional(),
|
|
@@ -378,6 +437,11 @@ export function registerAgents(server) {
|
|
|
378
437
|
throw new Error(`[agent_spawn:harness-not-allowed] "${harness.name}" is not in this project's hive.yml ` +
|
|
379
438
|
`agents: list (${allowed.join(", ")}). Add it to agents: to allow spawning it.`);
|
|
380
439
|
}
|
|
440
|
+
if (args.read_only && (!["claude", "codex"].includes(harness.name) ||
|
|
441
|
+
!/^[A-Za-z0-9_./-]+$/.test(baseCommand) ||
|
|
442
|
+
!readOnlyExtraArgsAllowed(harness.name, args.extra_args))) {
|
|
443
|
+
throw new Error("read_only requires a bare claude or codex executable and no extra_args; launch overrides can bypass its restrictions.");
|
|
444
|
+
}
|
|
381
445
|
const mintsSession = harness.mintsSessionId;
|
|
382
446
|
const sessionId = mintsSession && !requestsExistingSession(args.extra_args) ? randomUUID() : "";
|
|
383
447
|
const worktreeInstall = worktreeInstallNotice(cwd, project.path, projectConfig);
|
|
@@ -401,7 +465,7 @@ export function registerAgents(server) {
|
|
|
401
465
|
const briefPath = harness.briefDelivery ? writeAgentBrief(agentId, brief) : undefined;
|
|
402
466
|
let homeArgs = [];
|
|
403
467
|
if (codexHomeKey) {
|
|
404
|
-
const home = ensureCodexHome({ key: codexHomeKey, actorId, cwd, brief: brief, includePostToolUse: projectConfig?.context_checkpoint_percent != null });
|
|
468
|
+
const home = ensureCodexHome({ key: codexHomeKey, actorId, serverEnv: agentIdentityEnv(actorId, name, project.path), cwd, brief: brief, readOnly: args.read_only, includePostToolUse: projectConfig?.context_checkpoint_percent != null });
|
|
405
469
|
homeArgs = home.extraArgs;
|
|
406
470
|
codexInstructionLayers = home.instructionLayers;
|
|
407
471
|
}
|
|
@@ -415,7 +479,7 @@ export function registerAgents(server) {
|
|
|
415
479
|
? [...(sessionId ? ["--session-id", sessionId] : []), ...(args.extra_args ?? [])]
|
|
416
480
|
: (args.extra_args ?? [])),
|
|
417
481
|
],
|
|
418
|
-
settingsPath: harness.briefDelivery ? ensureWorkerHooksFile(agentId, { includePostToolUse: projectConfig?.context_checkpoint_percent != null }) : undefined,
|
|
482
|
+
settingsPath: harness.briefDelivery ? ensureWorkerHooksFile(agentId, { readOnly: args.read_only, includePostToolUse: projectConfig?.context_checkpoint_percent != null }) : undefined,
|
|
419
483
|
briefPath,
|
|
420
484
|
});
|
|
421
485
|
};
|
|
@@ -439,6 +503,9 @@ export function registerAgents(server) {
|
|
|
439
503
|
parentActor: parent,
|
|
440
504
|
sessionId,
|
|
441
505
|
codexHome: codexHomeKey,
|
|
506
|
+
model: args.model,
|
|
507
|
+
extraArgs: args.extra_args,
|
|
508
|
+
readOnly: args.read_only,
|
|
442
509
|
retainOnExit: harness.classifiesPaneScreen,
|
|
443
510
|
});
|
|
444
511
|
}
|
|
@@ -497,6 +564,7 @@ export function registerAgents(server) {
|
|
|
497
564
|
const codexInstructions = codexHomeKey ? codexInstructionsPhrase(codexInstructionLayers) : undefined;
|
|
498
565
|
return {
|
|
499
566
|
agent_id: agentId,
|
|
567
|
+
...(args.read_only ? { read_only: true } : {}),
|
|
500
568
|
actor_id: actorId,
|
|
501
569
|
name,
|
|
502
570
|
tmux_target: target,
|
|
@@ -546,6 +614,12 @@ export function registerAgents(server) {
|
|
|
546
614
|
}));
|
|
547
615
|
server.registerTool("agent_resume", {
|
|
548
616
|
description: "Resume a CLOSED claude or codex worker from its recorded session id (claude --resume / codex resume): a fresh pane, the same actor_id, and the worker's full prior context. Addressed by name or agent_id among closed agents (agent_list(include_closed: true)). Send it its next instruction with agent_send once resumed - this tool does not.",
|
|
617
|
+
annotations: {
|
|
618
|
+
readOnlyHint: false,
|
|
619
|
+
destructiveHint: false,
|
|
620
|
+
idempotentHint: false,
|
|
621
|
+
openWorldHint: false,
|
|
622
|
+
},
|
|
549
623
|
inputSchema: {
|
|
550
624
|
name: agentNameParam,
|
|
551
625
|
agent_id: agentIdParam,
|
|
@@ -614,6 +688,7 @@ export function registerAgents(server) {
|
|
|
614
688
|
const { config: projectConfig } = loadProjectYml(project.path);
|
|
615
689
|
const placement = projectConfig?.placement ?? (process.env.HIVE_SPAWN_PLACEMENT === "window" ? "window" : "split");
|
|
616
690
|
const layout = projectConfig?.layout ?? DEFAULT_LAYOUT;
|
|
691
|
+
const launchOptions = storedLaunchOptions(agent.extra_args);
|
|
617
692
|
let commandString;
|
|
618
693
|
let resumeEnv = contextCheckpointEnv(projectConfig);
|
|
619
694
|
if (resumeHarness.name === "codex") {
|
|
@@ -622,23 +697,34 @@ export function registerAgents(server) {
|
|
|
622
697
|
// Recovered from the row's own recorded command: every codex worker hive spawns
|
|
623
698
|
// carries a pinned model (codex has no bare alias), so dropping this would silently
|
|
624
699
|
// resume on codex's default model instead of the one the lane actually chose.
|
|
625
|
-
model: extractCommandFlag(agent.command, "--model"),
|
|
700
|
+
model: agent.model ?? extractCommandFlag(agent.command, "--model"),
|
|
626
701
|
displayName: agent.name,
|
|
627
702
|
// Subcommand positional, not a flag - live-verified on v0.149.0 (`codex resume --help`:
|
|
628
703
|
// "Usage: codex resume [OPTIONS] [SESSION_ID] [PROMPT]"). No --settings/hooks file: a
|
|
629
704
|
// codex worker's hooks come from its own home's hooks.json, never claude's shared one.
|
|
630
|
-
extraArgs: [
|
|
705
|
+
extraArgs: [
|
|
706
|
+
...codexLaunchArgs(agent.cwd, launchOptions.readOnly),
|
|
707
|
+
...stripSessionArgs(launchOptions.args),
|
|
708
|
+
"resume",
|
|
709
|
+
agent.session_id,
|
|
710
|
+
],
|
|
631
711
|
});
|
|
632
712
|
resumeEnv = { ...resumeEnv, CODEX_HOME: codexHomeDir(agent.codex_home) };
|
|
633
713
|
ensureCodexHooksFile(agent.codex_home, { includePostToolUse: projectConfig?.context_checkpoint_percent != null });
|
|
714
|
+
refreshCodexMcpEnv(agent.codex_home, agentIdentityEnv(agent.actor_id, agent.name, project.path));
|
|
634
715
|
}
|
|
635
716
|
else {
|
|
636
717
|
const claudeBinary = resolvedCommandPrefix(agent.command) || "claude";
|
|
637
718
|
commandString = workerCommandString({
|
|
638
719
|
command: claudeBinary,
|
|
639
720
|
displayName: agent.name,
|
|
640
|
-
|
|
641
|
-
|
|
721
|
+
model: agent.model ?? undefined,
|
|
722
|
+
extraArgs: [
|
|
723
|
+
...stripSessionArgs(launchOptions.args),
|
|
724
|
+
"--resume",
|
|
725
|
+
agent.session_id,
|
|
726
|
+
],
|
|
727
|
+
settingsPath: ensureWorkerHooksFile(agent.id, { readOnly: launchOptions.readOnly, includePostToolUse: projectConfig?.context_checkpoint_percent != null }),
|
|
642
728
|
});
|
|
643
729
|
}
|
|
644
730
|
const { target, landedInProjectId } = resumeAgent({
|
|
@@ -671,6 +757,12 @@ export function registerAgents(server) {
|
|
|
671
757
|
}));
|
|
672
758
|
server.registerTool("agent_park", {
|
|
673
759
|
description: "Park a claude or codex worker for the night: kill its pane, mark the row PARKED rather than plain closed, record the branch, and hand back a board line plus the one call that brings it back. Use this instead of agent_close when the lane is paused, not finished - `closed` alone cannot tell a next-morning lead which is which. Resume it with agent_resume.",
|
|
760
|
+
annotations: {
|
|
761
|
+
readOnlyHint: false,
|
|
762
|
+
destructiveHint: true,
|
|
763
|
+
idempotentHint: false,
|
|
764
|
+
openWorldHint: false,
|
|
765
|
+
},
|
|
674
766
|
inputSchema: {
|
|
675
767
|
name: agentNameParam,
|
|
676
768
|
agent_id: agentIdParam,
|
|
@@ -746,6 +838,12 @@ export function registerAgents(server) {
|
|
|
746
838
|
}));
|
|
747
839
|
server.registerTool("agent_rename", {
|
|
748
840
|
description: "Change a worker's display name. Its actor_id (agent:N) does not change, so every pad write, todo comment and lease it has already made stays attributable. A live claude worker is also told to retitle its own session, which shows up in its pane; that arrives as a user turn, so rename between assignments rather than mid-task. Refuses a lead target outright.",
|
|
841
|
+
annotations: {
|
|
842
|
+
readOnlyHint: false,
|
|
843
|
+
destructiveHint: true,
|
|
844
|
+
idempotentHint: false,
|
|
845
|
+
openWorldHint: false,
|
|
846
|
+
},
|
|
749
847
|
inputSchema: {
|
|
750
848
|
name: agentNameParam,
|
|
751
849
|
agent_id: agentIdParam,
|
|
@@ -838,6 +936,12 @@ export function registerAgents(server) {
|
|
|
838
936
|
}));
|
|
839
937
|
server.registerTool("agent_list", {
|
|
840
938
|
description: "List this project's agents with live status. Without include_closed, this is every running agent, in full. With include_closed, it is every agent (running and closed/parked), newest first, bounded by limit (default 20, max 100); when the receipt carries next_before_id, page through the rest by passing it back as before_id.",
|
|
939
|
+
annotations: {
|
|
940
|
+
readOnlyHint: false,
|
|
941
|
+
destructiveHint: false,
|
|
942
|
+
idempotentHint: true,
|
|
943
|
+
openWorldHint: false,
|
|
944
|
+
},
|
|
841
945
|
inputSchema: {
|
|
842
946
|
include_closed: z.boolean().optional(),
|
|
843
947
|
limit: z
|
|
@@ -907,6 +1011,12 @@ export function registerAgents(server) {
|
|
|
907
1011
|
}));
|
|
908
1012
|
server.registerTool("agent_status", {
|
|
909
1013
|
description: "Detailed status for one agent, addressed by name (or agent_id), including a short tail of its terminal. include_brief=true returns the exact brief this worker was given; hive keeps that copy because an appended system prompt appears in no transcript.",
|
|
1014
|
+
annotations: {
|
|
1015
|
+
readOnlyHint: false,
|
|
1016
|
+
destructiveHint: false,
|
|
1017
|
+
idempotentHint: true,
|
|
1018
|
+
openWorldHint: false,
|
|
1019
|
+
},
|
|
910
1020
|
inputSchema: {
|
|
911
1021
|
name: agentNameParam,
|
|
912
1022
|
agent_id: agentIdParam,
|
|
@@ -937,6 +1047,12 @@ export function registerAgents(server) {
|
|
|
937
1047
|
}));
|
|
938
1048
|
server.registerTool("agent_send", {
|
|
939
1049
|
description: "Type into an agent's terminal, addressed by name (or agent_id). text of any shape is prefixed with the sender tag, delivered as one bracketed paste and submitted with Enter unless submit=false. ONE EXCEPTION: text over 300 characters sent to a LEAD by anyone who is not that lead is stored and delivered as a one-line pointer instead, because a lead's pane is a human's own window; the receipt says so and names agent_message_get for the full text. Worker-bound text is never shortened at any length. Alternatively pass keys (tmux key names like Escape, C-c, Enter). wait_ms (250-10000) returns the terminal tail after sending. A claude worker is already briefed by agent_spawn. A worker whose screen hive cannot classify is REFUSED on the text path entirely (its brief is at the spawn receipt's brief_path); keys still reaches it. A pane in tmux copy mode is REFUSED too, and retriably: tmux clears its bracketed-paste flag there, so the paste would lose its markers and the Enter would be eaten - leave copy mode (or agent_send(keys: [\"-X\", \"cancel\"]) to cancel it deliberately) and send again.",
|
|
1050
|
+
annotations: {
|
|
1051
|
+
readOnlyHint: false,
|
|
1052
|
+
destructiveHint: true,
|
|
1053
|
+
idempotentHint: false,
|
|
1054
|
+
openWorldHint: false,
|
|
1055
|
+
},
|
|
940
1056
|
inputSchema: {
|
|
941
1057
|
name: agentNameParam,
|
|
942
1058
|
agent_id: agentIdParam,
|
|
@@ -1121,6 +1237,12 @@ export function registerAgents(server) {
|
|
|
1121
1237
|
}));
|
|
1122
1238
|
server.registerTool("agent_message_get", {
|
|
1123
1239
|
description: "Read one agent-to-lead message in full, by the id in a \"[hive:worker NAME] [message #N ...]\" pointer line. hive stores a message here only when it shortens one: text over 300 characters sent to a lead by someone who is not that lead. Every other send is typed with its sender tag and stores nothing, so there is no id to read. Messages are pruned after 7 days, and a lookup for a pruned id says so rather than reporting it missing.",
|
|
1240
|
+
annotations: {
|
|
1241
|
+
readOnlyHint: false,
|
|
1242
|
+
destructiveHint: false,
|
|
1243
|
+
idempotentHint: true,
|
|
1244
|
+
openWorldHint: false,
|
|
1245
|
+
},
|
|
1124
1246
|
inputSchema: {
|
|
1125
1247
|
message_id: idParam,
|
|
1126
1248
|
project_id: projectIdParam,
|
|
@@ -1143,6 +1265,12 @@ export function registerAgents(server) {
|
|
|
1143
1265
|
}));
|
|
1144
1266
|
server.registerTool("agent_output", {
|
|
1145
1267
|
description: "Read the rendered terminal of an agent (default 50 lines, max 200), addressed by name or agent_id. Read REAL output before declaring a worker done.",
|
|
1268
|
+
annotations: {
|
|
1269
|
+
readOnlyHint: false,
|
|
1270
|
+
destructiveHint: false,
|
|
1271
|
+
idempotentHint: true,
|
|
1272
|
+
openWorldHint: false,
|
|
1273
|
+
},
|
|
1146
1274
|
inputSchema: {
|
|
1147
1275
|
name: agentNameParam,
|
|
1148
1276
|
agent_id: agentIdParam,
|
|
@@ -1172,6 +1300,12 @@ export function registerAgents(server) {
|
|
|
1172
1300
|
}));
|
|
1173
1301
|
server.registerTool("agent_close", {
|
|
1174
1302
|
description: "Kill an agent's tmux window and mark it closed, addressed by name (or agent_id). Capture handoffs (todo comments, pads) BEFORE closing; terminal output is not retained. Closing yourself requires confirm_self=true. Refuses a lead target whose pane is live; retires one whose pane is confirmed dead. A worker may never close a lead, live or dead.",
|
|
1303
|
+
annotations: {
|
|
1304
|
+
readOnlyHint: false,
|
|
1305
|
+
destructiveHint: true,
|
|
1306
|
+
idempotentHint: true,
|
|
1307
|
+
openWorldHint: false,
|
|
1308
|
+
},
|
|
1175
1309
|
inputSchema: {
|
|
1176
1310
|
name: agentNameParam,
|
|
1177
1311
|
agent_id: agentIdParam,
|
package/dist/tools/kv.js
CHANGED
|
@@ -9,6 +9,12 @@ function purgeExpired(projectId) {
|
|
|
9
9
|
export function registerKv(server) {
|
|
10
10
|
server.registerTool("kv_set", {
|
|
11
11
|
description: "Set a small shared JSON value other sessions can discover. Optional TTL in seconds.",
|
|
12
|
+
annotations: {
|
|
13
|
+
readOnlyHint: false,
|
|
14
|
+
destructiveHint: true,
|
|
15
|
+
idempotentHint: false,
|
|
16
|
+
openWorldHint: false,
|
|
17
|
+
},
|
|
12
18
|
inputSchema: {
|
|
13
19
|
key: z.string(),
|
|
14
20
|
value: z.any().describe("Any JSON value."),
|
|
@@ -31,6 +37,12 @@ export function registerKv(server) {
|
|
|
31
37
|
}));
|
|
32
38
|
server.registerTool("kv_get", {
|
|
33
39
|
description: "Get a shared JSON value by key.",
|
|
40
|
+
annotations: {
|
|
41
|
+
readOnlyHint: false,
|
|
42
|
+
destructiveHint: true,
|
|
43
|
+
idempotentHint: true,
|
|
44
|
+
openWorldHint: false,
|
|
45
|
+
},
|
|
34
46
|
inputSchema: { key: z.string(), project_id: projectIdParam },
|
|
35
47
|
}, (args) => run(() => {
|
|
36
48
|
const projectId = effectiveProjectId(args.project_id);
|
|
@@ -52,6 +64,12 @@ export function registerKv(server) {
|
|
|
52
64
|
}));
|
|
53
65
|
server.registerTool("kv_list", {
|
|
54
66
|
description: "List shared values, optionally filtered by key prefix.",
|
|
67
|
+
annotations: {
|
|
68
|
+
readOnlyHint: false,
|
|
69
|
+
destructiveHint: true,
|
|
70
|
+
idempotentHint: true,
|
|
71
|
+
openWorldHint: false,
|
|
72
|
+
},
|
|
55
73
|
inputSchema: { prefix: z.string().optional(), project_id: projectIdParam },
|
|
56
74
|
}, (args) => run(() => {
|
|
57
75
|
const projectId = effectiveProjectId(args.project_id);
|
|
@@ -77,6 +95,12 @@ export function registerKv(server) {
|
|
|
77
95
|
}));
|
|
78
96
|
server.registerTool("kv_delete", {
|
|
79
97
|
description: "Delete a shared value by key.",
|
|
98
|
+
annotations: {
|
|
99
|
+
readOnlyHint: false,
|
|
100
|
+
destructiveHint: true,
|
|
101
|
+
idempotentHint: true,
|
|
102
|
+
openWorldHint: false,
|
|
103
|
+
},
|
|
80
104
|
inputSchema: { key: z.string(), project_id: projectIdParam },
|
|
81
105
|
outputSchema: { project_id: idParam, key: z.string(), deleted: z.boolean() },
|
|
82
106
|
}, (args) => run(() => {
|
package/dist/tools/leases.js
CHANGED
|
@@ -19,6 +19,12 @@ export function extendOwnedLease(projectId, key, actor, ttlSeconds) {
|
|
|
19
19
|
export function registerLeases(server) {
|
|
20
20
|
server.registerTool("lease_acquire", {
|
|
21
21
|
description: "Try to take a named lease on a shared work area (non-blocking). Re-taking your own lease extends it. Leases expire on their own.",
|
|
22
|
+
annotations: {
|
|
23
|
+
readOnlyHint: false,
|
|
24
|
+
destructiveHint: false,
|
|
25
|
+
idempotentHint: false,
|
|
26
|
+
openWorldHint: false,
|
|
27
|
+
},
|
|
22
28
|
inputSchema: {
|
|
23
29
|
key: z.string().describe('Stable and specific, like "file:src/api/routes.ts".'),
|
|
24
30
|
ttl_seconds: z.number().int().positive(),
|
|
@@ -75,6 +81,12 @@ export function registerLeases(server) {
|
|
|
75
81
|
}));
|
|
76
82
|
server.registerTool("lease_release", {
|
|
77
83
|
description: "Release a lease you own.",
|
|
84
|
+
annotations: {
|
|
85
|
+
readOnlyHint: false,
|
|
86
|
+
destructiveHint: true,
|
|
87
|
+
idempotentHint: true,
|
|
88
|
+
openWorldHint: false,
|
|
89
|
+
},
|
|
78
90
|
inputSchema: { key: z.string(), project_id: projectIdParam },
|
|
79
91
|
outputSchema: { project_id: idParam, key: z.string(), released: z.boolean() },
|
|
80
92
|
}, (args) => run(() => {
|
package/dist/tools/meta.js
CHANGED
|
@@ -60,6 +60,12 @@ const pruneActorIfInert = db.transaction((actorId) => {
|
|
|
60
60
|
export function registerMeta(server) {
|
|
61
61
|
server.registerTool("whoami", {
|
|
62
62
|
description: "Show this session's actor identity and effective project scope. Call this first in a new session.",
|
|
63
|
+
annotations: {
|
|
64
|
+
readOnlyHint: false,
|
|
65
|
+
destructiveHint: false,
|
|
66
|
+
idempotentHint: false,
|
|
67
|
+
openWorldHint: false,
|
|
68
|
+
},
|
|
63
69
|
inputSchema: {},
|
|
64
70
|
}, () => run(() => {
|
|
65
71
|
const actorId = currentActor();
|
|
@@ -83,6 +89,12 @@ export function registerMeta(server) {
|
|
|
83
89
|
}));
|
|
84
90
|
server.registerTool("help", {
|
|
85
91
|
description: `Hive usage guidance. Omit topic for an overview, or pass one of: ${Object.keys(HELP_TOPICS).join(", ")}.`,
|
|
92
|
+
annotations: {
|
|
93
|
+
readOnlyHint: true,
|
|
94
|
+
destructiveHint: false,
|
|
95
|
+
idempotentHint: true,
|
|
96
|
+
openWorldHint: false,
|
|
97
|
+
},
|
|
86
98
|
inputSchema: { topic: z.string().optional() },
|
|
87
99
|
}, ({ topic }) => run(() => {
|
|
88
100
|
if (!topic)
|
|
@@ -95,6 +107,12 @@ export function registerMeta(server) {
|
|
|
95
107
|
}));
|
|
96
108
|
server.registerTool("project_list", {
|
|
97
109
|
description: "List registered projects and the currently selected one.",
|
|
110
|
+
annotations: {
|
|
111
|
+
readOnlyHint: false,
|
|
112
|
+
destructiveHint: false,
|
|
113
|
+
idempotentHint: true,
|
|
114
|
+
openWorldHint: false,
|
|
115
|
+
},
|
|
98
116
|
inputSchema: {},
|
|
99
117
|
}, () => run(() => ({
|
|
100
118
|
projects: listProjects(),
|
|
@@ -102,6 +120,12 @@ export function registerMeta(server) {
|
|
|
102
120
|
})));
|
|
103
121
|
server.registerTool("project_add", {
|
|
104
122
|
description: "Register a directory as a project. Defaults to the current working directory. Returns the existing project if the path is already registered.",
|
|
123
|
+
annotations: {
|
|
124
|
+
readOnlyHint: false,
|
|
125
|
+
destructiveHint: false,
|
|
126
|
+
idempotentHint: true,
|
|
127
|
+
openWorldHint: false,
|
|
128
|
+
},
|
|
105
129
|
inputSchema: {
|
|
106
130
|
path: z.string().optional(),
|
|
107
131
|
name: z.string().optional(),
|
|
@@ -110,10 +134,22 @@ export function registerMeta(server) {
|
|
|
110
134
|
}, ({ path, name }) => run(() => addProject(path, name)));
|
|
111
135
|
server.registerTool("project_select", {
|
|
112
136
|
description: "Set which project later tools act on in this session.",
|
|
137
|
+
annotations: {
|
|
138
|
+
readOnlyHint: false,
|
|
139
|
+
destructiveHint: false,
|
|
140
|
+
idempotentHint: true,
|
|
141
|
+
openWorldHint: false,
|
|
142
|
+
},
|
|
113
143
|
inputSchema: { project_id: idParam },
|
|
114
144
|
}, ({ project_id }) => run(() => selectProjectById(project_id)));
|
|
115
145
|
server.registerTool("project_prune", {
|
|
116
146
|
description: "Delete every registered project that owns no rows anywhere in the store (pads, todos, kv, leases, agents, wakes, command_trust), verified individually before each delete. Never prunes the caller's own project. Refuses under HIVE_PROJECT_LOCK=1: this is a whole-store sweep, and a project-locked session may only touch its own project. Immediate, permanent: no dry-run mode.",
|
|
147
|
+
annotations: {
|
|
148
|
+
readOnlyHint: false,
|
|
149
|
+
destructiveHint: true,
|
|
150
|
+
idempotentHint: true,
|
|
151
|
+
openWorldHint: false,
|
|
152
|
+
},
|
|
117
153
|
inputSchema: {},
|
|
118
154
|
outputSchema: {
|
|
119
155
|
deleted: z.array(z.object({ id: idParam, name: z.string() })),
|
|
@@ -145,6 +181,12 @@ export function registerMeta(server) {
|
|
|
145
181
|
}));
|
|
146
182
|
server.registerTool("actor_prune", {
|
|
147
183
|
description: "Delete every actor that owns no rows anywhere in the store and has not been active in the last minute: agents.actor_id, agents.parent_actor_id, todos.locked_by, todo_comments.author, kv.updated_by, leases.owner, pads.updated_by, wakes.owner, wakes.deliver_actor, agent_state_log.actor_id. The scan is global across every project, never scoped to the caller's: actors carry no project_id, so an actor can own rows in a project the caller cannot see, and a project-scoped scan would misread that actor as inert and delete it. Never prunes the caller's own actor. Refuses under HIVE_PROJECT_LOCK=1: this is a whole-store sweep. Run this after project_prune when sweeping the store: an empty project owns no agents rows either, so today the order cannot orphan an actor, but that stops being true the day project deletion ever covers a non-empty project, and this ordering is the one that stays safe if it does. Immediate, permanent: no dry-run mode.",
|
|
184
|
+
annotations: {
|
|
185
|
+
readOnlyHint: false,
|
|
186
|
+
destructiveHint: true,
|
|
187
|
+
idempotentHint: true,
|
|
188
|
+
openWorldHint: false,
|
|
189
|
+
},
|
|
148
190
|
inputSchema: {},
|
|
149
191
|
outputSchema: {
|
|
150
192
|
deleted: z.array(z.object({ id: z.string(), name: z.string() })),
|
package/dist/tools/pads.js
CHANGED
|
@@ -80,6 +80,12 @@ export function createPad(projectId, name, content, tags) {
|
|
|
80
80
|
export function registerPads(server) {
|
|
81
81
|
server.registerTool("pad_write", {
|
|
82
82
|
description: "Create a pad, or fully overwrite one by passing pad_id plus expected_revision. Pad names are unique per project. Prefer pad_append/pad_edit for targeted changes.",
|
|
83
|
+
annotations: {
|
|
84
|
+
readOnlyHint: false,
|
|
85
|
+
destructiveHint: true,
|
|
86
|
+
idempotentHint: false,
|
|
87
|
+
openWorldHint: false,
|
|
88
|
+
},
|
|
83
89
|
inputSchema: {
|
|
84
90
|
name: z.string(),
|
|
85
91
|
content: z.string(),
|
|
@@ -105,6 +111,12 @@ export function registerPads(server) {
|
|
|
105
111
|
}));
|
|
106
112
|
server.registerTool("pad_read", {
|
|
107
113
|
description: "Read a pad's content, revision, and metadata by pad_id or name.",
|
|
114
|
+
annotations: {
|
|
115
|
+
readOnlyHint: false,
|
|
116
|
+
destructiveHint: false,
|
|
117
|
+
idempotentHint: true,
|
|
118
|
+
openWorldHint: false,
|
|
119
|
+
},
|
|
108
120
|
inputSchema: {
|
|
109
121
|
pad_id: idParam.optional(),
|
|
110
122
|
name: z.string().optional(),
|
|
@@ -139,6 +151,12 @@ export function registerPads(server) {
|
|
|
139
151
|
}));
|
|
140
152
|
server.registerTool("pad_append", {
|
|
141
153
|
description: "Append content to the end of a pad. Optional expected_revision guards against concurrent writes.",
|
|
154
|
+
annotations: {
|
|
155
|
+
readOnlyHint: false,
|
|
156
|
+
destructiveHint: false,
|
|
157
|
+
idempotentHint: false,
|
|
158
|
+
openWorldHint: false,
|
|
159
|
+
},
|
|
142
160
|
inputSchema: {
|
|
143
161
|
pad_id: idParam,
|
|
144
162
|
content: z.string(),
|
|
@@ -155,6 +173,12 @@ export function registerPads(server) {
|
|
|
155
173
|
}));
|
|
156
174
|
server.registerTool("pad_edit", {
|
|
157
175
|
description: "Replace one literal occurrence of old_text with new_text in a pad. old_text must match exactly once; include surrounding context to disambiguate.",
|
|
176
|
+
annotations: {
|
|
177
|
+
readOnlyHint: false,
|
|
178
|
+
destructiveHint: true,
|
|
179
|
+
idempotentHint: false,
|
|
180
|
+
openWorldHint: false,
|
|
181
|
+
},
|
|
158
182
|
inputSchema: {
|
|
159
183
|
pad_id: idParam,
|
|
160
184
|
old_text: z.string(),
|
|
@@ -180,6 +204,12 @@ export function registerPads(server) {
|
|
|
180
204
|
}));
|
|
181
205
|
server.registerTool("pad_archive", {
|
|
182
206
|
description: "Archive a pad (or unarchive with archived=false). Archiving frees the name for a new active pad; the old content stays readable by pad_id.",
|
|
207
|
+
annotations: {
|
|
208
|
+
readOnlyHint: false,
|
|
209
|
+
destructiveHint: true,
|
|
210
|
+
idempotentHint: false,
|
|
211
|
+
openWorldHint: false,
|
|
212
|
+
},
|
|
183
213
|
inputSchema: {
|
|
184
214
|
pad_id: idParam,
|
|
185
215
|
archived: z.boolean().optional().describe("Default true. Pass false to unarchive."),
|
|
@@ -207,6 +237,12 @@ export function registerPads(server) {
|
|
|
207
237
|
}));
|
|
208
238
|
server.registerTool("pad_delete", {
|
|
209
239
|
description: "Permanently delete a pad. Irreversible; prefer pad_archive. Optional expected_revision guards against deleting a pad someone just updated.",
|
|
240
|
+
annotations: {
|
|
241
|
+
readOnlyHint: false,
|
|
242
|
+
destructiveHint: true,
|
|
243
|
+
idempotentHint: true,
|
|
244
|
+
openWorldHint: false,
|
|
245
|
+
},
|
|
210
246
|
inputSchema: {
|
|
211
247
|
pad_id: idParam,
|
|
212
248
|
expected_revision: idParam.optional(),
|
|
@@ -223,6 +259,12 @@ export function registerPads(server) {
|
|
|
223
259
|
}));
|
|
224
260
|
server.registerTool("pad_list", {
|
|
225
261
|
description: "List pads without full content. query matches names and content (returns a snippet); tags matches any listed tag.",
|
|
262
|
+
annotations: {
|
|
263
|
+
readOnlyHint: false,
|
|
264
|
+
destructiveHint: false,
|
|
265
|
+
idempotentHint: true,
|
|
266
|
+
openWorldHint: false,
|
|
267
|
+
},
|
|
226
268
|
inputSchema: {
|
|
227
269
|
query: z.string().optional(),
|
|
228
270
|
tags: z.array(z.string()).optional(),
|