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.
Files changed (68) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +48 -347
  3. package/cli.js +78 -0
  4. package/engine.js +124 -0
  5. package/index.js +160 -0
  6. package/package.json +37 -43
  7. package/test.js +20 -0
  8. package/tools.js +47 -0
  9. package/bin/gybackend.cjs +0 -398530
  10. package/plugins/agentspan-bridge/agentspan-bridge.extreme.spec.ts +0 -328
  11. package/plugins/agentspan-bridge/agentspan-bridge.phase2.extreme.spec.ts +0 -282
  12. package/plugins/agentspan-bridge/conductorClient.mjs +0 -182
  13. package/plugins/agentspan-bridge/index.mjs +0 -389
  14. package/plugins/agentspan-bridge/playbookToWorkflowDef.mjs +0 -209
  15. package/plugins/agentspan-bridge/plugin.json +0 -27
  16. package/plugins/fraudops/index.d.mts +0 -9
  17. package/plugins/fraudops/index.mjs +0 -185
  18. package/plugins/fraudops/plugin.json +0 -10
  19. package/plugins/iam-connector/index.d.mts +0 -10
  20. package/plugins/iam-connector/index.mjs +0 -229
  21. package/plugins/iam-connector/plugin.json +0 -10
  22. package/plugins/mitmproxy-bridge/index.d.mts +0 -20
  23. package/plugins/mitmproxy-bridge/index.mjs +0 -256
  24. package/plugins/mitmproxy-bridge/plugin.json +0 -17
  25. package/plugins/monid-bridge/index.mjs +0 -228
  26. package/plugins/monid-bridge/monid-bridge.extreme.spec.mjs +0 -75
  27. package/plugins/monid-bridge/plugin.json +0 -15
  28. package/plugins/netdata-rterm/index.mjs +0 -229
  29. package/plugins/netdata-rterm/netdata-rterm.extreme.spec.ts +0 -252
  30. package/plugins/netdata-rterm/plugin.json +0 -22
  31. package/plugins/netexec-bridge/index.d.mts +0 -28
  32. package/plugins/netexec-bridge/index.mjs +0 -325
  33. package/plugins/netexec-bridge/plugin.json +0 -20
  34. package/plugins/neuralos/index.mjs +0 -137
  35. package/plugins/neuralos/neuralos.extreme.spec.mts +0 -386
  36. package/plugins/neuralos/neuralosEngine.mjs +0 -369
  37. package/plugins/neuralos/plugin.json +0 -15
  38. package/plugins/numbat-bridge/index.mjs +0 -229
  39. package/plugins/numbat-bridge/numbat-bridge.extreme.spec.mjs +0 -148
  40. package/plugins/numbat-bridge/plugin.json +0 -21
  41. package/plugins/patch-manager/index.d.mts +0 -10
  42. package/plugins/patch-manager/index.mjs +0 -288
  43. package/plugins/patch-manager/plugin.json +0 -10
  44. package/plugins/promptfoo-redteam/index.d.mts +0 -15
  45. package/plugins/promptfoo-redteam/index.mjs +0 -277
  46. package/plugins/promptfoo-redteam/plugin.json +0 -16
  47. package/plugins/reactorpro-bridge/index.mjs +0 -499
  48. package/plugins/reactorpro-bridge/plugin.json +0 -26
  49. package/plugins/reactorpro-bridge/reactorpro-bridge.extreme.spec.mts +0 -409
  50. package/plugins/reactorpro-bridge/reactorproAgent.mjs +0 -399
  51. package/plugins/request-router/index.d.mts +0 -9
  52. package/plugins/request-router/index.mjs +0 -239
  53. package/plugins/request-router/plugin.json +0 -10
  54. package/plugins/sample-k8s-slo/index.ts +0 -58
  55. package/plugins/sample-k8s-slo/plugin.json +0 -11
  56. package/plugins/sop-assistant/index.d.mts +0 -9
  57. package/plugins/sop-assistant/index.mjs +0 -325
  58. package/plugins/sop-assistant/plugin.json +0 -10
  59. package/plugins/synapse-bridge/index.mjs +0 -484
  60. package/plugins/synapse-bridge/plugin.json +0 -29
  61. package/plugins/synapse-bridge/synapse-bridge.extreme.spec.mjs +0 -204
  62. package/plugins/synapse-bridge/synapseAgent.extreme.spec.mjs +0 -236
  63. package/plugins/synapse-bridge/synapseAgent.mjs +0 -254
  64. package/plugins/web-intel/index.mjs +0 -381
  65. package/plugins/web-intel/plugin.json +0 -28
  66. package/plugins/web-intel/sidecar.mjs +0 -134
  67. package/plugins/web-intel/web-intel.extreme.spec.ts +0 -263
  68. package/plugins/web-intel/wigoloClient.mjs +0 -145
@@ -1,137 +0,0 @@
1
- /**
2
- * neuralos — neuralOS instances as RTerm agent tools.
3
- *
4
- * neuralOS instances (~/neuralos-instances) are on-device data agents: a
5
- * verified menu of probes over a real data source (databases, APIs, log
6
- * sets), selected by the 121M neuralOS engine and executed by each
7
- * instance's Python bridge. This plugin wires four agent tools:
8
- *
9
- * neuralos_list_instances — what can be queried
10
- * neuralos_ask — plain-English question -> verified digest
11
- * neuralos_graph — relationship maps (overview/neighbors/connect)
12
- * neuralos_admin — write probes, confirm='yes' interlocked
13
- *
14
- * The engine ships with the RTerm desktop bundle ({resourcesPath}/neuralos);
15
- * the standalone backend auto-provisions ~/.cache/neuralos on first use
16
- * (one ~36 MB fetch from HuggingFace, offline forever after).
17
- *
18
- * Config (settings.neuralos, or env NEURALOS_*):
19
- * instancesDir — instances root (default ~/neuralos-instances)
20
- * pythonBin — interpreter with pydantic for the bridges (default python3)
21
- * engineBin / engineWeights — explicit engine override
22
- * autoDownload — provision the shared cache when nothing local (default true)
23
- */
24
-
25
- import { resolveConfig, listInstances, instanceDirFor, resolveEngine, engineSelect, executeProbe, graphProbe, adminProbe, truncate, defaultExec } from './neuralosEngine.mjs'
26
-
27
- export { resolveConfig } from './neuralosEngine.mjs'
28
-
29
- export function register(ctx) {
30
- const cfg = resolveConfig(ctx)
31
- const exec = defaultExec()
32
- const log = (line) => {
33
- try {
34
- if (typeof ctx.log === 'function') ctx.log(`[neuralos] ${line}`)
35
- } catch { /* logging must never break a tool */ }
36
- }
37
-
38
- const settings = (typeof ctx.getSettings === 'function' ? ctx.getSettings() : ctx.settings) || {}
39
- if (settings.neuralos?.enabled === false) {
40
- log('disabled in settings — not registering tools')
41
- return
42
- }
43
-
44
- registerList(ctx, cfg, log)
45
- registerAsk(ctx, cfg, exec, log)
46
- registerGraph(ctx, cfg, exec, log)
47
- registerAdmin(ctx, cfg, exec, log)
48
-
49
- log('registered neuralos tools (list_instances, ask, graph, admin)')
50
- }
51
-
52
- function registerList(ctx, cfg, log) {
53
- ctx.registerTool({
54
- name: 'neuralos_list_instances',
55
- description:
56
- 'List the available neuralOS instances (on-device data agents). Each instance is a verified menu of probes over a real data source (a database, API, or file set). Use this first to see what can be queried.',
57
- params: {},
58
- handler: async () => {
59
- const result = await listInstances(cfg)
60
- log('list_instances')
61
- return result
62
- },
63
- })
64
- }
65
-
66
- function registerAsk(ctx, cfg, exec, log) {
67
- ctx.registerTool({
68
- name: 'neuralos_ask',
69
- description:
70
- 'Ask a neuralOS instance a question in plain English. The on-device engine selects the probe, the instance bridge executes it against the real data source, and a verified digest comes back. Route data questions through this instead of querying the source directly.',
71
- params: {
72
- instance: { type: 'string', description: 'instance name from neuralos_list_instances' },
73
- question: { type: 'string', description: 'the question in plain English, verbatim' },
74
- },
75
- handler: async (p) => {
76
- const instance = String(p?.instance ?? '')
77
- const question = String(p?.question ?? '')
78
- const dir = await instanceDirFor(cfg, instance)
79
- if (typeof dir !== 'string') return dir
80
- const engine = await resolveEngine(cfg)
81
- if (engine.error) return engine
82
- const selection = await engineSelect(exec, engine, `${dir}/needle_menu.json`, question, { timeoutMs: cfg.timeoutMs })
83
- if (selection.error) return { instance, question, ...selection }
84
- const result = await executeProbe(exec, cfg, dir, selection.pick, selection.args, { timeoutMs: cfg.timeoutMs })
85
- log(`ask ${instance} -> ${selection.pick} (conf ${selection.confidence})`)
86
- return truncate({ instance, question, pick: selection.pick, confidence: selection.confidence, result })
87
- },
88
- })
89
- }
90
-
91
- function registerGraph(ctx, cfg, exec, log) {
92
- ctx.registerTool({
93
- name: 'neuralos_graph',
94
- description:
95
- 'Relationship questions over an instance: overview (the verified entity/edge map), neighbors (one-hop adjacency for a node fragment), or connect (path between two entities). Executes the graph probe directly.',
96
- params: {
97
- instance: { type: 'string', description: 'instance name' },
98
- op: { type: 'string', description: 'overview | neighbors | connect' },
99
- node: { type: 'string', description: 'node fragment for neighbors', optional: true },
100
- a: { type: 'string', description: 'first entity for connect', optional: true },
101
- b: { type: 'string', description: 'second entity for connect', optional: true },
102
- },
103
- handler: async (p) => {
104
- const dir = await instanceDirFor(cfg, String(p?.instance ?? ''))
105
- if (typeof dir !== 'string') return dir
106
- const result = await graphProbe(exec, cfg, dir, { op: String(p?.op ?? 'overview'), node: p?.node, a: p?.a, b: p?.b })
107
- log(`graph ${p?.op}`)
108
- return truncate(result)
109
- },
110
- })
111
- }
112
-
113
- function registerAdmin(ctx, cfg, exec, log) {
114
- ctx.registerTool({
115
- name: 'neuralos_admin',
116
- description:
117
- 'Execute a WRITE/admin probe on an instance (boot a server, tag a resource, purge DNS). Destructive and reversible only per the instance design; the confirm parameter must be the literal string "yes" — that is the operator interlock. Read-only questions must use neuralos_ask instead.',
118
- params: {
119
- instance: { type: 'string', description: 'instance name' },
120
- probe: { type: 'string', description: 'admin probe name, e.g. aws_power' },
121
- args: { type: 'object', description: 'probe arguments as JSON', optional: true },
122
- confirm: { type: 'string', description: 'must be the literal "yes"' },
123
- },
124
- handler: async (p) => {
125
- if (p?.confirm !== 'yes') return { error: 'admin probes require confirm="yes" — restate what you intend to run and pass confirm="yes"' }
126
- const dir = await instanceDirFor(cfg, String(p?.instance ?? ''))
127
- if (typeof dir !== 'string') return dir
128
- const result = await adminProbe(exec, cfg, dir, String(p?.probe ?? ''), p?.args ?? {})
129
- log(`admin ${p?.probe} on ${p?.instance}`)
130
- return truncate(result)
131
- },
132
- })
133
- }
134
-
135
- export function unregister() {
136
- /* tools deregister with the plugin record; nothing to stop here */
137
- }
@@ -1,386 +0,0 @@
1
- /**
2
- * neuralos — plugin extreme spec.
3
- *
4
- * Pure invariants, always run, no network and no engine required:
5
- * config precedence, engine-name mapping, JSON recovery, instance
6
- * discovery, engine selection against a FAKE engine script (node is the
7
- * only dependency), probe execution against a FAKE python (the runner is
8
- * exercised via an injected exec), the graph direct-execution rule, and
9
- * the admin confirm interlock. The auto-download path is tested with an
10
- * injected fetch that writes local files — CI never touches HuggingFace.
11
- *
12
- * Run: npx tsx plugins/neuralos/neuralos.extreme.spec.mts
13
- */
14
- import { spawnSync } from 'node:child_process'
15
- import { mkdtempSync, writeFileSync, mkdirSync, existsSync, chmodSync } from 'node:fs'
16
- import { homedir, tmpdir } from 'node:os'
17
- import { join } from 'node:path'
18
-
19
- import {
20
- HF_BASE,
21
- adminProbe,
22
- bundledEngineName,
23
- defaultExec,
24
- engineSelect,
25
- ensureEngine,
26
- executeProbe,
27
- graphProbe,
28
- listInstances,
29
- parseJsonObject,
30
- resolveConfig,
31
- resolveEngine,
32
- truncate,
33
- } from './neuralosEngine.mjs'
34
- import { register } from './index.mjs'
35
-
36
- let pass = 0
37
- let fail = 0
38
- const failures: string[] = []
39
-
40
- function ok(cond: unknown, label: string, note = ''): void {
41
- if (cond) {
42
- pass++
43
- console.log(`PASS ${label}`)
44
- } else {
45
- fail++
46
- failures.push(`${label}${note ? ` — ${note}` : ''}`)
47
- console.log(`FAIL ${label}${note ? ` — ${note}` : ''}`)
48
- }
49
- }
50
-
51
- // ---------------------------------------------------------------------------
52
- // 1. Config precedence: settings block > env > default; blank = unset.
53
- // ---------------------------------------------------------------------------
54
- {
55
- const cfg = resolveConfig({ getSettings: () => ({ neuralos: { pythonBin: '/framework/python3.12', instancesDir: ' ' } }) }, {})
56
- ok(cfg.pythonBin === '/framework/python3.12', 'settings block wins for pythonBin')
57
- ok(cfg.instancesDir === join(homedir(), 'neuralos-instances'), 'blank settings value falls through to default', cfg.instancesDir)
58
-
59
- const cfgEnv = resolveConfig({ getSettings: () => ({}) }, { NEURALOS_INSTANCES_DIR: '/custom/instances' })
60
- ok(cfgEnv.instancesDir === '/custom/instances', 'env wins when settings are silent')
61
-
62
- const cfgOff = resolveConfig({ getSettings: () => ({ neuralos: { autoDownload: false } }) }, {})
63
- ok(cfgOff.autoDownload === false, 'autoDownload can be disabled in settings')
64
- const cfgOn = resolveConfig({ getSettings: () => ({}) }, {})
65
- ok(cfgOn.autoDownload === true, 'autoDownload defaults on')
66
- }
67
- // ---------------------------------------------------------------------------
68
- // 2. Engine name mapping (macos-x64 has no published engine).
69
- // ---------------------------------------------------------------------------
70
- {
71
- ok(bundledEngineName('darwin', 'arm64') === 'engine-macos-arm64', 'darwin/arm64 maps to engine-macos-arm64')
72
- ok(bundledEngineName('linux', 'x64') === 'engine-linux-x86_64', 'linux/x64 maps to engine-linux-x86_64')
73
- ok(bundledEngineName('linux', 'arm64') === 'engine-linux-arm64', 'linux/arm64 maps to engine-linux-arm64')
74
- ok(bundledEngineName('win32', 'x64') === 'engine-windows-x86_64.exe', 'win32/x64 maps to engine-windows-x86_64.exe')
75
- ok(bundledEngineName('darwin', 'x64') === null, 'darwin/x64 has no published engine')
76
- }
77
-
78
- // ---------------------------------------------------------------------------
79
- // 3. JSON recovery from noisy engine output.
80
- // ---------------------------------------------------------------------------
81
- {
82
- const parsed = parseJsonObject('noise {"function_calls":[{"name":"p"}], "confidence": 0.9} trailing')
83
- ok(parsed?.function_calls?.[0]?.name === 'p', 'parseJsonObject recovers JSON embedded in noise')
84
- ok(parseJsonObject('not json') === null, 'parseJsonObject returns null for garbage')
85
- }
86
-
87
- // ---------------------------------------------------------------------------
88
- // 4. Instance discovery (fake instances root in a temp dir).
89
- // ---------------------------------------------------------------------------
90
- {
91
- const root = mkdtempSync(join(tmpdir(), 'neuralos-spec-'))
92
- mkdirSync(join(root, 'chinook'))
93
- writeFileSync(join(root, 'chinook', 'needle_menu.json'), JSON.stringify([{ name: 'p1' }, { name: 'p2' }]))
94
- mkdirSync(join(root, 'not-an-instance'))
95
-
96
- const cfg = resolveConfig({}, { NEURALOS_INSTANCES_DIR: root })
97
- const listed = await listInstances(cfg)
98
- ok(
99
- Array.isArray(listed) && listed.length === 1 && listed[0].name === 'chinook' && listed[0].probeCount === 2,
100
- 'listInstances finds only dirs with a needle_menu.json',
101
- JSON.stringify(listed),
102
- )
103
-
104
- const missing = await listInstances(cfg, { readdir: async () => { throw new Error('ENOENT') }, readFile: async () => '' })
105
- ok(
106
- (missing as { error?: string }).error?.includes('no instances directory'),
107
- 'listInstances reports a missing root as data',
108
- )
109
- }
110
-
111
- // ---------------------------------------------------------------------------
112
- // 5. Engine selection against a FAKE engine (a node script).
113
- // ---------------------------------------------------------------------------
114
- {
115
- const dir = mkdtempSync(join(tmpdir(), 'neuralos-engine-'))
116
- const fakeEngine = join(dir, 'fake-engine.mjs')
117
- writeFileSync(
118
- fakeEngine,
119
- `const args = process.argv.slice(2)
120
- const qi = args.indexOf('--prompt')
121
- const q = qi >= 0 ? args[qi + 1] : ''
122
- if (q === 'refuse me') { console.log(JSON.stringify({ function_calls: [] })) }
123
- else if (q === 'garbage') { console.log('not json at all') }
124
- else if (q === 'crash') { process.stderr.write('boom'); process.exit(1) }
125
- else { console.log(JSON.stringify({ function_calls: [{ name: 'transactions_count', arguments: { limit: 5 } }], confidence: 0.98 })) }
126
- `,
127
- )
128
- const runEngine = (q: string) =>
129
- engineSelect(
130
- (cmd: string, args: string[]) => {
131
- void cmd
132
- const r = spawnSync(process.execPath, [fakeEngine, ...args.slice(2)], { encoding: 'utf8' })
133
- return Promise.resolve({ ok: r.status === 0, code: r.status ?? 1, stdout: r.stdout ?? '', stderr: r.stderr ?? '' })
134
- },
135
- { engineBin: fakeEngine, engineWeights: join(dir, 'weights.cact') },
136
- '/i/needle_menu.json',
137
- q,
138
- )
139
-
140
- const hit = await runEngine('count invoices')
141
- ok(hit.pick === 'transactions_count' && hit.confidence === 0.98, 'engineSelect parses the selection with confidence', JSON.stringify(hit))
142
- const refused = await runEngine('refuse me')
143
- ok((refused as { error?: string }).error === 'engine refused the question (no call selected)', 'refusal is data, not an exception')
144
- const garbage = await runEngine('garbage')
145
- ok((garbage as { error?: string }).error?.includes('engine output not JSON'), 'non-JSON engine output is data')
146
- const crash = await runEngine('crash')
147
- ok((crash as { error?: string }).error === 'engine exited 1: boom', 'engine crash with empty stdout is data')
148
- }
149
-
150
- // ---------------------------------------------------------------------------
151
- // 6. Probe execution via an injected exec (the real path spawns python).
152
- // ---------------------------------------------------------------------------
153
- {
154
- const cfg = resolveConfig({}, { NEURALOS_PYTHON: '/framework/python3.12' })
155
- const seen: Array<{ cmd: string; args: string[]; env: Record<string, string> }> = []
156
- const exec = (cmd: string, args: string[], opts: { env?: Record<string, string> }) => {
157
- seen.push({ cmd, args, env: opts.env ?? {} })
158
- return Promise.resolve({ ok: true, code: 0, stdout: '{"count": 42}', stderr: '' })
159
- }
160
- const result = await executeProbe(exec, cfg, '/i', 'transactions_count', { limit: 5 })
161
- ok(result.count === 42, 'executeProbe returns the parsed digest')
162
- ok(seen[0].cmd === '/framework/python3.12' && seen[0].args[0] === '-c', 'executeProbe spawns the configured python with the runner')
163
- ok(
164
- seen[0].env.NEURALOS_INSTANCE_DIR === '/i' && seen[0].env.NEURALOS_PROBE === 'transactions_count' && seen[0].env.NEURALOS_ARGS === '{"limit":5}',
165
- 'probe name and args travel through the environment, never the command line',
166
- )
167
-
168
- const failed = await executeProbe((() => Promise.resolve({ ok: false, code: 1, stdout: '', stderr: 'boom' })) as never, cfg, '/i', 'p', {})
169
- ok((failed as { error?: string }).error === 'probe exited 1: boom', 'probe failure surfaces as data')
170
- }
171
-
172
- // ---------------------------------------------------------------------------
173
- // 7. Graph direct execution + admin interlock.
174
- // ---------------------------------------------------------------------------
175
- {
176
- const dir = mkdtempSync(join(tmpdir(), 'neuralos-graph-'))
177
- writeFileSync(
178
- join(dir, 'needle_menu.json'),
179
- JSON.stringify([{ name: 'cb_graph_overview' }, { name: 'cb_graph_neighbors' }, { name: 'transactions_count' }]),
180
- )
181
- const cfg = resolveConfig({}, {})
182
- const exec = () => Promise.resolve({ ok: true, code: 0, stdout: '{"nodes":[]}', stderr: '' })
183
-
184
- const overview = await graphProbe(exec, cfg, dir, { op: 'overview' })
185
- ok((overview as { probe?: string }).probe === 'cb_graph_overview' && (overview as { nodes?: unknown[] }).nodes?.length === 0, 'graph overview executes the probe directly by name')
186
- const neighbors = await graphProbe(exec, cfg, dir, { op: 'neighbors', node: '4001234567' })
187
- ok((neighbors as { probe?: string }).probe === 'cb_graph_neighbors', 'graph neighbors executes by name (numeric fragments bypass engine grounding)')
188
- const missingOp = await graphProbe(exec, cfg, dir, { op: 'connect', a: 'x', b: 'y' })
189
- ok(
190
- (missingOp as { error?: string }).error === 'this instance has no connect graph probe' &&
191
- Array.isArray((missingOp as { available_graph_probes?: string[] }).available_graph_probes),
192
- 'a missing graph op lists what exists instead of guessing',
193
- )
194
-
195
- const admin = await adminProbe(exec, cfg, dir, 'aws_tag', { confirm: 'yes' })
196
- ok(admin.admin === true && admin.probe === 'aws_tag', 'admin wraps the result with the probe name and admin flag')
197
- const badName = await adminProbe(exec, cfg, dir, 'rm -rf', {})
198
- ok((badName as { error?: string }).error === 'invalid probe name: rm -rf', 'admin rejects probe names that are not identifiers')
199
- }
200
-
201
- // ---------------------------------------------------------------------------
202
- // 8. Engine resolution: settings override wins; auto-download provisions the
203
- // cache via an injected fetch (no network in CI); idempotent on re-run.
204
- // ---------------------------------------------------------------------------
205
- {
206
- const explicitDir = mkdtempSync(join(tmpdir(), 'neuralos-explicit-'))
207
- writeFileSync(join(explicitDir, 'needle'), 'fake-engine')
208
- writeFileSync(join(explicitDir, 'needle3.cact'), 'fake-weights')
209
- const cacheAlt = mkdtempSync(join(tmpdir(), 'neuralos-cache-alt-'))
210
- writeFileSync(join(cacheAlt, 'engine-macos-arm64'), 'cache-engine')
211
- writeFileSync(join(cacheAlt, 'needle3.cact'), 'cache-weights')
212
- const explicit = await resolveEngine(
213
- resolveConfig({ getSettings: () => ({ neuralos: { engineBin: join(explicitDir, 'needle'), engineWeights: join(explicitDir, 'needle3.cact'), cacheDir: cacheAlt } }) }, {}),
214
- { platform: 'darwin', arch: 'arm64' },
215
- )
216
- ok(explicit.engineBin === join(explicitDir, 'needle'), 'explicit settings engine wins over a populated cache')
217
-
218
- const cache = mkdtempSync(join(tmpdir(), 'neuralos-cache-'))
219
- const downloads: string[] = []
220
- const fetchImpl = async (url: string, target: string) => {
221
- downloads.push(url)
222
- writeFileSync(target, `fake:${url}`)
223
- }
224
- const cfg = resolveConfig({ getSettings: () => ({}) }, { NEURALOS_INSTANCES_DIR: mkdtempSync(join(tmpdir(), 'neuralos-none-')), NEURALOS_CACHE_DIR: cache })
225
- const first = await ensureEngine({ platform: 'darwin', arch: 'arm64', cacheDir: cache, fetchImpl })
226
- ok(first.engineBin === join(cache, 'engine-macos-arm64') && first.engineWeights === join(cache, 'needle3.cact'), 'ensureEngine provisions weights + per-arch engine into the cache', JSON.stringify(first))
227
- ok(downloads.length === 2 && downloads[0] === `${HF_BASE}/needle3.cact` && downloads[1].includes('macos-arm64/needle'), 'downloads come from the public Cactus-Compute/needle3 URLs')
228
- ok(existsSync(join(cache, 'engine-macos-arm64')), 'the engine file exists on disk after provisioning')
229
-
230
- const second = await ensureEngine({ platform: 'darwin', arch: 'arm64', cacheDir: cache, fetchImpl })
231
- ok(second.engineBin === first.engineBin && downloads.length === 2, 'provisioning is idempotent — no re-download when cached')
232
-
233
- // resolution now finds the cache without any download
234
- const resolved = await resolveEngine(cfg, { platform: 'darwin', arch: 'arm64' })
235
- ok(resolved.engineBin === join(cache, 'engine-macos-arm64'), 'resolveEngine picks the provisioned cache copy')
236
- }
237
-
238
- // ---------------------------------------------------------------------------
239
- // 9. Tool registration + the admin confirm interlock at the tool layer.
240
- // ---------------------------------------------------------------------------
241
- {
242
- const registered: Array<{ name: string; params: Record<string, unknown>; handler: (args: unknown) => Promise<unknown> }> = []
243
- const logged: string[] = []
244
- register({
245
- registerTool: (t) => registered.push(t as never),
246
- log: (l: string) => logged.push(l),
247
- getSettings: () => ({}),
248
- })
249
- ok(
250
- registered.map((t) => t.name).sort().join(',') === 'neuralos_admin,neuralos_ask,neuralos_graph,neuralos_list_instances',
251
- 'register() wires exactly the four neuralos tools',
252
- )
253
- const admin = registered.find((t) => t.name === 'neuralos_admin')
254
- const denied = (await admin.handler({ instance: 'aws', probe: 'aws_power', confirm: 'no' })) as { error?: string }
255
- ok(denied.error?.includes('confirm="yes"'), 'the tool layer refuses admin without confirm="yes"')
256
- const unknown = registered.find((t) => t.name === 'neuralos_ask')
257
- const answered = (await unknown.handler({ instance: 'does-not-exist', question: 'x' })) as { error?: string }
258
- ok(answered.error?.includes("no instance named 'does-not-exist'"), 'ask answers an unknown instance as data')
259
- ok(logged.some((l) => l.includes('registered neuralos tools')), 'the plugin announces registration through ctx.log')
260
-
261
- // the enabled=false settings gate: the Settings panel switch is honest
262
- const off: Array<{ name: string }> = []
263
- register({
264
- registerTool: (t) => off.push(t as never),
265
- log: () => {},
266
- getSettings: () => ({ neuralos: { enabled: false } }),
267
- })
268
- ok(off.length === 0, 'enabled=false registers zero tools')
269
- }
270
-
271
- // ---------------------------------------------------------------------------
272
- // 10. Digest truncation keeps tool results bounded.
273
- // ---------------------------------------------------------------------------
274
- {
275
- const big = { rows: 'x'.repeat(20_000) }
276
- const text = truncate(big, 6000)
277
- ok(text.length < 21_000 && text.includes('truncated at 6000 chars'), 'truncate caps large digests with an honest marker')
278
- ok(truncate('small') === 'small', 'small digests pass through untouched')
279
- }
280
-
281
- // ---------------------------------------------------------------------------
282
- // 11. The REAL defaultExec forwards opts.env (probe transport) to the child
283
- // while keeping the parent environment (PATH etc.) intact.
284
- // ---------------------------------------------------------------------------
285
- {
286
- const exec = defaultExec()
287
- const out = await exec(process.execPath, ['-e', 'console.log(process.env.NEURALOS_SPEC_PROBE ?? "missing")'], { env: { NEURALOS_SPEC_PROBE: 'forwarded' } })
288
- ok(out.code === 0 && out.stdout.trim() === 'forwarded', 'defaultExec forwards opts.env to the child process')
289
- const plain = await exec(process.execPath, ['-e', 'console.log(Boolean(process.env.PATH))'])
290
- ok(plain.code === 0 && plain.stdout.trim() === 'true', 'defaultExec keeps the parent environment for plain calls')
291
- }
292
-
293
- // ---------------------------------------------------------------------------
294
- // 12. The lost-exec-bit family (v3.9.2). curl -o / electron-builder
295
- // extraResources ship engines with mode 0644; spawning them dies with
296
- // EACCES and an EMPTY stderr. Resolution must check executability, not
297
- // existence — a broken bundle must never shadow a runnable engine.
298
- // ---------------------------------------------------------------------------
299
- {
300
- // 12a. A 0644 bundled engine is skipped, and the next candidate wins.
301
- const root = mkdtempSync(join(tmpdir(), 'neuralos-execbit-'))
302
- const resources = join(root, 'resources')
303
- const cache = join(root, 'cache')
304
- const instances = join(root, 'instances')
305
- mkdirSync(join(resources, 'neuralos'), { recursive: true })
306
- mkdirSync(cache, { recursive: true })
307
- mkdirSync(join(instances, 'engine'), { recursive: true })
308
- // bundled copy: present but NOT executable (the v3.9.1 bug)
309
- writeFileSync(join(resources, 'neuralos', 'engine-macos-arm64'), 'bundled-broken')
310
- writeFileSync(join(resources, 'neuralos', 'needle3.cact'), 'bundled-weights')
311
- chmodSync(join(resources, 'neuralos', 'engine-macos-arm64'), 0o644)
312
- // fleet-convention copy: executable
313
- writeFileSync(join(instances, 'engine', 'needle'), '#!/bin/sh\necho ok')
314
- writeFileSync(join(instances, 'engine', 'needle3.cact'), 'fleet-weights')
315
- chmodSync(join(instances, 'engine', 'needle'), 0o755)
316
-
317
- const cfg = resolveConfig({ getSettings: () => ({}) }, { NEURALOS_INSTANCES_DIR: instances, NEURALOS_CACHE_DIR: cache, NEURALOS_AUTO_DOWNLOAD: '0' })
318
- const resolved = await resolveEngine(cfg, { platform: 'darwin', arch: 'arm64', resourcesPath: resources })
319
- ok(resolved.engineBin === join(instances, 'engine', 'needle'), 'a 0644 bundled engine is skipped for the runnable fleet engine', JSON.stringify(resolved))
320
-
321
- // 12b. chmod recovery: a 0644 engine we OWN is fixed in place, not skipped.
322
- const ownedDir = mkdtempSync(join(tmpdir(), 'neuralos-owned-'))
323
- writeFileSync(join(ownedDir, 'engine-macos-arm64'), 'owned')
324
- writeFileSync(join(ownedDir, 'needle3.cact'), 'owned-weights')
325
- chmodSync(join(ownedDir, 'engine-macos-arm64'), 0o644)
326
- const cfgOwned = resolveConfig({ getSettings: () => ({}) }, { NEURALOS_INSTANCES_DIR: join(root, 'nope'), NEURALOS_CACHE_DIR: ownedDir, NEURALOS_AUTO_DOWNLOAD: '0' })
327
- const recovered = await resolveEngine(cfgOwned, { platform: 'darwin', arch: 'arm64' })
328
- ok(recovered.engineBin === join(ownedDir, 'engine-macos-arm64'), 'a 0644 engine we own is chmod-recovered instead of skipped', JSON.stringify(recovered))
329
-
330
- // 12c. EACCES at spawn time is named, with the fix in the message.
331
- const denied = await engineSelect(
332
- () => Promise.resolve({ ok: false, code: 'EACCES', stdout: '', stderr: '' }),
333
- { engineBin: '/broken/engine', engineWeights: '/w' },
334
- '/m',
335
- 'q',
336
- )
337
- ok(
338
- (denied as { error?: string }).error?.includes('engine not executable') && (denied as { error?: string }).error?.includes('chmod +x'),
339
- 'EACCES spawn failure names the fix (chmod +x / engineBin override)',
340
- JSON.stringify(denied),
341
- )
342
-
343
- // 12d. A non-EACCES crash keeps the original shape.
344
- const crash = await engineSelect(
345
- () => Promise.resolve({ ok: false, code: 1, stdout: '', stderr: 'boom' }),
346
- { engineBin: '/e', engineWeights: '/w' },
347
- '/m',
348
- 'q',
349
- )
350
- ok((crash as { error?: string }).error === 'engine exited 1: boom', 'non-EACCES crashes keep the engine exited N: stderr shape')
351
-
352
- // 12e. defaultExec preserves a real EACCES code from the OS.
353
- const realExec = defaultExec()
354
- const noExecFile = join(root, 'not-executable')
355
- writeFileSync(noExecFile, 'x')
356
- chmodSync(noExecFile, 0o644)
357
- const eacc = await realExec(noExecFile, [])
358
- ok(eacc.code === 'EACCES', 'defaultExec surfaces EACCES as the code (not a generic 1)', JSON.stringify(eacc.code))
359
-
360
- // 12f. defaultExec reports timeout kills honestly.
361
- const slow = await realExec(process.execPath, ['-e', 'setTimeout(() => {}, 30000)'], { timeoutMs: 300 })
362
- ok(String(slow.code).includes('killed') || String(slow.code).includes('ETIMEDOUT'), 'a timeout kill is reported as killed/ETIMEDOUT, not exit 1', JSON.stringify(slow.code))
363
-
364
- // 12g. truncate(undefined) must not throw (JSON.stringify(undefined) is undefined).
365
- const undef = truncate(undefined as never)
366
- ok(undef === 'null', 'truncate(undefined) renders honestly instead of crashing', String(undef))
367
-
368
- // 12h. A corrupt menu is data for graphProbe, never an exception.
369
- const corruptDir = mkdtempSync(join(tmpdir(), 'neuralos-corrupt-'))
370
- writeFileSync(join(corruptDir, 'needle_menu.json'), 'NOT JSON {{{')
371
- const corrupt = await graphProbe(() => Promise.resolve({ ok: true, code: 0, stdout: '{}', stderr: '' }), resolveConfig({}, {}), corruptDir, { op: 'overview' })
372
- ok((corrupt as { error?: string }).error?.includes('instance menu unreadable'), 'graphProbe reports a corrupt menu as data')
373
-
374
- // 12i. A menu that is valid JSON but not an array is also rejected as data.
375
- const nonArrayDir = mkdtempSync(join(tmpdir(), 'neuralos-nonarray-'))
376
- writeFileSync(join(nonArrayDir, 'needle_menu.json'), '{"not":"an array"}')
377
- const nonArray = await graphProbe(() => Promise.resolve({ ok: true, code: 0, stdout: '{}', stderr: '' }), resolveConfig({}, {}), nonArrayDir, { op: 'overview' })
378
- ok((nonArray as { error?: string }).error?.includes('instance menu unreadable'), 'a non-array menu is rejected as data')
379
- }
380
-
381
- console.log(`\n${pass} passed, ${fail} failed`)
382
- if (fail > 0) {
383
- console.log(`FAILURES:\n${failures.map((f) => ` - ${f}`).join('\n')}`)
384
- process.exit(1)
385
- }
386
- console.log('neuralos: ALL TESTS PASSED')