hivecode-mcp 0.14.1 → 0.14.3

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.
Files changed (72) hide show
  1. package/HIVE_RULES.md +98 -98
  2. package/LICENSE +21 -21
  3. package/README.md +421 -421
  4. package/core.js +91 -91
  5. package/docs/ALWAYS_ON.md +310 -310
  6. package/docs/GITHUB_WORKFLOW.md +394 -394
  7. package/docs/HIVE_RULES.md +41 -41
  8. package/docs/MCP.md +157 -157
  9. package/docs/QUICKSTART.md +266 -266
  10. package/docs/SELF_HOSTING.md +256 -256
  11. package/docs/SLACK_SETUP.md +474 -474
  12. package/docs/TROUBLESHOOTING.md +106 -105
  13. package/hive-activity.js +336 -336
  14. package/hive-authors.js +46 -44
  15. package/hive-await.js +230 -230
  16. package/hive-branch.js +674 -607
  17. package/hive-busy.js +88 -88
  18. package/hive-coord.js +110 -110
  19. package/hive-gate.js +135 -135
  20. package/hive-github.js +3 -1
  21. package/hive-hook-install.js +179 -179
  22. package/hive-instructions.js +74 -74
  23. package/hive-land.js +709 -691
  24. package/hive-listen.js +81 -81
  25. package/hive-mailbox.js +520 -520
  26. package/hive-mcp-http.js +591 -591
  27. package/hive-mcp.js +92 -12
  28. package/hive-mux-proto.js +58 -58
  29. package/hive-mux.js +354 -351
  30. package/hive-notify.js +234 -233
  31. package/hive-obligations.js +247 -247
  32. package/hive-poll-proto.js +50 -50
  33. package/hive-pr-staleness.js +189 -189
  34. package/hive-pr.js +637 -512
  35. package/hive-privacy.js +327 -327
  36. package/hive-receipts.js +233 -233
  37. package/hive-relay-health.js +234 -234
  38. package/hive-repos.js +430 -430
  39. package/hive-room-key.js +210 -210
  40. package/hive-room-mode.js +1761 -1668
  41. package/hive-room-watcher.js +623 -561
  42. package/hive-secrets.js +89 -89
  43. package/hive-session.js +114 -114
  44. package/hive-setup.js +560 -560
  45. package/hive-slack-agent.js +408 -408
  46. package/hive-slack.js +2417 -2367
  47. package/hive-stdio.js +172 -172
  48. package/hive-stop-hook.js +293 -293
  49. package/hive-throttle.js +118 -118
  50. package/hive-time.js +71 -71
  51. package/hive-transport.js +617 -578
  52. package/hive-update.js +58 -58
  53. package/hive-version.js +110 -110
  54. package/hive-webhook.js +55 -54
  55. package/icr-merge.js +58 -58
  56. package/icr.js +856 -851
  57. package/lang-brace.js +305 -305
  58. package/lang-js.js +695 -651
  59. package/lang-json.js +211 -211
  60. package/lang-python.js +588 -588
  61. package/lang-ruby.js +230 -230
  62. package/lang-toml.js +136 -136
  63. package/lang-yaml.js +183 -183
  64. package/limits.js +413 -413
  65. package/package.json +11 -3
  66. package/reconcile.cjs +621 -621
  67. package/slack-app-manifest-agent.json +61 -61
  68. package/slack-app-manifest-inbound.json +36 -36
  69. package/slack-app-manifest.json +27 -27
  70. package/substrate.js +252 -252
  71. package/sync.js +434 -60
  72. package/token.js +201 -201
package/hive-throttle.js CHANGED
@@ -1,118 +1,118 @@
1
- // hive-throttle.js — say it again only if they can no longer see it.
2
- //
3
- // TWO OPPOSITE WASTES, ONE CAUSE. Hivecode repeats some blocks to an agent that still has them
4
- // verbatim, and never repeats others to an agent that lost them hours ago. Both are the same
5
- // missing question: *is this still in their context?*
6
- //
7
- // [GLOBAL PROJECT STATE] rebuilt and returned on EVERY hive_wait, unchanged, forever.
8
- // An agent waiting in a loop is handed the same JSON hundreds of
9
- // times, and pays for it hundreds of times.
10
- //
11
- // HIVE_RULES.md 6,288 bytes handed over ONCE, in the hive_join tool result. It sits
12
- // in the transcript, so it is exactly what compaction throws away
13
- // first -- and it is the ROOM-specific half of the rules: lanes, gate
14
- // commands, who owns what. Six hours in, it is simply gone.
15
- //
16
- // WHAT IS *NOT* IN SCOPE, AND THIS WAS WORTH CHECKING BEFORE WRITING ANYTHING. `INSTRUCTIONS`
17
- // (2,477 chars) is passed to `new Server(..., { instructions })`, which puts it in the agent's
18
- // SYSTEM PROMPT. A system prompt is not part of the compactable transcript, so those rules are
19
- // present on every single turn and re-sending them would be the precise waste this file exists
20
- // to remove. Half of an earlier draft of this module did exactly that.
21
- //
22
- // THE PROXY, AND ITS HONEST LIMIT. There is no way to ask an agent what is still in its context.
23
- // Agent Relay's broker faces the same wall and answers it with a measurement it *does* have --
24
- // bytes of PTY output since the last reminder:
25
- //
26
- // "Output volume is the proxy for 'the agent actually worked and may have compacted the
27
- // earlier reminder out of its context'. An agent that only sat at the prompt receiving
28
- // messages still has the previous reminder verbatim in context, so repeating it only
29
- // burns tokens." -- crates/broker/src/broker/injection_format.rs
30
- //
31
- // There is no PTY here. The equivalent measurement is the one thing this server controls exactly:
32
- // BYTES IT HAS HANDED THE AGENT since the last send. Every tool result goes out through one
33
- // `text()`, so the count is precise rather than sampled.
34
- //
35
- // This is a proxy and nothing more. It does not detect compaction; it estimates that enough has
36
- // happened for compaction to be plausible. It will sometimes repeat something still in context,
37
- // and sometimes stay quiet about something already gone. Both failures are cheap. Pretending the
38
- // number means more than it does would not be.
39
- //
40
- // THREE RULES, IN PRIORITY ORDER, AND THE ORDER IS THE DESIGN:
41
- //
42
- // 1. never sent -> SEND. They have not seen it.
43
- // 2. content changed -> SEND. Correctness outranks economy: a suppressed state change is a
44
- // wrong answer, while a repeated one is only a cost.
45
- // 3. otherwise -> send only if BOTH the cooldown has elapsed AND enough bytes have gone
46
- // out. Either gate alone is wrong. Time alone nags an idle agent that
47
- // still has the text. Bytes alone fires mid-burst on an agent that read
48
- // it ninety seconds ago.
49
-
50
- /** Long enough that a burst of work does not trigger a repeat, short enough to catch a stall. */
51
- export const COOLDOWN_MS = 5 * 60000
52
-
53
- /** Agent Relay's number, and a reasonable one: ~16k tokens of traffic since they last saw it. */
54
- export const OUTPUT_BYTES = 64 * 1024
55
-
56
- /** Cheap, order-insensitive change detection. Not a hash -- a hash would be more code for a
57
- * comparison that is already exact on the strings these callers pass. */
58
- const norm = (c) => (typeof c === 'string' ? c : JSON.stringify(c === undefined ? null : c))
59
-
60
- export function makeThrottle({ now = Date.now, cooldownMs = COOLDOWN_MS, bytes = OUTPUT_BYTES } = {}) {
61
- // key -> { at, content, bytesSince, sends }
62
- const seen = new Map()
63
- let noted = 0 // total bytes handed to the agent, for reporting only
64
-
65
- /**
66
- * Record output handed to the agent.
67
- *
68
- * Counting for a key starts only once that key has been sent at least once -- the volume only
69
- * means anything relative to "since you last saw this". Agent Relay makes the same choice and
70
- * for the same reason; a counter running before the first send would make the first repeat fire
71
- * immediately on a busy agent.
72
- */
73
- function note(n) {
74
- const add = Math.max(0, Number(n) || 0)
75
- noted += add
76
- for (const e of seen.values()) e.bytesSince += add
77
- }
78
-
79
- function should(key, content) {
80
- const e = seen.get(key)
81
- if (!e) return { send: true, why: 'first' }
82
- if (norm(content) !== e.content) return { send: true, why: 'changed' }
83
- const waited = now() - e.at
84
- if (waited < cooldownMs) return { send: false, why: 'recent', waited }
85
- if (e.bytesSince < bytes) return { send: false, why: 'quiet', bytesSince: e.bytesSince }
86
- return { send: true, why: 'likely-compacted', waited, bytesSince: e.bytesSince }
87
- }
88
-
89
- function sent(key, content) {
90
- const prev = seen.get(key)
91
- seen.set(key, { at: now(), content: norm(content), bytesSince: 0, sends: (prev ? prev.sends : 0) + 1 })
92
- }
93
-
94
- /** should() and sent() in one call, for the common site: `if (send(k, c)) include(c)`. */
95
- function send(key, content) {
96
- const d = should(key, content)
97
- if (d.send) sent(key, content)
98
- return d.send
99
- }
100
-
101
- /** What was actually saved, so the claim is measured rather than asserted. */
102
- function stats() {
103
- const out = { keys: {}, bytesHandled: noted }
104
- for (const [k, e] of seen.entries()) out.keys[k] = { sends: e.sends, bytesSince: e.bytesSince }
105
- return out
106
- }
107
-
108
- return { note, should, sent, send, stats }
109
- }
110
-
111
- /** Cheap enough to be worth repeating; says nothing an agent's system prompt already carries.
112
- * Deliberately a POINTER, not a copy: the file is on the agent's own disk, and a second
113
- * hand-written summary of it here would be one more description to drift. */
114
- export const RULES_NUDGE =
115
- '[ROOM RULES] You have been working a while — the room rules you were handed when you joined ' +
116
- 'are probably out of your context by now. They are ROOM-specific (lanes, gate command, who ' +
117
- 'owns what), so the general rules you still have do not cover them. Re-read HIVE_RULES.md in ' +
118
- 'your own folder before your next claim.'
1
+ // hive-throttle.js — say it again only if they can no longer see it.
2
+ //
3
+ // TWO OPPOSITE WASTES, ONE CAUSE. Hivecode repeats some blocks to an agent that still has them
4
+ // verbatim, and never repeats others to an agent that lost them hours ago. Both are the same
5
+ // missing question: *is this still in their context?*
6
+ //
7
+ // [GLOBAL PROJECT STATE] rebuilt and returned on EVERY hive_wait, unchanged, forever.
8
+ // An agent waiting in a loop is handed the same JSON hundreds of
9
+ // times, and pays for it hundreds of times.
10
+ //
11
+ // HIVE_RULES.md 6,288 bytes handed over ONCE, in the hive_join tool result. It sits
12
+ // in the transcript, so it is exactly what compaction throws away
13
+ // first -- and it is the ROOM-specific half of the rules: lanes, gate
14
+ // commands, who owns what. Six hours in, it is simply gone.
15
+ //
16
+ // WHAT IS *NOT* IN SCOPE, AND THIS WAS WORTH CHECKING BEFORE WRITING ANYTHING. `INSTRUCTIONS`
17
+ // (2,477 chars) is passed to `new Server(..., { instructions })`, which puts it in the agent's
18
+ // SYSTEM PROMPT. A system prompt is not part of the compactable transcript, so those rules are
19
+ // present on every single turn and re-sending them would be the precise waste this file exists
20
+ // to remove. Half of an earlier draft of this module did exactly that.
21
+ //
22
+ // THE PROXY, AND ITS HONEST LIMIT. There is no way to ask an agent what is still in its context.
23
+ // Agent Relay's broker faces the same wall and answers it with a measurement it *does* have --
24
+ // bytes of PTY output since the last reminder:
25
+ //
26
+ // "Output volume is the proxy for 'the agent actually worked and may have compacted the
27
+ // earlier reminder out of its context'. An agent that only sat at the prompt receiving
28
+ // messages still has the previous reminder verbatim in context, so repeating it only
29
+ // burns tokens." -- crates/broker/src/broker/injection_format.rs
30
+ //
31
+ // There is no PTY here. The equivalent measurement is the one thing this server controls exactly:
32
+ // BYTES IT HAS HANDED THE AGENT since the last send. Every tool result goes out through one
33
+ // `text()`, so the count is precise rather than sampled.
34
+ //
35
+ // This is a proxy and nothing more. It does not detect compaction; it estimates that enough has
36
+ // happened for compaction to be plausible. It will sometimes repeat something still in context,
37
+ // and sometimes stay quiet about something already gone. Both failures are cheap. Pretending the
38
+ // number means more than it does would not be.
39
+ //
40
+ // THREE RULES, IN PRIORITY ORDER, AND THE ORDER IS THE DESIGN:
41
+ //
42
+ // 1. never sent -> SEND. They have not seen it.
43
+ // 2. content changed -> SEND. Correctness outranks economy: a suppressed state change is a
44
+ // wrong answer, while a repeated one is only a cost.
45
+ // 3. otherwise -> send only if BOTH the cooldown has elapsed AND enough bytes have gone
46
+ // out. Either gate alone is wrong. Time alone nags an idle agent that
47
+ // still has the text. Bytes alone fires mid-burst on an agent that read
48
+ // it ninety seconds ago.
49
+
50
+ /** Long enough that a burst of work does not trigger a repeat, short enough to catch a stall. */
51
+ export const COOLDOWN_MS = 5 * 60000
52
+
53
+ /** Agent Relay's number, and a reasonable one: ~16k tokens of traffic since they last saw it. */
54
+ export const OUTPUT_BYTES = 64 * 1024
55
+
56
+ /** Cheap, order-insensitive change detection. Not a hash -- a hash would be more code for a
57
+ * comparison that is already exact on the strings these callers pass. */
58
+ const norm = (c) => (typeof c === 'string' ? c : JSON.stringify(c === undefined ? null : c))
59
+
60
+ export function makeThrottle({ now = Date.now, cooldownMs = COOLDOWN_MS, bytes = OUTPUT_BYTES } = {}) {
61
+ // key -> { at, content, bytesSince, sends }
62
+ const seen = new Map()
63
+ let noted = 0 // total bytes handed to the agent, for reporting only
64
+
65
+ /**
66
+ * Record output handed to the agent.
67
+ *
68
+ * Counting for a key starts only once that key has been sent at least once -- the volume only
69
+ * means anything relative to "since you last saw this". Agent Relay makes the same choice and
70
+ * for the same reason; a counter running before the first send would make the first repeat fire
71
+ * immediately on a busy agent.
72
+ */
73
+ function note(n) {
74
+ const add = Math.max(0, Number(n) || 0)
75
+ noted += add
76
+ for (const e of seen.values()) e.bytesSince += add
77
+ }
78
+
79
+ function should(key, content) {
80
+ const e = seen.get(key)
81
+ if (!e) return { send: true, why: 'first' }
82
+ if (norm(content) !== e.content) return { send: true, why: 'changed' }
83
+ const waited = now() - e.at
84
+ if (waited < cooldownMs) return { send: false, why: 'recent', waited }
85
+ if (e.bytesSince < bytes) return { send: false, why: 'quiet', bytesSince: e.bytesSince }
86
+ return { send: true, why: 'likely-compacted', waited, bytesSince: e.bytesSince }
87
+ }
88
+
89
+ function sent(key, content) {
90
+ const prev = seen.get(key)
91
+ seen.set(key, { at: now(), content: norm(content), bytesSince: 0, sends: (prev ? prev.sends : 0) + 1 })
92
+ }
93
+
94
+ /** should() and sent() in one call, for the common site: `if (send(k, c)) include(c)`. */
95
+ function send(key, content) {
96
+ const d = should(key, content)
97
+ if (d.send) sent(key, content)
98
+ return d.send
99
+ }
100
+
101
+ /** What was actually saved, so the claim is measured rather than asserted. */
102
+ function stats() {
103
+ const out = { keys: {}, bytesHandled: noted }
104
+ for (const [k, e] of seen.entries()) out.keys[k] = { sends: e.sends, bytesSince: e.bytesSince }
105
+ return out
106
+ }
107
+
108
+ return { note, should, sent, send, stats }
109
+ }
110
+
111
+ /** Cheap enough to be worth repeating; says nothing an agent's system prompt already carries.
112
+ * Deliberately a POINTER, not a copy: the file is on the agent's own disk, and a second
113
+ * hand-written summary of it here would be one more description to drift. */
114
+ export const RULES_NUDGE =
115
+ '[ROOM RULES] You have been working a while — the room rules you were handed when you joined ' +
116
+ 'are probably out of your context by now. They are ROOM-specific (lanes, gate command, who ' +
117
+ 'owns what), so the general rules you still have do not cover them. Re-read HIVE_RULES.md in ' +
118
+ 'your own folder before your next claim.'
package/hive-time.js CHANGED
@@ -1,71 +1,71 @@
1
- /**
2
- * Unified timestamp parsing and formatting for Hivecode chat and receipts.
3
- * Single source of truth used by:
4
- * 1. public/control.html (browser Control Room ago() and chat timestamps)
5
- * 2. hive-mailbox.js (hive_read_chat formatMessage)
6
- * 3. sync.js (HIVE_CHAT.md renderChat)
7
- *
8
- * Handles:
9
- * - Unix microseconds (> 1e14), as emitted by hive-slack.js, hive-mcp-http.js, hive-gate.js
10
- * - Unix milliseconds (> 1e11)
11
- * - Existing "HH:MM:SS" strings (passed through as-is per Claude's directive)
12
- */
13
-
14
- export function toMillis(at) {
15
- if (at == null || at === '') return null
16
- const num = typeof at === 'number' ? at : Number(at)
17
- if (!isNaN(num) && num > 0) {
18
- if (num > 1e14) {
19
- // Unix microseconds (> 100,000,000,000,000)
20
- return Math.round(num / 1000)
21
- }
22
- if (num > 1e11) {
23
- // Unix milliseconds (> 100,000,000,000)
24
- return Math.round(num)
25
- }
26
- if (num > 1e9) {
27
- // Unix seconds (> 1,000,000,000)
28
- return Math.round(num * 1000)
29
- }
30
- return Math.round(num)
31
- }
32
- return null
33
- }
34
-
35
- export function ago(at, now = Date.now()) {
36
- if (!at) return ''
37
- if (typeof at === 'string') {
38
- const num = Number(at)
39
- if (!isNaN(num) && num > 1e11) {
40
- return ago(num, now)
41
- }
42
- // String with clock time or text: print as is (matches control.html behavior)
43
- return at
44
- }
45
- const ms = toMillis(at)
46
- if (ms == null) return String(at)
47
- const d = Math.max(0, now - ms)
48
- const m = Math.floor(d / 60000)
49
- if (m < 1) return 'now'
50
- if (m < 60) return `${m}m`
51
- const h = Math.floor(m / 60)
52
- return h < 24 ? `${h}h` : `${Math.floor(h / 24)}d`
53
- }
54
-
55
- export function formatTime(at, now = Date.now()) {
56
- if (!at) return ''
57
- if (typeof at === 'string') {
58
- const num = Number(at)
59
- if (!isNaN(num) && num > 1e11) {
60
- return formatTime(num, now)
61
- }
62
- return at
63
- }
64
- const ms = toMillis(at)
65
- if (ms == null) return String(at)
66
- return new Date(ms).toTimeString().slice(0, 8)
67
- }
68
-
69
- export function formatAt(at, { ago: isAgo = false, now = Date.now() } = {}) {
70
- return isAgo ? ago(at, now) : formatTime(at, now)
71
- }
1
+ /**
2
+ * Unified timestamp parsing and formatting for Hivecode chat and receipts.
3
+ * Single source of truth used by:
4
+ * 1. public/control.html (browser Control Room ago() and chat timestamps)
5
+ * 2. hive-mailbox.js (hive_read_chat formatMessage)
6
+ * 3. sync.js (HIVE_CHAT.md renderChat)
7
+ *
8
+ * Handles:
9
+ * - Unix microseconds (> 1e14), as emitted by hive-slack.js, hive-mcp-http.js, hive-gate.js
10
+ * - Unix milliseconds (> 1e11)
11
+ * - Existing "HH:MM:SS" strings (passed through as-is per Claude's directive)
12
+ */
13
+
14
+ export function toMillis(at) {
15
+ if (at == null || at === '') return null
16
+ const num = typeof at === 'number' ? at : Number(at)
17
+ if (!isNaN(num) && num > 0) {
18
+ if (num > 1e14) {
19
+ // Unix microseconds (> 100,000,000,000,000)
20
+ return Math.round(num / 1000)
21
+ }
22
+ if (num > 1e11) {
23
+ // Unix milliseconds (> 100,000,000,000)
24
+ return Math.round(num)
25
+ }
26
+ if (num > 1e9) {
27
+ // Unix seconds (> 1,000,000,000)
28
+ return Math.round(num * 1000)
29
+ }
30
+ return Math.round(num)
31
+ }
32
+ return null
33
+ }
34
+
35
+ export function ago(at, now = Date.now()) {
36
+ if (!at) return ''
37
+ if (typeof at === 'string') {
38
+ const num = Number(at)
39
+ if (!isNaN(num) && num > 1e11) {
40
+ return ago(num, now)
41
+ }
42
+ // String with clock time or text: print as is (matches control.html behavior)
43
+ return at
44
+ }
45
+ const ms = toMillis(at)
46
+ if (ms == null) return String(at)
47
+ const d = Math.max(0, now - ms)
48
+ const m = Math.floor(d / 60000)
49
+ if (m < 1) return 'now'
50
+ if (m < 60) return `${m}m`
51
+ const h = Math.floor(m / 60)
52
+ return h < 24 ? `${h}h` : `${Math.floor(h / 24)}d`
53
+ }
54
+
55
+ export function formatTime(at, now = Date.now()) {
56
+ if (!at) return ''
57
+ if (typeof at === 'string') {
58
+ const num = Number(at)
59
+ if (!isNaN(num) && num > 1e11) {
60
+ return formatTime(num, now)
61
+ }
62
+ return at
63
+ }
64
+ const ms = toMillis(at)
65
+ if (ms == null) return String(at)
66
+ return new Date(ms).toTimeString().slice(0, 8)
67
+ }
68
+
69
+ export function formatAt(at, { ago: isAgo = false, now = Date.now() } = {}) {
70
+ return isAgo ? ago(at, now) : formatTime(at, now)
71
+ }