@cmgmyr/hive 0.1.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/LICENSE +21 -0
- package/README.md +70 -0
- package/claude-plugin/.claude-plugin/plugin.json +7 -0
- package/claude-plugin/README.md +47 -0
- package/claude-plugin/hooks/hooks.json +14 -0
- package/claude-plugin/kickoff.mjs +41 -0
- package/claude-plugin/skills/cleanup/SKILL.md +60 -0
- package/dist/abi.js +220 -0
- package/dist/abiProbe.js +9 -0
- package/dist/backgroundTasks.js +51 -0
- package/dist/backup.js +378 -0
- package/dist/brief.js +113 -0
- package/dist/build-info.json +1 -0
- package/dist/claudeDir.js +5 -0
- package/dist/cli.js +2901 -0
- package/dist/codexHome.js +297 -0
- package/dist/config.js +72 -0
- package/dist/context.js +256 -0
- package/dist/contextCheckpoint.js +55 -0
- package/dist/dashboard.js +1158 -0
- package/dist/dataDir.js +89 -0
- package/dist/db.js +421 -0
- package/dist/dispatcher.js +150 -0
- package/dist/firstPrompt.js +6 -0
- package/dist/harnesses.js +173 -0
- package/dist/help.js +353 -0
- package/dist/hook.js +197 -0
- package/dist/hooks.js +50 -0
- package/dist/index.js +34 -0
- package/dist/kickoff.js +191 -0
- package/dist/leadMessage.js +98 -0
- package/dist/mcpConfig.js +121 -0
- package/dist/processes.js +161 -0
- package/dist/profiles.js +278 -0
- package/dist/projectYml.js +254 -0
- package/dist/prompts.js +18 -0
- package/dist/ptys.js +146 -0
- package/dist/result.js +53 -0
- package/dist/scheduler.js +1622 -0
- package/dist/sessionProbe.js +131 -0
- package/dist/slug.js +29 -0
- package/dist/spawn.js +370 -0
- package/dist/stateProvenance.js +97 -0
- package/dist/statusline.js +82 -0
- package/dist/strictInput.js +52 -0
- package/dist/teardown.js +89 -0
- package/dist/tmux.js +1295 -0
- package/dist/tools/agents.js +1246 -0
- package/dist/tools/kv.js +89 -0
- package/dist/tools/leases.js +87 -0
- package/dist/tools/meta.js +183 -0
- package/dist/tools/pads.js +279 -0
- package/dist/tools/params.js +14 -0
- package/dist/tools/todos.js +382 -0
- package/dist/tools/wakes.js +466 -0
- package/dist/transcript.js +123 -0
- package/dist/version.js +59 -0
- package/package.json +62 -0
- package/profiles/orchestration/posture.md +27 -0
- package/profiles/orchestration/runbook.md +72 -0
- package/profiles/orchestration/worker.md +38 -0
- package/profiles/simple/posture.md +14 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { claudeConfigDir } from "./claudeDir.js";
|
|
7
|
+
import { claudeWindowPath } from "./transcript.js";
|
|
8
|
+
import { shellQuote } from "./tmux.js";
|
|
9
|
+
export function effectiveClaudeStatusLine(cwd) {
|
|
10
|
+
let setting = null;
|
|
11
|
+
for (const path of [
|
|
12
|
+
join(claudeConfigDir(), "settings.json"),
|
|
13
|
+
join(cwd, ".claude", "settings.json"),
|
|
14
|
+
join(cwd, ".claude", "settings.local.json"),
|
|
15
|
+
]) {
|
|
16
|
+
try {
|
|
17
|
+
const value = JSON.parse(readFileSync(path, "utf8")).statusLine;
|
|
18
|
+
if (value === undefined)
|
|
19
|
+
continue;
|
|
20
|
+
setting = value?.type === "command" && typeof value.command === "string" ? value : null;
|
|
21
|
+
}
|
|
22
|
+
catch { }
|
|
23
|
+
}
|
|
24
|
+
return setting;
|
|
25
|
+
}
|
|
26
|
+
export function recordClaudeWindowSize(actorId, statusLineInput) {
|
|
27
|
+
if (!actorId.startsWith("agent:"))
|
|
28
|
+
return;
|
|
29
|
+
let staging;
|
|
30
|
+
try {
|
|
31
|
+
const size = JSON.parse(statusLineInput)?.context_window?.context_window_size;
|
|
32
|
+
if (typeof size !== "number" || !Number.isSafeInteger(size) || size <= 0)
|
|
33
|
+
return;
|
|
34
|
+
const path = claudeWindowPath(actorId);
|
|
35
|
+
const body = JSON.stringify(size) + "\n";
|
|
36
|
+
try {
|
|
37
|
+
if (readFileSync(path, "utf8") === body)
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
catch { }
|
|
41
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
42
|
+
staging = `${path}.${randomUUID()}.tmp`;
|
|
43
|
+
writeFileSync(staging, body, { flag: "wx", mode: 0o600 });
|
|
44
|
+
renameSync(staging, path);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
if (staging) {
|
|
50
|
+
try {
|
|
51
|
+
unlinkSync(staging);
|
|
52
|
+
}
|
|
53
|
+
catch { }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export function statusLineEntry(userSetting) {
|
|
58
|
+
const script = fileURLToPath(import.meta.url);
|
|
59
|
+
return {
|
|
60
|
+
...userSetting,
|
|
61
|
+
type: "command",
|
|
62
|
+
command: `${shellQuote(process.execPath)} ${shellQuote(script)} ${shellQuote(userSetting?.command ?? "")}`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
66
|
+
try {
|
|
67
|
+
const input = readFileSync(0);
|
|
68
|
+
recordClaudeWindowSize(process.env.HIVE_AGENT_ID ?? "", input.toString("utf8"));
|
|
69
|
+
const command = process.argv[2];
|
|
70
|
+
if (command) {
|
|
71
|
+
try {
|
|
72
|
+
process.stdout.write(execFileSync("/bin/sh", ["-c", command], { input, stdio: ["pipe", "pipe", "inherit"] }));
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
const stdout = error.stdout;
|
|
76
|
+
if (stdout)
|
|
77
|
+
process.stdout.write(stdout);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch { }
|
|
82
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
function isBuiltSchema(value) {
|
|
3
|
+
return typeof value === "object" && value !== null && ("_zod" in value || "_def" in value);
|
|
4
|
+
}
|
|
5
|
+
function strictSchemaFor(toolName, inputSchema) {
|
|
6
|
+
if (inputSchema === undefined)
|
|
7
|
+
return undefined;
|
|
8
|
+
if (isBuiltSchema(inputSchema)) {
|
|
9
|
+
throw new Error(`Tool "${toolName}" passed a built Zod schema as its inputSchema. hive registers tools with a ` +
|
|
10
|
+
"raw shape so src/strictInput.ts can make every tool strict in one place. Pass the shape, or " +
|
|
11
|
+
"make your schema strict yourself and widen this check deliberately.");
|
|
12
|
+
}
|
|
13
|
+
return z.strictObject(inputSchema);
|
|
14
|
+
}
|
|
15
|
+
// The SDK requires structuredContent whenever outputSchema is declared but never populates it itself,
|
|
16
|
+
// so this stays scoped to declaring tools rather than attached blanket in src/result.ts's ok().
|
|
17
|
+
function withStructuredContent(name, cb) {
|
|
18
|
+
return async (...args) => {
|
|
19
|
+
const result = (await cb(...args));
|
|
20
|
+
if (result.isError || result.structuredContent)
|
|
21
|
+
return result;
|
|
22
|
+
const first = result.content[0];
|
|
23
|
+
const text = first?.type === "text" ? first.text : undefined;
|
|
24
|
+
let parsed;
|
|
25
|
+
try {
|
|
26
|
+
parsed = text !== undefined ? JSON.parse(text) : undefined;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
parsed = undefined;
|
|
30
|
+
}
|
|
31
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
32
|
+
result.structuredContent = parsed;
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
content: [
|
|
37
|
+
{
|
|
38
|
+
type: "text",
|
|
39
|
+
text: `hive: tool "${name}" declares outputSchema but its result was not a JSON object, so ` +
|
|
40
|
+
"src/strictInput.ts's withStructuredContent could not build structuredContent from it. This " +
|
|
41
|
+
"is a bug in the tool's handler, not in this call's input.",
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
isError: true,
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export function enforceStrictInput(server) {
|
|
49
|
+
const register = server.registerTool.bind(server);
|
|
50
|
+
server.registerTool = ((name, config, cb) => register(name, { ...config, inputSchema: strictSchemaFor(name, config.inputSchema) }, (config.outputSchema ? withStructuredContent(name, cb) : cb)));
|
|
51
|
+
return server;
|
|
52
|
+
}
|
package/dist/teardown.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
export const TEARDOWN_LOG = "teardowns.jsonl";
|
|
4
|
+
// A backstop against a runaway writer, not a retention policy.
|
|
5
|
+
export const TEARDOWN_MAX_RECORDS = 200;
|
|
6
|
+
export const NOT_ATTRIBUTED = "not attributed";
|
|
7
|
+
export const TEARDOWN_TRIGGER = "no panes on the socket";
|
|
8
|
+
export function teardownLogPath(dataDir) {
|
|
9
|
+
return join(dataDir, TEARDOWN_LOG);
|
|
10
|
+
}
|
|
11
|
+
export function sqlNow(now = new Date()) {
|
|
12
|
+
return now.toISOString().replace("T", " ").slice(0, 23);
|
|
13
|
+
}
|
|
14
|
+
const sqlToDate = (ts) => new Date(`${ts.replace(" ", "T")}Z`);
|
|
15
|
+
export function teardownWindow(from, to, basis) {
|
|
16
|
+
const seconds = (sqlToDate(to).getTime() - sqlToDate(from).getTime()) / 1000;
|
|
17
|
+
return { from, to, basis, width_seconds: Number.isFinite(seconds) ? Math.max(0, seconds) : 0 };
|
|
18
|
+
}
|
|
19
|
+
export function appendTeardown(dataDir, record) {
|
|
20
|
+
const path = teardownLogPath(dataDir);
|
|
21
|
+
const line = JSON.stringify(record);
|
|
22
|
+
const existing = existsSync(path) ? readFileSync(path, "utf8").split("\n").filter(Boolean) : [];
|
|
23
|
+
if (existing.length + 1 > TEARDOWN_MAX_RECORDS) {
|
|
24
|
+
// Trimming rewrites the whole file, so it goes via a temp path and a rename: a crash mid-write
|
|
25
|
+
// would otherwise cost the entire log, and this file is the only durable trace of the thing it
|
|
26
|
+
// describes.
|
|
27
|
+
const staging = `${path}.trimming`;
|
|
28
|
+
writeFileSync(staging, `${[...existing, line].slice(-TEARDOWN_MAX_RECORDS).join("\n")}\n`);
|
|
29
|
+
renameSync(staging, path);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
appendFileSync(path, `${line}\n`);
|
|
33
|
+
}
|
|
34
|
+
// Every field a reader DEREFERENCES is checked, members included. project_id is deliberately not
|
|
35
|
+
// required: a record written before that field existed is readable, and its members count as
|
|
36
|
+
// unknown-project rather than costing the whole record.
|
|
37
|
+
function isTeardownMember(v) {
|
|
38
|
+
const m = v;
|
|
39
|
+
return (typeof m === "object" &&
|
|
40
|
+
m !== null &&
|
|
41
|
+
typeof m.agent_id === "number" &&
|
|
42
|
+
typeof m.actor_id === "string" &&
|
|
43
|
+
typeof m.name === "string" &&
|
|
44
|
+
typeof m.cwd === "string" &&
|
|
45
|
+
typeof m.session_id === "string" &&
|
|
46
|
+
typeof m.swept === "boolean");
|
|
47
|
+
}
|
|
48
|
+
function isTeardownRecord(v) {
|
|
49
|
+
const r = v;
|
|
50
|
+
return (typeof r === "object" &&
|
|
51
|
+
r !== null &&
|
|
52
|
+
typeof r.detected_at === "string" &&
|
|
53
|
+
typeof r.socket === "string" &&
|
|
54
|
+
typeof r.attribution === "string" &&
|
|
55
|
+
typeof r.window === "object" &&
|
|
56
|
+
r.window !== null &&
|
|
57
|
+
typeof r.window.from === "string" &&
|
|
58
|
+
typeof r.window.to === "string" &&
|
|
59
|
+
typeof r.window.width_seconds === "number" &&
|
|
60
|
+
Array.isArray(r.crew) &&
|
|
61
|
+
r.crew.every(isTeardownMember));
|
|
62
|
+
}
|
|
63
|
+
// This file sits in the user's data dir and doctor is what people run once things are ALREADY
|
|
64
|
+
// broken, so a record hive did not write must cost that record and never the report.
|
|
65
|
+
export function readTeardowns(dataDir) {
|
|
66
|
+
const path = teardownLogPath(dataDir);
|
|
67
|
+
if (!existsSync(path))
|
|
68
|
+
return [];
|
|
69
|
+
const records = [];
|
|
70
|
+
for (const line of readFileSync(path, "utf8").split("\n")) {
|
|
71
|
+
if (!line.trim())
|
|
72
|
+
continue;
|
|
73
|
+
try {
|
|
74
|
+
const parsed = JSON.parse(line);
|
|
75
|
+
if (isTeardownRecord(parsed))
|
|
76
|
+
records.push(parsed);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// A half-written last line costs the reader that record, never the whole file.
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return records;
|
|
83
|
+
}
|
|
84
|
+
export function describeTeardownWindow(w) {
|
|
85
|
+
const width = w.width_seconds < 90 ? `${Math.round(w.width_seconds)}s` : `${Math.round(w.width_seconds / 60)}m`;
|
|
86
|
+
return w.basis === "observed"
|
|
87
|
+
? `between ${w.from} and ${w.to} (${width} wide; hive was watching this socket and saw it alive at the start of that window)`
|
|
88
|
+
: `between ${w.from} and ${w.to} (${width} wide; NO hive process survived to watch, so the start is only the newest evidence in the store, not a sighting)`;
|
|
89
|
+
}
|