@cmgmyr/hive 1.2.2 → 1.4.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 +6 -1
- package/claude-plugin/README.md +4 -1
- 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/brief.js +1 -1
- package/dist/build-info.json +1 -1
- package/dist/cli.js +436 -112
- package/dist/codexHome.js +23 -8
- package/dist/config.js +8 -61
- package/dist/context.js +191 -2
- package/dist/db.js +80 -20
- package/dist/firstMessage.js +5 -0
- package/dist/globalConfig.js +247 -0
- package/dist/harnesses.js +14 -4
- package/dist/help.js +29 -11
- package/dist/hook.js +9 -0
- package/dist/hooks.js +2 -1
- package/dist/index.js +11 -5
- package/dist/kickoff.js +42 -29
- package/dist/leadState.js +63 -0
- package/dist/leadWatch.js +73 -0
- package/dist/portfolio.js +210 -0
- package/dist/projectYml.js +242 -178
- package/dist/queenAudit.js +118 -0
- package/dist/queenDashboard.js +573 -0
- package/dist/scheduler.js +190 -20
- package/dist/spawn.js +17 -4
- package/dist/strictInput.js +24 -7
- package/dist/tmux.js +65 -0
- package/dist/tools/agents.js +153 -15
- package/dist/tools/kv.js +24 -0
- package/dist/tools/leases.js +14 -2
- package/dist/tools/meta.js +65 -3
- package/dist/tools/pads.js +42 -0
- package/dist/tools/queenAudit.js +13 -0
- package/dist/tools/todos.js +72 -13
- package/dist/tools/wakes.js +233 -65
- package/package.json +4 -4
- package/profiles/orchestration/worker.md +1 -1
- package/profiles/queen/posture.md +31 -0
- package/profiles/queen/runbook.md +26 -0
package/dist/tools/agents.js
CHANGED
|
@@ -2,16 +2,18 @@ import { existsSync, rmSync, statSync, realpathSync } from "node:fs";
|
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { db } from "../db.js";
|
|
5
|
+
import { confirmQueenWrite } from "../queenAudit.js";
|
|
6
|
+
import { captureQueenWriteIdentity } from "../context.js";
|
|
5
7
|
import { agentBriefPath, mergedBriefVars, readAgentBrief, workerBrief, workerCommandString, writeAgentBrief, } from "../brief.js";
|
|
6
|
-
import { codexHomeDir, codexInstructionsPhrase, codexLaunchArgs, ensureCodexHome, ensureCodexHooksFile } from "../codexHome.js";
|
|
8
|
+
import { codexHomeDir, codexInstructionsPhrase, codexLaunchArgs, ensureCodexHome, ensureCodexHooksFile, refreshCodexMcpEnv } from "../codexHome.js";
|
|
7
9
|
import { currentActor, findProjectForDir, getProject, linkedWorktreePrimaryRoot, resolveProject } from "../context.js";
|
|
8
|
-
import { commandHead, harnessFor, harnessNames, paneClassifierFor, resolvedCommandPrefix, screenClassifiable, } from "../harnesses.js";
|
|
10
|
+
import { commandHead, harnessFor, harnessNames, readOnlyExtraArgsAllowed, paneClassifierFor, resolvedCommandPrefix, screenClassifiable, } from "../harnesses.js";
|
|
9
11
|
import { ensureWorkerHooksFile } from "../hooks.js";
|
|
10
12
|
import { activeProfile, allowedAgents, loadProjectYml } from "../projectYml.js";
|
|
11
13
|
import { classifyMiss, LEAD_MESSAGE_THRESHOLD, leadPointerMarker, missMessage, readLeadMessage, renderLeadPointer, senderTag, shortenedSendFailureClause, shortenedSendNote, storeLeadMessage, } from "../leadMessage.js";
|
|
12
14
|
import { run } from "../result.js";
|
|
13
15
|
import { markGoneReported } from "../scheduler.js";
|
|
14
|
-
import { branchAt, closeAgentRow, discardOrphanedPane, isReservedAgentName, isRunningLeadActor, killAgentPane, launchAgent, LEAD_KIND, parkAgentRow, reapCodexHomeForClosedAgent, releaseParkRow, renameAgent, resumeAgent, } from "../spawn.js";
|
|
16
|
+
import { agentIdentityEnv, branchAt, closeAgentRow, discardOrphanedPane, isReservedAgentName, isRunningLeadActor, killAgentPane, launchAgent, LEAD_KIND, parkAgentRow, reapCodexHomeForClosedAgent, releaseParkRow, renameAgent, resumeAgent, storedLaunchOptions, } from "../spawn.js";
|
|
15
17
|
import { COMMAND_KIND, runningCommandRow, stopLine, stopProcess, STOP_REASONS } from "../processes.js";
|
|
16
18
|
import { readContextTokens, readContextFill, resolveTranscriptDir } from "../transcript.js";
|
|
17
19
|
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 +163,67 @@ function requireNameFree(projectId, name, exceptAgentId) {
|
|
|
161
163
|
throw new Error(`A running agent named "${taken.name}" already exists. Pick another name.`);
|
|
162
164
|
}
|
|
163
165
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
166
|
+
function commandTokens(command) {
|
|
167
|
+
const tokens = [];
|
|
168
|
+
let token = "";
|
|
169
|
+
let quote = null;
|
|
170
|
+
let escaped = false;
|
|
171
|
+
for (const char of command) {
|
|
172
|
+
if (escaped) {
|
|
173
|
+
token += char;
|
|
174
|
+
escaped = false;
|
|
175
|
+
}
|
|
176
|
+
else if (char === "\\" && quote !== "'") {
|
|
177
|
+
escaped = true;
|
|
178
|
+
}
|
|
179
|
+
else if (quote) {
|
|
180
|
+
if (char === quote)
|
|
181
|
+
quote = null;
|
|
182
|
+
else
|
|
183
|
+
token += char;
|
|
184
|
+
}
|
|
185
|
+
else if (char === "'" || char === '"') {
|
|
186
|
+
quote = char;
|
|
187
|
+
}
|
|
188
|
+
else if (/\s/.test(char)) {
|
|
189
|
+
if (token) {
|
|
190
|
+
tokens.push(token);
|
|
191
|
+
token = "";
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
token += char;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (escaped)
|
|
199
|
+
token += "\\";
|
|
200
|
+
if (token)
|
|
201
|
+
tokens.push(token);
|
|
202
|
+
return tokens;
|
|
203
|
+
}
|
|
168
204
|
function extractCommandFlag(command, flag) {
|
|
169
|
-
const tokens = command
|
|
205
|
+
const tokens = commandTokens(command);
|
|
170
206
|
const i = tokens.indexOf(flag);
|
|
171
207
|
if (i === -1 || i + 1 >= tokens.length)
|
|
172
208
|
return undefined;
|
|
173
209
|
return tokens[i + 1];
|
|
174
210
|
}
|
|
211
|
+
function stripSessionArgs(args) {
|
|
212
|
+
const stripped = [];
|
|
213
|
+
for (let i = 0; i < args.length; i++) {
|
|
214
|
+
const arg = args[i];
|
|
215
|
+
if (arg === "--resume" || arg === "--session-id" || arg === "-r") {
|
|
216
|
+
i++;
|
|
217
|
+
}
|
|
218
|
+
else if (arg === "--last" || arg === "--fork-session" || arg.startsWith("--resume=") || arg.startsWith("--session-id=")) {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
stripped.push(arg);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return stripped;
|
|
226
|
+
}
|
|
175
227
|
function requestsExistingSession(extraArgs) {
|
|
176
228
|
return (extraArgs ?? []).some((arg) => arg === "--resume" ||
|
|
177
229
|
arg.startsWith("--resume=") ||
|
|
@@ -261,6 +313,7 @@ function agentSummary(row, snapshot) {
|
|
|
261
313
|
provenance,
|
|
262
314
|
...lastLogEventField(row),
|
|
263
315
|
...permissionModeField(row),
|
|
316
|
+
read_only: storedLaunchOptions(row.extra_args).readOnly,
|
|
264
317
|
...contextFillField(row),
|
|
265
318
|
...(row.parked_at ? { parked_at: row.parked_at, parked_branch: row.parked_branch || null } : {}),
|
|
266
319
|
tmux_target: row.tmux_target,
|
|
@@ -285,7 +338,13 @@ export function worktreeInstallNotice(cwd, projectPath, config) {
|
|
|
285
338
|
}
|
|
286
339
|
export function registerAgents(server) {
|
|
287
340
|
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.",
|
|
341
|
+
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.",
|
|
342
|
+
annotations: {
|
|
343
|
+
readOnlyHint: false,
|
|
344
|
+
destructiveHint: false,
|
|
345
|
+
idempotentHint: false,
|
|
346
|
+
openWorldHint: false,
|
|
347
|
+
},
|
|
289
348
|
inputSchema: {
|
|
290
349
|
name: z
|
|
291
350
|
.string()
|
|
@@ -301,6 +360,7 @@ export function registerAgents(server) {
|
|
|
301
360
|
.optional()
|
|
302
361
|
.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
362
|
extra_args: z.array(z.string()).optional().describe("Extra CLI arguments."),
|
|
363
|
+
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
364
|
cwd: z
|
|
305
365
|
.string()
|
|
306
366
|
.optional()
|
|
@@ -326,6 +386,7 @@ export function registerAgents(server) {
|
|
|
326
386
|
worktree_install: z.string().optional(),
|
|
327
387
|
brief_path: z.string().optional(),
|
|
328
388
|
codex_home: z.string().optional(),
|
|
389
|
+
read_only: z.boolean().optional(),
|
|
329
390
|
codex_instructions: z.string().optional(),
|
|
330
391
|
ready: z.boolean().optional(),
|
|
331
392
|
exited: z.boolean().optional(),
|
|
@@ -378,6 +439,11 @@ export function registerAgents(server) {
|
|
|
378
439
|
throw new Error(`[agent_spawn:harness-not-allowed] "${harness.name}" is not in this project's hive.yml ` +
|
|
379
440
|
`agents: list (${allowed.join(", ")}). Add it to agents: to allow spawning it.`);
|
|
380
441
|
}
|
|
442
|
+
if (args.read_only && (!["claude", "codex"].includes(harness.name) ||
|
|
443
|
+
!/^[A-Za-z0-9_./-]+$/.test(baseCommand) ||
|
|
444
|
+
!readOnlyExtraArgsAllowed(harness.name, args.extra_args))) {
|
|
445
|
+
throw new Error("read_only requires a bare claude or codex executable and no extra_args; launch overrides can bypass its restrictions.");
|
|
446
|
+
}
|
|
381
447
|
const mintsSession = harness.mintsSessionId;
|
|
382
448
|
const sessionId = mintsSession && !requestsExistingSession(args.extra_args) ? randomUUID() : "";
|
|
383
449
|
const worktreeInstall = worktreeInstallNotice(cwd, project.path, projectConfig);
|
|
@@ -401,7 +467,7 @@ export function registerAgents(server) {
|
|
|
401
467
|
const briefPath = harness.briefDelivery ? writeAgentBrief(agentId, brief) : undefined;
|
|
402
468
|
let homeArgs = [];
|
|
403
469
|
if (codexHomeKey) {
|
|
404
|
-
const home = ensureCodexHome({ key: codexHomeKey, actorId, cwd, brief: brief, includePostToolUse: projectConfig?.context_checkpoint_percent != null });
|
|
470
|
+
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
471
|
homeArgs = home.extraArgs;
|
|
406
472
|
codexInstructionLayers = home.instructionLayers;
|
|
407
473
|
}
|
|
@@ -415,7 +481,7 @@ export function registerAgents(server) {
|
|
|
415
481
|
? [...(sessionId ? ["--session-id", sessionId] : []), ...(args.extra_args ?? [])]
|
|
416
482
|
: (args.extra_args ?? [])),
|
|
417
483
|
],
|
|
418
|
-
settingsPath: harness.briefDelivery ? ensureWorkerHooksFile(agentId, { includePostToolUse: projectConfig?.context_checkpoint_percent != null }) : undefined,
|
|
484
|
+
settingsPath: harness.briefDelivery ? ensureWorkerHooksFile(agentId, { readOnly: args.read_only, includePostToolUse: projectConfig?.context_checkpoint_percent != null }) : undefined,
|
|
419
485
|
briefPath,
|
|
420
486
|
});
|
|
421
487
|
};
|
|
@@ -439,6 +505,9 @@ export function registerAgents(server) {
|
|
|
439
505
|
parentActor: parent,
|
|
440
506
|
sessionId,
|
|
441
507
|
codexHome: codexHomeKey,
|
|
508
|
+
model: args.model,
|
|
509
|
+
extraArgs: args.extra_args,
|
|
510
|
+
readOnly: args.read_only,
|
|
442
511
|
retainOnExit: harness.classifiesPaneScreen,
|
|
443
512
|
});
|
|
444
513
|
}
|
|
@@ -497,6 +566,7 @@ export function registerAgents(server) {
|
|
|
497
566
|
const codexInstructions = codexHomeKey ? codexInstructionsPhrase(codexInstructionLayers) : undefined;
|
|
498
567
|
return {
|
|
499
568
|
agent_id: agentId,
|
|
569
|
+
...(args.read_only ? { read_only: true } : {}),
|
|
500
570
|
actor_id: actorId,
|
|
501
571
|
name,
|
|
502
572
|
tmux_target: target,
|
|
@@ -546,6 +616,12 @@ export function registerAgents(server) {
|
|
|
546
616
|
}));
|
|
547
617
|
server.registerTool("agent_resume", {
|
|
548
618
|
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.",
|
|
619
|
+
annotations: {
|
|
620
|
+
readOnlyHint: false,
|
|
621
|
+
destructiveHint: false,
|
|
622
|
+
idempotentHint: false,
|
|
623
|
+
openWorldHint: false,
|
|
624
|
+
},
|
|
549
625
|
inputSchema: {
|
|
550
626
|
name: agentNameParam,
|
|
551
627
|
agent_id: agentIdParam,
|
|
@@ -614,6 +690,7 @@ export function registerAgents(server) {
|
|
|
614
690
|
const { config: projectConfig } = loadProjectYml(project.path);
|
|
615
691
|
const placement = projectConfig?.placement ?? (process.env.HIVE_SPAWN_PLACEMENT === "window" ? "window" : "split");
|
|
616
692
|
const layout = projectConfig?.layout ?? DEFAULT_LAYOUT;
|
|
693
|
+
const launchOptions = storedLaunchOptions(agent.extra_args);
|
|
617
694
|
let commandString;
|
|
618
695
|
let resumeEnv = contextCheckpointEnv(projectConfig);
|
|
619
696
|
if (resumeHarness.name === "codex") {
|
|
@@ -622,23 +699,34 @@ export function registerAgents(server) {
|
|
|
622
699
|
// Recovered from the row's own recorded command: every codex worker hive spawns
|
|
623
700
|
// carries a pinned model (codex has no bare alias), so dropping this would silently
|
|
624
701
|
// resume on codex's default model instead of the one the lane actually chose.
|
|
625
|
-
model: extractCommandFlag(agent.command, "--model"),
|
|
702
|
+
model: agent.model ?? extractCommandFlag(agent.command, "--model"),
|
|
626
703
|
displayName: agent.name,
|
|
627
704
|
// Subcommand positional, not a flag - live-verified on v0.149.0 (`codex resume --help`:
|
|
628
705
|
// "Usage: codex resume [OPTIONS] [SESSION_ID] [PROMPT]"). No --settings/hooks file: a
|
|
629
706
|
// codex worker's hooks come from its own home's hooks.json, never claude's shared one.
|
|
630
|
-
extraArgs: [
|
|
707
|
+
extraArgs: [
|
|
708
|
+
...codexLaunchArgs(agent.cwd, launchOptions.readOnly),
|
|
709
|
+
...stripSessionArgs(launchOptions.args),
|
|
710
|
+
"resume",
|
|
711
|
+
agent.session_id,
|
|
712
|
+
],
|
|
631
713
|
});
|
|
632
714
|
resumeEnv = { ...resumeEnv, CODEX_HOME: codexHomeDir(agent.codex_home) };
|
|
633
715
|
ensureCodexHooksFile(agent.codex_home, { includePostToolUse: projectConfig?.context_checkpoint_percent != null });
|
|
716
|
+
refreshCodexMcpEnv(agent.codex_home, agentIdentityEnv(agent.actor_id, agent.name, project.path));
|
|
634
717
|
}
|
|
635
718
|
else {
|
|
636
719
|
const claudeBinary = resolvedCommandPrefix(agent.command) || "claude";
|
|
637
720
|
commandString = workerCommandString({
|
|
638
721
|
command: claudeBinary,
|
|
639
722
|
displayName: agent.name,
|
|
640
|
-
|
|
641
|
-
|
|
723
|
+
model: agent.model ?? undefined,
|
|
724
|
+
extraArgs: [
|
|
725
|
+
...stripSessionArgs(launchOptions.args),
|
|
726
|
+
"--resume",
|
|
727
|
+
agent.session_id,
|
|
728
|
+
],
|
|
729
|
+
settingsPath: ensureWorkerHooksFile(agent.id, { readOnly: launchOptions.readOnly, includePostToolUse: projectConfig?.context_checkpoint_percent != null }),
|
|
642
730
|
});
|
|
643
731
|
}
|
|
644
732
|
const { target, landedInProjectId } = resumeAgent({
|
|
@@ -671,6 +759,12 @@ export function registerAgents(server) {
|
|
|
671
759
|
}));
|
|
672
760
|
server.registerTool("agent_park", {
|
|
673
761
|
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.",
|
|
762
|
+
annotations: {
|
|
763
|
+
readOnlyHint: false,
|
|
764
|
+
destructiveHint: true,
|
|
765
|
+
idempotentHint: false,
|
|
766
|
+
openWorldHint: false,
|
|
767
|
+
},
|
|
674
768
|
inputSchema: {
|
|
675
769
|
name: agentNameParam,
|
|
676
770
|
agent_id: agentIdParam,
|
|
@@ -746,6 +840,12 @@ export function registerAgents(server) {
|
|
|
746
840
|
}));
|
|
747
841
|
server.registerTool("agent_rename", {
|
|
748
842
|
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.",
|
|
843
|
+
annotations: {
|
|
844
|
+
readOnlyHint: false,
|
|
845
|
+
destructiveHint: true,
|
|
846
|
+
idempotentHint: false,
|
|
847
|
+
openWorldHint: false,
|
|
848
|
+
},
|
|
749
849
|
inputSchema: {
|
|
750
850
|
name: agentNameParam,
|
|
751
851
|
agent_id: agentIdParam,
|
|
@@ -838,6 +938,12 @@ export function registerAgents(server) {
|
|
|
838
938
|
}));
|
|
839
939
|
server.registerTool("agent_list", {
|
|
840
940
|
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.",
|
|
941
|
+
annotations: {
|
|
942
|
+
readOnlyHint: false,
|
|
943
|
+
destructiveHint: false,
|
|
944
|
+
idempotentHint: true,
|
|
945
|
+
openWorldHint: false,
|
|
946
|
+
},
|
|
841
947
|
inputSchema: {
|
|
842
948
|
include_closed: z.boolean().optional(),
|
|
843
949
|
limit: z
|
|
@@ -907,6 +1013,12 @@ export function registerAgents(server) {
|
|
|
907
1013
|
}));
|
|
908
1014
|
server.registerTool("agent_status", {
|
|
909
1015
|
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.",
|
|
1016
|
+
annotations: {
|
|
1017
|
+
readOnlyHint: false,
|
|
1018
|
+
destructiveHint: false,
|
|
1019
|
+
idempotentHint: true,
|
|
1020
|
+
openWorldHint: false,
|
|
1021
|
+
},
|
|
910
1022
|
inputSchema: {
|
|
911
1023
|
name: agentNameParam,
|
|
912
1024
|
agent_id: agentIdParam,
|
|
@@ -937,6 +1049,12 @@ export function registerAgents(server) {
|
|
|
937
1049
|
}));
|
|
938
1050
|
server.registerTool("agent_send", {
|
|
939
1051
|
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.",
|
|
1052
|
+
annotations: {
|
|
1053
|
+
readOnlyHint: false,
|
|
1054
|
+
destructiveHint: true,
|
|
1055
|
+
idempotentHint: false,
|
|
1056
|
+
openWorldHint: false,
|
|
1057
|
+
},
|
|
940
1058
|
inputSchema: {
|
|
941
1059
|
name: agentNameParam,
|
|
942
1060
|
agent_id: agentIdParam,
|
|
@@ -951,6 +1069,7 @@ export function registerAgents(server) {
|
|
|
951
1069
|
const agent = findAgent(project.id, args);
|
|
952
1070
|
requireLive(agent);
|
|
953
1071
|
const target = agent.tmux_target;
|
|
1072
|
+
const auditIdentity = captureQueenWriteIdentity(project.id);
|
|
954
1073
|
let outgoing = args.text ?? "";
|
|
955
1074
|
const tag = senderTag(project.id, currentActor());
|
|
956
1075
|
let shortened = null;
|
|
@@ -1094,6 +1213,7 @@ export function registerAgents(server) {
|
|
|
1094
1213
|
: `That target is a LEAD session, so agent_send's keys path is refused against it from a non-lead caller: you cannot finish this yourself. A human at that terminal, or another lead, has to press Enter there. Leave it and try again later.${shortened === null ? "" : " Pressing Enter there is the INTENDED finish, not a stray paste: that stranded line is hive's own pointer, and submitting it is exactly what this send was supposed to do."}`) +
|
|
1095
1214
|
shortenedClause, { cause: err });
|
|
1096
1215
|
}
|
|
1216
|
+
confirmQueenWrite("agent_send", project.id, args, { sent: true, agent_id: agent.id }, auditIdentity);
|
|
1097
1217
|
}
|
|
1098
1218
|
else {
|
|
1099
1219
|
throw new Error("Pass text or keys.");
|
|
@@ -1121,6 +1241,12 @@ export function registerAgents(server) {
|
|
|
1121
1241
|
}));
|
|
1122
1242
|
server.registerTool("agent_message_get", {
|
|
1123
1243
|
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.",
|
|
1244
|
+
annotations: {
|
|
1245
|
+
readOnlyHint: false,
|
|
1246
|
+
destructiveHint: false,
|
|
1247
|
+
idempotentHint: true,
|
|
1248
|
+
openWorldHint: false,
|
|
1249
|
+
},
|
|
1124
1250
|
inputSchema: {
|
|
1125
1251
|
message_id: idParam,
|
|
1126
1252
|
project_id: projectIdParam,
|
|
@@ -1143,6 +1269,12 @@ export function registerAgents(server) {
|
|
|
1143
1269
|
}));
|
|
1144
1270
|
server.registerTool("agent_output", {
|
|
1145
1271
|
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.",
|
|
1272
|
+
annotations: {
|
|
1273
|
+
readOnlyHint: false,
|
|
1274
|
+
destructiveHint: false,
|
|
1275
|
+
idempotentHint: true,
|
|
1276
|
+
openWorldHint: false,
|
|
1277
|
+
},
|
|
1146
1278
|
inputSchema: {
|
|
1147
1279
|
name: agentNameParam,
|
|
1148
1280
|
agent_id: agentIdParam,
|
|
@@ -1172,6 +1304,12 @@ export function registerAgents(server) {
|
|
|
1172
1304
|
}));
|
|
1173
1305
|
server.registerTool("agent_close", {
|
|
1174
1306
|
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.",
|
|
1307
|
+
annotations: {
|
|
1308
|
+
readOnlyHint: false,
|
|
1309
|
+
destructiveHint: true,
|
|
1310
|
+
idempotentHint: true,
|
|
1311
|
+
openWorldHint: false,
|
|
1312
|
+
},
|
|
1175
1313
|
inputSchema: {
|
|
1176
1314
|
name: agentNameParam,
|
|
1177
1315
|
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
|
@@ -18,9 +18,15 @@ export function extendOwnedLease(projectId, key, actor, ttlSeconds) {
|
|
|
18
18
|
}
|
|
19
19
|
export function registerLeases(server) {
|
|
20
20
|
server.registerTool("lease_acquire", {
|
|
21
|
-
description: "Try to take a named lease on a shared
|
|
21
|
+
description: "Try to take a named lease on state another session could also change, such as a shared checkout, a dev database or a port (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
|
-
key: z.string().describe('Stable and specific, like "
|
|
29
|
+
key: z.string().describe('Stable and specific, like "db:dev".'),
|
|
24
30
|
ttl_seconds: z.number().int().positive(),
|
|
25
31
|
project_id: projectIdParam,
|
|
26
32
|
},
|
|
@@ -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
|
@@ -24,6 +24,12 @@ function existsWhere(table, column, value) {
|
|
|
24
24
|
function projectOwnsRows(projectId) {
|
|
25
25
|
return PROJECT_OWNER_TABLES.some((table) => existsWhere(table, "project_id", projectId));
|
|
26
26
|
}
|
|
27
|
+
function ownedRowCounts(projectId) {
|
|
28
|
+
return PROJECT_OWNER_TABLES.flatMap((table) => {
|
|
29
|
+
const n = db.prepare(`SELECT COUNT(*) AS n FROM ${table} WHERE project_id = ?`).get(projectId).n;
|
|
30
|
+
return n > 0 ? [`${table}: ${n}`] : [];
|
|
31
|
+
});
|
|
32
|
+
}
|
|
27
33
|
const pruneProjectIfEmpty = db.transaction((projectId) => {
|
|
28
34
|
if (projectOwnsRows(projectId))
|
|
29
35
|
return false;
|
|
@@ -60,6 +66,12 @@ const pruneActorIfInert = db.transaction((actorId) => {
|
|
|
60
66
|
export function registerMeta(server) {
|
|
61
67
|
server.registerTool("whoami", {
|
|
62
68
|
description: "Show this session's actor identity and effective project scope. Call this first in a new session.",
|
|
69
|
+
annotations: {
|
|
70
|
+
readOnlyHint: false,
|
|
71
|
+
destructiveHint: false,
|
|
72
|
+
idempotentHint: false,
|
|
73
|
+
openWorldHint: false,
|
|
74
|
+
},
|
|
63
75
|
inputSchema: {},
|
|
64
76
|
}, () => run(() => {
|
|
65
77
|
const actorId = currentActor();
|
|
@@ -83,6 +95,12 @@ export function registerMeta(server) {
|
|
|
83
95
|
}));
|
|
84
96
|
server.registerTool("help", {
|
|
85
97
|
description: `Hive usage guidance. Omit topic for an overview, or pass one of: ${Object.keys(HELP_TOPICS).join(", ")}.`,
|
|
98
|
+
annotations: {
|
|
99
|
+
readOnlyHint: true,
|
|
100
|
+
destructiveHint: false,
|
|
101
|
+
idempotentHint: true,
|
|
102
|
+
openWorldHint: false,
|
|
103
|
+
},
|
|
86
104
|
inputSchema: { topic: z.string().optional() },
|
|
87
105
|
}, ({ topic }) => run(() => {
|
|
88
106
|
if (!topic)
|
|
@@ -95,6 +113,12 @@ export function registerMeta(server) {
|
|
|
95
113
|
}));
|
|
96
114
|
server.registerTool("project_list", {
|
|
97
115
|
description: "List registered projects and the currently selected one.",
|
|
116
|
+
annotations: {
|
|
117
|
+
readOnlyHint: false,
|
|
118
|
+
destructiveHint: false,
|
|
119
|
+
idempotentHint: true,
|
|
120
|
+
openWorldHint: false,
|
|
121
|
+
},
|
|
98
122
|
inputSchema: {},
|
|
99
123
|
}, () => run(() => ({
|
|
100
124
|
projects: listProjects(),
|
|
@@ -102,6 +126,12 @@ export function registerMeta(server) {
|
|
|
102
126
|
})));
|
|
103
127
|
server.registerTool("project_add", {
|
|
104
128
|
description: "Register a directory as a project. Defaults to the current working directory. Returns the existing project if the path is already registered.",
|
|
129
|
+
annotations: {
|
|
130
|
+
readOnlyHint: false,
|
|
131
|
+
destructiveHint: false,
|
|
132
|
+
idempotentHint: true,
|
|
133
|
+
openWorldHint: false,
|
|
134
|
+
},
|
|
105
135
|
inputSchema: {
|
|
106
136
|
path: z.string().optional(),
|
|
107
137
|
name: z.string().optional(),
|
|
@@ -110,19 +140,45 @@ export function registerMeta(server) {
|
|
|
110
140
|
}, ({ path, name }) => run(() => addProject(path, name)));
|
|
111
141
|
server.registerTool("project_select", {
|
|
112
142
|
description: "Set which project later tools act on in this session.",
|
|
143
|
+
annotations: {
|
|
144
|
+
readOnlyHint: false,
|
|
145
|
+
destructiveHint: false,
|
|
146
|
+
idempotentHint: true,
|
|
147
|
+
openWorldHint: false,
|
|
148
|
+
},
|
|
113
149
|
inputSchema: { project_id: idParam },
|
|
114
150
|
}, ({ project_id }) => run(() => selectProjectById(project_id)));
|
|
115
151
|
server.registerTool("project_prune", {
|
|
116
|
-
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.",
|
|
117
|
-
|
|
152
|
+
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. With project_id, removes exactly that one project instead of sweeping, and only if it owns no rows; otherwise refuses and names what it owns. 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.",
|
|
153
|
+
annotations: {
|
|
154
|
+
readOnlyHint: false,
|
|
155
|
+
destructiveHint: true,
|
|
156
|
+
idempotentHint: true,
|
|
157
|
+
openWorldHint: false,
|
|
158
|
+
},
|
|
159
|
+
inputSchema: { project_id: idParam.optional() },
|
|
118
160
|
outputSchema: {
|
|
119
161
|
deleted: z.array(z.object({ id: idParam, name: z.string() })),
|
|
120
162
|
held_back: z.object({ project_id: idParam, reason: z.string() }),
|
|
121
163
|
errors: z.array(z.object({ id: idParam, name: z.string(), error: z.string() })).optional(),
|
|
122
164
|
},
|
|
123
|
-
}, () => run(() => {
|
|
165
|
+
}, ({ project_id }) => run(() => {
|
|
124
166
|
refuseIfLocked("project_prune");
|
|
125
167
|
const homeId = resolveProject().id;
|
|
168
|
+
if (project_id !== undefined) {
|
|
169
|
+
if (project_id === homeId)
|
|
170
|
+
throw new Error(`project_prune: project ${project_id} is the caller's own project and is never pruned.`);
|
|
171
|
+
const target = listProjects().find((p) => p.id === project_id);
|
|
172
|
+
if (!target)
|
|
173
|
+
throw new Error(`project_prune: no project ${project_id}. List them with project_list.`);
|
|
174
|
+
if (!pruneProjectIfEmpty.immediate(project_id)) {
|
|
175
|
+
throw new Error(`project_prune: project ${project_id} ("${target.name}") is not empty, nothing deleted. It owns ${ownedRowCounts(project_id).join(", ")}.`);
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
deleted: [{ id: target.id, name: target.name }],
|
|
179
|
+
held_back: { project_id: homeId, reason: "caller's own project" },
|
|
180
|
+
};
|
|
181
|
+
}
|
|
126
182
|
const deleted = [];
|
|
127
183
|
const errors = [];
|
|
128
184
|
for (const project of listProjects()) {
|
|
@@ -145,6 +201,12 @@ export function registerMeta(server) {
|
|
|
145
201
|
}));
|
|
146
202
|
server.registerTool("actor_prune", {
|
|
147
203
|
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.",
|
|
204
|
+
annotations: {
|
|
205
|
+
readOnlyHint: false,
|
|
206
|
+
destructiveHint: true,
|
|
207
|
+
idempotentHint: true,
|
|
208
|
+
openWorldHint: false,
|
|
209
|
+
},
|
|
148
210
|
inputSchema: {},
|
|
149
211
|
outputSchema: {
|
|
150
212
|
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(),
|