@cmgmyr/hive 1.11.6 → 1.11.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/wakeSql.js CHANGED
@@ -1 +1,135 @@
1
- export const ACTIVE_TIMER_WHERE = "cancelled_at IS NULL AND (fired_at IS NULL OR repeat_every_ms IS NOT NULL)";
1
+ export const DELIVERY_METHOD = {
2
+ socket: "socket",
3
+ socketRepeating: "socket-repeating",
4
+ pty: "pty",
5
+ ptyAfterSocketTimeout: "pty-after-socket-timeout",
6
+ };
7
+ export function activeWakeWhere(prefix = "") {
8
+ return `${prefix}cancelled_at IS NULL AND (${prefix}fired_at IS NULL OR ${prefix}repeat_every_ms IS NOT NULL)`;
9
+ }
10
+ export const ACTIVE_TIMER_WHERE = activeWakeWhere();
11
+ export const LOG_RETENTION = "-7 days";
12
+ export const NOTICE_RETRY_AFTER = "-60 seconds";
13
+ export function pendingOneShotWhere(prefix = "") {
14
+ return `${prefix}cancelled_at IS NULL AND ${prefix}fired_at IS NULL`;
15
+ }
16
+ export function firedOneShotWhere(prefix = "") {
17
+ return `${prefix}cancelled_at IS NULL AND ${prefix}fired_at IS NOT NULL AND ${prefix}repeat_every_ms IS NULL`;
18
+ }
19
+ export function heldWakeWhere(prefix = "") {
20
+ return `${prefix}held_at IS NOT NULL`;
21
+ }
22
+ export function sentWakeAtSql(prefix = "") {
23
+ return `COALESCE(${prefix}socket_attempt_at, ${prefix}typed_at)`;
24
+ }
25
+ export function schedulerCandidateWhere(prefix = "") {
26
+ return `${prefix}cancelled_at IS NULL AND (
27
+ (${prefix}kind = 'delay' AND ${prefix}due_at <= datetime('now')
28
+ AND (${prefix}fired_at IS NULL OR ${prefix}repeat_every_ms IS NOT NULL))
29
+ OR (${prefix}kind != 'delay' AND ${prefix}fired_at IS NULL)
30
+ )`;
31
+ }
32
+ export const socketAwaitingWhere = (prefix = "") => `${prefix}delivery_method = '${DELIVERY_METHOD.socket}' AND ${prefix}confirmed_at IS NULL AND ${prefix}typed_at IS NULL AND ${prefix}cancelled_at IS NULL`;
33
+ export const currentAttempt = (prefix = "") => `${prefix}socket_attempt_at IS NOT NULL AND ${prefix}confirmed_at IS NULL AND ${prefix}typed_at IS NULL AND ${prefix}cancelled_at IS NULL
34
+ AND ${prefix}fired_at IS NOT NULL AND ${prefix}socket_attempt_at >= ${prefix}fired_at`;
35
+ export const repeatingSocketAwaitingWhere = (prefix = "") => `${prefix}delivery_method = '${DELIVERY_METHOD.socketRepeating}' AND ${currentAttempt(prefix)}`;
36
+ export const FIRING_DROPPED_NOTE = "dropped";
37
+ export const repeatingFallbackClaimedWhere = (prefix = "") => `${prefix}repeat_every_ms IS NOT NULL AND ${prefix}delivery_method = '${DELIVERY_METHOD.ptyAfterSocketTimeout}' AND ${currentAttempt(prefix)}
38
+ AND ${prefix}socket_delivery_note IS NOT '${FIRING_DROPPED_NOTE}'`;
39
+ export const repeatingFiringPendingWhere = (prefix = "") => `((${repeatingSocketAwaitingWhere(prefix)}) OR (${repeatingFallbackClaimedWhere(prefix)}))
40
+ AND ${prefix}socket_attempt_at >= datetime('now', '${LOG_RETENTION}')`;
41
+ export const repeatingSocketFiringWhere = (prefix = "") => `${prefix}repeat_every_ms IS NOT NULL AND COALESCE(${prefix}delivery_method, '') IN ('${DELIVERY_METHOD.socketRepeating}', '${DELIVERY_METHOD.ptyAfterSocketTimeout}')`;
42
+ export function spentUntypedNotice(alias) {
43
+ return `${alias}.fired_at IS NOT NULL AND COALESCE(${alias}.typed_at, ${alias}.socket_attempt_at) IS NULL ` +
44
+ `AND ${alias}.cancelled_at IS NULL AND ${alias}.fired_at < datetime('now', '${NOTICE_RETRY_AFTER}')`;
45
+ }
46
+ export function watchWatchingWhere(prefix = "") {
47
+ return `${prefix}cancelled_at IS NULL AND ${prefix}fired_at IS NULL
48
+ AND (${prefix}max_wait_at IS NULL OR ${prefix}max_wait_at > datetime('now'))`;
49
+ }
50
+ export function handoffSocketInFlightWhere(prefix = "") {
51
+ return `${prefix}fired_at IS NOT NULL AND ${prefix}cancelled_at IS NULL
52
+ AND ${prefix}delivery_method LIKE 'socket%' AND ${prefix}confirmed_at IS NULL
53
+ AND ${prefix}socket_attempt_at >= datetime('now', ?)`;
54
+ }
55
+ export function holdableWakeWhere(prefix = "") {
56
+ return `${prefix}cancelled_at IS NULL
57
+ AND (${prefix}fired_at IS NULL OR (${prefix}repeat_every_ms IS NOT NULL AND ${prefix}due_at = ? AND (? IS NULL OR ${prefix}fire_count = ?))
58
+ OR (${socketAwaitingWhere(prefix)}))`;
59
+ }
60
+ export function wakeConfirmation(row, hasChannel) {
61
+ if (row.typed_at == null && row.socket_attempt_at == null)
62
+ return null;
63
+ if (row.confirmed_at != null)
64
+ return "confirmed";
65
+ if (!hasChannel)
66
+ return "no_confirmation_channel";
67
+ if (row.typed_busy === 1)
68
+ return "unconfirmed_busy";
69
+ return "unconfirmed";
70
+ }
71
+ export const HELD_REASON_MODAL_CHOICE = "pane is awaiting a modal choice (folder-trust or /model picker)";
72
+ export const HELD_REASON_LEAD_BOOT_INPUT_BOX = "the lead's input box is not on screen yet; holding while its session starts";
73
+ export const HELD_REASON_LEAD_PANE_DEAD = "the lead's pane is not live right now (likely mid-restart); lead-owned wakes are exempt from " +
74
+ "cancellation for this alone, so it is held rather than lost";
75
+ export const HELD_REASON_PANE_REISSUED_PREFIX = "the pane id recorded for this wake now belongs to a different pane than the one it was set " +
76
+ "against (its pid no longer matches, most likely a tmux server restart reissuing the id); held " +
77
+ "rather than typed into the wrong pane - ";
78
+ export const HELD_REASON_PANE_REISSUED_LEAD = `${HELD_REASON_PANE_REISSUED_PREFIX}run \`hive lead\` to re-point it at the live one`;
79
+ export const HELD_REASON_PANE_REISSUED_WORKER = `${HELD_REASON_PANE_REISSUED_PREFIX}nothing re-points a worker's wake automatically, so cancel ` +
80
+ "it with wake_cancel and set a fresh one once the worker's pane is confirmed live, or leave it: " +
81
+ "it will keep holding rather than deliver wrongly";
82
+ export const HELD_REASON_PANE_REISSUED_THEN_DEAD = `${HELD_REASON_PANE_REISSUED_PREFIX}the pane it was reissued to has since gone dead too; nothing ` +
83
+ "re-points a worker's wake automatically, so cancel it with wake_cancel (any running lead may do " +
84
+ "this even though the wake is not theirs) if it is no longer needed";
85
+ export function wasHeldForPaneReissue(heldReason) {
86
+ return heldReason != null && heldReason.startsWith(HELD_REASON_PANE_REISSUED_PREFIX);
87
+ }
88
+ export const HELD_REASON_PANE_UNKNOWN_PREFIX = "pane ownership for this wake's row cannot be verified (no pane pid was recorded for it, its row now " +
89
+ "records a different pane, or the pane is on another tmux socket); held rather than typed into a pane " +
90
+ "that may not be its own - ";
91
+ export const HELD_REASON_PANE_UNKNOWN_LEAD = `${HELD_REASON_PANE_UNKNOWN_PREFIX}run \`hive lead\` to start or adopt a lead with a recorded pane and re-point it`;
92
+ export const HELD_REASON_PANE_UNKNOWN_WORKER = `${HELD_REASON_PANE_UNKNOWN_PREFIX}nothing re-points a worker's wake automatically, so cancel it with ` +
93
+ "wake_cancel (any running lead may, even though the wake is not theirs), or leave it: it keeps holding " +
94
+ "rather than deliver wrongly";
95
+ export const HELD_REASON_PANE_UNKNOWN_THEN_DEAD = `${HELD_REASON_PANE_UNKNOWN_PREFIX}that pane has since gone dead too; nothing re-points a worker's wake ` +
96
+ "automatically, so cancel it with wake_cancel (any running lead may) if it is no longer needed";
97
+ export function wasHeldForPaneIdentity(heldReason) {
98
+ return wasHeldForPaneReissue(heldReason) || (heldReason?.startsWith(HELD_REASON_PANE_UNKNOWN_PREFIX) ?? false);
99
+ }
100
+ export const identityThenDead = (heldReason) => wasHeldForPaneReissue(heldReason) ? HELD_REASON_PANE_REISSUED_THEN_DEAD : HELD_REASON_PANE_UNKNOWN_THEN_DEAD;
101
+ export const HELD_REASON_ACTOR_CLOSED = "delivery actor closed: the row this wake was bound to is closed, so nothing was typed and the wake was " +
102
+ "cancelled rather than delivered into a pane that row no longer owns";
103
+ export const HELD_REASON_LEAD_ROW_CLOSED = "the lead's row is closed, so no pane is this wake's to type into; held until `hive lead` starts or adopts a " +
104
+ "lead and re-points it";
105
+ export function isLeadRowClosedHold(heldReason) {
106
+ return heldReason === HELD_REASON_LEAD_ROW_CLOSED;
107
+ }
108
+ export const HELD_REASON_OWNERSHIP_LOST_AFTER_CLAIM = "delivery stopped after claim: ";
109
+ export const HELD_REASON_UNCLASSIFIABLE_PANE_PREFIX = "the pane's harness is not one hive can classify; ";
110
+ export const HELD_REASON_UNCLASSIFIABLE_PANE = `${HELD_REASON_UNCLASSIFIABLE_PANE_PREFIX}neither the dialog guard nor the unsubmitted-text ` +
111
+ "guard can read this screen and delivery would type blind. wake_set and wake_when_idle refuse such a " +
112
+ "target outright now, so this wake predates that or its pane's command changed underneath it. Nothing " +
113
+ "hive does on its own clears this: it lifts only when that pane is replaced by one hive starts itself " +
114
+ "(`hive lead` after the pane exits). Cancel it with wake_cancel (any running lead may, even though the " +
115
+ "wake is not theirs) and drive that pane with agent_send(keys: [...])";
116
+ export function isUnclassifiablePaneHold(heldReason) {
117
+ return heldReason != null && heldReason.startsWith(HELD_REASON_UNCLASSIFIABLE_PANE_PREFIX);
118
+ }
119
+ export const HELD_REASON_COPY_MODE = "the pane is in tmux copy mode; tmux clears its bracketed-paste flag there, so the body would arrive " +
120
+ "with no paste markers (losing everything before the last 1022-byte write) and the Enter would be " +
121
+ "eaten by the mode instead of submitting - both silently, with tmux reporting success. It delivers " +
122
+ "once whoever is reading that pane leaves copy mode";
123
+ export const HELD_REASON_UNSUBMITTED_INPUT_PREFIX = "the pane's input box has unsubmitted human text; ";
124
+ export const HELD_REASON_UNSUBMITTED_INPUT = `${HELD_REASON_UNSUBMITTED_INPUT_PREFIX}delivering now would paste the wake body onto it and submit ` +
125
+ "both as one message";
126
+ export const HELD_REASON_UNSUBMITTED_INPUT_OWNER_UNRESOLVED = `${HELD_REASON_UNSUBMITTED_INPUT_PREFIX}hive could not resolve a live pane for the wake's owner on ` +
127
+ "this tick - retrying, since that may be transient rather than permanent";
128
+ export function isUnsubmittedInputHold(heldReason) {
129
+ return heldReason != null && heldReason.startsWith(HELD_REASON_UNSUBMITTED_INPUT_PREFIX);
130
+ }
131
+ export const HELD_REASON_CONVERSATION = "a human talked to this lead more recently than the conversation-hold window; holding so a wake " +
132
+ "does not split an in-progress discussion - it delivers once the window passes or the hold's own " +
133
+ "ceiling is reached, whichever comes first";
134
+ export const HELD_REASON_HANDOFF = "the lead is handing off to a fresh session; held through the wind-down and the successor's boot, and delivered to " +
135
+ "the lead row once the successor completes its first turn, or when the handoff fails or releases its hold";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmgmyr/hive",
3
- "version": "1.11.6",
3
+ "version": "1.11.8",
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": {