dsh-agora-plugin 0.6.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.
- package/README.md +520 -0
- package/client/index.js +492 -0
- package/cordis.patch.yml +14 -0
- package/dsh.plugin.json +7 -0
- package/lib/agora-client.d.ts +73 -0
- package/lib/agora-client.d.ts.map +1 -0
- package/lib/agora-client.js +207 -0
- package/lib/agora-client.js.map +1 -0
- package/lib/client.js +492 -0
- package/lib/command-adapter.d.ts +44 -0
- package/lib/command-adapter.d.ts.map +1 -0
- package/lib/command-adapter.js +70 -0
- package/lib/command-adapter.js.map +1 -0
- package/lib/command.d.ts +53 -0
- package/lib/command.d.ts.map +1 -0
- package/lib/command.js +375 -0
- package/lib/command.js.map +1 -0
- package/lib/context-types.d.ts +51 -0
- package/lib/context-types.d.ts.map +1 -0
- package/lib/context-types.js +2 -0
- package/lib/context-types.js.map +1 -0
- package/lib/contracts.d.ts +402 -0
- package/lib/contracts.d.ts.map +1 -0
- package/lib/contracts.js +2 -0
- package/lib/contracts.js.map +1 -0
- package/lib/extension-sdk.d.ts +74 -0
- package/lib/extension-sdk.d.ts.map +1 -0
- package/lib/extension-sdk.js +160 -0
- package/lib/extension-sdk.js.map +1 -0
- package/lib/harness-runtime.d.ts +29 -0
- package/lib/harness-runtime.d.ts.map +1 -0
- package/lib/harness-runtime.js +422 -0
- package/lib/harness-runtime.js.map +1 -0
- package/lib/http-api.d.ts +10 -0
- package/lib/http-api.d.ts.map +1 -0
- package/lib/http-api.js +282 -0
- package/lib/http-api.js.map +1 -0
- package/lib/im-bridge-v1.d.ts +37 -0
- package/lib/im-bridge-v1.d.ts.map +1 -0
- package/lib/im-bridge-v1.js +43 -0
- package/lib/im-bridge-v1.js.map +1 -0
- package/lib/im-gateway.d.ts +24 -0
- package/lib/im-gateway.d.ts.map +1 -0
- package/lib/im-gateway.js +53 -0
- package/lib/im-gateway.js.map +1 -0
- package/lib/index.d.ts +46 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +210 -0
- package/lib/index.js.map +1 -0
- package/lib/node-worker.d.ts +52 -0
- package/lib/node-worker.d.ts.map +1 -0
- package/lib/node-worker.js +372 -0
- package/lib/node-worker.js.map +1 -0
- package/lib/service.d.ts +51 -0
- package/lib/service.d.ts.map +1 -0
- package/lib/service.js +196 -0
- package/lib/service.js.map +1 -0
- package/lib/tool.d.ts +72 -0
- package/lib/tool.d.ts.map +1 -0
- package/lib/tool.js +206 -0
- package/lib/tool.js.map +1 -0
- package/package.json +74 -0
- package/patches/dsh-im/@xmanrui__dsh-im@2.1.0.patch +920 -0
- package/patches/dsh-im/@xmanrui__dsh-im@2.3.0.patch +984 -0
- package/patches/dsh-im/README.md +82 -0
- package/src/agora-client.ts +333 -0
- package/src/command-adapter.ts +106 -0
- package/src/command.ts +424 -0
- package/src/context-types.ts +54 -0
- package/src/contracts.ts +413 -0
- package/src/extension-sdk.ts +225 -0
- package/src/harness-runtime.ts +518 -0
- package/src/http-api.ts +269 -0
- package/src/im-bridge-v1.ts +73 -0
- package/src/im-gateway.ts +82 -0
- package/src/index.ts +260 -0
- package/src/node-worker.ts +442 -0
- package/src/service.ts +262 -0
- package/src/tool.ts +269 -0
package/src/command.ts
ADDED
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto'
|
|
2
|
+
import type {
|
|
3
|
+
AgoraCommandResult,
|
|
4
|
+
AgoraRequestContext,
|
|
5
|
+
AgoraTask,
|
|
6
|
+
AgoraTaskStatus,
|
|
7
|
+
CreateAgoraTaskInput,
|
|
8
|
+
DshAgoraServiceApi,
|
|
9
|
+
TaskPriority,
|
|
10
|
+
CoordinationMode,
|
|
11
|
+
CoordinationRun,
|
|
12
|
+
} from './contracts.js'
|
|
13
|
+
|
|
14
|
+
export type AgoraCommand =
|
|
15
|
+
| { readonly kind: 'help' }
|
|
16
|
+
| { readonly kind: 'health' }
|
|
17
|
+
| { readonly kind: 'nodes' | 'agents' }
|
|
18
|
+
| { readonly kind: 'dashboard' }
|
|
19
|
+
| { readonly kind: 'im' }
|
|
20
|
+
| { readonly kind: 'list'; readonly state?: string; readonly projectId?: string }
|
|
21
|
+
| { readonly kind: 'show'; readonly taskId: string }
|
|
22
|
+
| { readonly kind: 'status'; readonly taskId: string }
|
|
23
|
+
| { readonly kind: 'dispatch-status'; readonly dispatchId: string }
|
|
24
|
+
| { readonly kind: 'runs' }
|
|
25
|
+
| { readonly kind: 'run-status'; readonly runId: string }
|
|
26
|
+
| { readonly kind: 'scorecards'; readonly taskType?: string }
|
|
27
|
+
| { readonly kind: 'run'; readonly input: {
|
|
28
|
+
readonly mode: CoordinationMode
|
|
29
|
+
readonly runtimeTargets: readonly string[]
|
|
30
|
+
readonly prompt: string
|
|
31
|
+
readonly taskType: string
|
|
32
|
+
readonly maxAgents?: number
|
|
33
|
+
readonly maxDispatches?: number
|
|
34
|
+
readonly maxSeconds?: number
|
|
35
|
+
} }
|
|
36
|
+
| { readonly kind: 'create'; readonly input: CreateAgoraTaskInput }
|
|
37
|
+
|
|
38
|
+
export class AgoraCommandParseError extends Error {
|
|
39
|
+
constructor(message: string) {
|
|
40
|
+
super(message)
|
|
41
|
+
this.name = 'AgoraCommandParseError'
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const USAGE = [
|
|
46
|
+
'Agora commands:',
|
|
47
|
+
'/agora health',
|
|
48
|
+
'/agora nodes',
|
|
49
|
+
'/agora agents',
|
|
50
|
+
'/agora list [--state <state>] [--project <project-id>]',
|
|
51
|
+
'/agora show <task-id>',
|
|
52
|
+
'/agora status <task-id>',
|
|
53
|
+
'/agora dispatch-status <dispatch-id>',
|
|
54
|
+
'/agora runs',
|
|
55
|
+
'/agora run-status <run-id>',
|
|
56
|
+
'/agora scorecards [task-type]',
|
|
57
|
+
'/agora run --mode fanout --agents <target,target,...> [--max-agents N] [--max-dispatches N] [--max-seconds N] <prompt>',
|
|
58
|
+
'/agora create [--type <type>] [--priority low|normal|high] [--project <id>] <title>',
|
|
59
|
+
'/agora dashboard',
|
|
60
|
+
'/agora im',
|
|
61
|
+
'',
|
|
62
|
+
'Human approval and rejection remain in the authenticated Agora surface.',
|
|
63
|
+
].join('\n')
|
|
64
|
+
|
|
65
|
+
export function parseAgoraCommand(rawInput: string): AgoraCommand {
|
|
66
|
+
const tokens = tokenize(rawInput)
|
|
67
|
+
const verb = tokens.shift()?.toLowerCase() ?? 'help'
|
|
68
|
+
switch (verb) {
|
|
69
|
+
case 'help':
|
|
70
|
+
case '--help':
|
|
71
|
+
case '-h':
|
|
72
|
+
noArguments(tokens, verb)
|
|
73
|
+
return { kind: 'help' }
|
|
74
|
+
case 'health':
|
|
75
|
+
noArguments(tokens, verb)
|
|
76
|
+
return { kind: 'health' }
|
|
77
|
+
case 'nodes':
|
|
78
|
+
case 'agents':
|
|
79
|
+
noArguments(tokens, verb)
|
|
80
|
+
return { kind: verb }
|
|
81
|
+
case 'dashboard':
|
|
82
|
+
noArguments(tokens, verb)
|
|
83
|
+
return { kind: 'dashboard' }
|
|
84
|
+
case 'im':
|
|
85
|
+
noArguments(tokens, verb)
|
|
86
|
+
return { kind: 'im' }
|
|
87
|
+
case 'show':
|
|
88
|
+
return { kind: 'show', taskId: oneArgument(tokens, 'show') }
|
|
89
|
+
case 'status':
|
|
90
|
+
return { kind: 'status', taskId: oneArgument(tokens, 'status') }
|
|
91
|
+
case 'dispatch-status':
|
|
92
|
+
return { kind: 'dispatch-status', dispatchId: oneArgument(tokens, 'dispatch-status') }
|
|
93
|
+
case 'runs':
|
|
94
|
+
noArguments(tokens, verb)
|
|
95
|
+
return { kind: 'runs' }
|
|
96
|
+
case 'run-status':
|
|
97
|
+
return { kind: 'run-status', runId: oneArgument(tokens, 'run-status') }
|
|
98
|
+
case 'scorecards':
|
|
99
|
+
if (tokens.length > 1) throw new AgoraCommandParseError('scorecards accepts at most one task type')
|
|
100
|
+
return { kind: 'scorecards', ...(tokens[0] ? { taskType: tokens[0] } : {}) }
|
|
101
|
+
case 'run': {
|
|
102
|
+
const flags = parseFlags(tokens, new Set(['mode', 'agents', 'task-type', 'max-agents', 'max-dispatches', 'max-seconds']))
|
|
103
|
+
const prompt = flags.positionals.join(' ').trim()
|
|
104
|
+
if (!prompt) throw new AgoraCommandParseError('run requires a prompt')
|
|
105
|
+
const mode = parseCoordinationMode(flags.values.mode)
|
|
106
|
+
const runtimeTargets = (flags.values.agents ?? '').split(',').map(value => value.trim()).filter(Boolean)
|
|
107
|
+
if (runtimeTargets.length === 0) throw new AgoraCommandParseError('run requires --agents <target,target,...>')
|
|
108
|
+
return { kind: 'run', input: {
|
|
109
|
+
mode,
|
|
110
|
+
runtimeTargets,
|
|
111
|
+
prompt,
|
|
112
|
+
taskType: flags.values['task-type']?.trim() || 'general',
|
|
113
|
+
...optionalPositiveInteger(flags.values['max-agents'], 'max-agents'),
|
|
114
|
+
...optionalPositiveInteger(flags.values['max-dispatches'], 'max-dispatches'),
|
|
115
|
+
...optionalPositiveInteger(flags.values['max-seconds'], 'max-seconds'),
|
|
116
|
+
} }
|
|
117
|
+
}
|
|
118
|
+
case 'list': {
|
|
119
|
+
const flags = parseFlags(tokens, new Set(['state', 'project']))
|
|
120
|
+
if (flags.positionals.length > 0) throw new AgoraCommandParseError('list only accepts --state and --project')
|
|
121
|
+
return {
|
|
122
|
+
kind: 'list',
|
|
123
|
+
...(flags.values.state === undefined ? {} : { state: flags.values.state }),
|
|
124
|
+
...(flags.values.project === undefined ? {} : { projectId: flags.values.project }),
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
case 'create': {
|
|
128
|
+
const flags = parseFlags(tokens, new Set(['type', 'priority', 'project']))
|
|
129
|
+
const title = flags.positionals.join(' ').trim()
|
|
130
|
+
if (title === '') throw new AgoraCommandParseError('create requires a task title')
|
|
131
|
+
const priority = parsePriority(flags.values.priority)
|
|
132
|
+
return {
|
|
133
|
+
kind: 'create',
|
|
134
|
+
input: {
|
|
135
|
+
title,
|
|
136
|
+
...(flags.values.type === undefined ? {} : { type: flags.values.type }),
|
|
137
|
+
...(priority === undefined ? {} : { priority }),
|
|
138
|
+
...(flags.values.project === undefined ? {} : { projectId: flags.values.project }),
|
|
139
|
+
},
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
default:
|
|
143
|
+
throw new AgoraCommandParseError(`unknown Agora command "${verb}"`)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export async function executeAgoraCommand(
|
|
148
|
+
service: DshAgoraServiceApi,
|
|
149
|
+
rawInput: string,
|
|
150
|
+
context: AgoraRequestContext = {},
|
|
151
|
+
signal?: AbortSignal,
|
|
152
|
+
): Promise<AgoraCommandResult> {
|
|
153
|
+
let command: AgoraCommand
|
|
154
|
+
try {
|
|
155
|
+
command = parseAgoraCommand(rawInput)
|
|
156
|
+
} catch (error) {
|
|
157
|
+
return { kind: 'error', text: `${messageOf(error)}\n\n${USAGE}` }
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
switch (command.kind) {
|
|
162
|
+
case 'help': return { kind: 'success', text: USAGE }
|
|
163
|
+
case 'dashboard': return { kind: 'success', text: `${service.serverUrl}/dashboard/` }
|
|
164
|
+
case 'im': return { kind: 'success', text: formatImStatus(service.snapshot()) }
|
|
165
|
+
case 'health': {
|
|
166
|
+
const health = await service.health(signal)
|
|
167
|
+
const label = health.service ?? health.status ?? (health.ok === false ? 'unhealthy' : 'online')
|
|
168
|
+
return { kind: 'success', text: `Agora: ${String(label)}\nServer: ${service.serverUrl}` }
|
|
169
|
+
}
|
|
170
|
+
case 'nodes': {
|
|
171
|
+
const nodes = await service.listRuntimeNodes(signal)
|
|
172
|
+
return {
|
|
173
|
+
kind: 'success',
|
|
174
|
+
text: nodes.length === 0 ? 'No DSH runtime nodes registered.' : nodes.map(node => (
|
|
175
|
+
`${node.node_id} [${node.presence}] agents=${node.agents.length} bots=${node.bots.filter(bot => bot.connected).length}/${node.bots.length}`
|
|
176
|
+
)).join('\n'),
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
case 'agents': {
|
|
180
|
+
const targets = (await service.listRuntimeTargets(signal)).filter(target => target.runtime_provider === 'dsh')
|
|
181
|
+
return {
|
|
182
|
+
kind: 'success',
|
|
183
|
+
text: targets.length === 0 ? 'No DSH runtime agents registered.' : targets.map(target => (
|
|
184
|
+
`${target.runtime_target_ref} ${target.primary_model ?? '-'} [${target.presentation_mode}]`
|
|
185
|
+
)).join('\n'),
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
case 'list': return { kind: 'success', text: formatTaskList(await service.listTasks(command.state, command.projectId, signal)) }
|
|
189
|
+
case 'show': return { kind: 'success', text: formatTask(await service.getTask(command.taskId, signal)) }
|
|
190
|
+
case 'status': return { kind: 'success', text: formatStatus(await service.taskStatus(command.taskId, signal)) }
|
|
191
|
+
case 'dispatch-status': {
|
|
192
|
+
const dispatch = await service.getRuntimeDispatch(command.dispatchId, signal)
|
|
193
|
+
const progress = await service.listRuntimeDispatchProgress(command.dispatchId, signal)
|
|
194
|
+
const latest = dispatch.latest_progress ?? progress.at(-1) ?? null
|
|
195
|
+
const envelope = dispatch.result_envelope
|
|
196
|
+
return {
|
|
197
|
+
kind: dispatch.status === 'failed' ? 'error' : 'success',
|
|
198
|
+
text: [
|
|
199
|
+
`${dispatch.id}: ${dispatch.status}`,
|
|
200
|
+
`Target: ${dispatch.runtime_target_ref}`,
|
|
201
|
+
`Session: ${dispatch.session_id ?? '-'}`,
|
|
202
|
+
`Lease heartbeat: ${dispatch.claim_renewed_at ?? '-'}`,
|
|
203
|
+
`Work progress: ${latest ? `${latest.phase} (#${latest.attempt}.${latest.sequence})${latest.percent === null || latest.percent === undefined ? '' : ` ${latest.percent}%`}` : '-'}`,
|
|
204
|
+
...(latest?.message ? [`Progress detail: ${latest.message}`] : []),
|
|
205
|
+
...(envelope ? [
|
|
206
|
+
`Evidence: ${envelope.evidence.length} item(s), ${envelope.claims.length} claim(s)`,
|
|
207
|
+
...(envelope.confidence === null || envelope.confidence === undefined ? [] : [`Confidence: ${Math.round(envelope.confidence * 100)}%`]),
|
|
208
|
+
] : []),
|
|
209
|
+
...(dispatch.error ? [`Error: ${dispatch.error}`] : []),
|
|
210
|
+
].join('\n'),
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
case 'runs': {
|
|
214
|
+
const runs = await service.listCoordinationRuns(undefined, signal)
|
|
215
|
+
return { kind: 'success', text: runs.length === 0 ? 'No coordination runs.' : runs.map(formatRunLine).join('\n') }
|
|
216
|
+
}
|
|
217
|
+
case 'run-status': return { kind: 'success', text: formatRun(await service.getCoordinationRun(command.runId, signal)) }
|
|
218
|
+
case 'scorecards': {
|
|
219
|
+
const cards = await service.listAgentScorecards(command.taskType, signal)
|
|
220
|
+
return { kind: 'success', text: cards.length === 0 ? 'No Agent scorecard observations.' : cards.map(card => (
|
|
221
|
+
`${card.runtime_target_ref} [${card.task_type}] score=${card.score.toFixed(1)} observations=${card.observations} success=${formatRatio(card.success_rate)} p95=${card.p95_duration_ms ?? '-'}ms evidence=${card.evidence_yield?.toFixed(2) ?? '-'}`
|
|
222
|
+
)).join('\n') }
|
|
223
|
+
}
|
|
224
|
+
case 'run': {
|
|
225
|
+
const budget = {
|
|
226
|
+
...(command.input.maxAgents === undefined ? {} : { max_agents: command.input.maxAgents }),
|
|
227
|
+
...(command.input.maxDispatches === undefined ? {} : { max_dispatches: command.input.maxDispatches }),
|
|
228
|
+
...(command.input.maxSeconds === undefined ? {} : { max_wall_clock_seconds: command.input.maxSeconds }),
|
|
229
|
+
}
|
|
230
|
+
const run = await service.createCoordinationRun({
|
|
231
|
+
mode: command.input.mode,
|
|
232
|
+
prompt: command.input.prompt,
|
|
233
|
+
task_type: command.input.taskType,
|
|
234
|
+
candidates: command.input.runtimeTargets.map(runtime_target_ref => ({ runtime_target_ref })),
|
|
235
|
+
...(Object.keys(budget).length === 0 ? {} : { budget }),
|
|
236
|
+
idempotency_key: `dsh-command:${randomUUID()}`,
|
|
237
|
+
metadata: context.actorId ? { requested_by: context.actorId } : null,
|
|
238
|
+
}, signal)
|
|
239
|
+
return { kind: 'success', text: `Created coordination run ${run.id}\n${formatRun(run)}` }
|
|
240
|
+
}
|
|
241
|
+
case 'create': {
|
|
242
|
+
const task = await service.createTask({
|
|
243
|
+
...command.input,
|
|
244
|
+
...(context.actorId === undefined ? {} : { creator: context.actorId }),
|
|
245
|
+
...imTargetFrom(context),
|
|
246
|
+
}, signal)
|
|
247
|
+
return { kind: 'success', text: `Created ${task.id}: ${task.title}\nState: ${task.state}\nStage: ${task.current_stage ?? '-'}` }
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
} catch (error) {
|
|
251
|
+
return { kind: 'error', text: messageOf(error) }
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function formatTaskList(tasks: readonly AgoraTask[]): string {
|
|
256
|
+
if (tasks.length === 0) return 'No Agora tasks matched.'
|
|
257
|
+
return tasks.map(task => `${task.id} [${task.state}] ${task.title}${task.current_stage ? ` (${task.current_stage})` : ''}`).join('\n')
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function formatTask(task: AgoraTask): string {
|
|
261
|
+
return [
|
|
262
|
+
`${task.id}: ${task.title}`,
|
|
263
|
+
`State: ${task.state}`,
|
|
264
|
+
`Stage: ${task.current_stage ?? '-'}`,
|
|
265
|
+
`Type: ${task.type}`,
|
|
266
|
+
`Priority: ${task.priority}`,
|
|
267
|
+
`Project: ${task.project_id ?? '-'}`,
|
|
268
|
+
...(task.error_detail ? [`Error: ${task.error_detail}`] : []),
|
|
269
|
+
].join('\n')
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function formatStatus(status: AgoraTaskStatus): string {
|
|
273
|
+
return [
|
|
274
|
+
formatTask(status.task),
|
|
275
|
+
`Subtasks: ${status.subtasks.length}`,
|
|
276
|
+
`Flow events: ${status.flow_log.length}`,
|
|
277
|
+
`Progress entries: ${status.progress_log.length}`,
|
|
278
|
+
].join('\n')
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function formatRunLine(run: CoordinationRun): string {
|
|
282
|
+
return `${run.id} [${run.status}] ${run.mode} members=${run.members.length} ${run.stop_reason ?? ''}`.trim()
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function formatRun(run: CoordinationRun): string {
|
|
286
|
+
return [
|
|
287
|
+
formatRunLine(run),
|
|
288
|
+
`Deadline: ${run.deadline_at}`,
|
|
289
|
+
`Usage: tokens=${run.usage.total_tokens ?? '-'} tools=${run.usage.tool_calls ?? '-'} cost=${run.usage.cost_usd ?? '-'}`,
|
|
290
|
+
`Evidence: ${run.synthesis?.evidence_ids.length ?? 0}; conflicts: ${run.synthesis?.conflicts.length ?? 0}; verified: ${run.synthesis?.verified ?? false}`,
|
|
291
|
+
...run.members.map(member => `- r${member.round} ${member.role} ${member.runtime_target_ref}: ${member.status} score=${member.selection_score.toFixed(1)}`),
|
|
292
|
+
...(run.synthesis?.answer ? ['', run.synthesis.answer] : []),
|
|
293
|
+
].join('\n')
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function formatImStatus(snapshot: ReturnType<DshAgoraServiceApi['snapshot']>): string {
|
|
297
|
+
const adapter = `First-party command adapter: ${snapshot.commandAdapter.state} (${snapshot.commandAdapter.protocol})`
|
|
298
|
+
const gateway = snapshot.im.state === 'connected'
|
|
299
|
+
? `Command gateway: connected (${snapshot.im.service}, ${snapshot.im.protocol})`
|
|
300
|
+
: `Command gateway: ${snapshot.im.state} (${snapshot.im.reason})`
|
|
301
|
+
const bridge = snapshot.imBridge.state === 'connected'
|
|
302
|
+
? `IM bridge: connected (${snapshot.imBridge.service}, ${snapshot.imBridge.protocol})`
|
|
303
|
+
: `IM bridge: ${snapshot.imBridge.state} (${snapshot.imBridge.reason})`
|
|
304
|
+
const node = snapshot.node.state === 'online'
|
|
305
|
+
? `Runtime node: online (${snapshot.node.nodeId})`
|
|
306
|
+
: snapshot.node.state === 'error'
|
|
307
|
+
? `Runtime node: error (${snapshot.node.nodeId}, ${snapshot.node.error})`
|
|
308
|
+
: `Runtime node: ${snapshot.node.state} (${snapshot.node.nodeId})`
|
|
309
|
+
return [adapter, gateway, bridge, node, `Extensions: ${snapshot.extensions.map(item => item.id).join(', ') || '-'}`].join('\n')
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function imTargetFrom(context: AgoraRequestContext): Pick<CreateAgoraTaskInput, 'imTarget'> | Record<string, never> {
|
|
313
|
+
if (context.provider === undefined && context.conversationRef === undefined && context.threadRef === undefined) return {}
|
|
314
|
+
return {
|
|
315
|
+
imTarget: {
|
|
316
|
+
...(context.provider === undefined ? {} : { provider: context.provider }),
|
|
317
|
+
...(context.conversationRef === undefined ? {} : { conversation_ref: context.conversationRef }),
|
|
318
|
+
...(context.threadRef === undefined ? {} : { thread_ref: context.threadRef }),
|
|
319
|
+
},
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function parsePriority(value: string | undefined): TaskPriority | undefined {
|
|
324
|
+
if (value === undefined) return undefined
|
|
325
|
+
if (value === 'low' || value === 'normal' || value === 'high') return value
|
|
326
|
+
throw new AgoraCommandParseError('priority must be low, normal, or high')
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function parseCoordinationMode(value: string | undefined): CoordinationMode {
|
|
330
|
+
if (value === 'single' || value === 'fanout' || value === 'review' || value === 'debate' || value === 'council') return value
|
|
331
|
+
throw new AgoraCommandParseError('run requires --mode single|fanout|review|debate|council')
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function optionalPositiveInteger(
|
|
335
|
+
value: string | undefined,
|
|
336
|
+
field: 'max-agents' | 'max-dispatches' | 'max-seconds',
|
|
337
|
+
): Partial<{ maxAgents: number; maxDispatches: number; maxSeconds: number }> {
|
|
338
|
+
if (value === undefined) return {}
|
|
339
|
+
const parsed = Number(value)
|
|
340
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0) throw new AgoraCommandParseError(`--${field} must be a positive integer`)
|
|
341
|
+
const key = field === 'max-agents' ? 'maxAgents' : field === 'max-dispatches' ? 'maxDispatches' : 'maxSeconds'
|
|
342
|
+
return { [key]: parsed }
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function formatRatio(value: number | null): string { return value === null ? '-' : `${Math.round(value * 100)}%` }
|
|
346
|
+
|
|
347
|
+
function oneArgument(tokens: string[], verb: string): string {
|
|
348
|
+
if (tokens.length !== 1) throw new AgoraCommandParseError(`${verb} requires exactly one task id`)
|
|
349
|
+
return tokens[0]!
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function noArguments(tokens: string[], verb: string): void {
|
|
353
|
+
if (tokens.length > 0) throw new AgoraCommandParseError(`${verb} does not accept arguments`)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function parseFlags(tokens: string[], allowed: ReadonlySet<string>): {
|
|
357
|
+
values: Record<string, string | undefined>
|
|
358
|
+
positionals: string[]
|
|
359
|
+
} {
|
|
360
|
+
const values: Record<string, string | undefined> = {}
|
|
361
|
+
const positionals: string[] = []
|
|
362
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
363
|
+
const token = tokens[index]!
|
|
364
|
+
if (!token.startsWith('--')) {
|
|
365
|
+
positionals.push(token)
|
|
366
|
+
continue
|
|
367
|
+
}
|
|
368
|
+
const name = token.slice(2)
|
|
369
|
+
if (!allowed.has(name)) throw new AgoraCommandParseError(`unknown option --${name}`)
|
|
370
|
+
if (values[name] !== undefined) throw new AgoraCommandParseError(`option --${name} was supplied more than once`)
|
|
371
|
+
const value = tokens[index + 1]
|
|
372
|
+
if (value === undefined || value.startsWith('--')) throw new AgoraCommandParseError(`option --${name} requires a value`)
|
|
373
|
+
values[name] = value
|
|
374
|
+
index += 1
|
|
375
|
+
}
|
|
376
|
+
return { values, positionals }
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function tokenize(input: string): string[] {
|
|
380
|
+
const tokens: string[] = []
|
|
381
|
+
let token = ''
|
|
382
|
+
let quote: 'single' | 'double' | undefined
|
|
383
|
+
let escaped = false
|
|
384
|
+
let started = false
|
|
385
|
+
for (const char of input.trim()) {
|
|
386
|
+
if (escaped) {
|
|
387
|
+
token += char
|
|
388
|
+
escaped = false
|
|
389
|
+
started = true
|
|
390
|
+
continue
|
|
391
|
+
}
|
|
392
|
+
if (char === '\\' && quote !== 'single') {
|
|
393
|
+
escaped = true
|
|
394
|
+
started = true
|
|
395
|
+
continue
|
|
396
|
+
}
|
|
397
|
+
if (char === "'" && quote !== 'double') {
|
|
398
|
+
quote = quote === 'single' ? undefined : 'single'
|
|
399
|
+
started = true
|
|
400
|
+
continue
|
|
401
|
+
}
|
|
402
|
+
if (char === '"' && quote !== 'single') {
|
|
403
|
+
quote = quote === 'double' ? undefined : 'double'
|
|
404
|
+
started = true
|
|
405
|
+
continue
|
|
406
|
+
}
|
|
407
|
+
if (/\s/u.test(char) && quote === undefined) {
|
|
408
|
+
if (started) tokens.push(token)
|
|
409
|
+
token = ''
|
|
410
|
+
started = false
|
|
411
|
+
continue
|
|
412
|
+
}
|
|
413
|
+
token += char
|
|
414
|
+
started = true
|
|
415
|
+
}
|
|
416
|
+
if (escaped) throw new AgoraCommandParseError('unfinished escape at end of command')
|
|
417
|
+
if (quote !== undefined) throw new AgoraCommandParseError('unclosed quote in command')
|
|
418
|
+
if (started) tokens.push(token)
|
|
419
|
+
return tokens
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function messageOf(error: unknown): string {
|
|
423
|
+
return error instanceof Error ? error.message : String(error)
|
|
424
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
2
|
+
import type {} from 'cordis'
|
|
3
|
+
import type { AgoraCommandResult, DshAgoraServiceApi } from './contracts.js'
|
|
4
|
+
import type { DshToolRegistry } from './tool.js'
|
|
5
|
+
import type { DshAgoraExtensionRegistryApi } from './extension-sdk.js'
|
|
6
|
+
|
|
7
|
+
export interface DshAgentLike {
|
|
8
|
+
readonly id?: string
|
|
9
|
+
readonly session?: { readonly id?: string }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface DshCommandInvocation {
|
|
13
|
+
readonly rawInput: string
|
|
14
|
+
readonly agent: DshAgentLike
|
|
15
|
+
readonly signal: AbortSignal
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface DshCommandDefinition {
|
|
19
|
+
readonly name: string
|
|
20
|
+
readonly description: string
|
|
21
|
+
readonly input?: { readonly hint: string; readonly images?: boolean }
|
|
22
|
+
handler(invocation: DshCommandInvocation): AgoraCommandResult | Promise<AgoraCommandResult>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface DshCommands {
|
|
26
|
+
register(definition: DshCommandDefinition): () => void
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface DshWebServer {
|
|
30
|
+
readonly port?: number
|
|
31
|
+
register(route: {
|
|
32
|
+
readonly kind: 'exact' | 'prefix'
|
|
33
|
+
readonly path: string
|
|
34
|
+
handler(req: IncomingMessage, res: ServerResponse): void | Promise<void>
|
|
35
|
+
}): () => void
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface DshAgoraContext {
|
|
39
|
+
readonly commands: DshCommands
|
|
40
|
+
readonly tools: DshToolRegistry
|
|
41
|
+
get?(name: string): unknown
|
|
42
|
+
inject?(
|
|
43
|
+
services: readonly string[],
|
|
44
|
+
callback: (ctx: DshAgoraContext) => void | (() => void),
|
|
45
|
+
): unknown
|
|
46
|
+
accessor?(name: string, descriptor: { get(): unknown }): unknown
|
|
47
|
+
effect?(effect: () => void | (() => void), label?: string): void
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
declare module 'cordis' {
|
|
51
|
+
interface Context {
|
|
52
|
+
dshAgora: DshAgoraServiceApi & DshAgoraExtensionRegistryApi
|
|
53
|
+
}
|
|
54
|
+
}
|