buzz: agent-control deployed at ac.ch4t.buzz, hermes mapped; /pair sidecar for mobile pairing; web client at client.ch4t.buzz

This commit is contained in:
zai-agent 2026-09-17 18:50:30 +00:00
parent ee331db179
commit 50eba7b558
2 changed files with 98 additions and 18 deletions

View file

@ -0,0 +1,52 @@
---
apiVersion: v1
kind: Service
metadata:
name: buzz-agent-control
spec:
ports:
- port: 80
targetPort: 8095
---
apiVersion: v1
kind: Endpoints
metadata:
name: buzz-agent-control
subsets:
- addresses:
- ip: 10.0.0.215
ports:
- port: 8095
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: buzz-agent-control
spec:
rules:
- host: ac.ch4t.buzz
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: buzz-agent-control
port:
number: 80
tls:
- hosts:
- ac.ch4t.buzz
secretName: ac-ch4t-tls
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: ac-ch4t-buzz
spec:
secretName: ac-ch4t-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- ac.ch4t.buzz

View file

@ -14,13 +14,55 @@ import { execFileSync } from 'node:child_process'
const RELAY = 'wss://ch4t.buzz' const RELAY = 'wss://ch4t.buzz'
const ME = '4abe5fcdf9695be34bdfc8fe82297aaaf0cf65b5ca92b6c789f6f8783b5ee197' const ME = '4abe5fcdf9695be34bdfc8fe82297aaaf0cf65b5ca92b6c789f6f8783b5ee197'
const INBOX = '/home/opc/.buzz-inbox' const INBOX = '/home/opc/.buzz-inbox'
const REACTED = '/home/opc/.buzz-reacted'
const STATE = '/home/opc/.buzz-last-seen' const STATE = '/home/opc/.buzz-last-seen'
const PENDING = '/home/opc/.buzz-pending.json'
const MAX_LIFETIME = 120 * 1000 // reconnect every 2 min: replays since lastSeen (zombie-socket proof) const MAX_LIFETIME = 120 * 1000 // reconnect every 2 min: replays since lastSeen (zombie-socket proof)
const keyText = fs.readFileSync('/home/opc/buzz-agent-key.txt', 'utf8') const keyText = fs.readFileSync('/home/opc/buzz-agent-key.txt', 'utf8')
const SK = Uint8Array.from(keyText.match(/SECRET:\s*(\S+)/)[1].match(/.{2}/g).map((h) => parseInt(h, 16))) const SK = Uint8Array.from(keyText.match(/SECRET:\s*(\S+)/)[1].match(/.{2}/g).map((h) => parseInt(h, 16)))
const MY_PUBKEY = getPublicKey(SK) const MY_PUBKEY = getPublicKey(SK)
const RECEIPT_EMOJIS = ['👀', '💬']
function loadPending() {
try { return JSON.parse(fs.readFileSync(PENDING, 'utf8')) } catch { return {} }
}
function savePending(o) { fs.writeFileSync(PENDING, JSON.stringify(o)) }
// 👀 = seen, 💬 = working. Both removed when zai posts its reply.
function addReceipts(evt, ch) {
const p = loadPending()
p[ch] = p[ch] || []
if (p[ch].includes(evt.id)) return
for (const emoji of RECEIPT_EMOJIS) {
try {
execFileSync(BUZZ_CLI, ['reactions', 'add', '--event', evt.id, '--emoji', emoji], {
env: { ...process.env, BUZZ_RELAY_URL: 'https://ch4t.buzz', BUZZ_PRIVATE_KEY: keyText.match(/SECRET:\s*(\S+)/)[1] },
stdio: 'ignore',
timeout: 15000,
})
} catch (e) {
console.error('reaction add failed:', emoji, String(e).slice(0, 80))
}
}
p[ch].push(evt.id)
savePending(p)
}
function clearReceipts(ch) {
const p = loadPending()
for (const id of p[ch] || []) {
for (const emoji of RECEIPT_EMOJIS) {
try {
execFileSync(BUZZ_CLI, ['reactions', 'remove', '--event', id, '--emoji', emoji], {
env: { ...process.env, BUZZ_RELAY_URL: 'https://ch4t.buzz', BUZZ_PRIVATE_KEY: keyText.match(/SECRET:\s*(\S+)/)[1] },
stdio: 'ignore',
timeout: 15000,
})
} catch {}
}
}
delete p[ch]
savePending(p)
}
function lastSeen() { function lastSeen() {
try { return parseInt(fs.readFileSync(STATE, 'utf8').trim(), 10) } catch { return Math.floor(Date.now() / 1000) } try { return parseInt(fs.readFileSync(STATE, 'utf8').trim(), 10) } catch { return Math.floor(Date.now() / 1000) }
@ -112,23 +154,9 @@ ws.onmessage = (msg) => {
fs.appendFileSync(INBOX, JSON.stringify(evt) + '\n') fs.appendFileSync(INBOX, JSON.stringify(evt) + '\n')
console.error('message received from', evt.pubkey.slice(0, 10)) console.error('message received from', evt.pubkey.slice(0, 10))
const inCh = (evt.tags.find((t) => t[0] === 'h') || [])[1] const inCh = (evt.tags.find((t) => t[0] === 'h') || [])[1]
if (inCh) startTyping(inCh) if (inCh) {
// Instant receipt: emoji reaction (NIP-25 kind 7) on the sender's message, startTyping(inCh)
// so the sender always sees that zai is alive — before any LLM work starts. addReceipts(evt, inCh)
let reacted = false
try { reacted = fs.readFileSync(REACTED, 'utf8').split('\n').includes(evt.id) } catch {}
if (!reacted) {
try {
execFileSync(BUZZ_CLI, ['reactions', 'add', '--event', evt.id, '--emoji', '👀'], {
env: { ...process.env, BUZZ_RELAY_URL: 'https://ch4t.buzz', BUZZ_PRIVATE_KEY: keyText.match(/SECRET:\s*(\S+)/)[1] },
stdio: 'ignore',
timeout: 15000,
})
fs.appendFileSync(REACTED, evt.id + '\n')
console.error('👀 receipt sent')
} catch (e) {
console.error('reaction failed:', String(e).slice(0, 100))
}
} }
} }
if (type === 'EOSE') console.error('EOSE — live') if (type === 'EOSE') console.error('EOSE — live')