neuralos 3.9.5 → 3.10.0

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 (69) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +103 -337
  3. package/cli.js +103 -0
  4. package/engine.js +124 -0
  5. package/index.js +230 -0
  6. package/package.json +38 -43
  7. package/test.js +20 -0
  8. package/tools.js +89 -0
  9. package/validate.js +263 -0
  10. package/bin/gybackend.cjs +0 -411663
  11. package/plugins/agentspan-bridge/agentspan-bridge.extreme.spec.ts +0 -328
  12. package/plugins/agentspan-bridge/agentspan-bridge.phase2.extreme.spec.ts +0 -282
  13. package/plugins/agentspan-bridge/conductorClient.mjs +0 -182
  14. package/plugins/agentspan-bridge/index.mjs +0 -389
  15. package/plugins/agentspan-bridge/playbookToWorkflowDef.mjs +0 -209
  16. package/plugins/agentspan-bridge/plugin.json +0 -27
  17. package/plugins/fraudops/index.d.mts +0 -9
  18. package/plugins/fraudops/index.mjs +0 -185
  19. package/plugins/fraudops/plugin.json +0 -10
  20. package/plugins/iam-connector/index.d.mts +0 -10
  21. package/plugins/iam-connector/index.mjs +0 -229
  22. package/plugins/iam-connector/plugin.json +0 -10
  23. package/plugins/mitmproxy-bridge/index.d.mts +0 -20
  24. package/plugins/mitmproxy-bridge/index.mjs +0 -256
  25. package/plugins/mitmproxy-bridge/plugin.json +0 -17
  26. package/plugins/monid-bridge/index.mjs +0 -228
  27. package/plugins/monid-bridge/monid-bridge.extreme.spec.mjs +0 -75
  28. package/plugins/monid-bridge/plugin.json +0 -15
  29. package/plugins/netdata-rterm/index.mjs +0 -229
  30. package/plugins/netdata-rterm/netdata-rterm.extreme.spec.ts +0 -252
  31. package/plugins/netdata-rterm/plugin.json +0 -22
  32. package/plugins/netexec-bridge/index.d.mts +0 -28
  33. package/plugins/netexec-bridge/index.mjs +0 -325
  34. package/plugins/netexec-bridge/plugin.json +0 -20
  35. package/plugins/neuralos/index.mjs +0 -137
  36. package/plugins/neuralos/neuralos.extreme.spec.mts +0 -386
  37. package/plugins/neuralos/neuralosEngine.mjs +0 -369
  38. package/plugins/neuralos/plugin.json +0 -15
  39. package/plugins/numbat-bridge/index.mjs +0 -229
  40. package/plugins/numbat-bridge/numbat-bridge.extreme.spec.mjs +0 -148
  41. package/plugins/numbat-bridge/plugin.json +0 -21
  42. package/plugins/patch-manager/index.d.mts +0 -10
  43. package/plugins/patch-manager/index.mjs +0 -288
  44. package/plugins/patch-manager/plugin.json +0 -10
  45. package/plugins/promptfoo-redteam/index.d.mts +0 -15
  46. package/plugins/promptfoo-redteam/index.mjs +0 -277
  47. package/plugins/promptfoo-redteam/plugin.json +0 -16
  48. package/plugins/reactorpro-bridge/index.mjs +0 -499
  49. package/plugins/reactorpro-bridge/plugin.json +0 -26
  50. package/plugins/reactorpro-bridge/reactorpro-bridge.extreme.spec.mts +0 -409
  51. package/plugins/reactorpro-bridge/reactorproAgent.mjs +0 -399
  52. package/plugins/request-router/index.d.mts +0 -9
  53. package/plugins/request-router/index.mjs +0 -239
  54. package/plugins/request-router/plugin.json +0 -10
  55. package/plugins/sample-k8s-slo/index.ts +0 -58
  56. package/plugins/sample-k8s-slo/plugin.json +0 -11
  57. package/plugins/sop-assistant/index.d.mts +0 -9
  58. package/plugins/sop-assistant/index.mjs +0 -325
  59. package/plugins/sop-assistant/plugin.json +0 -10
  60. package/plugins/synapse-bridge/index.mjs +0 -484
  61. package/plugins/synapse-bridge/plugin.json +0 -29
  62. package/plugins/synapse-bridge/synapse-bridge.extreme.spec.mjs +0 -204
  63. package/plugins/synapse-bridge/synapseAgent.extreme.spec.mjs +0 -236
  64. package/plugins/synapse-bridge/synapseAgent.mjs +0 -254
  65. package/plugins/web-intel/index.mjs +0 -381
  66. package/plugins/web-intel/plugin.json +0 -28
  67. package/plugins/web-intel/sidecar.mjs +0 -134
  68. package/plugins/web-intel/web-intel.extreme.spec.ts +0 -263
  69. package/plugins/web-intel/wigoloClient.mjs +0 -145
@@ -1,182 +0,0 @@
1
- /**
2
- * conductorClient.mjs — a minimal, dependency-free HTTP client for the
3
- * AgentSpan / Netflix-Conductor REST API.
4
- *
5
- * AgentSpan server runs on Conductor (default http://localhost:6767) and
6
- * exposes both its own `/api/agent/*` lifecycle surface and Conductor's
7
- * `/api/workflow/*` engine surface. This client is pure + injectable (a
8
- * `fetchImpl` is passed in) so it is fully unit-testable offline and has no
9
- * runtime network dependency baked in — the plugin wires the real `fetch`.
10
- *
11
- * Auth: AgentSpan standalone auth uses X-Auth-Key / X-Auth-Secret headers
12
- * (AGENTSPAN_AUTH_KEY / AGENTSPAN_AUTH_SECRET). Conductor OSS uses no auth by
13
- * default. The client sends the headers only when both are provided.
14
- */
15
-
16
- export const DEFAULT_BASE_URL = 'http://localhost:6767'
17
-
18
- /** Build the auth headers for a request (only when both key+secret are set). */
19
- export function authHeaders(auth) {
20
- const h = { 'content-type': 'application/json', accept: 'application/json' }
21
- if (auth && auth.key && auth.secret) {
22
- h['X-Auth-Key'] = auth.key
23
- h['X-Auth-Secret'] = auth.secret
24
- }
25
- return h
26
- }
27
-
28
- /** Join a base URL + path safely (single slash). */
29
- export function joinUrl(base, path) {
30
- const b = String(base || DEFAULT_BASE_URL).replace(/\/+$/, '')
31
- const p = String(path || '').startsWith('/') ? String(path) : `/${path}`
32
- return `${b}${p}`
33
- }
34
-
35
- /** Parse a response body as JSON when possible, else return raw text. */
36
- async function parseBody(res) {
37
- const text = await res.text()
38
- if (!text) return null
39
- try { return JSON.parse(text) } catch { return text }
40
- }
41
-
42
- export class ConductorApiError extends Error {
43
- constructor(status, path, body) {
44
- super(`conductor ${status} ${path}: ${typeof body === 'string' ? body.slice(0, 300) : JSON.stringify(body)?.slice(0, 300)}`)
45
- this.status = status
46
- this.path = path
47
- this.body = body
48
- }
49
- }
50
-
51
- export class ConductorClient {
52
- /**
53
- * @param {{ baseUrl?: string, auth?: {key?:string, secret?:string}, fetchImpl: Function }} opts
54
- * fetchImpl(url, {method, headers, body}) -> Promise<{ok,status,text:()=>Promise<string>}>
55
- */
56
- constructor(opts = {}) {
57
- if (typeof opts.fetchImpl !== 'function') throw new Error('ConductorClient needs a fetchImpl')
58
- this.baseUrl = opts.baseUrl || DEFAULT_BASE_URL
59
- this.auth = opts.auth
60
- this.fetchImpl = opts.fetchImpl
61
- }
62
-
63
- async request(method, path, body) {
64
- const url = joinUrl(this.baseUrl, path)
65
- const res = await this.fetchImpl(url, {
66
- method,
67
- headers: authHeaders(this.auth),
68
- ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
69
- })
70
- const parsed = await parseBody(res)
71
- if (!res.ok) throw new ConductorApiError(res.status, `${method} ${path}`, parsed)
72
- return parsed
73
- }
74
-
75
- // ── Health ────────────────────────────────────────────────────────────────
76
- /** AgentSpan/Conductor health (Spring Actuator). */
77
- async health() {
78
- try {
79
- const r = await this.request('GET', '/actuator/health')
80
- return { ok: true, status: r?.status ?? 'UP', raw: r }
81
- } catch (e) {
82
- return { ok: false, status: 'DOWN', error: e.message }
83
- }
84
- }
85
-
86
- // ── Agent lifecycle (AgentSpan /api/agent) ───────────────────────────────
87
- /** Compile + register + start an agent from an AgentConfig; returns { executionId }. */
88
- async runAgent(agentConfig, prompt) {
89
- const body = prompt !== undefined ? { agent: agentConfig, input: prompt } : agentConfig
90
- const r = await this.request('POST', '/api/agent/start', body)
91
- return { executionId: r?.executionId ?? r?.workflowId ?? r?.id ?? null, raw: r }
92
- }
93
-
94
- /** Compile only (returns the WorkflowDef without executing). */
95
- async compileAgent(agentConfig) {
96
- return this.request('POST', '/api/agent/compile', agentConfig)
97
- }
98
-
99
- /** Detailed status of a running/finished execution. */
100
- async agentStatus(executionId) {
101
- if (!executionId) throw new Error('agentStatus needs an executionId')
102
- return this.request('GET', `/api/agent/${encodeURIComponent(executionId)}`)
103
- }
104
-
105
- /** Human-in-the-loop respond: complete a paused HUMAN task + resume. */
106
- async agentRespond(executionId, output) {
107
- if (!executionId) throw new Error('agentRespond needs an executionId')
108
- return this.request('POST', `/api/agent/${encodeURIComponent(executionId)}/respond`, output ?? {})
109
- }
110
-
111
- /** Stop (cancel) an execution. */
112
- async agentStop(executionId) {
113
- if (!executionId) throw new Error('agentStop needs an executionId')
114
- return this.request('POST', `/api/agent/${encodeURIComponent(executionId)}/stop`, {})
115
- }
116
-
117
- /** Server-sent event log for an execution. */
118
- async agentEvents(executionId) {
119
- if (!executionId) throw new Error('agentEvents needs an executionId')
120
- return this.request('GET', `/api/agent/events/${encodeURIComponent(executionId)}`)
121
- }
122
-
123
- /** List agent definitions registered on the server. */
124
- async listAgentDefinitions() {
125
- return this.request('GET', '/api/agent/definitions')
126
- }
127
-
128
- // ── Workflow engine (Conductor /api/workflow) ────────────────────────────
129
- /** Start a named Conductor workflow; returns the workflowId string. */
130
- async startWorkflow(name, input, opts = {}) {
131
- if (!name) throw new Error('startWorkflow needs a workflow name')
132
- const q = new URLSearchParams()
133
- if (opts.version !== undefined) q.set('version', String(opts.version))
134
- if (opts.correlationId) q.set('correlationId', String(opts.correlationId))
135
- const path = `/api/workflow/${encodeURIComponent(name)}${q.size ? `?${q}` : ''}`
136
- const r = await this.request('POST', path, input ?? {})
137
- return typeof r === 'string' ? r : (r?.workflowId ?? r?.id ?? null)
138
- }
139
-
140
- /** Get a workflow execution (status + tasks). */
141
- async getWorkflow(workflowId, includeTasks = true) {
142
- if (!workflowId) throw new Error('getWorkflow needs a workflowId')
143
- return this.request('GET', `/api/workflow/${encodeURIComponent(workflowId)}?includeTasks=${includeTasks}`)
144
- }
145
-
146
- /** Terminate a workflow execution. */
147
- async terminateWorkflow(workflowId, reason) {
148
- if (!workflowId) throw new Error('terminateWorkflow needs a workflowId')
149
- const q = reason ? `?reason=${encodeURIComponent(reason)}` : ''
150
- return this.request('DELETE', `/api/workflow/${encodeURIComponent(workflowId)}${q}`)
151
- }
152
-
153
- /** Retry a failed workflow from the last failed task (durable resume). */
154
- async retryWorkflow(workflowId) {
155
- if (!workflowId) throw new Error('retryWorkflow needs a workflowId')
156
- return this.request('POST', `/api/workflow/${encodeURIComponent(workflowId)}/retry`, {})
157
- }
158
-
159
- /** Search workflow executions (freeText query, e.g. status:FAILED). */
160
- async searchWorkflows(query = '*', size = 20) {
161
- const q = new URLSearchParams({ freeText: query, size: String(size), sort: 'startTime:DESC' })
162
- return this.request('GET', `/api/workflow/search?${q}`)
163
- }
164
-
165
- // ── Metadata (WorkflowDef registration) ──────────────────────────────────
166
- /** Register (create/update) a WorkflowDef on the server (POST /api/metadata/workflow).
167
- * Accepts a single def or an array (Conductor accepts a JSON array of defs). */
168
- async registerWorkflowDef(defOrDefs) {
169
- if (!defOrDefs) throw new Error('registerWorkflowDef needs a WorkflowDef')
170
- const body = Array.isArray(defOrDefs) ? defOrDefs : [defOrDefs]
171
- return this.request('POST', '/api/metadata/workflow', body)
172
- }
173
-
174
- /** Get a registered WorkflowDef by name (+ optional version). */
175
- async getWorkflowDef(name, version) {
176
- if (!name) throw new Error('getWorkflowDef needs a name')
177
- const q = version !== undefined ? `?version=${version}` : ''
178
- return this.request('GET', `/api/metadata/workflow/${encodeURIComponent(name)}${q}`)
179
- }
180
- }
181
-
182
- export default ConductorClient
@@ -1,389 +0,0 @@
1
- /**
2
- * agentspan-bridge plugin — run durable, crash-resilient agents + workflows on
3
- * an AgentSpan (Netflix Conductor) server from RTerm.
4
- *
5
- * What this adds that RTerm doesn't already have:
6
- * - Durable agent execution: a crashed/restarted run resumes from the last
7
- * completed step (Conductor engine), not just a ledger entry.
8
- * - Plan-execute determinism (LLM plans once → immutable sub-workflow).
9
- * - Enterprise event triggers (Kafka/SQS/AMQP/DB) via the Conductor server.
10
- * - A live visual execution UI (served by the AgentSpan server).
11
- *
12
- * Config (Settings → agentspan block, resolved here from the RTerm settings
13
- * service via ctx.settings if present, else env):
14
- * serverUrl — e.g. http://localhost:6767 (default)
15
- * authSecretRef — vault key holding "AUTH_KEY=...\nAUTH_SECRET=..." (optional;
16
- * only needed when the AgentSpan server has standalone auth on)
17
- *
18
- * The client is dependency-free (conductorClient.mjs) and the plugin never
19
- * crashes RTerm when the AgentSpan server is down — every tool returns a clear
20
- * "server unreachable" result instead of throwing.
21
- */
22
-
23
- import { ConductorClient, DEFAULT_BASE_URL, joinUrl } from './conductorClient.mjs'
24
- import { playbookToWorkflowDef } from './playbookToWorkflowDef.mjs'
25
-
26
- // ─── config resolution ──────────────────────────────────────────────────────
27
-
28
- /** Read the agentspan config block from RTerm settings (ctx.settings) or env. */
29
- export function resolveConfig(ctx = {}, env = process.env) {
30
- const s = (typeof ctx.getSettings === 'function' ? ctx.getSettings() : ctx.settings) || {}
31
- const block = s.agentspan || {}
32
- return {
33
- serverUrl: (block.serverUrl || env.AGENTSPAN_SERVER_URL || DEFAULT_BASE_URL).replace(/\/+$/, ''),
34
- authSecretRef: block.authSecretRef || env.AGENTSPAN_AUTH_SECRET_REF || undefined,
35
- enabled: block.enabled !== false,
36
- }
37
- }
38
-
39
- /** Parse a vault "KEY=VAL" blob into an {key, secret} auth object. */
40
- export function parseAuthBlob(blob) {
41
- if (!blob || typeof blob !== 'string') return undefined
42
- const out = {}
43
- for (const line of blob.split(/\r?\n/)) {
44
- const m = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(line)
45
- if (m) out[m[1]] = m[2].trim()
46
- }
47
- const key = out.AGENTSPAN_AUTH_KEY || out.AUTH_KEY || out.KEY
48
- const secret = out.AGENTSPAN_AUTH_SECRET || out.AUTH_SECRET || out.SECRET
49
- return key && secret ? { key, secret } : undefined
50
- }
51
-
52
- /** Real fetch adapter (matches conductorClient's expected {ok,status,text}). */
53
- async function realFetch(url, init) {
54
- const res = await fetch(url, { method: init.method, headers: init.headers, body: init.body })
55
- return { ok: res.ok, status: res.status, text: () => res.text() }
56
- }
57
-
58
- /** Build a configured ConductorClient from ctx (settings + vault). */
59
- export function buildClient(ctx = {}, fetchImpl = realFetch) {
60
- const cfg = resolveConfig(ctx)
61
- let auth
62
- if (cfg.authSecretRef && typeof ctx.getSecret === 'function') {
63
- try {
64
- const blob = ctx.getSecret(cfg.authSecretRef)
65
- auth = parseAuthBlob(blob)
66
- } catch { auth = undefined }
67
- }
68
- return { client: new ConductorClient({ baseUrl: cfg.serverUrl, auth, fetchImpl }), config: cfg }
69
- }
70
-
71
- // ─── formatting helpers (pure) ─────────────────────────────────────────────
72
-
73
- /** Normalize an execution/workflow status payload into a compact summary. */
74
- export function summarizeStatus(payload) {
75
- if (!payload || typeof payload !== 'object') return { status: 'UNKNOWN' }
76
- const status = payload.status ?? payload.workflowStatus ?? payload.state ?? 'UNKNOWN'
77
- const name = payload.workflowName ?? payload.name ?? payload.agentName ?? payload.workflowType
78
- const id = payload.workflowId ?? payload.executionId ?? payload.id
79
- const tasks = Array.isArray(payload.tasks) ? payload.tasks : []
80
- const failed = tasks.filter((t) => String(t.status).toUpperCase().includes('FAIL')).length
81
- const done = tasks.filter((t) => String(t.status).toUpperCase() === 'COMPLETED').length
82
- return {
83
- id,
84
- name,
85
- status: String(status).toUpperCase(),
86
- taskCount: tasks.length,
87
- completedTasks: done,
88
- failedTasks: failed,
89
- startTime: payload.startTime ?? payload.createTime,
90
- endTime: payload.endTime ?? payload.updateTime,
91
- reason: payload.reasonForIncompletion ?? payload.failedReason,
92
- }
93
- }
94
-
95
- /** Normalize a search/list result into rows for the panel + agentspan_list. */
96
- export function toExecutionRows(payload) {
97
- const results = payload?.results ?? payload?.workflows ?? (Array.isArray(payload) ? payload : [])
98
- if (!Array.isArray(results)) return []
99
- return results.map((w) => ({
100
- id: w.workflowId ?? w.executionId ?? w.id,
101
- name: w.workflowName ?? w.name ?? w.workflowType,
102
- status: String(w.status ?? w.workflowStatus ?? '').toUpperCase(),
103
- startTime: w.startTime ?? w.createTime,
104
- endTime: w.endTime ?? w.updateTime,
105
- }))
106
- }
107
-
108
- /** Map an execution status to a trigger event (fires on FAILED). */
109
- export function isFailedExecution(status) {
110
- const s = String(status || '').toUpperCase()
111
- return s === 'FAILED' || s === 'TERMINATED' || s === 'TIMED_OUT'
112
- }
113
-
114
- // ─── unreachable-server helper ─────────────────────────────────────────────
115
-
116
- async function guarded(fn, log) {
117
- try {
118
- return await fn()
119
- } catch (e) {
120
- const msg = e?.message ?? String(e)
121
- log?.(`[agentspan] ${msg}`)
122
- return { error: msg, hint: 'Is the AgentSpan server running? (agentspan server start, default http://localhost:6767). Configure the URL in Settings → agentspan.' }
123
- }
124
- }
125
-
126
- // ─── playbook resolution (from RTerm AutomationManager / settings) ─────────
127
-
128
- /** Find a named RTerm playbook from the plugin context (AutomationManager or
129
- * settings.automation.playbooks), by id or name. */
130
- export function findPlaybook(ctx = {}, nameOrId) {
131
- if (!nameOrId) return undefined
132
- const want = String(nameOrId)
133
- // 1) AutomationManager (runtime handle)
134
- try {
135
- const am = ctx.automationManager
136
- if (am) {
137
- if (typeof am.getPlaybook === 'function') {
138
- const p = am.getPlaybook(want)
139
- if (p) return p
140
- }
141
- if (typeof am.listPlaybooks === 'function') {
142
- const found = (am.listPlaybooks() || []).find((x) => x.id === want || x.name === want)
143
- if (found) return found
144
- }
145
- }
146
- } catch { /* fall through to settings */ }
147
- // 2) settings.automation.playbooks
148
- try {
149
- const s = (typeof ctx.getSettings === 'function' ? ctx.getSettings() : ctx.settings) || {}
150
- const list = s.automation?.playbooks || []
151
- return list.find((x) => x.id === want || x.name === want)
152
- } catch {
153
- return undefined
154
- }
155
- }
156
-
157
- /** Build a default durable AgentConfig for `agentspan_delegate` (a simple
158
- * tool-using ReAct agent compiled + started on the server). */
159
- export function buildDelegateAgentConfig(name, prompt, opts = {}) {
160
- return {
161
- name: name || 'rterm_delegate',
162
- model: opts.model || 'openai/gpt-4o',
163
- instructions: opts.instructions || 'You are a durable RTerm delegate agent. Complete the task, calling tools as needed.',
164
- input: prompt,
165
- }
166
- }
167
-
168
- // ─── plugin entry ───────────────────────────────────────────────────────────
169
-
170
- export function register(ctx) {
171
- const { registerTool, registerTrigger, registerPanel, log } = ctx
172
- const { client, config } = buildClient(ctx)
173
-
174
- // Tool: agentspan_health — check the server is reachable + its status.
175
- registerTool({
176
- name: 'agentspan_health',
177
- description: 'Check whether the AgentSpan/Conductor server is reachable and healthy. Returns the server URL, health status, and whether auth is configured.',
178
- params: {},
179
- handler: async () => guarded(async () => {
180
- const h = await client.health()
181
- if (!h.ok) {
182
- return {
183
- serverUrl: config.serverUrl,
184
- authConfigured: Boolean(config.authSecretRef),
185
- ...h,
186
- hint: 'Is the AgentSpan server running? (agentspan server start, default http://localhost:6767). Configure the URL in Settings → agentspan.',
187
- }
188
- }
189
- return { serverUrl: config.serverUrl, authConfigured: Boolean(config.authSecretRef), ...h }
190
- }, log),
191
- })
192
-
193
- // Tool: agentspan_run — run a durable agent (AgentConfig) or named workflow.
194
- registerTool({
195
- name: 'agentspan_run',
196
- description: 'Start a durable, crash-resilient agent or workflow on the AgentSpan server. Pass either an `agentConfig` (compiled+started via /api/agent/start) or a `workflow` name (Conductor /api/workflow/{name}). Returns the executionId/workflowId — the run survives process restarts and resumes from the last completed step.',
197
- params: {
198
- agentConfig: { type: 'object', description: 'An AgentConfig JSON (agent tree) to compile+run durably', optional: true },
199
- workflow: { type: 'string', description: 'Name of a registered Conductor workflow to start', optional: true },
200
- input: { type: 'object', description: 'Input payload for the agent/workflow', optional: true },
201
- prompt: { type: 'string', description: 'Natural-language prompt for the agent', optional: true },
202
- },
203
- handler: async (p) => guarded(async () => {
204
- if (p?.agentConfig) {
205
- const r = await client.runAgent(p.agentConfig, p.prompt ?? p.input)
206
- return { kind: 'agent', executionId: r.executionId, serverUrl: config.serverUrl, uiUrl: joinUrl(config.serverUrl, `/execution/${r.executionId}`), raw: r.raw }
207
- }
208
- if (p?.workflow) {
209
- const id = await client.startWorkflow(p.workflow, p.input ?? {})
210
- return { kind: 'workflow', workflowId: id, serverUrl: config.serverUrl, uiUrl: joinUrl(config.serverUrl, `/execution/${id}`) }
211
- }
212
- return { error: 'agentspan_run needs either agentConfig or workflow' }
213
- }, log),
214
- })
215
-
216
- // Tool: agentspan_status — detailed status of an execution/workflow.
217
- registerTool({
218
- name: 'agentspan_status',
219
- description: 'Get the detailed status of a durable execution (agent or workflow) by id — current status, per-task progress, failed tasks, and failure reason if any.',
220
- params: { executionId: { type: 'string', description: 'The executionId or workflowId' } },
221
- handler: async (p) => guarded(async () => {
222
- if (!p?.executionId) return { error: 'agentspan_status needs executionId' }
223
- // Try the agent lifecycle surface first, fall back to the workflow engine.
224
- try {
225
- const a = await client.agentStatus(p.executionId)
226
- return { kind: 'agent', ...summarizeStatus(a), raw: a }
227
- } catch {
228
- const w = await client.getWorkflow(p.executionId)
229
- return { kind: 'workflow', ...summarizeStatus(w) }
230
- }
231
- }, log),
232
- })
233
-
234
- // Tool: agentspan_approve — human-in-the-loop respond to a paused HUMAN task.
235
- registerTool({
236
- name: 'agentspan_approve',
237
- description: 'Respond to a paused human-in-the-loop task in a durable execution (completes the HUMAN task and resumes the run). Pass the executionId and an `output` object (e.g. {approved:true, comment:"..."}).',
238
- params: {
239
- executionId: { type: 'string', description: 'The executionId with a paused HUMAN task' },
240
- output: { type: 'object', description: 'The response output map (e.g. {approved:true})' },
241
- },
242
- handler: async (p) => guarded(async () => {
243
- if (!p?.executionId) return { error: 'agentspan_approve needs executionId' }
244
- await client.agentRespond(p.executionId, p.output ?? {})
245
- const s = await client.agentStatus(p.executionId).catch(() => null)
246
- return { responded: true, executionId: p.executionId, ...(s ? summarizeStatus(s) : {}) }
247
- }, log),
248
- })
249
-
250
- // Tool: agentspan_list — list recent executions (optionally filtered).
251
- registerTool({
252
- name: 'agentspan_list',
253
- description: 'List recent durable executions on the AgentSpan server. Optional `query` (Conductor freeText, e.g. "status:FAILED" or "workflowName:cleanup") and `size`. Returns id/name/status/start/end rows.',
254
- params: {
255
- query: { type: 'string', description: 'Conductor freeText query (default *)', optional: true },
256
- size: { type: 'number', description: 'Max results (default 20)', optional: true },
257
- },
258
- handler: async (p) => guarded(async () => {
259
- const r = await client.searchWorkflows(p?.query ?? '*', p?.size ?? 20)
260
- const rows = toExecutionRows(r)
261
- return { count: rows.length, executions: rows }
262
- }, log),
263
- })
264
-
265
- // Tool: agentspan_stop — terminate a running execution.
266
- registerTool({
267
- name: 'agentspan_stop',
268
- description: 'Stop (terminate) a running durable execution by id. Optionally pass a `reason`.',
269
- params: {
270
- executionId: { type: 'string', description: 'The executionId/workflowId to stop' },
271
- reason: { type: 'string', description: 'Optional termination reason', optional: true },
272
- },
273
- handler: async (p) => guarded(async () => {
274
- if (!p?.executionId) return { error: 'agentspan_stop needs executionId' }
275
- try {
276
- await client.agentStop(p.executionId)
277
- } catch {
278
- await client.terminateWorkflow(p.executionId, p.reason)
279
- }
280
- return { stopped: true, executionId: p.executionId }
281
- }, log),
282
- })
283
-
284
- // Tool: agentspan_export_playbook — map a named RTerm playbook to a Conductor
285
- // WorkflowDef (returns the def; does NOT register it). Pure/dry-run preview.
286
- registerTool({
287
- name: 'agentspan_export_playbook',
288
- description: 'Map an RTerm playbook (by id or name) to a Conductor WorkflowDef and return it — a dry-run preview of what agentspan_register_playbook would register. command steps → HTTP run_command tasks, script steps → script-reference tasks, wait steps → WAIT tasks; sequential + dependsOn DAG + retries + rollback compensating tasks.',
289
- params: {
290
- playbook: { type: 'string', description: 'RTerm playbook id or name' },
291
- execUri: { type: 'string', description: 'Override the RTerm exec URI tasks call (default from settings)', optional: true },
292
- },
293
- handler: async (p) => guarded(async () => {
294
- const pb = findPlaybook(ctx, p?.playbook)
295
- if (!pb) return { error: `playbook not found: ${p?.playbook ?? '(none given)'}` }
296
- const execUri = p?.execUri || `${config.gatewayUrl ?? 'http://localhost:17888'}/rpc/exec`
297
- const def = playbookToWorkflowDef(pb, { execUri })
298
- return { name: def.name, taskCount: def.tasks.length, def }
299
- }, log),
300
- })
301
-
302
- // Tool: agentspan_register_playbook — register an RTerm playbook on the
303
- // AgentSpan server as a Conductor WorkflowDef (so agentspan_run {workflow:<name>}
304
- // and AgentSpan SUB_WORKFLOW steps can invoke it).
305
- registerTool({
306
- name: 'agentspan_register_playbook',
307
- description: 'Export an RTerm playbook to a Conductor WorkflowDef AND register it on the AgentSpan server (POST /api/metadata/workflow), so it can be run durably via agentspan_run {workflow:<name>} or invoked by AgentSpan agents as a SUB_WORKFLOW step.',
308
- params: {
309
- playbook: { type: 'string', description: 'RTerm playbook id or name' },
310
- execUri: { type: 'string', description: 'Override the RTerm exec URI tasks call (default from settings)', optional: true },
311
- },
312
- handler: async (p) => guarded(async () => {
313
- const pb = findPlaybook(ctx, p?.playbook)
314
- if (!pb) return { error: `playbook not found: ${p?.playbook ?? '(none given)'}` }
315
- const execUri = p?.execUri || `${config.gatewayUrl ?? 'http://localhost:17888'}/rpc/exec`
316
- const def = playbookToWorkflowDef(pb, { execUri })
317
- await client.registerWorkflowDef(def)
318
- return {
319
- registered: true,
320
- name: def.name,
321
- taskCount: def.tasks.length,
322
- runWith: { tool: 'agentspan_run', args: { workflow: def.name } },
323
- uiUrl: joinUrl(config.serverUrl, `/workflowDef/${def.name}`),
324
- }
325
- }, log),
326
- })
327
-
328
- // Tool: agentspan_delegate — run a prompt/task as a durable AgentSpan agent
329
- // (AgentConfig), returning an executionId that survives RTerm/host restart.
330
- registerTool({
331
- name: 'agentspan_delegate',
332
- description: 'Delegate a long-running task to AgentSpan: runs the prompt as a durable agent on the Conductor server (compiles + starts an AgentConfig) and returns an executionId that survives restarts. Follow up with agentspan_status / agentspan_approve / agentspan_stop.',
333
- params: {
334
- prompt: { type: 'string', description: 'The task/prompt for the durable agent' },
335
- name: { type: 'string', description: 'Agent name (default rterm_delegate)', optional: true },
336
- model: { type: 'string', description: 'Model id (default openai/gpt-4o)', optional: true },
337
- instructions: { type: 'string', description: 'Override system instructions', optional: true },
338
- },
339
- handler: async (p) => guarded(async () => {
340
- if (!p?.prompt) return { error: 'agentspan_delegate needs a prompt' }
341
- const cfg = buildDelegateAgentConfig(p.name, p.prompt, { model: p.model, instructions: p.instructions })
342
- const r = await client.runAgent(cfg)
343
- return {
344
- delegated: true,
345
- executionId: r.executionId,
346
- serverUrl: config.serverUrl,
347
- uiUrl: joinUrl(config.serverUrl, `/execution/${r.executionId}`),
348
- followUp: { status: 'agentspan_status', approve: 'agentspan_approve', stop: 'agentspan_stop' },
349
- }
350
- }, log),
351
- })
352
-
353
- // Trigger: agentspan_execution_failed — fires when a durable execution fails.
354
- registerTrigger({
355
- name: 'agentspan_execution_failed',
356
- description: 'Fires when an AgentSpan/Conductor durable execution transitions to FAILED/TERMINATED/TIMED_OUT. Use for auto-remediation or re-run playbooks.',
357
- match: (event) => {
358
- if (event?.source !== 'agentspan') return false
359
- return isFailedExecution(event.status)
360
- },
361
- action: 'propose-change',
362
- })
363
-
364
- // Panel: agentspan-executions — live feed of durable executions.
365
- registerPanel({
366
- name: 'agentspan-executions',
367
- title: 'AgentSpan Executions',
368
- render: (data) => {
369
- const rows = (Array.isArray(data) ? data : []).map((e) =>
370
- `<tr><td>${e.name ?? ''}</td><td>${e.id ?? ''}</td><td>${e.status ?? ''}</td><td>${e.startTime ?? ''}</td></tr>`
371
- ).join('')
372
- return `<div class="agentspan-executions"><h3>AgentSpan Durable Executions</h3><p>Server: ${config.serverUrl}</p><table><thead><tr><th>Name</th><th>Execution</th><th>Status</th><th>Started</th></tr></thead><tbody>${rows}</tbody></table></div>`
373
- },
374
- })
375
-
376
- log(`[agentspan] agentspan-bridge registered: 6 tools, 1 trigger, 1 panel (server=${config.serverUrl})`)
377
- }
378
-
379
- export default {
380
- register,
381
- resolveConfig,
382
- parseAuthBlob,
383
- buildClient,
384
- summarizeStatus,
385
- toExecutionRows,
386
- isFailedExecution,
387
- findPlaybook,
388
- buildDelegateAgentConfig,
389
- }