neuralos 3.9.4 → 3.9.6
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/LICENSE +202 -0
- package/README.md +48 -347
- package/cli.js +78 -0
- package/engine.js +124 -0
- package/index.js +160 -0
- package/package.json +37 -43
- package/test.js +20 -0
- package/tools.js +47 -0
- package/bin/gybackend.cjs +0 -398530
- package/plugins/agentspan-bridge/agentspan-bridge.extreme.spec.ts +0 -328
- package/plugins/agentspan-bridge/agentspan-bridge.phase2.extreme.spec.ts +0 -282
- package/plugins/agentspan-bridge/conductorClient.mjs +0 -182
- package/plugins/agentspan-bridge/index.mjs +0 -389
- package/plugins/agentspan-bridge/playbookToWorkflowDef.mjs +0 -209
- package/plugins/agentspan-bridge/plugin.json +0 -27
- package/plugins/fraudops/index.d.mts +0 -9
- package/plugins/fraudops/index.mjs +0 -185
- package/plugins/fraudops/plugin.json +0 -10
- package/plugins/iam-connector/index.d.mts +0 -10
- package/plugins/iam-connector/index.mjs +0 -229
- package/plugins/iam-connector/plugin.json +0 -10
- package/plugins/mitmproxy-bridge/index.d.mts +0 -20
- package/plugins/mitmproxy-bridge/index.mjs +0 -256
- package/plugins/mitmproxy-bridge/plugin.json +0 -17
- package/plugins/monid-bridge/index.mjs +0 -228
- package/plugins/monid-bridge/monid-bridge.extreme.spec.mjs +0 -75
- package/plugins/monid-bridge/plugin.json +0 -15
- package/plugins/netdata-rterm/index.mjs +0 -229
- package/plugins/netdata-rterm/netdata-rterm.extreme.spec.ts +0 -252
- package/plugins/netdata-rterm/plugin.json +0 -22
- package/plugins/netexec-bridge/index.d.mts +0 -28
- package/plugins/netexec-bridge/index.mjs +0 -325
- package/plugins/netexec-bridge/plugin.json +0 -20
- package/plugins/neuralos/index.mjs +0 -137
- package/plugins/neuralos/neuralos.extreme.spec.mts +0 -386
- package/plugins/neuralos/neuralosEngine.mjs +0 -369
- package/plugins/neuralos/plugin.json +0 -15
- package/plugins/numbat-bridge/index.mjs +0 -229
- package/plugins/numbat-bridge/numbat-bridge.extreme.spec.mjs +0 -148
- package/plugins/numbat-bridge/plugin.json +0 -21
- package/plugins/patch-manager/index.d.mts +0 -10
- package/plugins/patch-manager/index.mjs +0 -288
- package/plugins/patch-manager/plugin.json +0 -10
- package/plugins/promptfoo-redteam/index.d.mts +0 -15
- package/plugins/promptfoo-redteam/index.mjs +0 -277
- package/plugins/promptfoo-redteam/plugin.json +0 -16
- package/plugins/reactorpro-bridge/index.mjs +0 -499
- package/plugins/reactorpro-bridge/plugin.json +0 -26
- package/plugins/reactorpro-bridge/reactorpro-bridge.extreme.spec.mts +0 -409
- package/plugins/reactorpro-bridge/reactorproAgent.mjs +0 -399
- package/plugins/request-router/index.d.mts +0 -9
- package/plugins/request-router/index.mjs +0 -239
- package/plugins/request-router/plugin.json +0 -10
- package/plugins/sample-k8s-slo/index.ts +0 -58
- package/plugins/sample-k8s-slo/plugin.json +0 -11
- package/plugins/sop-assistant/index.d.mts +0 -9
- package/plugins/sop-assistant/index.mjs +0 -325
- package/plugins/sop-assistant/plugin.json +0 -10
- package/plugins/synapse-bridge/index.mjs +0 -484
- package/plugins/synapse-bridge/plugin.json +0 -29
- package/plugins/synapse-bridge/synapse-bridge.extreme.spec.mjs +0 -204
- package/plugins/synapse-bridge/synapseAgent.extreme.spec.mjs +0 -236
- package/plugins/synapse-bridge/synapseAgent.mjs +0 -254
- package/plugins/web-intel/index.mjs +0 -381
- package/plugins/web-intel/plugin.json +0 -28
- package/plugins/web-intel/sidecar.mjs +0 -134
- package/plugins/web-intel/web-intel.extreme.spec.ts +0 -263
- package/plugins/web-intel/wigoloClient.mjs +0 -145
|
@@ -1,484 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* synapse-bridge — RTerm ↔ Synapse mesh interop.
|
|
3
|
-
*
|
|
4
|
-
* Lets RTerm speak the Synapse protocol (v0.3.0) over a shared NATS server:
|
|
5
|
-
* discover live mesh agents, dispatch tasks to them, and register RTerm itself
|
|
6
|
-
* as a mesh agent (bidirectional federation). Built on the same NatsEventBus
|
|
7
|
-
* conventions (auth, request/reply, JetStream) added in v3.1.2.
|
|
8
|
-
*
|
|
9
|
-
* Config (settings.synapse, or env):
|
|
10
|
-
* url — NATS server (default nats://localhost:4222)
|
|
11
|
-
* servers — array of urls (takes precedence)
|
|
12
|
-
* prefix — mesh subject prefix (default "mesh")
|
|
13
|
-
* agentId — this instance's mesh agent id (default "rterm-001")
|
|
14
|
-
* auth — { token | username/password | nkeySeed | jwt/jwtSeed | creds | tls* }
|
|
15
|
-
* enabled — master switch (default true when a server is configured)
|
|
16
|
-
*
|
|
17
|
-
* Secrets may be inline or `secretRef` pointers resolved via the vault.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import { createRequire } from 'node:module'
|
|
21
|
-
import { randomUUID } from 'node:crypto'
|
|
22
|
-
import {
|
|
23
|
-
startResponder, buildRespond, executeSkill,
|
|
24
|
-
emitEvent, subscribeEvents,
|
|
25
|
-
ReputationStore, computeScore, updateRecord, newRecord,
|
|
26
|
-
requestApproval, respondApproval, startApprover,
|
|
27
|
-
} from './synapseAgent.mjs'
|
|
28
|
-
|
|
29
|
-
const require = createRequire(import.meta.url)
|
|
30
|
-
const enc = new TextEncoder()
|
|
31
|
-
const dec = new TextDecoder()
|
|
32
|
-
const j = (v) => enc.encode(JSON.stringify(v))
|
|
33
|
-
const uj = (b) => JSON.parse(dec.decode(b))
|
|
34
|
-
|
|
35
|
-
// ─── config resolution ──────────────────────────────────────────────────────
|
|
36
|
-
|
|
37
|
-
export function resolveConfig(ctx = {}, env = process.env) {
|
|
38
|
-
const s = (typeof ctx.getSettings === 'function' ? ctx.getSettings() : ctx.settings) || {}
|
|
39
|
-
const block = s.synapse || {}
|
|
40
|
-
const servers = Array.isArray(block.servers) && block.servers.length > 0
|
|
41
|
-
? block.servers
|
|
42
|
-
: (block.url || env.SYNAPSE_NATS_URL || env.NATS_URL || 'nats://localhost:4222')
|
|
43
|
-
return {
|
|
44
|
-
servers,
|
|
45
|
-
prefix: block.prefix || 'mesh',
|
|
46
|
-
agentId: block.agentId || env.SYNAPSE_AGENT_ID || 'rterm-001',
|
|
47
|
-
auth: block.auth || undefined,
|
|
48
|
-
enabled: block.enabled !== false,
|
|
49
|
-
autoServe: block.autoServe !== false,
|
|
50
|
-
/** dispatch timeout in ms (default 180s = the edge invoke floor; a real
|
|
51
|
-
* LLM agent turn takes 60s+, and callers time out at 30s without it). */
|
|
52
|
-
dispatchTimeout: block.dispatchTimeout ?? 180000,
|
|
53
|
-
/** multiple meshes (v3.2.0). Each: {name, url/servers, auth, prefix?}. */
|
|
54
|
-
meshes: Array.isArray(block.meshes) ? block.meshes : undefined,
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** Resolve auth secrets through the vault when secretRef-style values are used. */
|
|
59
|
-
function resolveAuth(ctx, auth) {
|
|
60
|
-
if (!auth) return undefined
|
|
61
|
-
const out = { ...auth }
|
|
62
|
-
if (out.passwordSecretRef && typeof ctx.getSecret === 'function') {
|
|
63
|
-
try { out.password = ctx.getSecret(out.passwordSecretRef); delete out.passwordSecretRef } catch { /* leave unset */ }
|
|
64
|
-
}
|
|
65
|
-
if (out.tokenSecretRef && typeof ctx.getSecret === 'function') {
|
|
66
|
-
try { out.token = ctx.getSecret(out.tokenSecretRef); delete out.tokenSecretRef } catch { /* leave unset */ }
|
|
67
|
-
}
|
|
68
|
-
return out
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// ─── transport (lazy NATS connection) ───────────────────────────────────────
|
|
72
|
-
|
|
73
|
-
function loadTransport() {
|
|
74
|
-
try { return require('@nats-io/transport-node') } catch {
|
|
75
|
-
throw new Error('NATS transport (@nats-io/transport-node) is not available in this build')
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function buildAuthenticator(t, auth) {
|
|
80
|
-
if (!auth) return undefined
|
|
81
|
-
const e = new TextEncoder()
|
|
82
|
-
if (auth.creds) return t.credsAuthenticator(typeof auth.creds === 'string' ? e.encode(auth.creds) : auth.creds)
|
|
83
|
-
if (auth.jwt) return t.jwtAuthenticator(auth.jwt, typeof auth.jwtSeed === 'string' ? e.encode(auth.jwtSeed) : auth.jwtSeed)
|
|
84
|
-
if (auth.nkeySeed) return t.nkeyAuthenticator(typeof auth.nkeySeed === 'string' ? e.encode(auth.nkeySeed) : auth.nkeySeed)
|
|
85
|
-
if (auth.token) return t.tokenAuthenticator(auth.token)
|
|
86
|
-
if (auth.username !== undefined) return t.usernamePasswordAuthenticator(auth.username, auth.password ?? '')
|
|
87
|
-
return undefined
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Connection cache keyed by config fingerprint — a settings change (different
|
|
91
|
-
// server/auth/agentId) opens a NEW connection instead of reusing a stale one to
|
|
92
|
-
// the wrong server. A failed connect clears the slot so the next call retries.
|
|
93
|
-
const _conns = new Map() // key -> Promise<conn> | conn
|
|
94
|
-
function _configKey(cfg) {
|
|
95
|
-
const servers = Array.isArray(cfg.servers) ? cfg.servers.join(',') : cfg.servers
|
|
96
|
-
const authKeys = cfg.auth ? Object.keys(cfg.auth).sort().join(',') : ''
|
|
97
|
-
return `${servers}|${cfg.agentId}|${authKeys}`
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async function connectMesh(ctx, overrideCfg) {
|
|
101
|
-
const cfg = overrideCfg || resolveConfig(ctx)
|
|
102
|
-
const key = _configKey(cfg)
|
|
103
|
-
const existing = _conns.get(key)
|
|
104
|
-
if (existing) {
|
|
105
|
-
const c = await existing
|
|
106
|
-
if (c && !c.isClosed()) return c
|
|
107
|
-
_conns.delete(key) // stale/closed — fall through and reconnect
|
|
108
|
-
}
|
|
109
|
-
const t = loadTransport()
|
|
110
|
-
const auth = buildAuthenticator(t, resolveAuth(ctx, cfg.auth))
|
|
111
|
-
const copts = { servers: cfg.servers, name: cfg.agentId, ...(auth ? { authenticator: auth } : {}) }
|
|
112
|
-
const connectFn = (typeof ctx.natsConnect === 'function') ? ctx.natsConnect : (o) => t.connect(o)
|
|
113
|
-
const p = (async () => {
|
|
114
|
-
try {
|
|
115
|
-
return await connectFn(copts)
|
|
116
|
-
} catch (e) {
|
|
117
|
-
_conns.delete(key) // don't cache a failed attempt — allow retry
|
|
118
|
-
throw e
|
|
119
|
-
}
|
|
120
|
-
})()
|
|
121
|
-
_conns.set(key, p)
|
|
122
|
-
return p
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/** Test hook: inject a fake connection for a given config (or clear all with null). */
|
|
126
|
-
export function __setConnForTest(c, cfg) {
|
|
127
|
-
if (c === null || c === undefined) { _conns.clear(); return }
|
|
128
|
-
const key = _configKey(cfg ?? resolveConfig({ settings: {} }))
|
|
129
|
-
_conns.set(key, Promise.resolve(c))
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// ─── Synapse envelope ───────────────────────────────────────────────────────
|
|
133
|
-
|
|
134
|
-
export function envelope(type, payload, cfg, extra = {}) {
|
|
135
|
-
return {
|
|
136
|
-
v: '0.3.0',
|
|
137
|
-
id: randomUUID(),
|
|
138
|
-
type,
|
|
139
|
-
ts: new Date().toISOString(),
|
|
140
|
-
from: cfg.agentId,
|
|
141
|
-
trace: { trace_id: randomUUID(), span_id: randomUUID() },
|
|
142
|
-
payload,
|
|
143
|
-
...extra,
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// ─── core ops ───────────────────────────────────────────────────────────────
|
|
148
|
-
|
|
149
|
-
export async function discoverAgents(ctx, filter = {}) {
|
|
150
|
-
const cfg = resolveConfig(ctx)
|
|
151
|
-
const meshes = cfg.meshes || [{ url: cfg.servers, auth: cfg.auth, prefix: cfg.prefix }]
|
|
152
|
-
const allAgents = []
|
|
153
|
-
for (const mesh of meshes) {
|
|
154
|
-
try {
|
|
155
|
-
const meshCfg = { ...cfg, servers: mesh.servers || (mesh.url ? [mesh.url] : cfg.servers), auth: mesh.auth || cfg.auth, prefix: mesh.prefix || cfg.prefix }
|
|
156
|
-
const nc = await connectMesh(ctx, meshCfg)
|
|
157
|
-
const msg = await nc.request(`${meshCfg.prefix}.registry.discover`, j(envelope('discover', filter, meshCfg)), { timeout: 5000 })
|
|
158
|
-
const reply = uj(msg.data)
|
|
159
|
-
const agents = Array.isArray(reply) ? reply : (reply.payload?.agents ?? reply.payload ?? reply)
|
|
160
|
-
if (Array.isArray(agents)) {
|
|
161
|
-
for (const a of agents) { if (typeof a === 'object') a._mesh = mesh.name || 'default' }
|
|
162
|
-
allAgents.push(...agents)
|
|
163
|
-
}
|
|
164
|
-
} catch { /* mesh unreachable — skip */ }
|
|
165
|
-
}
|
|
166
|
-
return allAgents
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
export async function dispatchTask(ctx, target, skill, input = {}, opts = {}) {
|
|
170
|
-
const cfg = resolveConfig(ctx)
|
|
171
|
-
const timeout = opts.timeout ?? cfg.dispatchTimeout ?? 180000
|
|
172
|
-
const nc = await connectMesh(ctx)
|
|
173
|
-
const reqEnv = envelope('request', { skill, input }, cfg, { to: target, task_id: randomUUID() })
|
|
174
|
-
const inbox = `${cfg.prefix}.agent.${target}.inbox`
|
|
175
|
-
const replySubject = `_INBOX.synapse.${randomUUID().slice(0, 12)}`
|
|
176
|
-
|
|
177
|
-
// Subscribe to the reply subject and collect messages, skipping JetStream acks.
|
|
178
|
-
// The JetStream ack is always {stream: "AGENT_INBOXES", seq: N} — skip it.
|
|
179
|
-
// The real respond is a Synapse envelope with {type: "respond", ...} — return it.
|
|
180
|
-
return new Promise((resolve, reject) => {
|
|
181
|
-
let settled = false
|
|
182
|
-
const sub = nc.subscribe(replySubject, { max: 10 })
|
|
183
|
-
const timer = setTimeout(() => {
|
|
184
|
-
if (!settled) {
|
|
185
|
-
settled = true
|
|
186
|
-
try { sub.unsubscribe() } catch {}
|
|
187
|
-
resolve({ error: `Agent ${target} did not respond within ${Math.round(timeout / 1000)}s. It may not have a Synapse responder running (check synapse_serve_status).`, task_id: reqEnv.task_id })
|
|
188
|
-
}
|
|
189
|
-
}, timeout)
|
|
190
|
-
|
|
191
|
-
;(async () => {
|
|
192
|
-
for await (const msg of sub) {
|
|
193
|
-
if (settled) break
|
|
194
|
-
try {
|
|
195
|
-
const parsed = uj(msg.data)
|
|
196
|
-
const isJetStreamAck = parsed.stream === 'AGENT_INBOXES' && typeof parsed.seq === 'number'
|
|
197
|
-
if (isJetStreamAck) continue // skip the ack, wait for the real respond
|
|
198
|
-
// This is the respond envelope
|
|
199
|
-
settled = true
|
|
200
|
-
clearTimeout(timer)
|
|
201
|
-
try { sub.unsubscribe() } catch {}
|
|
202
|
-
resolve(parsed)
|
|
203
|
-
} catch {
|
|
204
|
-
// non-JSON — skip
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
})().catch(() => {})
|
|
208
|
-
|
|
209
|
-
// Publish the request with the reply subject
|
|
210
|
-
nc.publish(inbox, j(reqEnv), { reply: replySubject })
|
|
211
|
-
})
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
export async function registerSelf(ctx, manifest = {}) {
|
|
215
|
-
const cfg = resolveConfig(ctx)
|
|
216
|
-
const nc = await connectMesh(ctx)
|
|
217
|
-
const payload = {
|
|
218
|
-
agent_id: cfg.agentId,
|
|
219
|
-
name: manifest.name || 'RTerm / neuralOS',
|
|
220
|
-
type: 'agent',
|
|
221
|
-
capabilities: manifest.capabilities || ['ops-automation', 'playbooks', 'fleet-orchestration', 'mop-changes'],
|
|
222
|
-
skills: manifest.skills || [],
|
|
223
|
-
endpoint: `${cfg.prefix}.agent.${cfg.agentId}.inbox`,
|
|
224
|
-
availability: 'online',
|
|
225
|
-
...manifest,
|
|
226
|
-
}
|
|
227
|
-
nc.publish(`${cfg.prefix}.registry.register`, j(envelope('register', payload, cfg)))
|
|
228
|
-
return { registered: cfg.agentId, endpoint: payload.endpoint }
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// ─── plugin registration ────────────────────────────────────────────────────
|
|
232
|
-
|
|
233
|
-
async function guarded(fn, log) {
|
|
234
|
-
try { return await fn() } catch (e) {
|
|
235
|
-
const msg = e?.message ?? String(e)
|
|
236
|
-
log?.(`[synapse] ${msg}`)
|
|
237
|
-
return { error: msg, hint: 'Is the NATS server running and the synapse block configured? (settings.synapse.url, default nats://localhost:4222).' }
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
export function register(ctx) {
|
|
242
|
-
const { registerTool, registerTrigger, registerPanel, log } = ctx
|
|
243
|
-
const cfg = resolveConfig(ctx)
|
|
244
|
-
|
|
245
|
-
registerTool({
|
|
246
|
-
name: 'synapse_health',
|
|
247
|
-
description: 'Check connectivity to the Synapse mesh (NATS server) and report the configured agent id + subject prefix.',
|
|
248
|
-
params: {},
|
|
249
|
-
handler: async () => guarded(async () => {
|
|
250
|
-
const nc = await connectMesh(ctx)
|
|
251
|
-
return { connected: !nc.isClosed(), agentId: cfg.agentId, prefix: cfg.prefix, servers: cfg.servers }
|
|
252
|
-
}, log),
|
|
253
|
-
})
|
|
254
|
-
|
|
255
|
-
registerTool({
|
|
256
|
-
name: 'synapse_discover',
|
|
257
|
-
description: 'Discover live Synapse mesh agents and their skills via the registry (mesh.registry.discover). Optional filter by capabilities/skill_ids/availability.',
|
|
258
|
-
params: {
|
|
259
|
-
capabilities: { type: 'array', description: 'Capabilities to match (all-of)', optional: true },
|
|
260
|
-
skill_ids: { type: 'array', description: 'Skill ids to match (all-of)', optional: true },
|
|
261
|
-
availability: { type: 'string', description: 'e.g. online', optional: true },
|
|
262
|
-
},
|
|
263
|
-
handler: async (p) => guarded(async () => {
|
|
264
|
-
const filter = {}
|
|
265
|
-
if (p?.capabilities) filter.capabilities = p.capabilities
|
|
266
|
-
if (p?.skill_ids) filter.skill_ids = p.skill_ids
|
|
267
|
-
if (p?.availability) filter.availability = p.availability
|
|
268
|
-
const agents = await discoverAgents(ctx, filter)
|
|
269
|
-
return { count: agents.length, agents }
|
|
270
|
-
}, log),
|
|
271
|
-
})
|
|
272
|
-
|
|
273
|
-
registerTool({
|
|
274
|
-
name: 'synapse_dispatch',
|
|
275
|
-
description: 'Dispatch a task to a Synapse mesh agent (mesh.agent.{id}.inbox) and await its response. The task is durably tracked in the mesh.',
|
|
276
|
-
params: {
|
|
277
|
-
target: { type: 'string', description: 'Target agent id (e.g. grip-cli-001)' },
|
|
278
|
-
skill: { type: 'string', description: 'Skill id from the target manifest' },
|
|
279
|
-
input: { type: 'object', description: 'Input payload for the skill', optional: true },
|
|
280
|
-
timeout: { type: 'number', description: 'Reply timeout ms (default 180000 — the edge invoke floor; a real LLM agent turn takes 60s+)', optional: true },
|
|
281
|
-
},
|
|
282
|
-
handler: async (p) => guarded(async () => {
|
|
283
|
-
if (!p?.target || !p?.skill) return { error: 'synapse_dispatch needs target and skill' }
|
|
284
|
-
const response = await dispatchTask(ctx, p.target, p.skill, p.input ?? {}, { timeout: p.timeout })
|
|
285
|
-
return { target: p.target, skill: p.skill, response }
|
|
286
|
-
}, log),
|
|
287
|
-
})
|
|
288
|
-
|
|
289
|
-
registerTool({
|
|
290
|
-
name: 'synapse_register',
|
|
291
|
-
description: 'Register this RTerm/neuralOS instance as a Synapse mesh agent (mesh.registry.register) so other mesh agents can discover and dispatch to it.',
|
|
292
|
-
params: {
|
|
293
|
-
name: { type: 'string', optional: true },
|
|
294
|
-
capabilities: { type: 'array', optional: true },
|
|
295
|
-
skills: { type: 'array', optional: true },
|
|
296
|
-
},
|
|
297
|
-
handler: async (p) => guarded(async () => registerSelf(ctx, p ?? {}), log),
|
|
298
|
-
})
|
|
299
|
-
|
|
300
|
-
registerTool({
|
|
301
|
-
name: 'synapse_agents_summary',
|
|
302
|
-
description: 'Compact summary of live Synapse mesh agents (id, name, skill count, first skills) for quick situational awareness.',
|
|
303
|
-
params: {},
|
|
304
|
-
handler: async () => guarded(async () => {
|
|
305
|
-
const agents = await discoverAgents(ctx, {})
|
|
306
|
-
return {
|
|
307
|
-
count: agents.length,
|
|
308
|
-
agents: agents.map((a) => ({
|
|
309
|
-
id: a.id ?? a.agent_id ?? a.name,
|
|
310
|
-
name: a.name,
|
|
311
|
-
skillCount: (a.skills ?? a.capabilities ?? []).length,
|
|
312
|
-
skills: (a.skills ?? a.capabilities ?? []).slice(0, 5).map((s) => (typeof s === 'string' ? s : s.id ?? s.name)),
|
|
313
|
-
})),
|
|
314
|
-
}
|
|
315
|
-
}, log),
|
|
316
|
-
})
|
|
317
|
-
|
|
318
|
-
// ─── full-duplex agent capabilities (responder, emit/subscribe, reputation, governance) ───
|
|
319
|
-
const repStore = new ReputationStore()
|
|
320
|
-
let responderStop = null
|
|
321
|
-
let servingSkills = []
|
|
322
|
-
|
|
323
|
-
/** Start (or restart) the responder with the given skills. Idempotent. */
|
|
324
|
-
async function serveSkills(skills) {
|
|
325
|
-
const nc = await connectMesh(ctx)
|
|
326
|
-
const serveCtx = { ...ctx, rtermSkills: skills ?? ctx.rtermSkills ?? {} }
|
|
327
|
-
if (responderStop) responderStop()
|
|
328
|
-
responderStop = await startResponder(nc, cfg, serveCtx, log)
|
|
329
|
-
servingSkills = Object.keys(serveCtx.rtermSkills)
|
|
330
|
-
return servingSkills
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/** Default skills RTerm serves when auto-starting (status + discover + dispatch). */
|
|
334
|
-
function defaultServeSkills() {
|
|
335
|
-
return {
|
|
336
|
-
status: async () => ({ up: true, agent: cfg.agentId, ts: new Date().toISOString() }),
|
|
337
|
-
discover: async (inp) => ({ agents: await discoverAgents(ctx, inp ?? {}) }),
|
|
338
|
-
...(ctx.rtermSkills ?? {}),
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
registerTool({
|
|
343
|
-
name: 'synapse_serve',
|
|
344
|
-
description: 'Start RTerm as a full Synapse agent: listen on mesh.agent.{id}.inbox and respond() to incoming Synapse requests by mapping them to RTerm skills (playbooks/tools via ctx.rtermSkills / getRtermSkills). Bidirectional federation — other agents can now task RTerm. Idempotent; auto-starts on boot when synapse.enabled and autoServe are true.',
|
|
345
|
-
params: {
|
|
346
|
-
skills: { type: 'object', description: 'Map of skillId -> async (input, ctx) => output, the skills RTerm serves (defaults to status+discover)', optional: true },
|
|
347
|
-
},
|
|
348
|
-
handler: async (p) => guarded(async () => {
|
|
349
|
-
const skills = await serveSkills(p?.skills ?? defaultServeSkills())
|
|
350
|
-
return { serving: true, inbox: `${cfg.prefix}.agent.${cfg.agentId}.inbox`, skills, note: 'RTerm is now a full Synapse agent (responder live)' }
|
|
351
|
-
}, log),
|
|
352
|
-
})
|
|
353
|
-
|
|
354
|
-
registerTool({
|
|
355
|
-
name: 'synapse_serve_status',
|
|
356
|
-
description: 'Report whether the Synapse responder is live (serving on mesh.agent.{id}.inbox) and which skills it serves.',
|
|
357
|
-
params: {},
|
|
358
|
-
handler: async () => guarded(async () => {
|
|
359
|
-
const nc = await connectMesh(ctx)
|
|
360
|
-
return { serving: responderStop !== null, connected: !nc.isClosed(), inbox: `${cfg.prefix}.agent.${cfg.agentId}.inbox`, skills: servingSkills, autoServe: cfg.autoServe }
|
|
361
|
-
}, log),
|
|
362
|
-
})
|
|
363
|
-
|
|
364
|
-
registerTool({
|
|
365
|
-
name: 'synapse_emit',
|
|
366
|
-
description: 'Emit a formal Synapse event on mesh.event.{type} (fire-and-forget broadcast to subscribers).',
|
|
367
|
-
params: {
|
|
368
|
-
type: { type: 'string', description: 'Event type, e.g. ops.change.committed' },
|
|
369
|
-
payload: { type: 'object', description: 'Event payload' },
|
|
370
|
-
},
|
|
371
|
-
handler: async (p) => guarded(async () => {
|
|
372
|
-
if (!p?.type) return { error: 'synapse_emit needs a type' }
|
|
373
|
-
const nc = await connectMesh(ctx)
|
|
374
|
-
emitEvent(nc, cfg, p.type, p.payload ?? {})
|
|
375
|
-
return { emitted: `${cfg.prefix}.event.${p.type}` }
|
|
376
|
-
}, log),
|
|
377
|
-
})
|
|
378
|
-
|
|
379
|
-
registerTool({
|
|
380
|
-
name: 'synapse_subscribe',
|
|
381
|
-
description: 'Subscribe to Synapse event/task subjects (supports wildcards, e.g. mesh.event.> or mesh.task.>.update). Events feed the local reputation store and the synapse_mesh_event trigger.',
|
|
382
|
-
params: {
|
|
383
|
-
subject: { type: 'string', description: 'Subject pattern, e.g. mesh.event.> or mesh.task.>.update' },
|
|
384
|
-
},
|
|
385
|
-
handler: async (p) => guarded(async () => {
|
|
386
|
-
const subject = p?.subject ?? `${cfg.prefix}.task.>.update`
|
|
387
|
-
const nc = await connectMesh(ctx)
|
|
388
|
-
const stop = await subscribeEvents(nc, subject, (env, subj) => {
|
|
389
|
-
repStore.handleTaskUpdate(env)
|
|
390
|
-
if (typeof ctx.emitEvent === 'function') ctx.emitEvent({ source: 'synapse', subject: subj, env })
|
|
391
|
-
})
|
|
392
|
-
return { subscribed: subject, note: 'events feed the reputation store + synapse_mesh_event trigger' }
|
|
393
|
-
}, log),
|
|
394
|
-
})
|
|
395
|
-
|
|
396
|
-
registerTool({
|
|
397
|
-
name: 'synapse_reputation',
|
|
398
|
-
description: 'Read the local Synapse reputation store (EXT-REPUTATION): per (agent, skill) success_rate, speed_score, freshness, composite score. Optionally record an outcome or list ranked agents.',
|
|
399
|
-
params: {
|
|
400
|
-
agent: { type: 'string', optional: true },
|
|
401
|
-
skill: { type: 'string', optional: true },
|
|
402
|
-
minScore: { type: 'number', description: 'Only agents at/above this score (discover-ranked)', optional: true },
|
|
403
|
-
},
|
|
404
|
-
handler: async (p) => guarded(async () => {
|
|
405
|
-
if (p?.agent && p?.skill) {
|
|
406
|
-
const rec = repStore.get(p.agent, p.skill)
|
|
407
|
-
return rec ?? { error: `no record for ${p.agent}::${p.skill}` }
|
|
408
|
-
}
|
|
409
|
-
const ranked = repStore.ranked(p?.minScore ?? 0)
|
|
410
|
-
return { count: ranked.length, agents: ranked.map((r) => ({ agent: r.agent_id, skill: r.skill, score: Number(r.score.toFixed(3)), successRate: Number(r.success_rate.toFixed(3)), speedScore: Number(r.speed_score.toFixed(3)), confidence: r.confidence })) }
|
|
411
|
-
}, log),
|
|
412
|
-
})
|
|
413
|
-
|
|
414
|
-
registerTool({
|
|
415
|
-
name: 'synapse_request_approval',
|
|
416
|
-
description: 'Request governance approval for a gated action (EXT-GOVERNANCE): publish mesh.approval.{taskId}.request and await the response. Use before a high-risk dispatched task.',
|
|
417
|
-
params: {
|
|
418
|
-
originalRequest: { type: 'object', description: 'The original request payload being gated' },
|
|
419
|
-
policyId: { type: 'string', optional: true },
|
|
420
|
-
ruleId: { type: 'string', optional: true },
|
|
421
|
-
reason: { type: 'string', description: 'Why approval is required' },
|
|
422
|
-
taskId: { type: 'string', optional: true },
|
|
423
|
-
timeout: { type: 'number', optional: true },
|
|
424
|
-
},
|
|
425
|
-
handler: async (p) => guarded(async () => {
|
|
426
|
-
const nc = await connectMesh(ctx)
|
|
427
|
-
const r = await requestApproval(nc, cfg, { taskId: p?.taskId, originalRequest: p?.originalRequest, policyId: p?.policyId, ruleId: p?.ruleId, reason: p?.reason ?? 'RTerm action requires mesh approval', timeout: p?.timeout })
|
|
428
|
-
return r.approved ? { approved: true, approver: r.approver, taskId: r.taskId } : { approved: false, taskId: r.taskId, note: 'denied or timed out' }
|
|
429
|
-
}, log),
|
|
430
|
-
})
|
|
431
|
-
|
|
432
|
-
registerTool({
|
|
433
|
-
name: 'synapse_approve',
|
|
434
|
-
description: 'Act as a governance approver (EXT-GOVERNANCE): listen on mesh.approval.*.request and answer each per a policy (allow-all, deny-all, or a decide map). Other agents route their gated actions through RTerm for approval.',
|
|
435
|
-
params: {
|
|
436
|
-
policy: { type: 'string', description: 'allow-all | deny-all (default allow-all)', optional: true },
|
|
437
|
-
},
|
|
438
|
-
handler: async (p) => guarded(async () => {
|
|
439
|
-
const nc = await connectMesh(ctx)
|
|
440
|
-
const policy = p?.policy ?? 'allow-all'
|
|
441
|
-
const decide = async () => ({ approved: policy !== 'deny-all', approver: `did:mesh:${cfg.agentId}` })
|
|
442
|
-
await startApprover(nc, cfg, decide, log)
|
|
443
|
-
return { approver: cfg.agentId, policy, listening: `${cfg.prefix}.approval.*.request` }
|
|
444
|
-
}, log),
|
|
445
|
-
})
|
|
446
|
-
|
|
447
|
-
registerTrigger({
|
|
448
|
-
name: 'synapse_mesh_event',
|
|
449
|
-
description: 'Fires when a Synapse mesh event (task failure, reputation penalty, approval request) is observed. Use for cross-mesh remediation.',
|
|
450
|
-
match: (event) => event?.source === 'synapse',
|
|
451
|
-
action: 'propose-change',
|
|
452
|
-
})
|
|
453
|
-
|
|
454
|
-
registerPanel({
|
|
455
|
-
name: 'synapse-mesh-agents',
|
|
456
|
-
title: 'Synapse Mesh Agents',
|
|
457
|
-
render: (data) => {
|
|
458
|
-
const rows = (Array.isArray(data) ? data : []).map((a) =>
|
|
459
|
-
`<tr><td>${a.id ?? ''}</td><td>${a.name ?? ''}</td><td>${a.skillCount ?? ''}</td></tr>`
|
|
460
|
-
).join('')
|
|
461
|
-
return `<div class="synapse-mesh"><h3>Synapse Mesh Agents</h3><p>Agent: ${cfg.agentId} · Prefix: ${cfg.prefix}</p><table><thead><tr><th>Id</th><th>Name</th><th>Skills</th></tr></thead><tbody>${rows}</tbody></table></div>`
|
|
462
|
-
},
|
|
463
|
-
})
|
|
464
|
-
|
|
465
|
-
log(`[synapse] synapse-bridge registered: 12 tools, 1 trigger, 1 panel (agent=${cfg.agentId}, prefix=${cfg.prefix}, full-duplex)`)
|
|
466
|
-
|
|
467
|
-
// ─── auto-start the full-duplex responder on boot (when enabled + autoServe) ───
|
|
468
|
-
// Makes "be tasked by them" always-on, not opt-in per session. Best-effort: a
|
|
469
|
-
// failed auto-start logs but never blocks plugin registration (server may be down).
|
|
470
|
-
// Wrapped in setTimeout(0) to ensure register() returns synchronously BEFORE the
|
|
471
|
-
// async NATS connect — so PluginRegistry.loadFromDir() never hangs on the auto-serve.
|
|
472
|
-
if (cfg.enabled && cfg.autoServe) {
|
|
473
|
-
setTimeout(() => {
|
|
474
|
-
Promise.race([
|
|
475
|
-
serveSkills(defaultServeSkills()),
|
|
476
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error('auto-serve connect timeout (5s)')), 5000)),
|
|
477
|
-
])
|
|
478
|
-
.then((skills) => log(`[synapse] auto-started responder on ${cfg.prefix}.agent.${cfg.agentId}.inbox (skills: ${skills.join(', ')})`))
|
|
479
|
-
.catch((e) => log(`[synapse] auto-serve deferred: ${e?.message ?? e} (responder will start on first synapse_serve call)`))
|
|
480
|
-
}, 0)
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
export default { register, resolveConfig, envelope, discoverAgents, dispatchTask, registerSelf }
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "synapse-bridge",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"description": "Synapse mesh bridge for RTerm — full-duplex Synapse agent (auto-started on boot): discover live agents, dispatch tasks, register RTerm as a mesh agent, AND serve as one (respond to inbound requests, auto-started), emit/subscribe mesh events, EXT-REPUTATION scoring, and EXT-GOVERNANCE approvals. Speaks the Synapse protocol (v0.3.0) over a shared NATS server using the v3.1.2 auth/request-reply/JetStream transport. Config in Settings (synapse block); secrets via the vault.",
|
|
5
|
-
"entry": "index.mjs",
|
|
6
|
-
"tools": [
|
|
7
|
-
"synapse_health",
|
|
8
|
-
"synapse_discover",
|
|
9
|
-
"synapse_dispatch",
|
|
10
|
-
"synapse_register",
|
|
11
|
-
"synapse_agents_summary",
|
|
12
|
-
"synapse_serve",
|
|
13
|
-
"synapse_serve_status",
|
|
14
|
-
"synapse_emit",
|
|
15
|
-
"synapse_subscribe",
|
|
16
|
-
"synapse_reputation",
|
|
17
|
-
"synapse_request_approval",
|
|
18
|
-
"synapse_approve"
|
|
19
|
-
],
|
|
20
|
-
"triggers": [
|
|
21
|
-
"synapse_mesh_event"
|
|
22
|
-
],
|
|
23
|
-
"panels": [
|
|
24
|
-
"synapse-mesh-agents"
|
|
25
|
-
],
|
|
26
|
-
"permissions": [
|
|
27
|
-
"exec"
|
|
28
|
-
]
|
|
29
|
-
}
|