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:
parent
ee331db179
commit
50eba7b558
2 changed files with 98 additions and 18 deletions
52
buzz/k8s/buzz-agent-control.yaml
Normal file
52
buzz/k8s/buzz-agent-control.yaml
Normal 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
|
||||||
|
|
@ -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')
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue