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,204 +0,0 @@
|
|
|
1
|
-
import { register, resolveConfig, envelope, discoverAgents, dispatchTask, registerSelf, __setConnForTest } from './index.mjs'
|
|
2
|
-
|
|
3
|
-
const cases = []
|
|
4
|
-
function test(n, r) { cases.push({ name: n, run: r }) }
|
|
5
|
-
function assert(c, m) { if (!c) throw new Error(m ?? 'assertion failed') }
|
|
6
|
-
function eq(a, b, m) { if (a !== b) throw new Error(`${m ?? 'eq'}: expected ${JSON.stringify(b)}, got ${JSON.stringify(a)}`) }
|
|
7
|
-
|
|
8
|
-
// ─── fake NATS connection (with wildcard + JetStream ack simulation) ─────────
|
|
9
|
-
function subjectMatches(pattern, subject) {
|
|
10
|
-
if (pattern === subject) return true
|
|
11
|
-
const p = pattern.split('.')
|
|
12
|
-
const s = subject.split('.')
|
|
13
|
-
for (let i = 0; i < p.length; i++) {
|
|
14
|
-
if (p[i] === '>') return true
|
|
15
|
-
if (i >= s.length) return false
|
|
16
|
-
if (p[i] !== '*' && p[i] !== s[i]) return false
|
|
17
|
-
}
|
|
18
|
-
return p.length === s.length
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function fakeConn() {
|
|
22
|
-
const published = []
|
|
23
|
-
const subs = new Map()
|
|
24
|
-
const requests = new Map()
|
|
25
|
-
const deliver = (subject, data, respondFn) => {
|
|
26
|
-
for (const [pattern, fns] of subs) {
|
|
27
|
-
if (subjectMatches(pattern, subject)) {
|
|
28
|
-
for (const fn of fns) fn({ data, subject, respond: respondFn })
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return {
|
|
33
|
-
isClosed: () => false,
|
|
34
|
-
publish(subject, data, opts) {
|
|
35
|
-
const env = JSON.parse(new TextDecoder().decode(data))
|
|
36
|
-
published.push({ subject, env, opts })
|
|
37
|
-
// Simulate JetStream ack on the reply subject (if reply-to is set)
|
|
38
|
-
if (opts?.reply) {
|
|
39
|
-
const ack = new TextEncoder().encode(JSON.stringify({ stream: 'AGENT_INBOXES', seq: published.length }))
|
|
40
|
-
deliver(opts.reply, ack, () => {})
|
|
41
|
-
}
|
|
42
|
-
// Deliver to any matching subscribers
|
|
43
|
-
deliver(subject, data, (p) => { published.push({ subject: '_reply', env: JSON.parse(new TextDecoder().decode(p)) }) })
|
|
44
|
-
},
|
|
45
|
-
subscribe(subject) {
|
|
46
|
-
return {
|
|
47
|
-
async *[Symbol.asyncIterator]() {
|
|
48
|
-
while (true) {
|
|
49
|
-
const m = await new Promise((res) => {
|
|
50
|
-
const l = subs.get(subject) ?? []; subs.set(subject, l); l.push(res)
|
|
51
|
-
})
|
|
52
|
-
yield m
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
unsubscribe: () => subs.delete(subject),
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
async request(subject, data, _opts) {
|
|
59
|
-
const env = JSON.parse(new TextDecoder().decode(data))
|
|
60
|
-
const h = requests.get(subject)
|
|
61
|
-
const reply = h ? h(env) : { ok: true }
|
|
62
|
-
return { data: new TextEncoder().encode(JSON.stringify(reply)) }
|
|
63
|
-
},
|
|
64
|
-
_deliver(subject, env) {
|
|
65
|
-
const data = new TextEncoder().encode(JSON.stringify(env))
|
|
66
|
-
deliver(subject, data, (p) => { published.push({ subject: '_reply', env: JSON.parse(new TextDecoder().decode(p)) }) })
|
|
67
|
-
},
|
|
68
|
-
_on(subject, h) { requests.set(subject, h) },
|
|
69
|
-
published,
|
|
70
|
-
drain: async () => {},
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function mkCtx(settings = {}, conn) {
|
|
75
|
-
const tools = new Map(); const triggers = []; const panels = []; const logs = []
|
|
76
|
-
const ctx = {
|
|
77
|
-
settings: { synapse: settings },
|
|
78
|
-
natsConnect: async () => conn,
|
|
79
|
-
registerTool: (t) => tools.set(t.name, t),
|
|
80
|
-
registerTrigger: (t) => triggers.push(t),
|
|
81
|
-
registerPanel: (p) => panels.push(p),
|
|
82
|
-
log: (l) => logs.push(l),
|
|
83
|
-
}
|
|
84
|
-
return { tools, triggers, panels, logs, ctx }
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// ─── config ─────────────────────────────────────────────────────────────────
|
|
88
|
-
|
|
89
|
-
test('resolveConfig includes dispatchTimeout (default 180000 = the edge invoke floor) + meshes', () => {
|
|
90
|
-
const c = resolveConfig({ settings: {} }, {})
|
|
91
|
-
eq(c.dispatchTimeout, 180000, 'default dispatchTimeout 180s (edge invoke floor; LLM turns take 60s+)')
|
|
92
|
-
eq(c.meshes, undefined, 'no meshes by default')
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
test('resolveConfig reads dispatchTimeout + meshes from settings', () => {
|
|
96
|
-
const c = resolveConfig({ settings: { synapse: { dispatchTimeout: 30000, meshes: [{ name: 'prod', url: 'nats://p:4222' }] } } }, {})
|
|
97
|
-
eq(c.dispatchTimeout, 30000, 'dispatchTimeout from settings')
|
|
98
|
-
assert(Array.isArray(c.meshes) && c.meshes.length === 1, 'meshes array')
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
// ─── dispatch: skip JetStream ack, wait for real respond ─────────────────────
|
|
102
|
-
|
|
103
|
-
test('dispatchTask skips JetStream ack and returns the real respond envelope', async () => {
|
|
104
|
-
__setConnForTest(null)
|
|
105
|
-
const conn = fakeConn()
|
|
106
|
-
const ctx = {
|
|
107
|
-
settings: { synapse: { url: 'nats://fake:4222', agentId: 'rterm-001', prefix: 'mesh', dispatchTimeout: 5000 } },
|
|
108
|
-
natsConnect: async () => conn,
|
|
109
|
-
registerTool: () => {}, registerTrigger: () => {}, registerPanel: () => {}, log: () => {},
|
|
110
|
-
}
|
|
111
|
-
// Simulate the agent responding after the JetStream ack
|
|
112
|
-
// The reply subject is _INBOX.synapse.* — we need to deliver the respond there
|
|
113
|
-
// The JetStream ack is auto-delivered by fakeConn.publish (opts.reply)
|
|
114
|
-
// We need to manually deliver the real respond to the reply subject
|
|
115
|
-
setTimeout(() => {
|
|
116
|
-
// Find the reply subject from published messages
|
|
117
|
-
const pub = conn.published.find(p => p.opts?.reply && p.subject === 'mesh.agent.grip-001.inbox')
|
|
118
|
-
if (pub) {
|
|
119
|
-
const respond = { v: '0.3.0', id: 'r1', type: 'respond', from: 'grip-001', to: 'rterm-001', payload: { output: { ok: true, incidents: [] } } }
|
|
120
|
-
conn._deliver(pub.opts.reply, respond)
|
|
121
|
-
}
|
|
122
|
-
}, 50)
|
|
123
|
-
|
|
124
|
-
const result = await dispatchTask(ctx, 'grip-001', 'status', {})
|
|
125
|
-
eq(result.type, 'respond', 'should be a respond envelope')
|
|
126
|
-
eq(result.from, 'grip-001', 'from the target agent')
|
|
127
|
-
assert(result.payload?.output?.ok === true, 'output present')
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
test('dispatchTask returns clear error on timeout (no respond)', async () => {
|
|
131
|
-
__setConnForTest(null)
|
|
132
|
-
const conn = fakeConn()
|
|
133
|
-
const ctx = {
|
|
134
|
-
settings: { synapse: { url: 'nats://fake:4222', agentId: 'rterm-001', prefix: 'mesh', dispatchTimeout: 500 } },
|
|
135
|
-
natsConnect: async () => conn,
|
|
136
|
-
registerTool: () => {}, registerTrigger: () => {}, registerPanel: () => {}, log: () => {},
|
|
137
|
-
}
|
|
138
|
-
// No agent responds — should timeout
|
|
139
|
-
const result = await dispatchTask(ctx, 'no-such-agent', 'status', {})
|
|
140
|
-
assert(result.error, 'should have error on timeout')
|
|
141
|
-
assert(result.error.includes('did not respond'), 'error message mentions timeout')
|
|
142
|
-
assert(result.error.includes('synapse_serve_status'), 'error hints at serve_status')
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
test('dispatchTask: JetStream ack is skipped, not returned as result', async () => {
|
|
146
|
-
__setConnForTest(null)
|
|
147
|
-
const conn = fakeConn()
|
|
148
|
-
const ctx = {
|
|
149
|
-
settings: { synapse: { url: 'nats://fake:4222', agentId: 'rterm-001', prefix: 'mesh', dispatchTimeout: 2000 } },
|
|
150
|
-
natsConnect: async () => conn,
|
|
151
|
-
registerTool: () => {}, registerTrigger: () => {}, registerPanel: () => {}, log: () => {},
|
|
152
|
-
}
|
|
153
|
-
// The fakeConn auto-delivers a JetStream ack on the reply subject.
|
|
154
|
-
// Then we deliver the real respond after 50ms.
|
|
155
|
-
setTimeout(() => {
|
|
156
|
-
const pub = conn.published.find(p => p.opts?.reply && p.subject === 'mesh.agent.test-001.inbox')
|
|
157
|
-
if (pub) {
|
|
158
|
-
conn._deliver(pub.opts.reply, { type: 'respond', from: 'test-001', payload: { output: { data: 42 } } })
|
|
159
|
-
}
|
|
160
|
-
}, 50)
|
|
161
|
-
|
|
162
|
-
const result = await dispatchTask(ctx, 'test-001', 'compute', { n: 42 })
|
|
163
|
-
// Must NOT be the JetStream ack
|
|
164
|
-
assert(!result.stream, 'must not be a JetStream ack')
|
|
165
|
-
eq(result.type, 'respond', 'must be a respond envelope')
|
|
166
|
-
eq(result.payload.output.data, 42, 'output data correct')
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
// ─── multi-mesh discover ─────────────────────────────────────────────────────
|
|
170
|
-
|
|
171
|
-
test('discoverAgents merges results from multiple meshes + tags _mesh', async () => {
|
|
172
|
-
__setConnForTest(null)
|
|
173
|
-
const connA = fakeConn()
|
|
174
|
-
const connB = fakeConn()
|
|
175
|
-
connA._on('mesh.registry.discover', () => [{ id: 'agent-a1', name: 'A1' }])
|
|
176
|
-
connB._on('mesh.registry.discover', () => [{ id: 'agent-b1', name: 'B1' }])
|
|
177
|
-
let which = 0
|
|
178
|
-
const ctx = {
|
|
179
|
-
settings: { synapse: { meshes: [
|
|
180
|
-
{ name: 'mesh-a', url: 'nats://a:4222' },
|
|
181
|
-
{ name: 'mesh-b', url: 'nats://b:4222' },
|
|
182
|
-
]}},
|
|
183
|
-
natsConnect: async () => { which++; return which === 1 ? connA : connB },
|
|
184
|
-
registerTool: () => {}, registerTrigger: () => {}, registerPanel: () => {}, log: () => {},
|
|
185
|
-
}
|
|
186
|
-
const agents = await discoverAgents(ctx, {})
|
|
187
|
-
eq(agents.length, 2, 'merged from both meshes')
|
|
188
|
-
const a1 = agents.find(a => a.id === 'agent-a1')
|
|
189
|
-
const b1 = agents.find(a => a.id === 'agent-b1')
|
|
190
|
-
assert(a1 && a1._mesh === 'mesh-a', 'agent-a1 tagged with mesh-a')
|
|
191
|
-
assert(b1 && b1._mesh === 'mesh-b', 'agent-b1 tagged with mesh-b')
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
// ─── runner ─────────────────────────────────────────────────────────────────
|
|
195
|
-
async function main() {
|
|
196
|
-
let pass = 0, fail = 0
|
|
197
|
-
for (const c of cases) {
|
|
198
|
-
try { await c.run(); pass++; console.log(`PASS ${c.name}`) }
|
|
199
|
-
catch (e) { fail++; console.log(`FAIL ${c.name}: ${e?.message ?? e}`) }
|
|
200
|
-
}
|
|
201
|
-
console.log(`\n${pass}/${pass + fail} passed, ${fail} failed`)
|
|
202
|
-
if (fail > 0) process.exit(1)
|
|
203
|
-
}
|
|
204
|
-
main()
|
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
buildRespond, executeSkill, startResponder,
|
|
3
|
-
emitEvent, subscribeEvents,
|
|
4
|
-
ReputationStore, computeScore, updateRecord, newRecord, REP_WEIGHTS,
|
|
5
|
-
requestApproval, respondApproval, startApprover,
|
|
6
|
-
} from './synapseAgent.mjs'
|
|
7
|
-
|
|
8
|
-
const cases = []
|
|
9
|
-
function test(n, r) { cases.push({ name: n, run: r }) }
|
|
10
|
-
function assert(c, m) { if (!c) throw new Error(m ?? 'assertion failed') }
|
|
11
|
-
function eq(a, b, m) { if (a !== b) throw new Error(`${m ?? 'eq'}: expected ${JSON.stringify(b)}, got ${JSON.stringify(a)}`) }
|
|
12
|
-
|
|
13
|
-
const enc = new TextEncoder()
|
|
14
|
-
const dec = new TextDecoder()
|
|
15
|
-
const CFG = { agentId: 'rterm-001', prefix: 'mesh' }
|
|
16
|
-
|
|
17
|
-
// ─── fake NATS connection (pub/sub + request/reply) ─────────────────────────
|
|
18
|
-
// NATS wildcard grammar: '*' matches one token, '>' matches one or more (suffix).
|
|
19
|
-
function subjectMatches(pattern, subject) {
|
|
20
|
-
if (pattern === subject) return true
|
|
21
|
-
const p = pattern.split('.')
|
|
22
|
-
const s = subject.split('.')
|
|
23
|
-
for (let i = 0; i < p.length; i++) {
|
|
24
|
-
if (p[i] === '>') return true
|
|
25
|
-
if (i >= s.length) return false
|
|
26
|
-
if (p[i] !== '*' && p[i] !== s[i]) return false
|
|
27
|
-
}
|
|
28
|
-
return p.length === s.length
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function fakeConn() {
|
|
32
|
-
const published = []
|
|
33
|
-
const subs = new Map()
|
|
34
|
-
const requests = new Map()
|
|
35
|
-
const deliver = (subject, data, respondFn) => {
|
|
36
|
-
for (const [pattern, fns] of subs) {
|
|
37
|
-
if (subjectMatches(pattern, subject)) {
|
|
38
|
-
for (const fn of fns) fn({ data, subject, respond: respondFn })
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return {
|
|
43
|
-
isClosed: () => false,
|
|
44
|
-
published,
|
|
45
|
-
publish(subject, data) {
|
|
46
|
-
const env = JSON.parse(dec.decode(data))
|
|
47
|
-
published.push({ subject, env })
|
|
48
|
-
deliver(subject, data, (p) => { published.push({ subject: '_reply', env: JSON.parse(dec.decode(p)) }) })
|
|
49
|
-
},
|
|
50
|
-
subscribe(subject) {
|
|
51
|
-
return {
|
|
52
|
-
async *[Symbol.asyncIterator]() {
|
|
53
|
-
while (true) {
|
|
54
|
-
const m = await new Promise((res) => {
|
|
55
|
-
const l = subs.get(subject) ?? []; subs.set(subject, l); l.push(res)
|
|
56
|
-
})
|
|
57
|
-
yield m
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
|
-
unsubscribe: () => subs.delete(subject),
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
async request(subject, data, _o) {
|
|
64
|
-
const env = JSON.parse(dec.decode(data))
|
|
65
|
-
const h = requests.get(subject)
|
|
66
|
-
const reply = h ? h(env) : { payload: { approved: true, approver: 'did:mesh:approver' } }
|
|
67
|
-
return { data: enc.encode(JSON.stringify(reply)) }
|
|
68
|
-
},
|
|
69
|
-
_deliver(subject, env) {
|
|
70
|
-
const data = enc.encode(JSON.stringify(env))
|
|
71
|
-
deliver(subject, data, (p) => { published.push({ subject: '_reply', env: JSON.parse(dec.decode(p)) }) })
|
|
72
|
-
},
|
|
73
|
-
_onRequest(subject, h) { requests.set(subject, h) },
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// ─── 1. RESPONDER ────────────────────────────────────────────────────────────
|
|
78
|
-
|
|
79
|
-
test('buildRespond: output envelope has to/task_id/in_reply_to + output payload', () => {
|
|
80
|
-
const req = { id: 'req-1', from: 'caller-001', task_id: 'task-9', payload: { skill: 'x' } }
|
|
81
|
-
const r = buildRespond(req, CFG, { output: { result: 42 } })
|
|
82
|
-
eq(r.type, 'respond', 'type')
|
|
83
|
-
eq(r.to, 'caller-001', 'to = original from')
|
|
84
|
-
eq(r.task_id, 'task-9', 'task_id')
|
|
85
|
-
eq(r.in_reply_to, 'req-1', 'in_reply_to = original id')
|
|
86
|
-
eq(r.payload.output.result, 42, 'output payload')
|
|
87
|
-
assert(!r.payload.error, 'no error when output present')
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
test('buildRespond: error envelope has error payload, no output; output+error throws', () => {
|
|
91
|
-
const req = { id: 'r2', from: 'c', task_id: 't' }
|
|
92
|
-
const r = buildRespond(req, CFG, { error: { code: 3001, message: 'nf' } })
|
|
93
|
-
eq(r.payload.error.code, 3001, 'error code')
|
|
94
|
-
assert(!r.payload.output, 'no output when error present')
|
|
95
|
-
let threw = false
|
|
96
|
-
try { buildRespond(req, CFG, { output: {}, error: { code: 1 } }) } catch { threw = true }
|
|
97
|
-
assert(threw, 'output+error must throw')
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
test('executeSkill: known skill returns output; unknown returns 3001 SKILL_NOT_FOUND', async () => {
|
|
101
|
-
const ctx = { rtermSkills: { greet: async (inp) => ({ hello: inp.name }) } }
|
|
102
|
-
const ok = await executeSkill('greet', { name: 'mesh' }, ctx)
|
|
103
|
-
eq(ok.output.hello, 'mesh', 'skill output')
|
|
104
|
-
const nf = await executeSkill('nope', {}, ctx)
|
|
105
|
-
eq(nf.error.code, 3001, 'SKILL_NOT_FOUND code')
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
test('startResponder: inbound request executes skill + responds on reply inbox', async () => {
|
|
109
|
-
const nc = fakeConn()
|
|
110
|
-
const ctx = { rtermSkills: { status: async () => ({ up: true }) } }
|
|
111
|
-
const stop = await startResponder(nc, CFG, ctx)
|
|
112
|
-
nc._deliver('mesh.agent.rterm-001.inbox', { id: 'q1', from: 'caller-001', task_id: 't1', payload: { skill: 'status', input: {} } })
|
|
113
|
-
await new Promise((r) => setTimeout(r, 20))
|
|
114
|
-
const reply = nc.published.find((p) => p.subject === '_reply')
|
|
115
|
-
assert(reply, 'expected a respond on the reply inbox')
|
|
116
|
-
eq(reply.env.type, 'respond', 'respond type')
|
|
117
|
-
eq(reply.env.to, 'caller-001', 'respond to caller')
|
|
118
|
-
eq(reply.env.payload.output.up, true, 'skill output delivered')
|
|
119
|
-
stop()
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
// ─── 2. EMIT / SUBSCRIBE ─────────────────────────────────────────────────────
|
|
123
|
-
|
|
124
|
-
test('emitEvent publishes a formal emit envelope on mesh.event.{type}', () => {
|
|
125
|
-
const nc = fakeConn()
|
|
126
|
-
emitEvent(nc, CFG, 'ops.change.committed', { changeId: 'chg-1' })
|
|
127
|
-
const pub = nc.published.find((p) => p.subject === 'mesh.event.ops.change.committed')
|
|
128
|
-
assert(pub, 'expected emit publish')
|
|
129
|
-
eq(pub.env.type, 'emit', 'emit type')
|
|
130
|
-
eq(pub.env.payload.changeId, 'chg-1', 'emit payload')
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
test('subscribeEvents invokes handler for each event on the subject', async () => {
|
|
134
|
-
const nc = fakeConn()
|
|
135
|
-
const seen = []
|
|
136
|
-
const stop = await subscribeEvents(nc, 'mesh.event.>', (env, subj) => seen.push({ env, subj }))
|
|
137
|
-
nc._deliver('mesh.event.>', { type: 'emit', payload: { n: 1 } })
|
|
138
|
-
await new Promise((r) => setTimeout(r, 20))
|
|
139
|
-
eq(seen.length, 1, 'one event seen')
|
|
140
|
-
stop()
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
// ─── 3. REPUTATION ───────────────────────────────────────────────────────────
|
|
144
|
-
|
|
145
|
-
test('computeScore: perfect record scores high', () => {
|
|
146
|
-
const rec = newRecord('a1', 'respond')
|
|
147
|
-
for (let i = 0; i < 5; i++) updateRecord(rec, { status: 'completed', latencyMs: 100 })
|
|
148
|
-
const s = computeScore(rec)
|
|
149
|
-
eq(s.success_rate, 1, 'success_rate 1')
|
|
150
|
-
assert(s.score > 0.8, `high score expected, got ${s.score}`)
|
|
151
|
-
eq(s.confidence, 1.0, 'full confidence at >=min samples')
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
test('updateRecord: failures lower success_rate + score', () => {
|
|
155
|
-
const rec = newRecord('a2', 'respond')
|
|
156
|
-
updateRecord(rec, { status: 'completed', latencyMs: 100 })
|
|
157
|
-
updateRecord(rec, { status: 'failed' })
|
|
158
|
-
updateRecord(rec, { status: 'timeout' })
|
|
159
|
-
const s = computeScore(rec)
|
|
160
|
-
assert(s.success_rate < 0.5, `success_rate should be low, got ${s.success_rate}`)
|
|
161
|
-
assert(s.score < 0.7, `score should be lower, got ${s.score}`)
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
test('reputation: 3 consecutive SKILL_NOT_FOUND flags lying-agent + zeroes score', () => {
|
|
165
|
-
const rec = newRecord('liar', 'respond')
|
|
166
|
-
updateRecord(rec, { status: 'skill_not_found' })
|
|
167
|
-
updateRecord(rec, { status: 'skill_not_found' })
|
|
168
|
-
updateRecord(rec, { status: 'skill_not_found' })
|
|
169
|
-
assert(rec.flags.misleading_capabilities, 'lying-agent flag set')
|
|
170
|
-
eq(rec.flags.penalty_reason.includes('SKILL_NOT_FOUND'), true, 'penalty reason')
|
|
171
|
-
const s = computeScore(rec)
|
|
172
|
-
eq(s.score, 0, 'lying penalty zeroes the score')
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
test('ReputationStore: observe + ranked + handleTaskUpdate', () => {
|
|
176
|
-
const store = new ReputationStore()
|
|
177
|
-
store.observe('a1', 'respond', { status: 'completed', latencyMs: 50 })
|
|
178
|
-
store.observe('a1', 'respond', { status: 'completed', latencyMs: 60 })
|
|
179
|
-
store.observe('a2', 'respond', { status: 'failed' })
|
|
180
|
-
const rec = store.get('a1', 'respond')
|
|
181
|
-
eq(rec.successes, 2, 'two successes recorded')
|
|
182
|
-
// task_update event path
|
|
183
|
-
store.handleTaskUpdate({ payload: { agent: 'a1', skill: 'respond', status: 'completed', latencyMs: 40 } })
|
|
184
|
-
eq(store.get('a1', 'respond').successes, 3, 'task_update fed the store')
|
|
185
|
-
const ranked = store.ranked(0)
|
|
186
|
-
assert(ranked.length >= 2, 'ranked returns all')
|
|
187
|
-
eq(ranked[0].agent_id, 'a1', 'a1 (higher score) ranks first')
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
// ─── 4. GOVERNANCE ───────────────────────────────────────────────────────────
|
|
191
|
-
|
|
192
|
-
test('requestApproval publishes approval_request + returns approved response', async () => {
|
|
193
|
-
const nc = fakeConn()
|
|
194
|
-
let captured
|
|
195
|
-
nc._onRequest('mesh.approval.task-1.request', (env) => { captured = env; return { payload: { approved: true, approver: 'did:mesh:approver-001' } } })
|
|
196
|
-
const r = await requestApproval(nc, CFG, { taskId: 'task-1', originalRequest: { skill: 'pay' }, reason: 'payment needs approval' })
|
|
197
|
-
eq(captured.type, 'approval_request', 'request type')
|
|
198
|
-
eq(captured.task_id, 'task-1', 'request task_id')
|
|
199
|
-
eq(captured.payload.reason, 'payment needs approval', 'reason')
|
|
200
|
-
eq(r.approved, true, 'approved')
|
|
201
|
-
eq(r.approver, 'did:mesh:approver-001', 'approver')
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
test('respondApproval publishes approval_response on mesh.approval.{taskId}.response', () => {
|
|
205
|
-
const nc = fakeConn()
|
|
206
|
-
const req = { id: 'ar-1', from: 'agent-bob-001', task_id: 'task-7' }
|
|
207
|
-
respondApproval(nc, CFG, req, { approved: false, approver: 'did:mesh:rterm-001' })
|
|
208
|
-
const pub = nc.published.find((p) => p.subject === 'mesh.approval.task-7.response')
|
|
209
|
-
assert(pub, 'expected approval_response publish')
|
|
210
|
-
eq(pub.env.type, 'approval_response', 'response type')
|
|
211
|
-
eq(pub.env.to, 'agent-bob-001', 'to original requester')
|
|
212
|
-
eq(pub.env.payload.approved, false, 'denied')
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
test('startApprover answers inbound approval requests per the decide fn', async () => {
|
|
216
|
-
const nc = fakeConn()
|
|
217
|
-
const stop = await startApprover(nc, CFG, async () => ({ approved: true, approver: 'did:mesh:rterm-001' }))
|
|
218
|
-
nc._deliver('mesh.approval.task-9.request', { id: 'ar-9', from: 'agent-x', task_id: 'task-9', type: 'approval_request', payload: {} })
|
|
219
|
-
await new Promise((r) => setTimeout(r, 20))
|
|
220
|
-
const pub = nc.published.find((p) => p.subject === 'mesh.approval.task-9.response')
|
|
221
|
-
assert(pub, 'expected approver to answer')
|
|
222
|
-
eq(pub.env.payload.approved, true, 'approved by decide fn')
|
|
223
|
-
stop()
|
|
224
|
-
})
|
|
225
|
-
|
|
226
|
-
// ─── runner ─────────────────────────────────────────────────────────────────
|
|
227
|
-
async function main() {
|
|
228
|
-
let pass = 0, fail = 0
|
|
229
|
-
for (const c of cases) {
|
|
230
|
-
try { await c.run(); pass++; console.log(`PASS ${c.name}`) }
|
|
231
|
-
catch (e) { fail++; console.log(`FAIL ${c.name}: ${e?.message ?? e}`) }
|
|
232
|
-
}
|
|
233
|
-
console.log(`\n${pass}/${pass + fail} passed, ${fail} failed`)
|
|
234
|
-
if (fail > 0) process.exit(1)
|
|
235
|
-
}
|
|
236
|
-
main()
|