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
@@ -0,0 +1,413 @@
1
+ export type TaskPriority = 'low' | 'normal' | 'high'
2
+
3
+ export interface AgoraTask {
4
+ readonly id: string
5
+ readonly version?: number
6
+ readonly title: string
7
+ readonly description?: string | null
8
+ readonly type: string
9
+ readonly priority: TaskPriority
10
+ readonly creator: string
11
+ readonly locale?: string
12
+ readonly project_id?: string | null
13
+ readonly state: string
14
+ readonly current_stage: string | null
15
+ readonly error_detail?: string | null
16
+ readonly created_at?: string
17
+ readonly updated_at?: string
18
+ readonly [key: string]: unknown
19
+ }
20
+
21
+ export interface AgoraTaskStatus {
22
+ readonly task: AgoraTask
23
+ readonly flow_log: readonly Record<string, unknown>[]
24
+ readonly progress_log: readonly Record<string, unknown>[]
25
+ readonly subtasks: readonly Record<string, unknown>[]
26
+ readonly [key: string]: unknown
27
+ }
28
+
29
+ export interface AgoraHealth {
30
+ readonly ok?: boolean
31
+ readonly status?: string
32
+ readonly service?: string
33
+ readonly [key: string]: unknown
34
+ }
35
+
36
+ export const DSH_AGORA_NODE_PROTOCOL = 'dsh-agora.node/v1' as const
37
+
38
+ export interface RuntimeNodeAgent {
39
+ readonly agent_ref: string
40
+ readonly display_name?: string | null
41
+ readonly preset?: string | null
42
+ readonly model?: string | null
43
+ readonly workspace_alias?: string | null
44
+ readonly roles: readonly string[]
45
+ readonly capabilities: readonly string[]
46
+ readonly metadata?: Readonly<Record<string, unknown>> | null
47
+ }
48
+
49
+ export interface RuntimeNodeBot {
50
+ readonly provider: string
51
+ readonly bot_ref: string
52
+ readonly platform_id?: string | null
53
+ readonly display_name?: string | null
54
+ readonly agent_ref?: string | null
55
+ readonly connected: boolean
56
+ readonly capabilities: readonly string[]
57
+ }
58
+
59
+ export interface RuntimeNodeHeartbeatInput {
60
+ readonly protocol: typeof DSH_AGORA_NODE_PROTOCOL
61
+ readonly instance_id: string
62
+ readonly plugin_version: string
63
+ readonly host_framework: 'deepseek-harness'
64
+ readonly runtime_provider: 'dsh'
65
+ readonly agents: readonly RuntimeNodeAgent[]
66
+ readonly bots: readonly RuntimeNodeBot[]
67
+ readonly capacity: { readonly max_concurrent: number; readonly active: number }
68
+ readonly lease_seconds: number
69
+ readonly metadata?: Readonly<Record<string, unknown>> | null
70
+ }
71
+
72
+ export interface RuntimeNode extends RuntimeNodeHeartbeatInput {
73
+ readonly node_id: string
74
+ readonly presence: 'online' | 'stale'
75
+ readonly registered_at: string
76
+ readonly last_seen_at: string
77
+ readonly expires_at: string
78
+ }
79
+
80
+ export interface RuntimeTarget {
81
+ readonly runtime_target_ref: string
82
+ readonly runtime_provider: string | null
83
+ readonly runtime_flavor: string | null
84
+ readonly host_framework: string | null
85
+ readonly primary_model: string | null
86
+ readonly channel_providers: readonly string[]
87
+ readonly enabled: boolean
88
+ readonly display_name: string | null
89
+ readonly presentation_mode: 'headless' | 'im_presented'
90
+ readonly presentation_provider: string | null
91
+ readonly presentation_identity_ref: string | null
92
+ readonly [key: string]: unknown
93
+ }
94
+
95
+ export interface CreateRuntimeDispatchInput {
96
+ readonly task_id?: string | null
97
+ readonly participant_binding_id?: string | null
98
+ readonly runtime_target_ref: string
99
+ readonly session_id?: string | null
100
+ readonly workspace_alias?: string | null
101
+ readonly agent_preset?: string | null
102
+ readonly prompt: string
103
+ readonly idempotency_key: string
104
+ readonly metadata?: Readonly<Record<string, unknown>> | null
105
+ }
106
+
107
+ export interface DispatchAgentInput extends CreateRuntimeDispatchInput {
108
+ readonly source_session_id?: string
109
+ readonly wait_timeout_ms?: number
110
+ readonly presentation_mode?: 'source_bot' | 'destination_bot' | 'silent'
111
+ }
112
+
113
+ export interface RuntimeDispatch extends CreateRuntimeDispatchInput {
114
+ readonly id: string
115
+ readonly node_id: string
116
+ readonly status: 'pending' | 'claimed' | 'completed' | 'failed' | 'cancelled'
117
+ readonly claimed_by: string | null
118
+ readonly claim_token: string | null
119
+ readonly claim_expires_at: string | null
120
+ readonly attempt: number
121
+ readonly claimed_at: string | null
122
+ readonly claim_renewed_at: string | null
123
+ readonly latest_progress?: RuntimeDispatchProgress | null
124
+ readonly progress_updated_at?: string | null
125
+ readonly result: Readonly<Record<string, unknown>> | null
126
+ readonly result_envelope?: RuntimeResultEnvelope | null
127
+ readonly error: string | null
128
+ readonly created_at: string
129
+ readonly updated_at: string
130
+ readonly completed_at: string | null
131
+ }
132
+
133
+ export interface RuntimeDispatchProgressInput {
134
+ readonly phase: string
135
+ readonly message?: string | null
136
+ readonly percent?: number | null
137
+ readonly details?: Readonly<Record<string, unknown>> | null
138
+ }
139
+
140
+ export interface RecordRuntimeDispatchProgressInput extends RuntimeDispatchProgressInput {
141
+ readonly instance_id: string
142
+ readonly claim_token: string
143
+ readonly sequence: number
144
+ }
145
+
146
+ export interface RuntimeDispatchProgress extends RuntimeDispatchProgressInput {
147
+ readonly id: string
148
+ readonly dispatch_id: string
149
+ readonly node_id: string
150
+ readonly instance_id: string
151
+ readonly attempt: number
152
+ readonly sequence: number
153
+ readonly created_at: string
154
+ }
155
+
156
+ export interface RuntimeResultEvidence {
157
+ readonly id: string
158
+ readonly kind: 'file' | 'url' | 'commit' | 'measurement' | 'log' | 'command' | 'other'
159
+ readonly label?: string | null
160
+ readonly uri?: string | null
161
+ readonly content_hash?: string | null
162
+ readonly revision?: string | null
163
+ readonly line_start?: number | null
164
+ readonly line_end?: number | null
165
+ readonly metadata?: Readonly<Record<string, unknown>> | null
166
+ }
167
+
168
+ export interface RuntimeResultClaim {
169
+ readonly id: string
170
+ readonly statement: string
171
+ readonly evidence_ids: readonly string[]
172
+ readonly confidence?: number | null
173
+ }
174
+
175
+ export interface RuntimeResultEnvelope {
176
+ readonly schema: 'agora.runtime-result/v1'
177
+ readonly answer: string
178
+ readonly claims: readonly RuntimeResultClaim[]
179
+ readonly evidence: readonly RuntimeResultEvidence[]
180
+ readonly confidence?: number | null
181
+ readonly environment?: {
182
+ readonly runtime_provider: string
183
+ readonly agent_ref?: string | null
184
+ readonly model?: string | null
185
+ readonly workspace_alias?: string | null
186
+ readonly revision?: string | null
187
+ readonly metadata?: Readonly<Record<string, unknown>> | null
188
+ } | null
189
+ readonly usage?: RuntimeUsage | null
190
+ }
191
+
192
+ export interface RuntimeUsage {
193
+ readonly input_tokens: number | null
194
+ readonly output_tokens: number | null
195
+ readonly total_tokens: number | null
196
+ readonly tool_calls: number | null
197
+ readonly cost_usd: number | null
198
+ readonly duration_ms: number | null
199
+ }
200
+
201
+ export type CoordinationMode = 'single' | 'fanout' | 'review' | 'debate' | 'council'
202
+ export type CoordinationRunStatus = 'running' | 'verifying' | 'completed' | 'partial' | 'failed' | 'cancelled' | 'budget_exhausted'
203
+
204
+ export interface CoordinationBudget {
205
+ readonly max_agents: number
206
+ readonly max_dispatches: number
207
+ readonly max_wall_clock_seconds: number
208
+ readonly max_tokens: number | null
209
+ readonly max_tool_calls: number | null
210
+ readonly max_cost_usd: number | null
211
+ readonly min_information_gain: number
212
+ }
213
+
214
+ export interface CreateCoordinationRunInput {
215
+ readonly task_id?: string | null
216
+ readonly task_type?: string
217
+ readonly prompt: string
218
+ readonly mode: CoordinationMode
219
+ readonly candidates: readonly {
220
+ readonly runtime_target_ref: string
221
+ readonly capabilities?: readonly string[]
222
+ readonly priority?: number
223
+ }[]
224
+ readonly verifier_target_ref?: string | null
225
+ readonly budget?: Partial<CoordinationBudget>
226
+ readonly memory_scopes?: readonly ('task' | 'agent_private' | 'project_shared' | 'decision' | 'episodic')[]
227
+ readonly idempotency_key: string
228
+ readonly metadata?: Readonly<Record<string, unknown>> | null
229
+ }
230
+
231
+ export interface CoordinationRun {
232
+ readonly id: string
233
+ readonly task_id: string | null
234
+ readonly task_type: string
235
+ readonly prompt: string
236
+ readonly mode: CoordinationMode
237
+ readonly status: CoordinationRunStatus
238
+ readonly budget: CoordinationBudget
239
+ readonly usage: RuntimeUsage
240
+ readonly synthesis: {
241
+ readonly answer: string
242
+ readonly agreements: readonly string[]
243
+ readonly conflicts: readonly {
244
+ readonly id: string
245
+ readonly kind: 'claim_conflict' | 'environment_drift' | 'unsupported_claim'
246
+ readonly key: string
247
+ readonly member_ids: readonly string[]
248
+ readonly statements: readonly string[]
249
+ readonly detail: string
250
+ readonly resolved: boolean
251
+ }[]
252
+ readonly evidence_ids: readonly string[]
253
+ readonly verified: boolean
254
+ readonly information_gain: number
255
+ readonly result_envelope: RuntimeResultEnvelope | null
256
+ } | null
257
+ readonly stop_reason: string | null
258
+ readonly deadline_at: string
259
+ readonly created_at: string
260
+ readonly updated_at: string
261
+ readonly completed_at: string | null
262
+ readonly members: readonly {
263
+ readonly id: string
264
+ readonly dispatch_id: string
265
+ readonly runtime_target_ref: string
266
+ readonly role: 'primary' | 'investigator' | 'reviewer' | 'verifier' | 'arbitrator'
267
+ readonly round: number
268
+ readonly status: 'dispatched' | 'claimed' | 'completed' | 'failed' | 'cancelled'
269
+ readonly selection_score: number
270
+ readonly selection_reason: readonly string[]
271
+ readonly result_envelope: RuntimeResultEnvelope | null
272
+ readonly usage: RuntimeUsage | null
273
+ }[]
274
+ }
275
+
276
+ export interface AgentScorecard {
277
+ readonly runtime_target_ref: string
278
+ readonly task_type: string
279
+ readonly observations: number
280
+ readonly success_rate: number | null
281
+ readonly failure_rate: number | null
282
+ readonly cancellation_rate: number | null
283
+ readonly retry_rate: number | null
284
+ readonly timeout_rate: number | null
285
+ readonly median_duration_ms: number | null
286
+ readonly p95_duration_ms: number | null
287
+ readonly evidence_yield: number | null
288
+ readonly verifier_acceptance_rate: number | null
289
+ readonly agreement_rate: number | null
290
+ readonly unique_information_rate: number | null
291
+ readonly environment_drift_rate: number | null
292
+ readonly total_tokens: number | null
293
+ readonly total_cost_usd: number | null
294
+ readonly score: number
295
+ readonly updated_at: string | null
296
+ }
297
+
298
+ export interface RuntimeDelivery {
299
+ readonly id: string
300
+ readonly dispatch_id: string
301
+ readonly node_id: string
302
+ readonly payload: Readonly<Record<string, unknown>>
303
+ readonly status: 'pending' | 'claimed' | 'delivered' | 'failed'
304
+ readonly attempt: number
305
+ readonly claimed_by: string | null
306
+ readonly claim_token: string | null
307
+ readonly claim_expires_at: string | null
308
+ readonly next_attempt_at: string
309
+ readonly receipt: Readonly<Record<string, unknown>> | null
310
+ readonly error: string | null
311
+ readonly created_at: string
312
+ readonly updated_at: string
313
+ readonly delivered_at: string | null
314
+ }
315
+
316
+ export interface RuntimeSessionBinding {
317
+ readonly id: string
318
+ readonly participant_binding_id: string
319
+ readonly runtime_provider: 'dsh'
320
+ readonly runtime_session_ref: string
321
+ readonly runtime_actor_ref: string | null
322
+ readonly continuity_ref: string | null
323
+ readonly presence_state: 'active' | 'idle' | 'closed'
324
+ readonly last_seen_at: string
325
+ readonly [key: string]: unknown
326
+ }
327
+
328
+ export interface AgoraImTarget {
329
+ readonly provider?: string
330
+ readonly conversation_ref?: string
331
+ readonly thread_ref?: string
332
+ readonly visibility?: 'public' | 'private'
333
+ readonly participant_refs?: readonly string[]
334
+ }
335
+
336
+ export interface CreateAgoraTaskInput {
337
+ readonly title: string
338
+ readonly type?: string
339
+ readonly creator?: string
340
+ readonly description?: string
341
+ readonly priority?: TaskPriority
342
+ readonly locale?: 'zh-CN' | 'en-US'
343
+ readonly projectId?: string
344
+ readonly imTarget?: AgoraImTarget
345
+ }
346
+
347
+ export interface AgoraRequestContext {
348
+ readonly actorId?: string
349
+ readonly provider?: string
350
+ readonly conversationRef?: string
351
+ readonly threadRef?: string
352
+ }
353
+
354
+ export type AgoraCommandResult =
355
+ | { readonly kind: 'success'; readonly text?: string }
356
+ | { readonly kind: 'error'; readonly text: string }
357
+
358
+ export type DshAgoraImStatus =
359
+ | { readonly state: 'connected'; readonly service: string; readonly protocol: string }
360
+ | { readonly state: 'unavailable'; readonly reason: string }
361
+ | { readonly state: 'incompatible'; readonly service: string; readonly reason: string }
362
+
363
+ export interface DshAgoraSnapshot {
364
+ readonly serverUrl: string
365
+ readonly command: string
366
+ readonly im: DshAgoraImStatus
367
+ readonly imBridge: DshAgoraImStatus
368
+ readonly commandAdapter: {
369
+ readonly state: 'ready'
370
+ readonly protocol: 'dsh-agora.command-adapter/v1'
371
+ }
372
+ readonly node: DshAgoraNodeStatus
373
+ readonly extensions: readonly DshAgoraExtensionSummary[]
374
+ }
375
+
376
+ export type DshAgoraNodeStatus =
377
+ | { readonly state: 'disabled'; readonly nodeId: string }
378
+ | { readonly state: 'connecting'; readonly nodeId: string }
379
+ | { readonly state: 'online'; readonly nodeId: string; readonly lastHeartbeatAt: string }
380
+ | { readonly state: 'error'; readonly nodeId: string; readonly error: string; readonly lastHeartbeatAt?: string }
381
+
382
+ export interface DshAgoraExtensionSummary {
383
+ readonly id: string
384
+ readonly protocol: string
385
+ readonly kind: string
386
+ readonly capabilities: readonly string[]
387
+ }
388
+
389
+ export interface DshAgoraServiceApi {
390
+ readonly serverUrl: string
391
+ health(signal?: AbortSignal): Promise<AgoraHealth>
392
+ listTasks(state?: string, projectId?: string, signal?: AbortSignal): Promise<AgoraTask[]>
393
+ getTask(taskId: string, signal?: AbortSignal): Promise<AgoraTask>
394
+ taskStatus(taskId: string, signal?: AbortSignal): Promise<AgoraTaskStatus>
395
+ createTask(input: CreateAgoraTaskInput, signal?: AbortSignal): Promise<AgoraTask>
396
+ listRuntimeNodes(signal?: AbortSignal): Promise<RuntimeNode[]>
397
+ listRuntimeTargets(signal?: AbortSignal): Promise<RuntimeTarget[]>
398
+ createRuntimeDispatch(nodeId: string, input: CreateRuntimeDispatchInput, signal?: AbortSignal): Promise<RuntimeDispatch>
399
+ getRuntimeDispatch(dispatchId: string, signal?: AbortSignal): Promise<RuntimeDispatch>
400
+ listRuntimeDispatchProgress(dispatchId: string, signal?: AbortSignal): Promise<RuntimeDispatchProgress[]>
401
+ createCoordinationRun(input: CreateCoordinationRunInput, signal?: AbortSignal): Promise<CoordinationRun>
402
+ getCoordinationRun(runId: string, signal?: AbortSignal): Promise<CoordinationRun>
403
+ listCoordinationRuns(status?: CoordinationRunStatus, signal?: AbortSignal): Promise<CoordinationRun[]>
404
+ listAgentScorecards(taskType?: string, signal?: AbortSignal): Promise<AgentScorecard[]>
405
+ dispatchAgent(input: DispatchAgentInput, signal?: AbortSignal): Promise<RuntimeDispatch>
406
+ bindRuntimeSession(taskId: string, participantBindingId: string, sessionId: string, agentRef?: string, signal?: AbortSignal): Promise<RuntimeSessionBinding>
407
+ executeCommand(rawInput: string, context?: AgoraRequestContext, signal?: AbortSignal): Promise<AgoraCommandResult>
408
+ executeCommandEvent(
409
+ event: import('./command-adapter.js').DshAgoraCommandEventV1,
410
+ signal?: AbortSignal,
411
+ ): Promise<import('./command-adapter.js').DshAgoraCommandEventResultV1>
412
+ snapshot(): DshAgoraSnapshot
413
+ }
@@ -0,0 +1,225 @@
1
+ import { createHash, createPublicKey, verify as verifySignature } from 'node:crypto'
2
+ import type {
3
+ RuntimeDispatch,
4
+ RuntimeDispatchProgressInput,
5
+ RuntimeNodeAgent,
6
+ RuntimeResultEnvelope,
7
+ } from './contracts.js'
8
+
9
+ export const DSH_AGORA_EXTENSION_PROTOCOL = 'dsh-agora.extension/v1' as const
10
+ export const DSH_AGORA_RUNTIME_PROTOCOL = 'dsh-agora.runtime/v1' as const
11
+ export const DSH_AGORA_EXTENSION_MANIFEST_PROTOCOL = 'dsh-agora.extension-manifest/v1' as const
12
+
13
+ export interface RuntimeExecutionResult {
14
+ readonly sessionId: string
15
+ readonly answer: string
16
+ readonly reason?: string | null
17
+ readonly metadata?: Readonly<Record<string, unknown>>
18
+ readonly resultEnvelope?: RuntimeResultEnvelope
19
+ }
20
+
21
+ export interface RuntimeExecutionContext {
22
+ reportProgress(event: RuntimeDispatchProgressInput): Promise<void>
23
+ }
24
+
25
+ export interface DshAgoraRuntimeAdapterV1 {
26
+ readonly protocol: typeof DSH_AGORA_RUNTIME_PROTOCOL
27
+ supportsTarget?(runtimeTargetRef: string): boolean
28
+ describeAgents(): readonly RuntimeNodeAgent[] | Promise<readonly RuntimeNodeAgent[]>
29
+ execute(dispatch: RuntimeDispatch, signal: AbortSignal, context?: RuntimeExecutionContext): Promise<RuntimeExecutionResult>
30
+ cancel?(sessionId: string, signal: AbortSignal): Promise<boolean>
31
+ }
32
+
33
+ export interface DshAgoraExtensionV1 {
34
+ readonly protocol: typeof DSH_AGORA_EXTENSION_PROTOCOL
35
+ readonly id: string
36
+ readonly kind: 'runtime' | 'transport' | 'context' | 'workflow' | 'artifact' | 'event-sink' | 'policy'
37
+ readonly capabilities: readonly string[]
38
+ readonly runtime?: DshAgoraRuntimeAdapterV1
39
+ readonly metadata?: Readonly<Record<string, unknown>>
40
+ }
41
+
42
+ export interface DshAgoraExtensionManifestV1 {
43
+ readonly protocol: typeof DSH_AGORA_EXTENSION_MANIFEST_PROTOCOL
44
+ readonly id: string
45
+ readonly version: string
46
+ readonly kind: DshAgoraExtensionV1['kind']
47
+ readonly integrity_sha256: string
48
+ readonly capabilities: readonly string[]
49
+ readonly permissions: readonly {
50
+ readonly capability: string
51
+ readonly resources: readonly string[]
52
+ }[]
53
+ readonly publisher: {
54
+ readonly id: string
55
+ readonly key_id: string
56
+ }
57
+ readonly signature?: {
58
+ readonly algorithm: 'Ed25519'
59
+ readonly value: string
60
+ } | null
61
+ }
62
+
63
+ export interface DshAgoraExtensionTrustPolicy {
64
+ readonly requireSignedThirdParty?: boolean
65
+ readonly trustedPublicKeys?: Readonly<Record<string, string>>
66
+ readonly builtInExtensionIds?: readonly string[]
67
+ }
68
+
69
+ export interface DshAgoraExtensionRegistryApi {
70
+ registerExtension(extension: DshAgoraExtensionV1, manifest?: DshAgoraExtensionManifestV1, packageBytes?: Uint8Array): () => void
71
+ listExtensions(): readonly DshAgoraExtensionV1[]
72
+ }
73
+
74
+ export class DshAgoraExtensionRegistry implements DshAgoraExtensionRegistryApi {
75
+ private readonly extensions = new Map<string, DshAgoraExtensionV1>()
76
+ private readonly manifests = new Map<string, DshAgoraExtensionManifestV1>()
77
+
78
+ constructor(private readonly trustPolicy: DshAgoraExtensionTrustPolicy = {}) {}
79
+
80
+ registerExtension(extension: DshAgoraExtensionV1, manifest?: DshAgoraExtensionManifestV1, packageBytes?: Uint8Array): () => void {
81
+ validateExtension(extension)
82
+ const builtIn = new Set(this.trustPolicy.builtInExtensionIds ?? ['dsh-runtime']).has(extension.id)
83
+ if (manifest) {
84
+ validateExtensionManifest(manifest, extension)
85
+ if (this.trustPolicy.requireSignedThirdParty && !builtIn && packageBytes === undefined) {
86
+ throw new Error(`extension "${extension.id}" requires package bytes for integrity verification`)
87
+ }
88
+ verifyExtensionManifest(manifest, this.trustPolicy, packageBytes)
89
+ } else if (this.trustPolicy.requireSignedThirdParty && !builtIn) {
90
+ throw new Error(`extension "${extension.id}" requires a signed manifest`)
91
+ }
92
+ if (this.extensions.has(extension.id)) throw new Error(`dsh-agora extension "${extension.id}" is already registered`)
93
+ const frozen = Object.freeze({
94
+ ...extension,
95
+ capabilities: Object.freeze(unique(extension.capabilities)),
96
+ })
97
+ this.extensions.set(frozen.id, frozen)
98
+ if (manifest) this.manifests.set(frozen.id, Object.freeze({ ...manifest }))
99
+ return () => {
100
+ if (this.extensions.get(frozen.id) === frozen) {
101
+ this.extensions.delete(frozen.id)
102
+ this.manifests.delete(frozen.id)
103
+ }
104
+ }
105
+ }
106
+
107
+ manifestFor(id: string): DshAgoraExtensionManifestV1 | null {
108
+ return this.manifests.get(id) ?? null
109
+ }
110
+
111
+ listExtensions(): readonly DshAgoraExtensionV1[] {
112
+ return [...this.extensions.values()].sort((left, right) => left.id.localeCompare(right.id))
113
+ }
114
+
115
+ runtimeForTarget(runtimeTargetRef: string): DshAgoraRuntimeAdapterV1 | null {
116
+ const runtimes = [...this.extensions.values()].filter(extension => (
117
+ extension.runtime && extension.capabilities.includes('runtime.execute')
118
+ ))
119
+ const explicit = runtimes.find(extension => extension.runtime!.supportsTarget?.(runtimeTargetRef) === true)
120
+ if (explicit?.runtime) return explicit.runtime
121
+ if (runtimeTargetRef.startsWith('dsh:')) {
122
+ const builtIn = runtimes.find(extension => extension.id === 'dsh-runtime')
123
+ if (builtIn?.runtime) return builtIn.runtime
124
+ }
125
+ const legacy = runtimes.filter(extension => extension.runtime!.supportsTarget === undefined)
126
+ return legacy.length === 1 ? legacy[0]!.runtime! : null
127
+ }
128
+ }
129
+
130
+ export function verifyExtensionManifest(
131
+ manifest: DshAgoraExtensionManifestV1,
132
+ policy: DshAgoraExtensionTrustPolicy,
133
+ packageBytes?: Uint8Array,
134
+ ): boolean {
135
+ if (packageBytes && createHash('sha256').update(packageBytes).digest('hex') !== manifest.integrity_sha256) {
136
+ throw new Error(`extension manifest integrity mismatch for ${manifest.id}`)
137
+ }
138
+ if (!manifest.signature) {
139
+ if (policy.requireSignedThirdParty) throw new Error(`extension manifest ${manifest.id} is unsigned`)
140
+ return false
141
+ }
142
+ const keyRef = `${manifest.publisher.id}:${manifest.publisher.key_id}`
143
+ const publicKey = policy.trustedPublicKeys?.[keyRef]
144
+ if (!publicKey) throw new Error(`extension publisher key ${keyRef} is not trusted`)
145
+ const valid = verifySignature(
146
+ null,
147
+ Buffer.from(canonicalManifest(manifest)),
148
+ createPublicKey(publicKey),
149
+ Buffer.from(manifest.signature.value, 'base64url'),
150
+ )
151
+ if (!valid) throw new Error(`extension manifest signature is invalid for ${manifest.id}`)
152
+ return true
153
+ }
154
+
155
+ export async function runExtensionConformance(
156
+ extension: DshAgoraExtensionV1,
157
+ manifest?: DshAgoraExtensionManifestV1,
158
+ ): Promise<{ readonly ok: true; readonly checks: readonly string[] }> {
159
+ validateExtension(extension)
160
+ if (manifest) validateExtensionManifest(manifest, extension)
161
+ const checks = ['extension-shape', 'capability-uniqueness']
162
+ if (extension.runtime) {
163
+ const first = normalizeAgents(await extension.runtime.describeAgents())
164
+ const second = normalizeAgents(await extension.runtime.describeAgents())
165
+ if (JSON.stringify(first) !== JSON.stringify(second)) throw new Error('runtime describeAgents must be deterministic')
166
+ if (new Set(first.map(agent => agent.agent_ref)).size !== first.length) throw new Error('runtime describeAgents returned duplicate agent_ref values')
167
+ checks.push('runtime-protocol', 'runtime-deterministic-agents', 'runtime-unique-agent-refs')
168
+ }
169
+ if (manifest) checks.push('manifest-capability-permissions')
170
+ return { ok: true, checks }
171
+ }
172
+
173
+ function validateExtension(extension: DshAgoraExtensionV1): void {
174
+ if (extension.protocol !== DSH_AGORA_EXTENSION_PROTOCOL) {
175
+ throw new TypeError(`extension protocol must be ${DSH_AGORA_EXTENSION_PROTOCOL}`)
176
+ }
177
+ if (!/^[a-z0-9][a-z0-9._-]{0,127}$/u.test(extension.id)) throw new TypeError('extension id is invalid')
178
+ if (!Array.isArray(extension.capabilities) || extension.capabilities.some(value => typeof value !== 'string' || value.trim() === '')) {
179
+ throw new TypeError('extension capabilities must be non-empty strings')
180
+ }
181
+ if (unique(extension.capabilities).length !== extension.capabilities.length) throw new TypeError('extension capabilities must be unique')
182
+ if (extension.runtime !== undefined && extension.runtime.protocol !== DSH_AGORA_RUNTIME_PROTOCOL) {
183
+ throw new TypeError(`runtime adapter protocol must be ${DSH_AGORA_RUNTIME_PROTOCOL}`)
184
+ }
185
+ }
186
+
187
+ function validateExtensionManifest(manifest: DshAgoraExtensionManifestV1, extension: DshAgoraExtensionV1): void {
188
+ if (manifest.protocol !== DSH_AGORA_EXTENSION_MANIFEST_PROTOCOL) throw new TypeError(`manifest protocol must be ${DSH_AGORA_EXTENSION_MANIFEST_PROTOCOL}`)
189
+ if (manifest.id !== extension.id || manifest.kind !== extension.kind) throw new TypeError('manifest identity does not match extension')
190
+ if (!/^\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?$/u.test(manifest.version)) throw new TypeError('manifest version must be semantic')
191
+ if (!/^[a-f0-9]{64}$/u.test(manifest.integrity_sha256)) throw new TypeError('manifest integrity_sha256 is invalid')
192
+ const capabilities = unique(extension.capabilities)
193
+ if (JSON.stringify(unique(manifest.capabilities)) !== JSON.stringify(capabilities)) throw new TypeError('manifest capabilities do not match extension capabilities')
194
+ const granted = new Set(manifest.permissions.map(permission => permission.capability))
195
+ if (granted.size !== manifest.permissions.length) throw new TypeError('manifest permissions contain duplicate capabilities')
196
+ for (const capability of capabilities) if (!granted.has(capability)) throw new TypeError(`manifest permission missing for capability ${capability}`)
197
+ for (const permission of manifest.permissions) {
198
+ if (!capabilities.includes(permission.capability)) throw new TypeError(`manifest permission exceeds declared capability ${permission.capability}`)
199
+ if (permission.resources.length === 0 || permission.resources.some(resource => !resource.trim())) {
200
+ throw new TypeError(`manifest permission ${permission.capability} requires explicit resources`)
201
+ }
202
+ }
203
+ if (!/^[a-z0-9][a-z0-9._-]{0,127}$/u.test(manifest.publisher.id) || !manifest.publisher.key_id.trim()) throw new TypeError('manifest publisher is invalid')
204
+ if (manifest.signature && manifest.signature.algorithm !== 'Ed25519') throw new TypeError('only Ed25519 extension signatures are supported')
205
+ }
206
+
207
+ function canonicalManifest(manifest: DshAgoraExtensionManifestV1): string {
208
+ const { signature: _signature, ...unsigned } = manifest
209
+ return stableJson(unsigned)
210
+ }
211
+
212
+ function stableJson(value: unknown): string {
213
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(',')}]`
214
+ if (value && typeof value === 'object') return `{${Object.entries(value as Record<string, unknown>).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`).join(',')}}`
215
+ return JSON.stringify(value) ?? 'null'
216
+ }
217
+
218
+ function normalizeAgents(agents: readonly RuntimeNodeAgent[]): RuntimeNodeAgent[] {
219
+ return agents.map(agent => ({ ...agent, roles: unique(agent.roles), capabilities: unique(agent.capabilities) }))
220
+ .sort((left, right) => left.agent_ref.localeCompare(right.agent_ref))
221
+ }
222
+
223
+ function unique(values: readonly string[]): string[] {
224
+ return [...new Set(values.map(value => value.trim()).filter(Boolean))].sort()
225
+ }