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/tool.ts
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto'
|
|
2
|
+
import type { DshAgoraServiceApi, TaskPriority } from './contracts.js'
|
|
3
|
+
|
|
4
|
+
export interface DshToolRunContext {
|
|
5
|
+
readonly signal: AbortSignal
|
|
6
|
+
readonly agent?: { readonly id?: string }
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface DshToolDefinition {
|
|
10
|
+
readonly name: string
|
|
11
|
+
readonly description: string
|
|
12
|
+
readonly parameters: Record<string, unknown>
|
|
13
|
+
readonly output: {
|
|
14
|
+
readonly schema: Record<string, unknown>
|
|
15
|
+
render(args: unknown, value: unknown): Array<{ readonly type: 'text'; readonly text: string }>
|
|
16
|
+
}
|
|
17
|
+
execute(args: unknown, exec: DshToolRunContext): Promise<unknown>
|
|
18
|
+
presentCall?(args: unknown): { readonly card: 'generic'; readonly title: string; readonly kind?: 'read' | 'execute' }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface DshToolRegistry {
|
|
22
|
+
register(definition: DshToolDefinition): () => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type AgoraToolArgs =
|
|
26
|
+
| { readonly action: 'health' }
|
|
27
|
+
| { readonly action: 'nodes' | 'agents' }
|
|
28
|
+
| { readonly action: 'list'; readonly state?: string; readonly project_id?: string }
|
|
29
|
+
| { readonly action: 'show' | 'status'; readonly task_id: string }
|
|
30
|
+
| { readonly action: 'dispatch_status'; readonly dispatch_id: string }
|
|
31
|
+
| {
|
|
32
|
+
readonly action: 'attach_session'
|
|
33
|
+
readonly task_id: string
|
|
34
|
+
readonly participant_binding_id: string
|
|
35
|
+
readonly session_id: string
|
|
36
|
+
readonly runtime_target_ref?: string
|
|
37
|
+
}
|
|
38
|
+
| {
|
|
39
|
+
readonly action: 'dispatch'
|
|
40
|
+
readonly runtime_target_ref: string
|
|
41
|
+
readonly prompt: string
|
|
42
|
+
readonly task_id?: string
|
|
43
|
+
readonly participant_binding_id?: string
|
|
44
|
+
readonly session_id?: string
|
|
45
|
+
readonly workspace_alias?: string
|
|
46
|
+
readonly agent_preset?: string
|
|
47
|
+
readonly idempotency_key?: string
|
|
48
|
+
readonly wait_seconds?: number
|
|
49
|
+
readonly presentation_mode?: 'source_bot' | 'destination_bot' | 'silent'
|
|
50
|
+
}
|
|
51
|
+
| {
|
|
52
|
+
readonly action: 'create'
|
|
53
|
+
readonly title: string
|
|
54
|
+
readonly task_type?: string
|
|
55
|
+
readonly description?: string
|
|
56
|
+
readonly priority?: TaskPriority
|
|
57
|
+
readonly project_id?: string
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function createAgoraTool(service: DshAgoraServiceApi): DshToolDefinition {
|
|
61
|
+
return {
|
|
62
|
+
name: 'agora_task',
|
|
63
|
+
description: [
|
|
64
|
+
'Create and inspect governed tasks in the connected Agora server.',
|
|
65
|
+
'Discover DSH agents, dispatch work to another Harness node, attach existing Sessions, and inspect governed tasks.',
|
|
66
|
+
'This tool cannot approve or reject a human governance gate.',
|
|
67
|
+
].join(' '),
|
|
68
|
+
parameters: {
|
|
69
|
+
type: 'object',
|
|
70
|
+
additionalProperties: false,
|
|
71
|
+
properties: {
|
|
72
|
+
action: { type: 'string', enum: ['health', 'nodes', 'agents', 'list', 'show', 'status', 'create', 'dispatch', 'dispatch_status', 'attach_session'], description: 'Operation to perform.' },
|
|
73
|
+
task_id: { type: 'string', description: 'Agora task id for show or status.' },
|
|
74
|
+
state: { type: 'string', description: 'Optional state filter for list.' },
|
|
75
|
+
project_id: { type: 'string', description: 'Optional project filter or task project id.' },
|
|
76
|
+
title: { type: 'string', description: 'Task title for create.' },
|
|
77
|
+
task_type: { type: 'string', description: 'Agora workflow/task type for create; defaults to general.' },
|
|
78
|
+
description: { type: 'string', description: 'Optional task description.' },
|
|
79
|
+
priority: { type: 'string', enum: ['low', 'normal', 'high'], description: 'Optional task priority.' },
|
|
80
|
+
runtime_target_ref: { type: 'string', description: 'Target in dsh:<node-id>:<agent-ref> form.' },
|
|
81
|
+
participant_binding_id: { type: 'string', description: 'Agora participant binding for a durable task-to-Session link.' },
|
|
82
|
+
session_id: { type: 'string', description: 'Existing DSH Session to resume or attach.' },
|
|
83
|
+
dispatch_id: { type: 'string', description: 'Runtime dispatch id.' },
|
|
84
|
+
prompt: { type: 'string', description: 'Work instruction for the destination Agent.' },
|
|
85
|
+
workspace_alias: { type: 'string', description: 'Optional destination workspace alias.' },
|
|
86
|
+
agent_preset: { type: 'string', description: 'Optional destination Agent Preset.' },
|
|
87
|
+
idempotency_key: { type: 'string', description: 'Stable retry key for dispatch.' },
|
|
88
|
+
wait_seconds: { type: 'integer', minimum: 0, maximum: 600, description: 'Optionally wait for the remote Agent result.' },
|
|
89
|
+
presentation_mode: { type: 'string', enum: ['source_bot', 'destination_bot', 'silent'], description: 'How a bridged IM result is presented.' },
|
|
90
|
+
},
|
|
91
|
+
required: ['action'],
|
|
92
|
+
},
|
|
93
|
+
output: {
|
|
94
|
+
schema: {
|
|
95
|
+
type: 'object',
|
|
96
|
+
additionalProperties: true,
|
|
97
|
+
properties: {
|
|
98
|
+
ok: { type: 'boolean' },
|
|
99
|
+
action: { type: 'string' },
|
|
100
|
+
data: {},
|
|
101
|
+
},
|
|
102
|
+
required: ['ok', 'action', 'data'],
|
|
103
|
+
},
|
|
104
|
+
render: (_args, value) => [{ type: 'text', text: JSON.stringify(value, null, 2) }],
|
|
105
|
+
},
|
|
106
|
+
async execute(rawArgs, exec) {
|
|
107
|
+
const args = parseToolArgs(rawArgs)
|
|
108
|
+
switch (args.action) {
|
|
109
|
+
case 'health': return { ok: true, action: args.action, data: await service.health(exec.signal) }
|
|
110
|
+
case 'nodes': return { ok: true, action: args.action, data: await service.listRuntimeNodes(exec.signal) }
|
|
111
|
+
case 'agents': return {
|
|
112
|
+
ok: true, action: args.action,
|
|
113
|
+
data: (await service.listRuntimeTargets(exec.signal)).filter(target => target.runtime_provider === 'dsh'),
|
|
114
|
+
}
|
|
115
|
+
case 'list': return { ok: true, action: args.action, data: await service.listTasks(args.state, args.project_id, exec.signal) }
|
|
116
|
+
case 'show': return { ok: true, action: args.action, data: await service.getTask(args.task_id, exec.signal) }
|
|
117
|
+
case 'status': return { ok: true, action: args.action, data: await service.taskStatus(args.task_id, exec.signal) }
|
|
118
|
+
case 'dispatch_status': return { ok: true, action: args.action, data: await service.getRuntimeDispatch(args.dispatch_id, exec.signal) }
|
|
119
|
+
case 'attach_session': return {
|
|
120
|
+
ok: true,
|
|
121
|
+
action: args.action,
|
|
122
|
+
data: await service.bindRuntimeSession(
|
|
123
|
+
args.task_id,
|
|
124
|
+
args.participant_binding_id,
|
|
125
|
+
args.session_id,
|
|
126
|
+
args.runtime_target_ref,
|
|
127
|
+
exec.signal,
|
|
128
|
+
),
|
|
129
|
+
}
|
|
130
|
+
case 'dispatch': return {
|
|
131
|
+
ok: true,
|
|
132
|
+
action: args.action,
|
|
133
|
+
data: await service.dispatchAgent({
|
|
134
|
+
runtime_target_ref: args.runtime_target_ref,
|
|
135
|
+
prompt: args.prompt,
|
|
136
|
+
idempotency_key: args.idempotency_key ?? `dsh-tool-${randomUUID()}`,
|
|
137
|
+
wait_timeout_ms: (args.wait_seconds ?? 0) * 1_000,
|
|
138
|
+
presentation_mode: args.presentation_mode ?? 'destination_bot',
|
|
139
|
+
...(exec.agent?.id === undefined ? {} : { source_session_id: exec.agent.id }),
|
|
140
|
+
...(args.task_id === undefined ? {} : { task_id: args.task_id }),
|
|
141
|
+
...(args.participant_binding_id === undefined ? {} : { participant_binding_id: args.participant_binding_id }),
|
|
142
|
+
...(args.session_id === undefined ? {} : { session_id: args.session_id }),
|
|
143
|
+
...(args.workspace_alias === undefined ? {} : { workspace_alias: args.workspace_alias }),
|
|
144
|
+
...(args.agent_preset === undefined ? {} : { agent_preset: args.agent_preset }),
|
|
145
|
+
}, exec.signal),
|
|
146
|
+
}
|
|
147
|
+
case 'create': return {
|
|
148
|
+
ok: true,
|
|
149
|
+
action: args.action,
|
|
150
|
+
data: await service.createTask({
|
|
151
|
+
title: args.title,
|
|
152
|
+
...(exec.agent?.id === undefined ? {} : { creator: exec.agent.id }),
|
|
153
|
+
...(args.task_type === undefined ? {} : { type: args.task_type }),
|
|
154
|
+
...(args.description === undefined ? {} : { description: args.description }),
|
|
155
|
+
...(args.priority === undefined ? {} : { priority: args.priority }),
|
|
156
|
+
...(args.project_id === undefined ? {} : { projectId: args.project_id }),
|
|
157
|
+
}, exec.signal),
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
presentCall: rawArgs => {
|
|
162
|
+
const action = isRecord(rawArgs) && typeof rawArgs.action === 'string' ? rawArgs.action : 'operation'
|
|
163
|
+
return { card: 'generic', title: `Agora ${action}`, kind: action === 'create' ? 'execute' : 'read' }
|
|
164
|
+
},
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function parseToolArgs(value: unknown): AgoraToolArgs {
|
|
169
|
+
if (!isRecord(value)) throw new TypeError('agora_task arguments must be an object')
|
|
170
|
+
const action = requiredString(value.action, 'action')
|
|
171
|
+
assertAllowedKeys(value, [
|
|
172
|
+
'action', 'task_id', 'state', 'project_id', 'title', 'task_type', 'description', 'priority',
|
|
173
|
+
'runtime_target_ref', 'participant_binding_id', 'session_id', 'dispatch_id', 'prompt',
|
|
174
|
+
'workspace_alias', 'agent_preset', 'idempotency_key', 'wait_seconds', 'presentation_mode',
|
|
175
|
+
])
|
|
176
|
+
switch (action) {
|
|
177
|
+
case 'health':
|
|
178
|
+
return { action }
|
|
179
|
+
case 'nodes':
|
|
180
|
+
case 'agents':
|
|
181
|
+
return { action }
|
|
182
|
+
case 'list':
|
|
183
|
+
return { action, ...optionalFields(value, ['state', 'project_id']) }
|
|
184
|
+
case 'show':
|
|
185
|
+
case 'status':
|
|
186
|
+
return { action, task_id: requiredString(value.task_id, 'task_id') }
|
|
187
|
+
case 'dispatch_status':
|
|
188
|
+
return { action, dispatch_id: requiredString(value.dispatch_id, 'dispatch_id') }
|
|
189
|
+
case 'attach_session':
|
|
190
|
+
return {
|
|
191
|
+
action,
|
|
192
|
+
task_id: requiredString(value.task_id, 'task_id'),
|
|
193
|
+
participant_binding_id: requiredString(value.participant_binding_id, 'participant_binding_id'),
|
|
194
|
+
session_id: requiredString(value.session_id, 'session_id'),
|
|
195
|
+
...optionalFields(value, ['runtime_target_ref']),
|
|
196
|
+
}
|
|
197
|
+
case 'dispatch': {
|
|
198
|
+
const presentationMode = optionalString(value.presentation_mode, 'presentation_mode')
|
|
199
|
+
if (presentationMode !== undefined && !['source_bot', 'destination_bot', 'silent'].includes(presentationMode)) {
|
|
200
|
+
throw new TypeError('presentation_mode must be source_bot, destination_bot, or silent')
|
|
201
|
+
}
|
|
202
|
+
const waitSeconds = optionalInteger(value.wait_seconds, 'wait_seconds', 0, 600)
|
|
203
|
+
return {
|
|
204
|
+
action,
|
|
205
|
+
runtime_target_ref: requiredString(value.runtime_target_ref, 'runtime_target_ref'),
|
|
206
|
+
prompt: requiredString(value.prompt, 'prompt'),
|
|
207
|
+
...optionalFields(value, [
|
|
208
|
+
'task_id', 'participant_binding_id', 'session_id', 'workspace_alias', 'agent_preset', 'idempotency_key',
|
|
209
|
+
]),
|
|
210
|
+
...(waitSeconds === undefined ? {} : { wait_seconds: waitSeconds }),
|
|
211
|
+
...(presentationMode === undefined ? {} : { presentation_mode: presentationMode as 'source_bot' | 'destination_bot' | 'silent' }),
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
case 'create': {
|
|
215
|
+
const priority = optionalString(value.priority, 'priority')
|
|
216
|
+
if (priority !== undefined && priority !== 'low' && priority !== 'normal' && priority !== 'high') {
|
|
217
|
+
throw new TypeError('priority must be low, normal, or high')
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
action,
|
|
221
|
+
title: requiredString(value.title, 'title'),
|
|
222
|
+
...optionalFields(value, ['task_type', 'description', 'project_id']),
|
|
223
|
+
...(priority === undefined ? {} : { priority }),
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
default:
|
|
227
|
+
throw new TypeError(`unsupported agora_task action "${action}"`)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function optionalFields<T extends readonly string[]>(record: Record<string, unknown>, keys: T): Partial<Record<T[number], string>> {
|
|
232
|
+
const output: Partial<Record<T[number], string>> = {}
|
|
233
|
+
for (const key of keys) {
|
|
234
|
+
const value = optionalString(record[key], key)
|
|
235
|
+
if (value !== undefined) output[key as T[number]] = value
|
|
236
|
+
}
|
|
237
|
+
return output
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function optionalString(value: unknown, field: string): string | undefined {
|
|
241
|
+
if (value === undefined || value === null) return undefined
|
|
242
|
+
if (typeof value !== 'string') throw new TypeError(`${field} must be a string`)
|
|
243
|
+
const normalized = value.trim()
|
|
244
|
+
return normalized === '' ? undefined : normalized
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function requiredString(value: unknown, field: string): string {
|
|
248
|
+
const normalized = optionalString(value, field)
|
|
249
|
+
if (normalized === undefined) throw new TypeError(`${field} is required`)
|
|
250
|
+
return normalized
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function optionalInteger(value: unknown, field: string, minimum: number, maximum: number): number | undefined {
|
|
254
|
+
if (value === undefined || value === null) return undefined
|
|
255
|
+
if (!Number.isSafeInteger(value) || (value as number) < minimum || (value as number) > maximum) {
|
|
256
|
+
throw new TypeError(`${field} must be an integer between ${minimum} and ${maximum}`)
|
|
257
|
+
}
|
|
258
|
+
return value as number
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function assertAllowedKeys(record: Record<string, unknown>, allowed: readonly string[]): void {
|
|
262
|
+
const allowedSet = new Set(allowed)
|
|
263
|
+
const unknown = Object.keys(record).find(key => !allowedSet.has(key))
|
|
264
|
+
if (unknown !== undefined) throw new TypeError(`unknown agora_task argument "${unknown}"`)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
268
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
269
|
+
}
|