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
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { createHash, createPublicKey, verify as verifySignature } from 'node:crypto';
|
|
2
|
+
export const DSH_AGORA_EXTENSION_PROTOCOL = 'dsh-agora.extension/v1';
|
|
3
|
+
export const DSH_AGORA_RUNTIME_PROTOCOL = 'dsh-agora.runtime/v1';
|
|
4
|
+
export const DSH_AGORA_EXTENSION_MANIFEST_PROTOCOL = 'dsh-agora.extension-manifest/v1';
|
|
5
|
+
export class DshAgoraExtensionRegistry {
|
|
6
|
+
trustPolicy;
|
|
7
|
+
extensions = new Map();
|
|
8
|
+
manifests = new Map();
|
|
9
|
+
constructor(trustPolicy = {}) {
|
|
10
|
+
this.trustPolicy = trustPolicy;
|
|
11
|
+
}
|
|
12
|
+
registerExtension(extension, manifest, packageBytes) {
|
|
13
|
+
validateExtension(extension);
|
|
14
|
+
const builtIn = new Set(this.trustPolicy.builtInExtensionIds ?? ['dsh-runtime']).has(extension.id);
|
|
15
|
+
if (manifest) {
|
|
16
|
+
validateExtensionManifest(manifest, extension);
|
|
17
|
+
if (this.trustPolicy.requireSignedThirdParty && !builtIn && packageBytes === undefined) {
|
|
18
|
+
throw new Error(`extension "${extension.id}" requires package bytes for integrity verification`);
|
|
19
|
+
}
|
|
20
|
+
verifyExtensionManifest(manifest, this.trustPolicy, packageBytes);
|
|
21
|
+
}
|
|
22
|
+
else if (this.trustPolicy.requireSignedThirdParty && !builtIn) {
|
|
23
|
+
throw new Error(`extension "${extension.id}" requires a signed manifest`);
|
|
24
|
+
}
|
|
25
|
+
if (this.extensions.has(extension.id))
|
|
26
|
+
throw new Error(`dsh-agora extension "${extension.id}" is already registered`);
|
|
27
|
+
const frozen = Object.freeze({
|
|
28
|
+
...extension,
|
|
29
|
+
capabilities: Object.freeze(unique(extension.capabilities)),
|
|
30
|
+
});
|
|
31
|
+
this.extensions.set(frozen.id, frozen);
|
|
32
|
+
if (manifest)
|
|
33
|
+
this.manifests.set(frozen.id, Object.freeze({ ...manifest }));
|
|
34
|
+
return () => {
|
|
35
|
+
if (this.extensions.get(frozen.id) === frozen) {
|
|
36
|
+
this.extensions.delete(frozen.id);
|
|
37
|
+
this.manifests.delete(frozen.id);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
manifestFor(id) {
|
|
42
|
+
return this.manifests.get(id) ?? null;
|
|
43
|
+
}
|
|
44
|
+
listExtensions() {
|
|
45
|
+
return [...this.extensions.values()].sort((left, right) => left.id.localeCompare(right.id));
|
|
46
|
+
}
|
|
47
|
+
runtimeForTarget(runtimeTargetRef) {
|
|
48
|
+
const runtimes = [...this.extensions.values()].filter(extension => (extension.runtime && extension.capabilities.includes('runtime.execute')));
|
|
49
|
+
const explicit = runtimes.find(extension => extension.runtime.supportsTarget?.(runtimeTargetRef) === true);
|
|
50
|
+
if (explicit?.runtime)
|
|
51
|
+
return explicit.runtime;
|
|
52
|
+
if (runtimeTargetRef.startsWith('dsh:')) {
|
|
53
|
+
const builtIn = runtimes.find(extension => extension.id === 'dsh-runtime');
|
|
54
|
+
if (builtIn?.runtime)
|
|
55
|
+
return builtIn.runtime;
|
|
56
|
+
}
|
|
57
|
+
const legacy = runtimes.filter(extension => extension.runtime.supportsTarget === undefined);
|
|
58
|
+
return legacy.length === 1 ? legacy[0].runtime : null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export function verifyExtensionManifest(manifest, policy, packageBytes) {
|
|
62
|
+
if (packageBytes && createHash('sha256').update(packageBytes).digest('hex') !== manifest.integrity_sha256) {
|
|
63
|
+
throw new Error(`extension manifest integrity mismatch for ${manifest.id}`);
|
|
64
|
+
}
|
|
65
|
+
if (!manifest.signature) {
|
|
66
|
+
if (policy.requireSignedThirdParty)
|
|
67
|
+
throw new Error(`extension manifest ${manifest.id} is unsigned`);
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
const keyRef = `${manifest.publisher.id}:${manifest.publisher.key_id}`;
|
|
71
|
+
const publicKey = policy.trustedPublicKeys?.[keyRef];
|
|
72
|
+
if (!publicKey)
|
|
73
|
+
throw new Error(`extension publisher key ${keyRef} is not trusted`);
|
|
74
|
+
const valid = verifySignature(null, Buffer.from(canonicalManifest(manifest)), createPublicKey(publicKey), Buffer.from(manifest.signature.value, 'base64url'));
|
|
75
|
+
if (!valid)
|
|
76
|
+
throw new Error(`extension manifest signature is invalid for ${manifest.id}`);
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
export async function runExtensionConformance(extension, manifest) {
|
|
80
|
+
validateExtension(extension);
|
|
81
|
+
if (manifest)
|
|
82
|
+
validateExtensionManifest(manifest, extension);
|
|
83
|
+
const checks = ['extension-shape', 'capability-uniqueness'];
|
|
84
|
+
if (extension.runtime) {
|
|
85
|
+
const first = normalizeAgents(await extension.runtime.describeAgents());
|
|
86
|
+
const second = normalizeAgents(await extension.runtime.describeAgents());
|
|
87
|
+
if (JSON.stringify(first) !== JSON.stringify(second))
|
|
88
|
+
throw new Error('runtime describeAgents must be deterministic');
|
|
89
|
+
if (new Set(first.map(agent => agent.agent_ref)).size !== first.length)
|
|
90
|
+
throw new Error('runtime describeAgents returned duplicate agent_ref values');
|
|
91
|
+
checks.push('runtime-protocol', 'runtime-deterministic-agents', 'runtime-unique-agent-refs');
|
|
92
|
+
}
|
|
93
|
+
if (manifest)
|
|
94
|
+
checks.push('manifest-capability-permissions');
|
|
95
|
+
return { ok: true, checks };
|
|
96
|
+
}
|
|
97
|
+
function validateExtension(extension) {
|
|
98
|
+
if (extension.protocol !== DSH_AGORA_EXTENSION_PROTOCOL) {
|
|
99
|
+
throw new TypeError(`extension protocol must be ${DSH_AGORA_EXTENSION_PROTOCOL}`);
|
|
100
|
+
}
|
|
101
|
+
if (!/^[a-z0-9][a-z0-9._-]{0,127}$/u.test(extension.id))
|
|
102
|
+
throw new TypeError('extension id is invalid');
|
|
103
|
+
if (!Array.isArray(extension.capabilities) || extension.capabilities.some(value => typeof value !== 'string' || value.trim() === '')) {
|
|
104
|
+
throw new TypeError('extension capabilities must be non-empty strings');
|
|
105
|
+
}
|
|
106
|
+
if (unique(extension.capabilities).length !== extension.capabilities.length)
|
|
107
|
+
throw new TypeError('extension capabilities must be unique');
|
|
108
|
+
if (extension.runtime !== undefined && extension.runtime.protocol !== DSH_AGORA_RUNTIME_PROTOCOL) {
|
|
109
|
+
throw new TypeError(`runtime adapter protocol must be ${DSH_AGORA_RUNTIME_PROTOCOL}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function validateExtensionManifest(manifest, extension) {
|
|
113
|
+
if (manifest.protocol !== DSH_AGORA_EXTENSION_MANIFEST_PROTOCOL)
|
|
114
|
+
throw new TypeError(`manifest protocol must be ${DSH_AGORA_EXTENSION_MANIFEST_PROTOCOL}`);
|
|
115
|
+
if (manifest.id !== extension.id || manifest.kind !== extension.kind)
|
|
116
|
+
throw new TypeError('manifest identity does not match extension');
|
|
117
|
+
if (!/^\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?$/u.test(manifest.version))
|
|
118
|
+
throw new TypeError('manifest version must be semantic');
|
|
119
|
+
if (!/^[a-f0-9]{64}$/u.test(manifest.integrity_sha256))
|
|
120
|
+
throw new TypeError('manifest integrity_sha256 is invalid');
|
|
121
|
+
const capabilities = unique(extension.capabilities);
|
|
122
|
+
if (JSON.stringify(unique(manifest.capabilities)) !== JSON.stringify(capabilities))
|
|
123
|
+
throw new TypeError('manifest capabilities do not match extension capabilities');
|
|
124
|
+
const granted = new Set(manifest.permissions.map(permission => permission.capability));
|
|
125
|
+
if (granted.size !== manifest.permissions.length)
|
|
126
|
+
throw new TypeError('manifest permissions contain duplicate capabilities');
|
|
127
|
+
for (const capability of capabilities)
|
|
128
|
+
if (!granted.has(capability))
|
|
129
|
+
throw new TypeError(`manifest permission missing for capability ${capability}`);
|
|
130
|
+
for (const permission of manifest.permissions) {
|
|
131
|
+
if (!capabilities.includes(permission.capability))
|
|
132
|
+
throw new TypeError(`manifest permission exceeds declared capability ${permission.capability}`);
|
|
133
|
+
if (permission.resources.length === 0 || permission.resources.some(resource => !resource.trim())) {
|
|
134
|
+
throw new TypeError(`manifest permission ${permission.capability} requires explicit resources`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (!/^[a-z0-9][a-z0-9._-]{0,127}$/u.test(manifest.publisher.id) || !manifest.publisher.key_id.trim())
|
|
138
|
+
throw new TypeError('manifest publisher is invalid');
|
|
139
|
+
if (manifest.signature && manifest.signature.algorithm !== 'Ed25519')
|
|
140
|
+
throw new TypeError('only Ed25519 extension signatures are supported');
|
|
141
|
+
}
|
|
142
|
+
function canonicalManifest(manifest) {
|
|
143
|
+
const { signature: _signature, ...unsigned } = manifest;
|
|
144
|
+
return stableJson(unsigned);
|
|
145
|
+
}
|
|
146
|
+
function stableJson(value) {
|
|
147
|
+
if (Array.isArray(value))
|
|
148
|
+
return `[${value.map(stableJson).join(',')}]`;
|
|
149
|
+
if (value && typeof value === 'object')
|
|
150
|
+
return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`).join(',')}}`;
|
|
151
|
+
return JSON.stringify(value) ?? 'null';
|
|
152
|
+
}
|
|
153
|
+
function normalizeAgents(agents) {
|
|
154
|
+
return agents.map(agent => ({ ...agent, roles: unique(agent.roles), capabilities: unique(agent.capabilities) }))
|
|
155
|
+
.sort((left, right) => left.agent_ref.localeCompare(right.agent_ref));
|
|
156
|
+
}
|
|
157
|
+
function unique(values) {
|
|
158
|
+
return [...new Set(values.map(value => value.trim()).filter(Boolean))].sort();
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=extension-sdk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extension-sdk.js","sourceRoot":"","sources":["../src/extension-sdk.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { RuntimeDispatch, RuntimeNodeAgent } from './contracts.js';
|
|
2
|
+
import { type DshAgoraRuntimeAdapterV1, type RuntimeExecutionResult, type RuntimeExecutionContext } from './extension-sdk.js';
|
|
3
|
+
export interface ConfiguredDshAgent {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly displayName?: string;
|
|
6
|
+
readonly preset?: string;
|
|
7
|
+
readonly model?: string;
|
|
8
|
+
readonly workspace?: string;
|
|
9
|
+
readonly workspaceAlias?: string;
|
|
10
|
+
readonly roles?: readonly string[];
|
|
11
|
+
readonly capabilities?: readonly string[];
|
|
12
|
+
}
|
|
13
|
+
export interface HarnessRuntimeOptions {
|
|
14
|
+
readonly baseUrl: string;
|
|
15
|
+
readonly agents: readonly ConfiguredDshAgent[];
|
|
16
|
+
readonly replyTimeoutMs?: number;
|
|
17
|
+
readonly fetch?: typeof globalThis.fetch;
|
|
18
|
+
}
|
|
19
|
+
export declare class HarnessRuntimeAdapter implements DshAgoraRuntimeAdapterV1 {
|
|
20
|
+
readonly protocol: "dsh-agora.runtime/v1";
|
|
21
|
+
private readonly client;
|
|
22
|
+
private readonly agents;
|
|
23
|
+
private readonly replyTimeoutMs;
|
|
24
|
+
constructor(options: HarnessRuntimeOptions);
|
|
25
|
+
describeAgents(): readonly RuntimeNodeAgent[];
|
|
26
|
+
execute(dispatch: RuntimeDispatch, signal: AbortSignal, context?: RuntimeExecutionContext): Promise<RuntimeExecutionResult>;
|
|
27
|
+
cancel(sessionId: string, signal: AbortSignal): Promise<boolean>;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=harness-runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harness-runtime.d.ts","sourceRoot":"","sources":["../src/harness-runtime.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EAIjB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAEL,KAAK,wBAAwB,EAC7B,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,EAC7B,MAAM,oBAAoB,CAAA;AAE3B,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;IAChC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAClC,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAC1C;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,SAAS,kBAAkB,EAAE,CAAA;IAC9C,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;IAChC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAA;CACzC;AAED,qBAAa,qBAAsB,YAAW,wBAAwB;IACpE,QAAQ,CAAC,QAAQ,yBAA6B;IAC9C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA4B;IACnD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAQ;gBAE3B,OAAO,EAAE,qBAAqB;IAM1C,cAAc,IAAI,SAAS,gBAAgB,EAAE;IAYvC,OAAO,CACX,QAAQ,EAAE,eAAe,EACzB,MAAM,EAAE,WAAW,EACnB,OAAO,CAAC,EAAE,uBAAuB,GAChC,OAAO,CAAC,sBAAsB,CAAC;IAwE5B,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;CAIvE"}
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { DSH_AGORA_RUNTIME_PROTOCOL, } from './extension-sdk.js';
|
|
3
|
+
export class HarnessRuntimeAdapter {
|
|
4
|
+
protocol = DSH_AGORA_RUNTIME_PROTOCOL;
|
|
5
|
+
client;
|
|
6
|
+
agents;
|
|
7
|
+
replyTimeoutMs;
|
|
8
|
+
constructor(options) {
|
|
9
|
+
this.client = new HarnessRpcClient(options.baseUrl, options.fetch);
|
|
10
|
+
this.agents = normalizeAgents(options.agents);
|
|
11
|
+
this.replyTimeoutMs = normalizeReplyTimeout(options.replyTimeoutMs);
|
|
12
|
+
}
|
|
13
|
+
describeAgents() {
|
|
14
|
+
return this.agents.map(agent => ({
|
|
15
|
+
agent_ref: agent.id,
|
|
16
|
+
display_name: agent.displayName,
|
|
17
|
+
preset: agent.preset,
|
|
18
|
+
model: agent.model,
|
|
19
|
+
workspace_alias: agent.workspaceAlias,
|
|
20
|
+
roles: agent.roles,
|
|
21
|
+
capabilities: agent.capabilities,
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
async execute(dispatch, signal, context) {
|
|
25
|
+
const startedAt = Date.now();
|
|
26
|
+
const agentRef = dispatch.runtime_target_ref.split(':').at(-1);
|
|
27
|
+
const agent = this.agents.find(item => item.id === agentRef);
|
|
28
|
+
if (!agent)
|
|
29
|
+
throw new Error(`runtime target ${dispatch.runtime_target_ref} is not configured on this DSH node`);
|
|
30
|
+
const sessionId = dispatch.session_id ?? await this.client.createSession({
|
|
31
|
+
workspace: agent.workspace,
|
|
32
|
+
agentPreset: dispatch.agent_preset ?? agent.preset,
|
|
33
|
+
signal,
|
|
34
|
+
});
|
|
35
|
+
try {
|
|
36
|
+
await context?.reportProgress({
|
|
37
|
+
phase: 'session_ready',
|
|
38
|
+
message: dispatch.session_id ? 'Existing DSH Session resumed' : 'New DSH Session created',
|
|
39
|
+
percent: 10,
|
|
40
|
+
details: { session_id: sessionId },
|
|
41
|
+
});
|
|
42
|
+
const result = await this.client.runPrompt(sessionId, formatDispatchPrompt(dispatch), this.replyTimeoutMs, signal, `agora-dispatch-${dispatch.id}`, {
|
|
43
|
+
onPromptAccepted: () => context?.reportProgress({
|
|
44
|
+
phase: 'prompt_accepted',
|
|
45
|
+
message: 'Prompt accepted by DeepSeek Harness',
|
|
46
|
+
percent: 25,
|
|
47
|
+
}),
|
|
48
|
+
onResponseStarted: () => context?.reportProgress({
|
|
49
|
+
phase: 'response_started',
|
|
50
|
+
message: 'Agent started responding',
|
|
51
|
+
percent: 60,
|
|
52
|
+
}),
|
|
53
|
+
});
|
|
54
|
+
await context?.reportProgress({
|
|
55
|
+
phase: 'response_completed',
|
|
56
|
+
message: 'Agent response completed',
|
|
57
|
+
percent: 90,
|
|
58
|
+
});
|
|
59
|
+
const parsed = parseRuntimeResult(result.answer, agent, dispatch);
|
|
60
|
+
return {
|
|
61
|
+
sessionId,
|
|
62
|
+
answer: parsed.answer,
|
|
63
|
+
reason: result.reason,
|
|
64
|
+
metadata: { agent_ref: agent.id, runtime_target_ref: dispatch.runtime_target_ref },
|
|
65
|
+
resultEnvelope: {
|
|
66
|
+
...parsed.envelope,
|
|
67
|
+
usage: {
|
|
68
|
+
input_tokens: parsed.envelope.usage?.input_tokens ?? null,
|
|
69
|
+
output_tokens: parsed.envelope.usage?.output_tokens ?? null,
|
|
70
|
+
total_tokens: parsed.envelope.usage?.total_tokens ?? null,
|
|
71
|
+
tool_calls: parsed.envelope.usage?.tool_calls ?? null,
|
|
72
|
+
cost_usd: parsed.envelope.usage?.cost_usd ?? null,
|
|
73
|
+
duration_ms: Date.now() - startedAt,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (signal.aborted) {
|
|
80
|
+
try {
|
|
81
|
+
await this.cancel(sessionId, AbortSignal.timeout(30_000));
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// Preserve the original lease-loss or shutdown error. The central
|
|
85
|
+
// fencing token still prevents this abandoned execution from writing.
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async cancel(sessionId, signal) {
|
|
92
|
+
await this.client.rpc('session.cancel', { sessionId, keepInbox: true }, 30_000, signal);
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
class HarnessRpcClient {
|
|
97
|
+
origin;
|
|
98
|
+
fetchImpl;
|
|
99
|
+
constructor(baseUrl, fetchImpl = globalThis.fetch) {
|
|
100
|
+
this.origin = new URL(baseUrl);
|
|
101
|
+
this.fetchImpl = fetchImpl;
|
|
102
|
+
}
|
|
103
|
+
async rpc(method, payload, timeoutMs, signal, rpcId) {
|
|
104
|
+
const requestId = rpcId ?? `agora-${randomUUID()}`;
|
|
105
|
+
const timeout = AbortSignal.timeout(timeoutMs);
|
|
106
|
+
const combined = signal === undefined ? timeout : AbortSignal.any([signal, timeout]);
|
|
107
|
+
const response = await this.fetchImpl(new URL(`/api/${method}`, this.origin), {
|
|
108
|
+
method: 'POST',
|
|
109
|
+
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
|
110
|
+
body: JSON.stringify({ type: 'client-request', rpcId: requestId, method, payload }),
|
|
111
|
+
signal: combined,
|
|
112
|
+
});
|
|
113
|
+
if (!response.ok)
|
|
114
|
+
throw new Error(`DSH ${method} returned HTTP ${response.status}`);
|
|
115
|
+
const body = await response.json();
|
|
116
|
+
if (body.type !== 'server-response' || body.rpcId !== requestId || typeof body.result?.ok !== 'boolean') {
|
|
117
|
+
throw new Error(`DSH ${method} returned an invalid RPC envelope`);
|
|
118
|
+
}
|
|
119
|
+
if (!body.result.ok) {
|
|
120
|
+
const error = new Error(body.result.error?.message ?? `DSH ${method} failed`);
|
|
121
|
+
error.name = body.result.error?.code ?? 'HarnessRpcError';
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
return body.result.value;
|
|
125
|
+
}
|
|
126
|
+
async createSession(options) {
|
|
127
|
+
const workspaces = await this.rpc('workspace.list', {}, 30_000, options.signal);
|
|
128
|
+
let workspaceId = workspaces.items?.find(item => item.path === options.workspace)?.workspaceId;
|
|
129
|
+
if (!workspaceId) {
|
|
130
|
+
const created = await this.rpc('workspace.create', { path: options.workspace }, 30_000, options.signal);
|
|
131
|
+
workspaceId = created.workspace?.workspaceId;
|
|
132
|
+
}
|
|
133
|
+
if (!workspaceId)
|
|
134
|
+
throw new Error(`DSH could not resolve workspace ${options.workspace}`);
|
|
135
|
+
const created = await this.rpc('session.create', { workspaceId, ...(options.agentPreset ? { agentPreset: options.agentPreset } : {}) }, 30_000, options.signal);
|
|
136
|
+
if (!created.sessionId)
|
|
137
|
+
throw new Error('DSH session.create returned no sessionId');
|
|
138
|
+
return created.sessionId;
|
|
139
|
+
}
|
|
140
|
+
async runPrompt(sessionId, prompt, timeoutMs, signal, promptRpcId = `agora-dispatch-${randomUUID()}`, callbacks = {}) {
|
|
141
|
+
const timeout = AbortSignal.timeout(timeoutMs);
|
|
142
|
+
const combined = AbortSignal.any([signal, timeout]);
|
|
143
|
+
const initial = await this.rpc('session.history', { sessionId, maxMessages: 50 }, 30_000, combined);
|
|
144
|
+
const tracker = new ReplyTracker(maxSeq(initial.events ?? []));
|
|
145
|
+
tracker.promptRpcId = promptRpcId;
|
|
146
|
+
await this.rpc('session.prompt', {
|
|
147
|
+
sessionId,
|
|
148
|
+
mode: 'queue',
|
|
149
|
+
content: [{ type: 'text', text: prompt }],
|
|
150
|
+
clientTimeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
151
|
+
}, 30_000, combined, promptRpcId);
|
|
152
|
+
await callbacks.onPromptAccepted?.();
|
|
153
|
+
let responseStarted = false;
|
|
154
|
+
while (!tracker.finished) {
|
|
155
|
+
await sleep(350, combined);
|
|
156
|
+
const history = await this.rpc('session.history', { sessionId, maxMessages: 50 }, 30_000, combined);
|
|
157
|
+
tracker.consume(history.events ?? []);
|
|
158
|
+
if (!responseStarted && tracker.answer !== '') {
|
|
159
|
+
responseStarted = true;
|
|
160
|
+
await callbacks.onResponseStarted?.();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return { answer: tracker.answer, reason: tracker.reason };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
class ReplyTracker {
|
|
167
|
+
promptRpcId = '';
|
|
168
|
+
lastSeq;
|
|
169
|
+
openTurn = null;
|
|
170
|
+
targetTurn = null;
|
|
171
|
+
latestText = '';
|
|
172
|
+
chunks = new Map();
|
|
173
|
+
finished = false;
|
|
174
|
+
reason = null;
|
|
175
|
+
constructor(afterSeq) {
|
|
176
|
+
this.lastSeq = afterSeq;
|
|
177
|
+
}
|
|
178
|
+
get answer() {
|
|
179
|
+
return this.latestText.trim();
|
|
180
|
+
}
|
|
181
|
+
consume(entries) {
|
|
182
|
+
const events = entries
|
|
183
|
+
.map(entry => isRecord(entry) && isRecord(entry.event) ? entry.event : entry)
|
|
184
|
+
.filter(isRecord)
|
|
185
|
+
.sort((left, right) => numberValue(left.seq, -1) - numberValue(right.seq, -1));
|
|
186
|
+
for (const event of events) {
|
|
187
|
+
const seq = numberValue(event.seq, -1);
|
|
188
|
+
if (seq <= this.lastSeq)
|
|
189
|
+
continue;
|
|
190
|
+
this.lastSeq = seq;
|
|
191
|
+
const data = isRecord(event.data) ? event.data : {};
|
|
192
|
+
if (event.type === 'turn/start')
|
|
193
|
+
this.openTurn = data.turn ?? null;
|
|
194
|
+
if (event.type === 'user/message') {
|
|
195
|
+
const source = isRecord(data.source) ? data.source : {};
|
|
196
|
+
if (source.rpcId === this.promptRpcId)
|
|
197
|
+
this.targetTurn = this.openTurn;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (this.targetTurn === null)
|
|
201
|
+
continue;
|
|
202
|
+
if (event.type === 'turn/end' && data.turn === this.targetTurn) {
|
|
203
|
+
this.finished = true;
|
|
204
|
+
this.reason = typeof data.reason === 'string' ? data.reason : null;
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (data.turn !== this.targetTurn)
|
|
208
|
+
continue;
|
|
209
|
+
if (event.type === 'assistant/message') {
|
|
210
|
+
const message = isRecord(data.message) ? data.message : {};
|
|
211
|
+
const content = Array.isArray(message.content) ? message.content : [];
|
|
212
|
+
const text = content.filter(isRecord)
|
|
213
|
+
.filter(part => part.type === 'text' && typeof part.text === 'string')
|
|
214
|
+
.map(part => String(part.text)).join('\n').trim();
|
|
215
|
+
if (text)
|
|
216
|
+
this.latestText = text;
|
|
217
|
+
}
|
|
218
|
+
if (event.type === 'assistant/chunk') {
|
|
219
|
+
const chunk = isRecord(data.chunk) ? data.chunk : {};
|
|
220
|
+
if (chunk.type !== 'text-delta' || typeof chunk.text !== 'string')
|
|
221
|
+
continue;
|
|
222
|
+
const key = `${numberValue(data.step, 0)}:${numberValue(chunk.index, 0)}`;
|
|
223
|
+
this.chunks.set(key, (this.chunks.get(key) ?? '') + chunk.text);
|
|
224
|
+
const step = `${numberValue(data.step, 0)}:`;
|
|
225
|
+
const text = [...this.chunks.entries()]
|
|
226
|
+
.filter(([part]) => part.startsWith(step))
|
|
227
|
+
.sort(([left], [right]) => Number(left.split(':')[1]) - Number(right.split(':')[1]))
|
|
228
|
+
.map(([, value]) => value).join('\n').trim();
|
|
229
|
+
if (text)
|
|
230
|
+
this.latestText = text;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function normalizeAgents(agents) {
|
|
236
|
+
if (agents.length === 0)
|
|
237
|
+
throw new TypeError('at least one DSH runtime agent must be configured');
|
|
238
|
+
const seen = new Set();
|
|
239
|
+
return agents.map(agent => {
|
|
240
|
+
const id = required(agent.id, 'runtime agent id');
|
|
241
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/u.test(id))
|
|
242
|
+
throw new TypeError(`invalid runtime agent id "${id}"`);
|
|
243
|
+
if (seen.has(id))
|
|
244
|
+
throw new TypeError(`duplicate runtime agent id "${id}"`);
|
|
245
|
+
seen.add(id);
|
|
246
|
+
return Object.freeze({
|
|
247
|
+
id,
|
|
248
|
+
displayName: optional(agent.displayName),
|
|
249
|
+
preset: optional(agent.preset),
|
|
250
|
+
model: optional(agent.model),
|
|
251
|
+
workspace: optional(agent.workspace) ?? process.cwd(),
|
|
252
|
+
workspaceAlias: optional(agent.workspaceAlias),
|
|
253
|
+
roles: unique(agent.roles ?? []),
|
|
254
|
+
capabilities: unique(agent.capabilities ?? ['session.create', 'session.resume', 'session.prompt', 'session.cancel']),
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
function formatDispatchPrompt(dispatch) {
|
|
259
|
+
return [
|
|
260
|
+
'[Agora cross-agent dispatch]',
|
|
261
|
+
...(dispatch.task_id ? [`Task: ${dispatch.task_id}`] : []),
|
|
262
|
+
...(dispatch.participant_binding_id ? [`Participant binding: ${dispatch.participant_binding_id}`] : []),
|
|
263
|
+
`Dispatch: ${dispatch.id}`,
|
|
264
|
+
'',
|
|
265
|
+
dispatch.prompt,
|
|
266
|
+
'',
|
|
267
|
+
'Return a concise final result suitable for the requesting agent. Do not approve or reject human governance gates.',
|
|
268
|
+
'For verifiable claims, append one machine-readable block after the answer:',
|
|
269
|
+
'<agora-evidence>{"claims":[{"id":"claim-1","statement":"...","evidence_ids":["evidence-1"],"confidence":0.9}],"evidence":[{"id":"evidence-1","kind":"file|url|commit|measurement|log|command|other","uri":"...","revision":"..."}],"confidence":0.9,"revision":"workspace commit if known"}</agora-evidence>',
|
|
270
|
+
'Use only evidence you actually observed. Omit unknown fields and do not put the evidence block inside Markdown fences.',
|
|
271
|
+
].join('\n');
|
|
272
|
+
}
|
|
273
|
+
function parseRuntimeResult(rawAnswer, agent, dispatch) {
|
|
274
|
+
const match = /<agora-evidence>([\s\S]*?)<\/agora-evidence>/u.exec(rawAnswer);
|
|
275
|
+
const answer = (match ? rawAnswer.replace(match[0], '') : rawAnswer).trim();
|
|
276
|
+
const payload = match ? parseJsonRecord(match[1] ?? '') : null;
|
|
277
|
+
const evidence = parseEvidence(payload?.evidence);
|
|
278
|
+
const evidenceIds = new Set(evidence.map(item => item.id));
|
|
279
|
+
const claims = parseClaims(payload?.claims, evidenceIds);
|
|
280
|
+
const confidence = confidenceValue(payload?.confidence);
|
|
281
|
+
const revision = stringValue(payload?.revision);
|
|
282
|
+
return {
|
|
283
|
+
answer,
|
|
284
|
+
envelope: {
|
|
285
|
+
schema: 'agora.runtime-result/v1',
|
|
286
|
+
answer,
|
|
287
|
+
claims,
|
|
288
|
+
evidence,
|
|
289
|
+
...(confidence === null ? {} : { confidence }),
|
|
290
|
+
environment: {
|
|
291
|
+
runtime_provider: 'dsh',
|
|
292
|
+
agent_ref: agent.id,
|
|
293
|
+
model: agent.model,
|
|
294
|
+
workspace_alias: dispatch.workspace_alias ?? agent.workspaceAlias,
|
|
295
|
+
...(revision === null ? {} : { revision }),
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
const evidenceKinds = new Set([
|
|
301
|
+
'file', 'url', 'commit', 'measurement', 'log', 'command', 'other',
|
|
302
|
+
]);
|
|
303
|
+
function parseEvidence(value) {
|
|
304
|
+
if (!Array.isArray(value))
|
|
305
|
+
return [];
|
|
306
|
+
const seen = new Set();
|
|
307
|
+
const parsed = [];
|
|
308
|
+
for (const item of value) {
|
|
309
|
+
if (!isRecord(item))
|
|
310
|
+
continue;
|
|
311
|
+
const id = stringValue(item.id);
|
|
312
|
+
const kind = stringValue(item.kind);
|
|
313
|
+
if (!id || !kind || !evidenceKinds.has(kind) || seen.has(id))
|
|
314
|
+
continue;
|
|
315
|
+
seen.add(id);
|
|
316
|
+
const metadata = isRecord(item.metadata) ? item.metadata : null;
|
|
317
|
+
parsed.push({
|
|
318
|
+
id,
|
|
319
|
+
kind,
|
|
320
|
+
...(stringValue(item.label) === null ? {} : { label: stringValue(item.label) }),
|
|
321
|
+
...(stringValue(item.uri) === null ? {} : { uri: stringValue(item.uri) }),
|
|
322
|
+
...(stringValue(item.content_hash) === null ? {} : { content_hash: stringValue(item.content_hash) }),
|
|
323
|
+
...(stringValue(item.revision) === null ? {} : { revision: stringValue(item.revision) }),
|
|
324
|
+
...(positiveInteger(item.line_start) === null ? {} : { line_start: positiveInteger(item.line_start) }),
|
|
325
|
+
...(positiveInteger(item.line_end) === null ? {} : { line_end: positiveInteger(item.line_end) }),
|
|
326
|
+
...(metadata === null ? {} : { metadata }),
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
return parsed;
|
|
330
|
+
}
|
|
331
|
+
function parseClaims(value, evidenceIds) {
|
|
332
|
+
if (!Array.isArray(value))
|
|
333
|
+
return [];
|
|
334
|
+
const seen = new Set();
|
|
335
|
+
const parsed = [];
|
|
336
|
+
for (const item of value) {
|
|
337
|
+
if (!isRecord(item))
|
|
338
|
+
continue;
|
|
339
|
+
const id = stringValue(item.id);
|
|
340
|
+
const statement = stringValue(item.statement);
|
|
341
|
+
if (!id || !statement || seen.has(id))
|
|
342
|
+
continue;
|
|
343
|
+
seen.add(id);
|
|
344
|
+
const evidence_ids = Array.isArray(item.evidence_ids)
|
|
345
|
+
? [...new Set(item.evidence_ids.filter((candidate) => (typeof candidate === 'string' && evidenceIds.has(candidate))))]
|
|
346
|
+
: [];
|
|
347
|
+
const confidence = confidenceValue(item.confidence);
|
|
348
|
+
parsed.push({
|
|
349
|
+
id,
|
|
350
|
+
statement,
|
|
351
|
+
evidence_ids,
|
|
352
|
+
...(confidence === null ? {} : { confidence }),
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
return parsed;
|
|
356
|
+
}
|
|
357
|
+
function parseJsonRecord(value) {
|
|
358
|
+
try {
|
|
359
|
+
const parsed = JSON.parse(value);
|
|
360
|
+
return isRecord(parsed) ? parsed : null;
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function confidenceValue(value) {
|
|
367
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 1 ? value : null;
|
|
368
|
+
}
|
|
369
|
+
function positiveInteger(value) {
|
|
370
|
+
return typeof value === 'number' && Number.isInteger(value) && value > 0 ? value : null;
|
|
371
|
+
}
|
|
372
|
+
function maxSeq(entries) {
|
|
373
|
+
return entries.reduce((maximum, entry) => {
|
|
374
|
+
const event = isRecord(entry) && isRecord(entry.event) ? entry.event : entry;
|
|
375
|
+
return isRecord(event) ? Math.max(maximum, numberValue(event.seq, -1)) : maximum;
|
|
376
|
+
}, -1);
|
|
377
|
+
}
|
|
378
|
+
function sleep(ms, signal) {
|
|
379
|
+
return new Promise((resolve, reject) => {
|
|
380
|
+
if (signal.aborted)
|
|
381
|
+
return reject(signal.reason);
|
|
382
|
+
const timer = setTimeout(() => {
|
|
383
|
+
signal.removeEventListener('abort', onAbort);
|
|
384
|
+
resolve();
|
|
385
|
+
}, ms);
|
|
386
|
+
const onAbort = () => {
|
|
387
|
+
clearTimeout(timer);
|
|
388
|
+
reject(signal.reason);
|
|
389
|
+
};
|
|
390
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
function normalizeReplyTimeout(value) {
|
|
394
|
+
const timeout = value ?? 600_000;
|
|
395
|
+
if (!Number.isSafeInteger(timeout) || timeout < 10_000 || timeout > 3_600_000) {
|
|
396
|
+
throw new TypeError('runtimeReplyTimeoutMs must be an integer between 10000 and 3600000');
|
|
397
|
+
}
|
|
398
|
+
return timeout;
|
|
399
|
+
}
|
|
400
|
+
function required(value, label) {
|
|
401
|
+
const normalized = optional(value);
|
|
402
|
+
if (!normalized)
|
|
403
|
+
throw new TypeError(`${label} is required`);
|
|
404
|
+
return normalized;
|
|
405
|
+
}
|
|
406
|
+
function optional(value) {
|
|
407
|
+
const normalized = value?.trim();
|
|
408
|
+
return normalized ? normalized : null;
|
|
409
|
+
}
|
|
410
|
+
function unique(values) {
|
|
411
|
+
return [...new Set(values.map(value => value.trim()).filter(Boolean))].sort();
|
|
412
|
+
}
|
|
413
|
+
function isRecord(value) {
|
|
414
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
415
|
+
}
|
|
416
|
+
function numberValue(value, fallback) {
|
|
417
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
418
|
+
}
|
|
419
|
+
function stringValue(value) {
|
|
420
|
+
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
421
|
+
}
|
|
422
|
+
//# sourceMappingURL=harness-runtime.js.map
|