hivecode-mcp 0.13.1 → 0.14.1

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/hive-mux.js CHANGED
@@ -19,6 +19,7 @@ import * as decoding from 'lib0/decoding'
19
19
  import { ObservableV2 } from 'lib0/observable'
20
20
  import { WebsocketProvider } from 'y-websocket'
21
21
  import { F, encodeFrame, decodeFrame, muxPath, MUX_VERSION } from './hive-mux-proto.js'
22
+ import { relayFetch } from './hive-transport.js' // /health through the proxy, as the sockets go
22
23
 
23
24
  const messageSync = 0
24
25
 
@@ -257,7 +258,7 @@ class MuxConnection {
257
258
  * decided once -- HIVE_MUX=1 forces mux, HIVE_MUX=0 forces a socket per file, anything else asks
258
259
  * the relay's /health -- and file providers created before the answer are bound when it comes.
259
260
  */
260
- export function createFileTransport({ relay, room, wsOpts = {}, log = () => { }, env = process.env, fetchImpl = globalThis.fetch, onFallback = () => { }, retryMs = 1000, detectForMs = 90000 }) {
261
+ export function createFileTransport({ relay, room, wsOpts = {}, log = () => { }, env = process.env, fetchImpl = relayFetch, onFallback = () => { }, retryMs = 1000, detectForMs = 90000 }) {
261
262
  const forced = env.HIVE_MUX === '0' || env.HIVE_MUX === '1'
262
263
  let mode = env.HIVE_MUX === '0' ? 'socket' : env.HIVE_MUX === '1' ? 'mux' : null
263
264
  let mux = null
@@ -0,0 +1,50 @@
1
+ // hive-poll-proto.js -- the wire format of the HTTPS long-poll transport (relay-poll.js on the
2
+ // relay, hive-transport.js in clients). It exists for networks that refuse WebSocket upgrades but
3
+ // pass ordinary HTTPS: Antigravity's cloud VMs (their egress proxy answers "403 WebSocket upgrades
4
+ // are not allowed", 7 Nov 2026) and many company proxies.
5
+ //
6
+ // It carries EXACTLY the bytes a WebSocket would: each message is one frame, in order, so every
7
+ // gate on the relay (claims, read-only, the governor, rate limits) sees what it always sees.
8
+ //
9
+ // POST /_hive_poll/open JSON {path, v} -> 200 {sid, holdMs, idleMs, v} | the refusal an upgrade would get
10
+ // POST /_hive_poll/<sid>/send frames, header x-hive-seq: the first frame's number (1, 2, 3, ...)
11
+ // -> 204 once delivered. A retry of the same numbers is ignored, so a
12
+ // lost answer never applies a message twice; a gap is a 409.
13
+ // POST /_hive_poll/<sid>/recv header x-hive-ack: the last frame received, x-hive-hold: ms to wait
14
+ // -> 200 frames (x-hive-seq: the first one's number) | 204 nothing yet
15
+ // Frames up to the ack are forgotten; the rest are sent again, so a
16
+ // lost answer loses nothing.
17
+ // POST /_hive_poll/<sid>/close -> 204
18
+ // An unknown or ended session answers 404 {gone:true}: the client reconnects and resyncs.
19
+ //
20
+ // The room, the token and the name travel in the open request's BODY, never in a URL: a proxy logs
21
+ // URLs, and a room id is the key to the room.
22
+ export const POLL_PREFIX = '/_hive_poll/'
23
+ export const POLL_VERSION = 1
24
+
25
+ /** Frames to one buffer: for each, a 4-byte big-endian length, then its bytes. */
26
+ export function packFrames(frames) {
27
+ let total = 0
28
+ for (const f of frames) total += 4 + f.length
29
+ const out = Buffer.allocUnsafe(total)
30
+ let o = 0
31
+ for (const f of frames) {
32
+ out.writeUInt32BE(f.length, o); o += 4
33
+ Buffer.from(f.buffer, f.byteOffset, f.length).copy(out, o); o += f.length
34
+ }
35
+ return out
36
+ }
37
+
38
+ /** One buffer back to its frames. Throws on anything malformed, so nothing half-read is delivered. */
39
+ export function unpackFrames(buf) {
40
+ const b = Buffer.isBuffer(buf) ? buf : Buffer.from(buf)
41
+ const out = []
42
+ let o = 0
43
+ while (o < b.length) {
44
+ if (o + 4 > b.length) throw new Error('truncated frame length')
45
+ const n = b.readUInt32BE(o); o += 4
46
+ if (o + n > b.length) throw new Error('truncated frame')
47
+ out.push(b.subarray(o, o + n)); o += n
48
+ }
49
+ return out
50
+ }
@@ -35,6 +35,8 @@
35
35
  // the only place the wording lives, and tests/relay-diagnosis-test.mjs COUNTS the producers so a
36
36
  // third copy fails a test rather than shipping.
37
37
 
38
+ import { relayFetch } from './hive-transport.js'
39
+
38
40
  /** A free-tier per-room cap. At or below this, Q431 is the likely refusal and seats are the fix. */
39
41
  export const FREE_TIER_ROOM_CAP = 8
40
42
 
@@ -67,7 +69,9 @@ export function healthUrlFor(relay) {
67
69
  export async function probeRelayHealth(relay, { timeoutMs = 2500, fetchImpl } = {}) {
68
70
  const url = healthUrlFor(relay)
69
71
  if (!url) return { ok: false, url: null, error: 'no usable relay URL' }
70
- const f = fetchImpl || (typeof fetch === 'function' ? fetch : null)
72
+ // relayFetch, not the built-in fetch: behind a proxy the built-in one cannot reach the relay at all,
73
+ // and this diagnosis would then blame the relay for what is this machine's network.
74
+ const f = fetchImpl || relayFetch
71
75
  if (!f) return { ok: false, url, error: 'no fetch available in this runtime' }
72
76
  try {
73
77
  const ac = new AbortController()
package/hive-room-key.js CHANGED
@@ -22,6 +22,7 @@ import path from 'path'
22
22
  import crypto from 'crypto'
23
23
  import { sign, decodeUnsafe, keyFingerprint, roomFingerprint, isSecuredRoom, makeSecuredRoomId } from './token.js'
24
24
  import { validateMemberName } from './hive-mailbox.js'
25
+ import { relayFetch } from './hive-transport.js' // through the proxy, as every call to the relay
25
26
 
26
27
  export const keysDir = () => process.env.HIVE_KEYS_DIR || path.join(os.homedir(), '.hivecode', 'keys')
27
28
  const keyFile = (room) => path.join(keysDir(), `${room}.json`)
@@ -143,7 +144,7 @@ export async function revokeInvite({ room, relay, name }) {
143
144
  const failures = []
144
145
  for (const i of live) {
145
146
  try {
146
- const r = await fetch(`${httpBase(relay || rec.relay)}/__hive/revoke`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ room, token: owner, jti: i.jti }) })
147
+ const r = await relayFetch(`${httpBase(relay || rec.relay)}/__hive/revoke`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ room, token: owner, jti: i.jti }) })
147
148
  const body = await r.json().catch(() => ({}))
148
149
  if (r.ok && body.ok) { i.revokedAt = Date.now(); done++; dropped += Number(body.dropped) || 0 }
149
150
  else failures.push(`HTTP ${r.status}${body.error ? ` ${body.error}` : ''}`)
package/hive-setup.js CHANGED
@@ -40,6 +40,7 @@ import { VERSION } from './hive-version.js'
40
40
  import { currentRelayFor, controlUrl, PUBLIC_DEFAULT_RELAY } from './hive-privacy.js'
41
41
  import { validateMemberName } from './hive-mailbox.js'
42
42
  import { installStopHook, installAntigravityStopHook } from './hive-hook-install.js'
43
+ import { relayFetch } from './hive-transport.js' // /health through the proxy, as every call to the relay
43
44
 
44
45
  export const SERVER_KEY = 'hivecode'
45
46
  const BACKUP_SUFFIX = '.before-hivecode'
@@ -340,11 +341,12 @@ function defaultName() {
340
341
  return v.ok ? cand : v.suggested
341
342
  }
342
343
 
343
- async function wakeRelay(relay) {
344
+ /** Exported for tests/proxy-fetch-test.mjs. */
345
+ export async function wakeRelay(relay) {
344
346
  const url = relay.replace(/^wss:/, 'https:').replace(/^ws:/, 'http:').replace(/\/+$/, '') + '/health'
345
347
  const t0 = Date.now()
346
348
  try {
347
- const r = await fetch(url, { signal: AbortSignal.timeout(75000) })
349
+ const r = await relayFetch(url, { signal: AbortSignal.timeout(75000) })
348
350
  return { ok: r.ok, ms: Date.now() - t0, status: r.status }
349
351
  } catch (e) { return { ok: false, ms: Date.now() - t0, error: e.name === 'TimeoutError' ? 'no answer in 75s' : e.message } }
350
352
  }
@@ -26,6 +26,9 @@
26
26
 
27
27
  import WebSocket from 'ws'
28
28
  import { startSync, parseLink } from './sync.js'
29
+ import { realpathSync as __hiveRealpath } from 'fs'
30
+ import { resolve as __hiveResolve } from 'path'
31
+ import { fileURLToPath as __hiveFileURLToPath } from 'url'
29
32
  import {
30
33
  makeQueue,
31
34
  slackPost,
@@ -39,6 +42,7 @@ import {
39
42
  preflightSocketMode,
40
43
  connectSocketMode,
41
44
  parseSlackAllow,
45
+ notFromAPerson,
42
46
  } from './hive-slack.js'
43
47
 
44
48
  export { stopAll, resumeAll, resolveName, route }
@@ -219,7 +223,7 @@ export function makeAgent({
219
223
  /** The one entry point for Slack events. Only a message in the configured channel or in a DM, or
220
224
  * the native stop button, does anything; everything else is ignored before any actor lookup. */
221
225
  async function onEvent(ev) {
222
- if (!ev || ev.bot_id || ev.subtype || (botUserId && ev.user === botUserId)) return { ignored: 'bot_or_subtype' }
226
+ if (notFromAPerson(ev) || (botUserId && ev.user === botUserId)) return { ignored: 'bot_or_subtype' }
223
227
  const targetChannel = ev.channel || channel
224
228
  const isDm = ev.channel_type === 'im' || (typeof targetChannel === 'string' && targetChannel.startsWith('D'))
225
229
  const here = targetChannel === channel || isDm
@@ -391,6 +395,14 @@ export async function main(argv = process.argv.slice(2), {
391
395
  })
392
396
  }
393
397
 
394
- if (process.argv[1] && import.meta.url === `file:///${process.argv[1].replace(/\\/g, '/')}`) {
398
+ // Run directly (node <file>, or through an npx / node_modules/.bin symlink)? Compared by real path:
399
+ // the old `file:///${argv[1]}` test built "file:////..." on macOS and Linux, never matched, and the
400
+ // process exited 0 having done nothing.
401
+ function __hiveIsEntry() {
402
+ if (!process.argv[1]) return false
403
+ const me = __hiveFileURLToPath(import.meta.url)
404
+ try { return __hiveRealpath(process.argv[1]) === __hiveRealpath(me) } catch { return __hiveResolve(process.argv[1]) === __hiveResolve(me) }
405
+ }
406
+ if (__hiveIsEntry()) {
395
407
  main().then((code) => { if (code !== 0) process.exit(code) })
396
408
  }