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.
Files changed (79) hide show
  1. package/README.md +520 -0
  2. package/client/index.js +492 -0
  3. package/cordis.patch.yml +14 -0
  4. package/dsh.plugin.json +7 -0
  5. package/lib/agora-client.d.ts +73 -0
  6. package/lib/agora-client.d.ts.map +1 -0
  7. package/lib/agora-client.js +207 -0
  8. package/lib/agora-client.js.map +1 -0
  9. package/lib/client.js +492 -0
  10. package/lib/command-adapter.d.ts +44 -0
  11. package/lib/command-adapter.d.ts.map +1 -0
  12. package/lib/command-adapter.js +70 -0
  13. package/lib/command-adapter.js.map +1 -0
  14. package/lib/command.d.ts +53 -0
  15. package/lib/command.d.ts.map +1 -0
  16. package/lib/command.js +375 -0
  17. package/lib/command.js.map +1 -0
  18. package/lib/context-types.d.ts +51 -0
  19. package/lib/context-types.d.ts.map +1 -0
  20. package/lib/context-types.js +2 -0
  21. package/lib/context-types.js.map +1 -0
  22. package/lib/contracts.d.ts +402 -0
  23. package/lib/contracts.d.ts.map +1 -0
  24. package/lib/contracts.js +2 -0
  25. package/lib/contracts.js.map +1 -0
  26. package/lib/extension-sdk.d.ts +74 -0
  27. package/lib/extension-sdk.d.ts.map +1 -0
  28. package/lib/extension-sdk.js +160 -0
  29. package/lib/extension-sdk.js.map +1 -0
  30. package/lib/harness-runtime.d.ts +29 -0
  31. package/lib/harness-runtime.d.ts.map +1 -0
  32. package/lib/harness-runtime.js +422 -0
  33. package/lib/harness-runtime.js.map +1 -0
  34. package/lib/http-api.d.ts +10 -0
  35. package/lib/http-api.d.ts.map +1 -0
  36. package/lib/http-api.js +282 -0
  37. package/lib/http-api.js.map +1 -0
  38. package/lib/im-bridge-v1.d.ts +37 -0
  39. package/lib/im-bridge-v1.d.ts.map +1 -0
  40. package/lib/im-bridge-v1.js +43 -0
  41. package/lib/im-bridge-v1.js.map +1 -0
  42. package/lib/im-gateway.d.ts +24 -0
  43. package/lib/im-gateway.d.ts.map +1 -0
  44. package/lib/im-gateway.js +53 -0
  45. package/lib/im-gateway.js.map +1 -0
  46. package/lib/index.d.ts +46 -0
  47. package/lib/index.d.ts.map +1 -0
  48. package/lib/index.js +210 -0
  49. package/lib/index.js.map +1 -0
  50. package/lib/node-worker.d.ts +52 -0
  51. package/lib/node-worker.d.ts.map +1 -0
  52. package/lib/node-worker.js +372 -0
  53. package/lib/node-worker.js.map +1 -0
  54. package/lib/service.d.ts +51 -0
  55. package/lib/service.d.ts.map +1 -0
  56. package/lib/service.js +196 -0
  57. package/lib/service.js.map +1 -0
  58. package/lib/tool.d.ts +72 -0
  59. package/lib/tool.d.ts.map +1 -0
  60. package/lib/tool.js +206 -0
  61. package/lib/tool.js.map +1 -0
  62. package/package.json +74 -0
  63. package/patches/dsh-im/@xmanrui__dsh-im@2.1.0.patch +920 -0
  64. package/patches/dsh-im/@xmanrui__dsh-im@2.3.0.patch +984 -0
  65. package/patches/dsh-im/README.md +82 -0
  66. package/src/agora-client.ts +333 -0
  67. package/src/command-adapter.ts +106 -0
  68. package/src/command.ts +424 -0
  69. package/src/context-types.ts +54 -0
  70. package/src/contracts.ts +413 -0
  71. package/src/extension-sdk.ts +225 -0
  72. package/src/harness-runtime.ts +518 -0
  73. package/src/http-api.ts +269 -0
  74. package/src/im-bridge-v1.ts +73 -0
  75. package/src/im-gateway.ts +82 -0
  76. package/src/index.ts +260 -0
  77. package/src/node-worker.ts +442 -0
  78. package/src/service.ts +262 -0
  79. 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
+ }