@cmgmyr/hive 1.11.8 → 1.11.9

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 CHANGED
@@ -57,7 +57,7 @@ hive is a personal daily-driver tool, released low-key. It is single-user by des
57
57
 
58
58
  | Page | What's there |
59
59
  |---|---|
60
- | [Architecture](docs/architecture.md) | Seven diagrams: process topology, module layering, spawn sequence, wake lifecycle, worker state, project scoping, store and server identity |
60
+ | [Architecture](docs/architecture.md) | Eight diagrams: process topology, module layering, spawn sequence, wake lifecycle, worker state (with the lead handoff), project scoping, store and server identity |
61
61
  | [Patterns](docs/patterns.md) | Standing trades, refused approaches, evidence standards, and guard shapes distilled from the project's own decisions and dead-ends |
62
62
  | [Concepts](docs/concepts.md) | Vocabulary, why not subagents, identity, the workflow, project scope, the shared store |
63
63
  | [Daily driver](docs/daily-driver.md) | A day with hive, starting a session, watching workers, wake-ups |
@@ -51,4 +51,4 @@ path is ever written into a committed file.
51
51
 
52
52
  ## The crew mod
53
53
 
54
- `crew/` is a second, separate plugin (`hive-crew`) that `hive lead` loads with `--plugin-dir` only when `lead_sidebar: true` is set and the lead is Claude. It is not part of the symlink install above and workers never load it. See [docs/crew.md](../docs/crew.md).
54
+ `crew/` is a second, separate plugin (`hive-crew`) that `hive lead` loads with `--plugin-dir` for Claude leads by default. Set `lead_sidebar: false` to disable it. It is not part of the symlink install above and workers never load it. See [docs/crew.md](../docs/crew.md).
@@ -1 +1 @@
1
- {"version":"1.11.8","sha":"500220b","dirty":false,"build_id":"1286494bf19194350611bfbf75688ec5a3c7e4b033b7c8dc84a101f57528dcbb"}
1
+ {"version":"1.11.9","sha":"5d08b61","dirty":false,"build_id":"c6006fe410e3627175581a5b66e7d8fbb68b92f1b2edc49a00ce1c9256bacc30"}
@@ -1,7 +1,9 @@
1
1
  import { createConnection } from "node:net";
2
2
  import { join } from "node:path";
3
- import { db } from "./db.js";
4
- import { loadProjectYml } from "./projectYml.js";
3
+ import { dataDir, db } from "./db.js";
4
+ import { harnessFor } from "./harnesses.js";
5
+ import { loadProjectYml, quietMessagingEnabled } from "./projectYml.js";
6
+ import { shellQuote } from "./tmux.js";
5
7
  export { DELIVERY_METHOD } from "./wakeSql.js";
6
8
  export const CROSS_SESSION_CLOSE = "</cross-session-message>";
7
9
  export const ENVELOPE_OPEN = '<cross-session-message from="';
@@ -23,7 +25,12 @@ function claudeWakeFrame(sender, text) {
23
25
  }
24
26
  export function quietMessagingOn(projectId) {
25
27
  const project = db.prepare("SELECT path FROM projects WHERE id = ?").get(projectId);
26
- return project !== undefined && loadProjectYml(project.path).config?.quiet_messaging === true;
28
+ return project !== undefined && quietMessagingEnabled(loadProjectYml(project.path).config);
29
+ }
30
+ export function quietMessagingHooksLaunched(projectId, command) {
31
+ const path = join(dataDir, `lead-${projectId}-hooks.json`);
32
+ const args = harnessFor("claude").briefDelivery?.settingsArgs(path);
33
+ return args !== undefined && command.includes(args.map(shellQuote).join(" "));
27
34
  }
28
35
  export function promptMarkerEvidenceSql(markerAt) {
29
36
  const evPrompt = "(CASE WHEN json_valid(ev.payload) THEN COALESCE(json_extract(ev.payload, '$.prompt'), '') ELSE ev.payload END)";
package/dist/cli.js CHANGED
@@ -38,7 +38,7 @@ import { adoptableWindow, applyLayout, claimInitialWindow, configureHiveWindow,
38
38
  import { collectCrew, crewStoreProblem } from "./crew.js";
39
39
  import { describeVisibility, formatLocal, processCounts } from "./dashboard.js";
40
40
  import { runningCommandRow, runningCommandRows, snapshotProcesses, stopAllProcesses, stopLine, stopProcess, renderStart, STOP_REASONS, } from "./processes.js";
41
- import { activeProfile, agentVarKeys, BUILT_IN_PROJECT_YML, configHash, loadProjectYml, mergedProjectVars, NO_PROFILE, PROJECT_YML_KEYS, resolveCommandDir, resolveFirstMessage, } from "./projectYml.js";
41
+ import { activeProfile, agentVarKeys, BUILT_IN_PROJECT_YML, configHash, leadSidebarEnabled, loadProjectYml, mergedProjectVars, NO_PROFILE, PROJECT_YML_KEYS, quietMessagingEnabled, resolveCommandDir, resolveFirstMessage, } from "./projectYml.js";
42
42
  import { appendClaudeLeadArgs, crewPluginDir, isTrusted, leadIdentityEnv, renderLeadPosture } from "./leadLaunch.js";
43
43
  import { handoffStatusSegment } from "./leadHandoff.js";
44
44
  import { cmdLeadHandoff, runHandoffGrace, runHandoffStart } from "./leadHandoffCli.js";
@@ -421,7 +421,7 @@ async function cmdLead(argv) {
421
421
  for (const w of warnings)
422
422
  console.log(`! ${w}`);
423
423
  const session = sessionName();
424
- const hooksPath = ensureLeadHooksFile(project.id, config?.quiet_messaging === true);
424
+ const hooksPath = ensureLeadHooksFile(project.id, quietMessagingEnabled(config));
425
425
  let leadCommand = "claude";
426
426
  if (!config) {
427
427
  console.log("No hive.yml here; starting a plain claude lead. Add a hive.yml to define project commands and a custom lead (run hive --help for the format).");
@@ -464,7 +464,7 @@ async function cmdLead(argv) {
464
464
  projectId: project.id,
465
465
  projectName: project.name,
466
466
  hooksPath,
467
- sidebarPluginDir: config?.lead_sidebar === true && leadHarness.name === "claude" ? crewPluginDir() : null,
467
+ sidebarPluginDir: leadSidebarEnabled(config) && leadHarness.name === "claude" ? crewPluginDir() : null,
468
468
  posture: renderedPosture,
469
469
  firstMessage,
470
470
  }));
@@ -749,13 +749,13 @@ const HIVE_YML_TEMPLATE = `# hive project config. Read by \`hive lead\` from the
749
749
 
750
750
  # context_checkpoint_percent: null # unset means off; integer 1-100 to enable
751
751
 
752
- # lead_turn_budget: {warn: 300, stop: 600} # optional lead statusline thresholds
752
+ # lead_turn_budget: {warn: 300, stop: 600} # optional thresholds; auto_handoff defaults on
753
753
 
754
754
  # dashboard: true # write .hive/dashboard.html (default: false)
755
755
 
756
- # lead_sidebar: true # a crew pane in a claude lead (default: false; restart the lead)
756
+ # lead_sidebar: false # disable the crew pane in a claude lead; restart the lead
757
757
 
758
- # quiet_messaging: true # wakes and worker messages reach a claude lead as one row (default: false; restart the lead)
758
+ # quiet_messaging: false # use typed wakes and worker messages instead of one row; restart the lead
759
759
 
760
760
  # vars: # substituted into the profile runbook
761
761
  # repo: owner/name # {{repo}}
package/dist/help.js CHANGED
@@ -253,7 +253,9 @@ Delivery contract:
253
253
  and the next action. Plain English, no markup.
254
254
  Waking your OWN lead pane is the exception: it is not cold, so carry the
255
255
  action and the ids and point at where the detail lives.
256
- With lead_turn_budget.auto_handoff on, a lead past its warn budget gets
256
+ With a lead_turn_budget, auto_handoff is on by default. Set
257
+ lead_turn_budget.auto_handoff to false to keep the lead in this session.
258
+ When enabled, a lead past its warn budget gets
257
259
  a "hive lead handoff request #N" wake: follow its steps, then run
258
260
  hive lead-handoff. A human prompt during the grace postpones it.
259
261
 
@@ -11,8 +11,11 @@ import { readTurnCount } from "./turnCount.js";
11
11
  export { casHandoff, HELD_REASON_HANDOFF, handoffHoldsWake, readActiveHandoff, readHandoff, readHandoffGate, SUCCESSOR_NOT_READY, } from "./leadHandoffState.js";
12
12
  export const HANDOFF_PAD_NAME = "hive-lead-handoff";
13
13
  export const sha256 = (text) => createHash("sha256").update(text).digest("hex");
14
+ function autoHandoffEnabled(budget) {
15
+ return budget !== null && (budget.auto_handoff ?? true);
16
+ }
14
17
  export function handoffEligibility(lead, budget, snapshot = readLeadSafetySnapshot(lead.id)) {
15
- if (budget === null || budget.auto_handoff !== true)
18
+ if (budget === null || !autoHandoffEnabled(budget))
16
19
  return { eligible: false, why: "auto_handoff is not enabled" };
17
20
  if (harnessFor(lead.command).name !== "claude")
18
21
  return { eligible: false, why: "the lead is not a Claude lead" };
@@ -274,7 +277,7 @@ export function driveLeadHandoff(actorId, budgetFor) {
274
277
  settleAfterRespawn(row, lead);
275
278
  return;
276
279
  }
277
- if (budget?.auto_handoff !== true) {
280
+ if (!autoHandoffEnabled(budget)) {
278
281
  failHandoff(row.id, "lead_turn_budget.auto_handoff is no longer on");
279
282
  return;
280
283
  }
@@ -364,7 +367,7 @@ export function padProblem(pad, projectId, revision) {
364
367
  }
365
368
  // The final quiet check. A screen or probe that cannot be read waits; it never establishes quiet.
366
369
  export async function respawnVerdict(row, lead, budget) {
367
- if (budget?.auto_handoff !== true)
370
+ if (!autoHandoffEnabled(budget))
368
371
  return { fail: "lead_turn_budget.auto_handoff is no longer on" };
369
372
  if (lead === undefined || lead.pane_pid !== row.predecessor_pane_pid || lead.tmux_target !== row.pane_target) {
370
373
  return { fail: "the lead row no longer names the pane this handoff was requested for" };
@@ -7,7 +7,7 @@ import { cliPath } from "./dispatcher.js";
7
7
  import { FIRST_MESSAGE_SHA_ENV, firstMessageDigest } from "./firstMessage.js";
8
8
  import { ensureLeadHooksFile } from "./hooks.js";
9
9
  import { appendClaudeLeadArgs, crewPluginDir, isTrusted, leadIdentityEnv, renderLeadPosture } from "./leadLaunch.js";
10
- import { loadProjectYml } from "./projectYml.js";
10
+ import { leadSidebarEnabled, loadProjectYml, quietMessagingEnabled } from "./projectYml.js";
11
11
  import { buildEnvFlags } from "./spawn.js";
12
12
  import { panePidForRecord, rowOwnership, shellQuote, tmux, TmuxTimeoutError } from "./tmux.js";
13
13
  import { automationBlocker, failHandoff, missingHeadings, padProblem, readPad, RESPAWN_WAIT_LIMIT_SECONDS, respawnVerdict, runningLeadByActor, sha256, successorPrompt, } from "./leadHandoff.js";
@@ -152,8 +152,8 @@ async function successorLaunch(row, lead, attempt, token) {
152
152
  harness,
153
153
  projectId: lead.project_id,
154
154
  projectName: lead.name,
155
- hooksPath: ensureLeadHooksFile(lead.project_id, config?.quiet_messaging === true),
156
- sidebarPluginDir: config?.lead_sidebar === true ? crewPluginDir() : null,
155
+ hooksPath: ensureLeadHooksFile(lead.project_id, quietMessagingEnabled(config)),
156
+ sidebarPluginDir: leadSidebarEnabled(config) ? crewPluginDir() : null,
157
157
  posture: renderLeadPosture(config).rendered,
158
158
  firstMessage,
159
159
  });
@@ -1,5 +1,5 @@
1
1
  import { dataDir, db } from "./db.js";
2
- import { CROSS_SESSION_CLOSE, promptMarkerEvidenceSql, postClaudeWake, quietMessagingOn, socketGraceDue, socketSenderAddress, SOCKET_MESSAGE_FOOTER, } from "./claudeWake.js";
2
+ import { CROSS_SESSION_CLOSE, promptMarkerEvidenceSql, quietMessagingHooksLaunched, postClaudeWake, quietMessagingOn, socketGraceDue, socketSenderAddress, SOCKET_MESSAGE_FOOTER, } from "./claudeWake.js";
3
3
  import { harnessFor, paneClassifierFor } from "./harnesses.js";
4
4
  import { LEAD_MESSAGE_THRESHOLD, MESSAGE_METHOD, MESSAGE_RETENTION, MESSAGE_STATUS, renderLeadMessage, senderTag, shortenedSendNote, storeLeadMessage, } from "./leadMessage.js";
5
5
  import { readHandoffGate } from "./leadHandoffState.js";
@@ -30,7 +30,7 @@ function eligible(options) {
30
30
  const sender = db
31
31
  .prepare("SELECT 1 AS hit FROM agents WHERE project_id = ? AND actor_id = ? AND status = 'running' AND kind = 'agent'")
32
32
  .get(projectId, fromActor);
33
- return sender !== undefined && quietMessagingOn(projectId);
33
+ return sender !== undefined && quietMessagingOn(projectId) && quietMessagingHooksLaunched(projectId, target.command);
34
34
  }
35
35
  function registeredSocket(target) {
36
36
  return (target.claude_messaging_socket !== "" &&
@@ -18,8 +18,8 @@ export const BUILT_IN_PROJECT_YML = Object.freeze({
18
18
  context_checkpoint_percent: null,
19
19
  lead_turn_budget: null,
20
20
  dashboard: false,
21
- lead_sidebar: false,
22
- quiet_messaging: false,
21
+ lead_sidebar: true,
22
+ quiet_messaging: true,
23
23
  vars: Object.freeze({}),
24
24
  processes: Object.freeze({}),
25
25
  });
@@ -33,6 +33,12 @@ export function activeProfile(config) {
33
33
  const name = config?.profile;
34
34
  return name == null || name === NO_PROFILE ? null : name;
35
35
  }
36
+ export function quietMessagingEnabled(config) {
37
+ return config?.quiet_messaging ?? BUILT_IN_PROJECT_YML.quiet_messaging;
38
+ }
39
+ export function leadSidebarEnabled(config) {
40
+ return config?.lead_sidebar ?? BUILT_IN_PROJECT_YML.lead_sidebar;
41
+ }
36
42
  // First entry is the default; absent or empty means claude only (todo 526).
37
43
  export function allowedAgents(config) {
38
44
  const list = config?.agents;
@@ -184,8 +190,10 @@ function applyLayer(root, path, source, config, sources, warnings) {
184
190
  if (value.auto_handoff !== undefined) {
185
191
  if (typeof value.auto_handoff === "boolean")
186
192
  budget.auto_handoff = value.auto_handoff;
187
- else
193
+ else {
194
+ budget.auto_handoff = false;
188
195
  warn(`lead_turn_budget.auto_handoff must be true or false; ignoring "${String(value.auto_handoff)}", so automatic handoff stays off.`);
196
+ }
189
197
  }
190
198
  config.lead_turn_budget = budget;
191
199
  sources.lead_turn_budget = source;
@@ -210,7 +218,7 @@ function applyLayer(root, path, source, config, sources, warnings) {
210
218
  if (has("lead_sidebar")) {
211
219
  const value = root.lead_sidebar;
212
220
  if (value == null) {
213
- config.lead_sidebar = false;
221
+ config.lead_sidebar = BUILT_IN_PROJECT_YML.lead_sidebar;
214
222
  sources.lead_sidebar = source;
215
223
  }
216
224
  else if (typeof value === "boolean") {
@@ -223,7 +231,7 @@ function applyLayer(root, path, source, config, sources, warnings) {
223
231
  if (has("quiet_messaging")) {
224
232
  const value = root.quiet_messaging;
225
233
  if (value == null) {
226
- config.quiet_messaging = false;
234
+ config.quiet_messaging = BUILT_IN_PROJECT_YML.quiet_messaging;
227
235
  sources.quiet_messaging = source;
228
236
  }
229
237
  else if (typeof value === "boolean") {
package/dist/scheduler.js CHANGED
@@ -23,7 +23,7 @@ import { evaluateLeadWatch, leadSubscription } from "./leadWatch.js";
23
23
  import { describeLiveTasks, describeOneTask, liveBackgroundTasks, STALL_BOUND_SECONDS, } from "./backgroundTasks.js";
24
24
  import { MESSAGE_MAX_ROWS, MESSAGE_RETENTION } from "./leadMessage.js";
25
25
  import { PANE_LEASE_SETTLE_MS, releasePaneLease, tryAcquirePaneLease } from "./leaseStore.js";
26
- import { CROSS_SESSION_CLOSE, postClaudeWake, promptMarkerEvidenceSql, quietMessagingOn, socketGraceDue, socketSenderAddress, SOCKET_WAKE_FOOTER, } from "./claudeWake.js";
26
+ import { CROSS_SESSION_CLOSE, quietMessagingHooksLaunched, postClaudeWake, promptMarkerEvidenceSql, quietMessagingOn, socketGraceDue, socketSenderAddress, SOCKET_WAKE_FOOTER, } from "./claudeWake.js";
27
27
  import { driveLeadHandoff } from "./leadHandoff.js";
28
28
  import { handoffHoldsWake } from "./leadHandoffState.js";
29
29
  import { FIRST_MESSAGE_LOG_STATE, humanPromptSql, readLeadTurnState } from "./leadState.js";
@@ -1949,7 +1949,8 @@ function socketRoute(timer, owner) {
1949
1949
  timer.deliver_messaging_pane_pid !== "" &&
1950
1950
  timer.deliver_messaging_pane_pid === timer.deliver_pane_pid &&
1951
1951
  timer.deliver_row_target === timer.deliver_pane &&
1952
- quietMessagingOn(timer.project_id));
1952
+ quietMessagingOn(timer.project_id) &&
1953
+ quietMessagingHooksLaunched(timer.project_id, timer.deliver_command));
1953
1954
  }
1954
1955
  function socketStillRegistered(timer, owner) {
1955
1956
  requireStillOwned(owner.rowId, owner.identity, `wake #${timer.id} socket delivery`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmgmyr/hive",
3
- "version": "1.11.8",
3
+ "version": "1.11.9",
4
4
  "description": "Run a crew of Claude Code and Codex workers from one lead session: visible tmux panes, exact state from hooks, one local SQLite store.",
5
5
  "type": "module",
6
6
  "bin": {