openclaw-openagent 1.0.1 → 1.0.2
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/dist/index.d.ts +25 -0
- package/dist/index.js +105 -0
- package/dist/src/app/channel-tools.d.ts +28 -0
- package/dist/src/app/channel-tools.js +251 -0
- package/dist/src/app/discovery-tools.d.ts +35 -0
- package/dist/src/app/discovery-tools.js +243 -0
- package/dist/src/app/download-file-tool.d.ts +10 -0
- package/dist/src/app/download-file-tool.js +104 -0
- package/dist/src/app/hooks.d.ts +14 -0
- package/dist/src/app/hooks.js +118 -0
- package/dist/src/app/index.d.ts +13 -0
- package/dist/src/app/index.js +44 -0
- package/dist/src/app/messaging-tools.d.ts +11 -0
- package/dist/src/app/messaging-tools.js +79 -0
- package/dist/src/app/ops-tools.d.ts +21 -0
- package/dist/src/app/ops-tools.js +158 -0
- package/dist/src/app/remote-agent-tool.d.ts +27 -0
- package/dist/src/app/remote-agent-tool.js +461 -0
- package/dist/src/app/types.d.ts +61 -0
- package/dist/src/app/types.js +11 -0
- package/dist/src/app/upload-file-tool.d.ts +16 -0
- package/dist/src/app/upload-file-tool.js +353 -0
- package/dist/src/app/verbose-preflight.d.ts +2 -0
- package/dist/src/app/verbose-preflight.js +145 -0
- package/dist/src/auth/config.d.ts +79 -0
- package/dist/src/auth/config.js +133 -0
- package/dist/src/auth/credential-manager.d.ts +65 -0
- package/dist/src/auth/credential-manager.js +122 -0
- package/dist/src/auth/index.d.ts +6 -0
- package/dist/src/auth/index.js +6 -0
- package/dist/src/auth/verify.d.ts +42 -0
- package/dist/src/auth/verify.js +60 -0
- package/dist/src/channel.d.ts +269 -0
- package/dist/src/channel.js +488 -0
- package/dist/src/compat.d.ts +37 -0
- package/dist/src/compat.js +70 -0
- package/dist/src/config/config-schema.d.ts +56 -0
- package/dist/src/config/config-schema.js +34 -0
- package/dist/src/messaging/aggregator.d.ts +25 -0
- package/dist/src/messaging/aggregator.js +90 -0
- package/dist/src/messaging/collector.d.ts +27 -0
- package/dist/src/messaging/collector.js +76 -0
- package/dist/src/messaging/executor.d.ts +14 -0
- package/dist/src/messaging/executor.js +59 -0
- package/dist/src/messaging/inbound.d.ts +97 -0
- package/dist/src/messaging/inbound.js +63 -0
- package/dist/src/messaging/index.d.ts +10 -0
- package/dist/src/messaging/index.js +9 -0
- package/dist/src/messaging/mention-protocol.d.ts +42 -0
- package/dist/src/messaging/mention-protocol.js +74 -0
- package/dist/src/messaging/process-c2c-request.d.ts +55 -0
- package/dist/src/messaging/process-c2c-request.js +445 -0
- package/dist/src/messaging/process-message.d.ts +62 -0
- package/dist/src/messaging/process-message.js +282 -0
- package/dist/src/messaging/scheduler.d.ts +17 -0
- package/dist/src/messaging/scheduler.js +47 -0
- package/dist/src/messaging/types.d.ts +34 -0
- package/dist/src/messaging/types.js +4 -0
- package/dist/src/plugin-ui/assets/openagent-override.js +9267 -0
- package/dist/src/plugin-ui/index.d.ts +13 -0
- package/dist/src/plugin-ui/index.js +16 -0
- package/dist/src/plugin-ui/ui-extension-loader/backup.d.ts +23 -0
- package/dist/src/plugin-ui/ui-extension-loader/backup.js +82 -0
- package/dist/src/plugin-ui/ui-extension-loader/index.d.ts +28 -0
- package/dist/src/plugin-ui/ui-extension-loader/index.js +240 -0
- package/dist/src/plugin-ui/ui-extension-loader/locator.d.ts +35 -0
- package/dist/src/plugin-ui/ui-extension-loader/locator.js +129 -0
- package/dist/src/plugin-ui/ui-extension-loader/manifest.d.ts +26 -0
- package/dist/src/plugin-ui/ui-extension-loader/manifest.js +45 -0
- package/dist/src/plugin-ui/ui-extension-loader/registry-regex.d.ts +9 -0
- package/dist/src/plugin-ui/ui-extension-loader/registry-regex.js +701 -0
- package/dist/src/plugin-ui/ui-extension-loader/removed-extensions.d.ts +8 -0
- package/dist/src/plugin-ui/ui-extension-loader/removed-extensions.js +58 -0
- package/dist/src/plugin-ui/ui-extension-loader/types.d.ts +55 -0
- package/dist/src/plugin-ui/ui-extension-loader/types.js +23 -0
- package/dist/src/proxy/auth-proxy.d.ts +20 -0
- package/dist/src/proxy/auth-proxy.js +337 -0
- package/dist/src/runtime/account.d.ts +156 -0
- package/dist/src/runtime/account.js +491 -0
- package/dist/src/runtime/index.d.ts +5 -0
- package/dist/src/runtime/index.js +6 -0
- package/dist/src/runtime/plugin-runtime.d.ts +40 -0
- package/dist/src/runtime/plugin-runtime.js +72 -0
- package/dist/src/runtime/registry.d.ts +41 -0
- package/dist/src/runtime/registry.js +60 -0
- package/dist/src/sdk/CLASS_MAP.md +143 -0
- package/dist/src/sdk/index.d.ts +126 -0
- package/dist/src/sdk/index.js +23990 -0
- package/dist/src/sdk/modules/cloud-search-module.js +1117 -0
- package/dist/src/sdk/modules/follow-module.js +1069 -0
- package/dist/src/sdk/modules/group-module.js +7397 -0
- package/dist/src/sdk/modules/relationship-module.js +2269 -0
- package/dist/src/sdk/modules/signaling-module.js +1468 -0
- package/dist/src/sdk/modules/tim-upload-plugin.js +730 -0
- package/dist/src/sdk/node-env/http-request.js +90 -0
- package/dist/src/sdk/node-env/index.js +57 -0
- package/dist/src/sdk/node-env/storage.js +114 -0
- package/dist/src/sdk/package.json +10 -0
- package/dist/src/sdk/sdk/CLASS_MAP.md +143 -0
- package/dist/src/sdk/sdk/index.d.ts +126 -0
- package/dist/src/sdk/sdk/index.js +23990 -0
- package/dist/src/sdk/sdk/modules/cloud-search-module.js +1117 -0
- package/dist/src/sdk/sdk/modules/follow-module.js +1069 -0
- package/dist/src/sdk/sdk/modules/group-module.js +7397 -0
- package/dist/src/sdk/sdk/modules/relationship-module.js +2269 -0
- package/dist/src/sdk/sdk/modules/signaling-module.js +1468 -0
- package/dist/src/sdk/sdk/modules/tim-upload-plugin.js +730 -0
- package/dist/src/sdk/sdk/node-env/http-request.js +90 -0
- package/dist/src/sdk/sdk/node-env/index.js +57 -0
- package/dist/src/sdk/sdk/node-env/storage.js +114 -0
- package/dist/src/sdk/sdk/package.json +10 -0
- package/dist/src/sdk/sdk/tsconfig.json +16 -0
- package/dist/src/sdk/tsconfig.json +16 -0
- package/dist/src/state/pending-invocation-store.d.ts +26 -0
- package/dist/src/state/pending-invocation-store.js +39 -0
- package/dist/src/state/store.d.ts +165 -0
- package/dist/src/state/store.js +535 -0
- package/dist/src/tim/c2c.d.ts +83 -0
- package/dist/src/tim/c2c.js +318 -0
- package/dist/src/tim/channels.d.ts +95 -0
- package/dist/src/tim/channels.js +279 -0
- package/dist/src/tim/client.d.ts +54 -0
- package/dist/src/tim/client.js +268 -0
- package/dist/src/tim/index.d.ts +6 -0
- package/dist/src/tim/index.js +6 -0
- package/dist/src/tim/messages.d.ts +50 -0
- package/dist/src/tim/messages.js +104 -0
- package/dist/src/tim/sdk-logger-init.d.ts +13 -0
- package/dist/src/tim/sdk-logger-init.js +46 -0
- package/dist/src/tools.d.ts +9 -0
- package/dist/src/tools.js +8 -0
- package/dist/src/transport/factory.d.ts +63 -0
- package/dist/src/transport/factory.js +54 -0
- package/dist/src/transport/oasn/index.d.ts +12 -0
- package/dist/src/transport/oasn/index.js +9 -0
- package/dist/src/transport/oasn/oasn-agent-card.d.ts +38 -0
- package/dist/src/transport/oasn/oasn-agent-card.js +102 -0
- package/dist/src/transport/oasn/oasn-discovery.d.ts +33 -0
- package/dist/src/transport/oasn/oasn-discovery.js +97 -0
- package/dist/src/transport/oasn/oasn-files.d.ts +64 -0
- package/dist/src/transport/oasn/oasn-files.js +174 -0
- package/dist/src/transport/oasn/oasn-http.d.ts +98 -0
- package/dist/src/transport/oasn/oasn-http.js +362 -0
- package/dist/src/transport/oasn/oasn-invocation.d.ts +154 -0
- package/dist/src/transport/oasn/oasn-invocation.js +432 -0
- package/dist/src/transport/oasn/oasn-normalize.d.ts +6 -0
- package/dist/src/transport/oasn/oasn-normalize.js +112 -0
- package/dist/src/transport/oasn/oasn-register.d.ts +19 -0
- package/dist/src/transport/oasn/oasn-register.js +24 -0
- package/dist/src/transport/oasn/oasn-transport.d.ts +114 -0
- package/dist/src/transport/oasn/oasn-transport.js +230 -0
- package/dist/src/transport/oasn/oasn-types.d.ts +331 -0
- package/dist/src/transport/oasn/oasn-types.js +44 -0
- package/dist/src/transport/tim/index.d.ts +7 -0
- package/dist/src/transport/tim/index.js +6 -0
- package/dist/src/transport/tim/tim-transport.d.ts +122 -0
- package/dist/src/transport/tim/tim-transport.js +402 -0
- package/dist/src/transport/types.d.ts +450 -0
- package/dist/src/transport/types.js +38 -0
- package/dist/src/util/http.d.ts +21 -0
- package/dist/src/util/http.js +93 -0
- package/dist/src/util/logger.d.ts +20 -0
- package/dist/src/util/logger.js +100 -0
- package/dist/src/util/url-resolver.d.ts +7 -0
- package/dist/src/util/url-resolver.js +20 -0
- package/index.ts +11 -0
- package/openclaw.plugin.json +9 -0
- package/package.json +7 -4
- package/src/app/download-file-tool.ts +133 -0
- package/src/app/hooks.ts +89 -5
- package/src/app/index.ts +6 -0
- package/src/app/remote-agent-tool.ts +46 -0
- package/src/app/types.ts +1 -0
- package/src/app/upload-file-tool.ts +411 -0
- package/src/plugin-ui/assets/openagent-override.js +1 -63
- package/src/plugin-ui/modules/agent-book/panel/agent-book.js +0 -61
- package/src/plugin-ui/modules/agent-book/panel/agent-data.js +0 -1
- package/src/plugin-ui/ui-extension-loader/registry-regex.ts +30 -13
- package/src/runtime/account.ts +0 -23
- package/src/transport/oasn/oasn-files.ts +5 -5
- package/src/transport/oasn/oasn-invocation.ts +44 -2
- package/src/transport/oasn/oasn-normalize.ts +0 -26
- package/src/transport/oasn/oasn-register.ts +8 -81
- package/src/transport/oasn/oasn-transport.ts +9 -32
- package/src/transport/oasn/oasn-types.ts +53 -6
- package/src/transport/types.ts +33 -36
- package/src/util/url-resolver.ts +17 -0
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OASN Invocation —— 任务调用 + 轮询订阅 ⭐
|
|
3
|
+
*
|
|
4
|
+
* 设计依据:
|
|
5
|
+
* - docs/specs/2026-06-16-oasn-api-module-mapping.md §2.14 / §2.15
|
|
6
|
+
* - docs/specs/2026-06-16-oasn-transport-abstraction-design.md
|
|
7
|
+
* §11.1(idempotency_key 持久化)
|
|
8
|
+
* §11.2(abort 与 cancel 缺失)
|
|
9
|
+
* §11.3(timeoutMs ↔ effective_timeout_ms 协调)
|
|
10
|
+
* §11.8(subscribeToInvocation 抽象 + SSE 升级)
|
|
11
|
+
*
|
|
12
|
+
* 子模块包含 3 个核心能力:
|
|
13
|
+
*
|
|
14
|
+
* 1. createInvocation() POST /api/invocations
|
|
15
|
+
* - 维护 idempotency_key(接受外部传入,便于 §11.1 持久化复用)
|
|
16
|
+
* - 返回 invocation_id / session_id / effective_timeout_ms / next_poll_after_ms
|
|
17
|
+
*
|
|
18
|
+
* 2. subscribeToInvocation(id, opts)
|
|
19
|
+
* - 默认实现 = 轮询循环
|
|
20
|
+
* - 上层 waitForTaskResult 只调用此抽象,对推送/轮询无感知
|
|
21
|
+
* - 后续可在此处理 SSE 升级(探测 Accept: text/event-stream),保持外部 API 不变
|
|
22
|
+
*
|
|
23
|
+
* 3. waitForTaskResult(handle, opts)
|
|
24
|
+
* - 包装 subscribeToInvocation,转换为 TaskResult
|
|
25
|
+
* - 终态时拉取 artifacts 列表(不下载内容,仅引用)
|
|
26
|
+
* - 协调本地 timeoutMs 与服务端 effective_timeout_ms(§11.3)
|
|
27
|
+
* - signal.aborted → 标记 abandoned(§11.2,不调用 cancel API 因为不存在)
|
|
28
|
+
*/
|
|
29
|
+
import { randomUUID } from 'node:crypto';
|
|
30
|
+
import { logger } from '../../util/logger.js';
|
|
31
|
+
import { makeTransportError, } from '../types.js';
|
|
32
|
+
import { isInvocationTerminal, OASN_ENDPOINTS, } from './oasn-types.js';
|
|
33
|
+
/** 轮询常量 —— 服务端 next_poll_after_ms 的下限保护,避免风暴 */
|
|
34
|
+
const MIN_POLL_INTERVAL_MS = 500;
|
|
35
|
+
/** 轮询间隔上限(防止服务端返回异常大值) */
|
|
36
|
+
const MAX_POLL_INTERVAL_MS = 30_000;
|
|
37
|
+
/** 当 next_poll_after_ms 缺失时使用的兜底值 */
|
|
38
|
+
const FALLBACK_POLL_INTERVAL_MS = 2_000;
|
|
39
|
+
export class OasnInvocation {
|
|
40
|
+
_http;
|
|
41
|
+
_pendingStore;
|
|
42
|
+
/** invocation_id → effective_timeout_ms(waitForTaskResult §11.3 用) */
|
|
43
|
+
_effectiveTimeoutMap = new Map();
|
|
44
|
+
/** invocation_id → 首轮 next_poll_after_ms */
|
|
45
|
+
_initialPollMap = new Map();
|
|
46
|
+
constructor(_http, _pendingStore) {
|
|
47
|
+
this._http = _http;
|
|
48
|
+
this._pendingStore = _pendingStore;
|
|
49
|
+
}
|
|
50
|
+
// ═══════════════════════════════════════════════════════════════
|
|
51
|
+
// sendTask(POST /api/invocations)
|
|
52
|
+
// ═══════════════════════════════════════════════════════════════
|
|
53
|
+
/**
|
|
54
|
+
* 创建 Invocation。
|
|
55
|
+
*
|
|
56
|
+
* 返回 TaskHandle —— 含 invocation_id / session_id / effective_timeout_ms / next_poll_after_ms。
|
|
57
|
+
* 上层将此 handle 传给 waitForTaskResult。
|
|
58
|
+
*/
|
|
59
|
+
async sendTask(targetAgentId, task, options) {
|
|
60
|
+
// §11.1:尝试复用 pending 记录
|
|
61
|
+
let idempotencyKey;
|
|
62
|
+
let pendingRecord = null;
|
|
63
|
+
if (options?.toolCallId && this._pendingStore) {
|
|
64
|
+
pendingRecord = this._pendingStore.getPending(options.toolCallId);
|
|
65
|
+
}
|
|
66
|
+
if (pendingRecord) {
|
|
67
|
+
idempotencyKey = pendingRecord.idempotency_key;
|
|
68
|
+
logger.info(`[transport/oasn/invocation] reuse pending tool_call_id=${options?.toolCallId} `
|
|
69
|
+
+ `idempotency_key=${idempotencyKey}`);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
idempotencyKey = randomUUID();
|
|
73
|
+
}
|
|
74
|
+
const body = {
|
|
75
|
+
idempotency_key: idempotencyKey,
|
|
76
|
+
service_agent_id: targetAgentId,
|
|
77
|
+
input: { task: task.task },
|
|
78
|
+
};
|
|
79
|
+
if (task.sessionId)
|
|
80
|
+
body.session_id = task.sessionId;
|
|
81
|
+
if (task.inputFileRefs?.length)
|
|
82
|
+
body.input_file_refs = task.inputFileRefs;
|
|
83
|
+
if (task.inputArtifactRefs?.length)
|
|
84
|
+
body.input_artifact_refs = task.inputArtifactRefs;
|
|
85
|
+
if (options?.timeoutMs !== undefined)
|
|
86
|
+
body.timeout_ms = options.timeoutMs;
|
|
87
|
+
if (options?.clientRequestGroupId)
|
|
88
|
+
body.client_request_group_id = options.clientRequestGroupId;
|
|
89
|
+
logger.info(`[transport/oasn/invocation] sendTask target=${targetAgentId} `
|
|
90
|
+
+ `idempotency_key=${idempotencyKey} replay=${pendingRecord ? 'maybe' : 'new'}`);
|
|
91
|
+
// 注意:服务端按 idempotency_key 去重,重试是安全的;此处保留默认重试策略
|
|
92
|
+
const resp = await this._http.post(OASN_ENDPOINTS.INVOCATIONS, body);
|
|
93
|
+
const created = this._normalizeCreateInvocation(resp, options?.timeoutMs);
|
|
94
|
+
if (!created.invocationId) {
|
|
95
|
+
throw makeTransportError('INVALID_INVOCATION_RESPONSE', 'Create invocation response missing invocationId');
|
|
96
|
+
}
|
|
97
|
+
logger.info(`[transport/oasn/invocation] created invocation_id=${created.invocationId} `
|
|
98
|
+
+ `status=${created.status} effective_timeout_ms=${created.effectiveTimeoutMs} `
|
|
99
|
+
+ `replay=${created.idempotentReplay}`);
|
|
100
|
+
// 回填 pending 记录
|
|
101
|
+
if (options?.toolCallId && this._pendingStore) {
|
|
102
|
+
this._pendingStore.upsertPending({
|
|
103
|
+
tool_call_id: options.toolCallId,
|
|
104
|
+
idempotency_key: idempotencyKey,
|
|
105
|
+
invocation_id: created.invocationId,
|
|
106
|
+
session_id: created.sessionId,
|
|
107
|
+
target_agent_id: targetAgentId,
|
|
108
|
+
status: created.status,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
// 缓存服务端裁决参数,留给 waitForTaskResult 使用
|
|
112
|
+
this._effectiveTimeoutMap.set(created.invocationId, created.effectiveTimeoutMs);
|
|
113
|
+
this._initialPollMap.set(created.invocationId, created.nextPollAfterMs);
|
|
114
|
+
return {
|
|
115
|
+
invocationId: created.invocationId,
|
|
116
|
+
sessionId: created.sessionId,
|
|
117
|
+
effectiveTimeoutMs: created.effectiveTimeoutMs,
|
|
118
|
+
nextPollAfterMs: created.nextPollAfterMs,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
// ═══════════════════════════════════════════════════════════════
|
|
122
|
+
// subscribeToInvocation —— §11.8 抽象层
|
|
123
|
+
// ═══════════════════════════════════════════════════════════════
|
|
124
|
+
/**
|
|
125
|
+
* 订阅 Invocation 状态变化,直至终态或 abort/timeout。
|
|
126
|
+
*
|
|
127
|
+
* 当前实现:纯轮询循环。
|
|
128
|
+
* 后续若服务端支持 SSE,可在此函数内部探测升级,外部 API 保持不变。
|
|
129
|
+
*
|
|
130
|
+
* 返回 Promise,在终态/超时/abort 时 resolve(内部不抛错,错误由 onComplete 携带 final.status 判定)。
|
|
131
|
+
*/
|
|
132
|
+
async subscribeToInvocation(invocationId, options) {
|
|
133
|
+
const { onProgress, onComplete, signal, totalTimeoutMs, initialPollAfterMs } = options;
|
|
134
|
+
const startAt = Date.now();
|
|
135
|
+
let nextDelay = this._clampPollInterval(initialPollAfterMs ?? this._initialPollMap.get(invocationId));
|
|
136
|
+
while (true) {
|
|
137
|
+
// ── abort 检查 ────────────────────────────────────────────
|
|
138
|
+
if (signal?.aborted) {
|
|
139
|
+
logger.warn(`[transport/oasn/invocation] subscribe aborted invocation_id=${invocationId}`);
|
|
140
|
+
// §11.2:服务端没有 cancel API,只能本地标记
|
|
141
|
+
this._pendingStore?.markAbandoned(invocationId);
|
|
142
|
+
// 用一个虚拟的"cancelled"终态通知上层
|
|
143
|
+
onComplete(this._buildAbandonedResponse(invocationId));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
// ── 总超时检查 ────────────────────────────────────────────
|
|
147
|
+
const elapsed = Date.now() - startAt;
|
|
148
|
+
if (elapsed >= totalTimeoutMs) {
|
|
149
|
+
logger.warn(`[transport/oasn/invocation] subscribe timed out invocation_id=${invocationId} `
|
|
150
|
+
+ `elapsed=${elapsed}ms limit=${totalTimeoutMs}ms`);
|
|
151
|
+
this._pendingStore?.markAbandoned(invocationId);
|
|
152
|
+
onComplete(this._buildTimeoutResponse(invocationId));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
// ── 等待下次轮询 ──────────────────────────────────────────
|
|
156
|
+
const remaining = totalTimeoutMs - elapsed;
|
|
157
|
+
const waitMs = Math.min(nextDelay, remaining);
|
|
158
|
+
await this._sleepWithAbort(waitMs, signal);
|
|
159
|
+
if (signal?.aborted)
|
|
160
|
+
continue; // 让下一轮迭代命中上面 abort 分支统一处理
|
|
161
|
+
// ── 拉取最新状态 ──────────────────────────────────────────
|
|
162
|
+
let resp;
|
|
163
|
+
try {
|
|
164
|
+
resp = await this._http.get(OASN_ENDPOINTS.INVOCATION(invocationId));
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
const te = err;
|
|
168
|
+
// 404 当成 cancelled —— 服务端可能已清理
|
|
169
|
+
if (te.code === 'INVOCATION_NOT_FOUND' || te.code === 'HTTP_404') {
|
|
170
|
+
logger.warn(`[transport/oasn/invocation] invocation gone: ${invocationId}`);
|
|
171
|
+
onComplete(this._build404Response(invocationId));
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
// 其它错误:retryable 的等下一轮,否则向上抛
|
|
175
|
+
if (te.retryable) {
|
|
176
|
+
logger.warn(`[transport/oasn/invocation] poll retryable error code=${te.code} for ${invocationId}, will retry`);
|
|
177
|
+
// 延后下一轮(指数式,但不超过上限)
|
|
178
|
+
nextDelay = this._clampPollInterval(nextDelay * 2);
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
throw err;
|
|
182
|
+
}
|
|
183
|
+
// ── 进度推送 ───────────────────────────────────────────────
|
|
184
|
+
const safeStatusMessage = resp.progress?.safeStatusMessage ?? resp.progress?.safe_status_message;
|
|
185
|
+
if (onProgress && safeStatusMessage) {
|
|
186
|
+
try {
|
|
187
|
+
onProgress({
|
|
188
|
+
content: safeStatusMessage,
|
|
189
|
+
raw: resp.progress,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
catch (cbErr) {
|
|
193
|
+
// 不让回调异常影响轮询
|
|
194
|
+
logger.warn(`[transport/oasn/invocation] onProgress callback threw: ${cbErr.message}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// ── 终态判定 ───────────────────────────────────────────────
|
|
198
|
+
if (isInvocationTerminal(resp.status)) {
|
|
199
|
+
logger.info(`[transport/oasn/invocation] terminal status=${resp.status} invocation_id=${invocationId}`);
|
|
200
|
+
this._pendingStore?.markTerminal(invocationId, resp.status);
|
|
201
|
+
onComplete(resp);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
// ── 决定下次轮询间隔 ──────────────────────────────────────
|
|
205
|
+
nextDelay = this._clampPollInterval(resp.nextPollAfterMs ?? resp.next_poll_after_ms ?? FALLBACK_POLL_INTERVAL_MS);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// ═══════════════════════════════════════════════════════════════
|
|
209
|
+
// waitForTaskResult —— 包装 subscribeToInvocation 为 TaskResult
|
|
210
|
+
// ═══════════════════════════════════════════════════════════════
|
|
211
|
+
/**
|
|
212
|
+
* 等待任务结果(终态后转换为 TaskResult)。
|
|
213
|
+
*
|
|
214
|
+
* §11.3 协调:
|
|
215
|
+
* 实际等待时间 = max(本地 timeoutMs, 服务端 effective_timeout_ms)
|
|
216
|
+
* 差距 > 30% 时输出 warn,提示用户调整。
|
|
217
|
+
*/
|
|
218
|
+
async waitForTaskResult(handle, options) {
|
|
219
|
+
const effective = handle.effectiveTimeoutMs
|
|
220
|
+
?? this._effectiveTimeoutMap.get(handle.invocationId)
|
|
221
|
+
?? options.timeoutMs;
|
|
222
|
+
const totalTimeoutMs = Math.max(options.timeoutMs, effective);
|
|
223
|
+
// §11.3:差距超过 30% 时提示
|
|
224
|
+
if (effective > options.timeoutMs * 1.3) {
|
|
225
|
+
logger.warn(`[transport/oasn/invocation] effective_timeout_ms=${effective} exceeds local timeoutMs=${options.timeoutMs} `
|
|
226
|
+
+ `by >30% (invocation_id=${handle.invocationId}); consider increasing local timeout`);
|
|
227
|
+
}
|
|
228
|
+
else if (options.timeoutMs > effective * 1.3) {
|
|
229
|
+
logger.warn(`[transport/oasn/invocation] local timeoutMs=${options.timeoutMs} exceeds effective_timeout_ms=${effective} `
|
|
230
|
+
+ `by >30% (invocation_id=${handle.invocationId}); server may give up earlier`);
|
|
231
|
+
}
|
|
232
|
+
return new Promise((resolve, reject) => {
|
|
233
|
+
this.subscribeToInvocation(handle.invocationId, {
|
|
234
|
+
totalTimeoutMs,
|
|
235
|
+
signal: options.signal,
|
|
236
|
+
initialPollAfterMs: handle.nextPollAfterMs,
|
|
237
|
+
onProgress: options.onProgress
|
|
238
|
+
? (p) => options.onProgress({
|
|
239
|
+
requestId: handle.invocationId,
|
|
240
|
+
content: p.content,
|
|
241
|
+
})
|
|
242
|
+
: undefined,
|
|
243
|
+
onComplete: (final) => {
|
|
244
|
+
try {
|
|
245
|
+
const result = this._toTaskResult(handle.invocationId, final);
|
|
246
|
+
// 终态错误转换为 reject,便于上层 catch
|
|
247
|
+
if (result.status === 'error') {
|
|
248
|
+
reject(this._terminalError(final, handle));
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
resolve(result);
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
reject(err);
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
}).catch(reject);
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
// ═══════════════════════════════════════════════════════════════
|
|
261
|
+
// 内部工具
|
|
262
|
+
// ═══════════════════════════════════════════════════════════════
|
|
263
|
+
/**
|
|
264
|
+
* 把终态 Invocation 响应转换为 TaskResult。
|
|
265
|
+
*
|
|
266
|
+
* - succeeded → status:'complete' + content=summary + artifacts + webuiContinuation
|
|
267
|
+
* - 其它终态 → status:'error' + content=user_error.message
|
|
268
|
+
*
|
|
269
|
+
* content 的归一路径:
|
|
270
|
+
* inlineResult.summary > visible_result.inline_result_summary > ''
|
|
271
|
+
*
|
|
272
|
+
* webui_continuation 仅在 succeeded 时抽取(失败场景后端不保证存在)。
|
|
273
|
+
*/
|
|
274
|
+
_toTaskResult(invocationId, final) {
|
|
275
|
+
const webuiContinuation = this._toWebuiContinuation(final);
|
|
276
|
+
if (final.status === 'succeeded') {
|
|
277
|
+
const inlineResult = final.inlineResult ?? final.inline_result;
|
|
278
|
+
const visible = final.visibleResult ?? final.visible_result;
|
|
279
|
+
const summary = inlineResult?.summary
|
|
280
|
+
?? visible?.inlineResultSummary
|
|
281
|
+
?? visible?.inline_result_summary
|
|
282
|
+
?? '';
|
|
283
|
+
return {
|
|
284
|
+
requestId: invocationId,
|
|
285
|
+
status: 'complete',
|
|
286
|
+
content: summary,
|
|
287
|
+
artifacts: (final.artifacts ?? []).map(this._toArtifactRef),
|
|
288
|
+
webuiContinuation,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
// 失败/超时/取消 —— 文本统一用 user_error.message 兜底
|
|
292
|
+
const userError = final.userError ?? final.user_error;
|
|
293
|
+
const inlineResult = final.inlineResult ?? final.inline_result;
|
|
294
|
+
const visible = final.visibleResult ?? final.visible_result;
|
|
295
|
+
const message = userError?.message
|
|
296
|
+
?? inlineResult?.summary
|
|
297
|
+
?? visible?.inlineResultSummary
|
|
298
|
+
?? visible?.inline_result_summary
|
|
299
|
+
?? `Invocation ended with status=${final.status}`;
|
|
300
|
+
return {
|
|
301
|
+
requestId: invocationId,
|
|
302
|
+
status: 'error',
|
|
303
|
+
content: message,
|
|
304
|
+
artifacts: (final.artifacts ?? []).map(this._toArtifactRef),
|
|
305
|
+
webuiContinuation,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* 从终态响应里抽取 WebUI continuation(camelCase / snake_case 双归一)。
|
|
310
|
+
* 缺必要字段(id / launch_url)时返回 undefined,调用方按 falsy 处理即可。
|
|
311
|
+
*/
|
|
312
|
+
_toWebuiContinuation(final) {
|
|
313
|
+
const visible = final.visibleResult ?? final.visible_result;
|
|
314
|
+
const raw = visible?.webuiContinuation ?? visible?.webui_continuation;
|
|
315
|
+
if (!raw)
|
|
316
|
+
return undefined;
|
|
317
|
+
const id = raw.continuationId ?? raw.continuation_id;
|
|
318
|
+
const launchUrl = raw.launchUrl ?? raw.launch_url;
|
|
319
|
+
if (!id || !launchUrl)
|
|
320
|
+
return undefined;
|
|
321
|
+
return {
|
|
322
|
+
id,
|
|
323
|
+
launchUrl,
|
|
324
|
+
displayName: raw.displayName ?? raw.display_name ?? 'Open WebUI',
|
|
325
|
+
visibleState: raw.visibleState ?? raw.visible_state ?? 'available',
|
|
326
|
+
sessionId: raw.sessionId ?? raw.session_id,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
/** 终态错误专用 TransportError,便于上层 catch */
|
|
330
|
+
_terminalError(final, handle) {
|
|
331
|
+
const userError = final.userError ?? final.user_error;
|
|
332
|
+
const code = userError?.code ?? `INVOCATION_${final.status.toUpperCase()}`;
|
|
333
|
+
const msg = userError?.message ?? `Invocation ${final.status}`;
|
|
334
|
+
return makeTransportError(code, msg, {
|
|
335
|
+
invocationId: handle.invocationId,
|
|
336
|
+
sessionId: handle.sessionId,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
_toArtifactRef(art) {
|
|
340
|
+
return {
|
|
341
|
+
id: art.artifactId ?? art.artifact_id ?? '',
|
|
342
|
+
displayName: art.displayName ?? art.display_name ?? 'artifact',
|
|
343
|
+
mimeType: art.mimeType ?? art.mime_type ?? 'application/octet-stream',
|
|
344
|
+
sizeBytes: art.sizeBytes ?? art.size_bytes ?? 0,
|
|
345
|
+
contentAvailable: art.contentAvailable ?? art.content_available ?? false,
|
|
346
|
+
contentUrl: art.contentUrl ?? art.content_url ?? '',
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
_normalizeCreateInvocation(resp, requestedTimeoutMs) {
|
|
350
|
+
return {
|
|
351
|
+
invocationId: resp.invocationId ?? resp.invocation_id ?? '',
|
|
352
|
+
sessionId: resp.sessionId ?? resp.session_id,
|
|
353
|
+
status: resp.status,
|
|
354
|
+
effectiveTimeoutMs: resp.effectiveTimeoutMs ?? resp.effective_timeout_ms ?? requestedTimeoutMs ?? 0,
|
|
355
|
+
nextPollAfterMs: resp.nextPollAfterMs ?? resp.next_poll_after_ms ?? FALLBACK_POLL_INTERVAL_MS,
|
|
356
|
+
idempotentReplay: resp.idempotentReplay ?? resp.idempotent_replay ?? false,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
/** clamp 轮询间隔到合理范围 */
|
|
360
|
+
_clampPollInterval(ms) {
|
|
361
|
+
if (!ms || ms <= 0)
|
|
362
|
+
return FALLBACK_POLL_INTERVAL_MS;
|
|
363
|
+
return Math.min(MAX_POLL_INTERVAL_MS, Math.max(MIN_POLL_INTERVAL_MS, ms));
|
|
364
|
+
}
|
|
365
|
+
/** 可被 abort 中断的 sleep */
|
|
366
|
+
_sleepWithAbort(ms, signal) {
|
|
367
|
+
return new Promise((resolve) => {
|
|
368
|
+
if (signal?.aborted) {
|
|
369
|
+
resolve();
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
const t = setTimeout(() => {
|
|
373
|
+
if (signal)
|
|
374
|
+
signal.removeEventListener('abort', onAbort);
|
|
375
|
+
resolve();
|
|
376
|
+
}, ms);
|
|
377
|
+
const onAbort = () => {
|
|
378
|
+
clearTimeout(t);
|
|
379
|
+
resolve();
|
|
380
|
+
};
|
|
381
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
/** abort 场景虚拟终态 —— 不调用服务端 cancel(API 不存在 §11.2) */
|
|
385
|
+
_buildAbandonedResponse(invocationId) {
|
|
386
|
+
const now = new Date().toISOString();
|
|
387
|
+
return {
|
|
388
|
+
invocation_id: invocationId,
|
|
389
|
+
session_id: '',
|
|
390
|
+
status: 'cancelled',
|
|
391
|
+
next_poll_after_ms: null,
|
|
392
|
+
created_at: now,
|
|
393
|
+
updated_at: now,
|
|
394
|
+
user_error: {
|
|
395
|
+
code: 'CLIENT_ABORTED',
|
|
396
|
+
message: '已发起调用但放弃跟踪结果(服务端可能仍在执行)',
|
|
397
|
+
},
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
/** timeout 虚拟终态 */
|
|
401
|
+
_buildTimeoutResponse(invocationId) {
|
|
402
|
+
const now = new Date().toISOString();
|
|
403
|
+
return {
|
|
404
|
+
invocation_id: invocationId,
|
|
405
|
+
session_id: '',
|
|
406
|
+
status: 'timed_out',
|
|
407
|
+
next_poll_after_ms: null,
|
|
408
|
+
created_at: now,
|
|
409
|
+
updated_at: now,
|
|
410
|
+
user_error: {
|
|
411
|
+
code: 'CLIENT_TIMEOUT',
|
|
412
|
+
message: '本地等待超时,服务端可能仍在执行',
|
|
413
|
+
},
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
/** invocation_id 不存在时的虚拟终态 */
|
|
417
|
+
_build404Response(invocationId) {
|
|
418
|
+
const now = new Date().toISOString();
|
|
419
|
+
return {
|
|
420
|
+
invocation_id: invocationId,
|
|
421
|
+
session_id: '',
|
|
422
|
+
status: 'cancelled',
|
|
423
|
+
next_poll_after_ms: null,
|
|
424
|
+
created_at: now,
|
|
425
|
+
updated_at: now,
|
|
426
|
+
user_error: {
|
|
427
|
+
code: 'INVOCATION_NOT_FOUND',
|
|
428
|
+
message: 'Invocation 不存在或已清理',
|
|
429
|
+
},
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AgentCard, AgentStatusResult, CategoryTree, SearchResult } from '../types.js';
|
|
2
|
+
import type { AgentCardResponse, AgentStatusResponse, CategoryTreeResponse, SearchResponse } from './oasn-types.js';
|
|
3
|
+
export declare function normalizeAgentCard(resp: AgentCardResponse | unknown): AgentCard;
|
|
4
|
+
export declare function normalizeAgentStatus(resp: AgentStatusResponse | unknown): AgentStatusResult;
|
|
5
|
+
export declare function normalizeCategoryTree(resp: CategoryTreeResponse | unknown): CategoryTree;
|
|
6
|
+
export declare function normalizeSearchResponse(resp: SearchResponse | unknown): SearchResult;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
function asRecord(value) {
|
|
2
|
+
return value && typeof value === 'object' ? value : {};
|
|
3
|
+
}
|
|
4
|
+
function pickString(record, ...keys) {
|
|
5
|
+
for (const key of keys) {
|
|
6
|
+
const value = record[key];
|
|
7
|
+
if (typeof value === 'string')
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
return '';
|
|
11
|
+
}
|
|
12
|
+
function pickNullableString(record, ...keys) {
|
|
13
|
+
for (const key of keys) {
|
|
14
|
+
const value = record[key];
|
|
15
|
+
if (typeof value === 'string')
|
|
16
|
+
return value;
|
|
17
|
+
if (value === null)
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
function pickNumber(record, fallback, ...keys) {
|
|
23
|
+
for (const key of keys) {
|
|
24
|
+
const value = record[key];
|
|
25
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
return fallback;
|
|
29
|
+
}
|
|
30
|
+
function pickBoolean(record, fallback, ...keys) {
|
|
31
|
+
for (const key of keys) {
|
|
32
|
+
const value = record[key];
|
|
33
|
+
if (typeof value === 'boolean')
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
return fallback;
|
|
37
|
+
}
|
|
38
|
+
function pickRecord(record, ...keys) {
|
|
39
|
+
for (const key of keys) {
|
|
40
|
+
const value = record[key];
|
|
41
|
+
if (value && typeof value === 'object')
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
return {};
|
|
45
|
+
}
|
|
46
|
+
function pickArray(record, ...keys) {
|
|
47
|
+
for (const key of keys) {
|
|
48
|
+
const value = record[key];
|
|
49
|
+
if (Array.isArray(value))
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
function normalizeTag(value) {
|
|
55
|
+
if (typeof value === 'string')
|
|
56
|
+
return { tag_id: value, name: value };
|
|
57
|
+
const record = asRecord(value);
|
|
58
|
+
return {
|
|
59
|
+
tag_id: pickString(record, 'tagId', 'tag_id', 'id'),
|
|
60
|
+
name: pickString(record, 'name'),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
export function normalizeAgentCard(resp) {
|
|
64
|
+
const record = asRecord(resp);
|
|
65
|
+
return {
|
|
66
|
+
agent_id: pickString(record, 'agentId', 'agent_id', 'id'),
|
|
67
|
+
agent_type: (pickString(record, 'agentType', 'agent_type') || 'service'),
|
|
68
|
+
name: pickString(record, 'name') || 'Unknown',
|
|
69
|
+
description: pickNullableString(record, 'description'),
|
|
70
|
+
MOM: pickNullableString(record, 'MOM'),
|
|
71
|
+
tags: pickArray(record, 'tags').map(normalizeTag).filter((tag) => tag.name || tag.tag_id),
|
|
72
|
+
category: pickArray(record, 'category'),
|
|
73
|
+
avatar_url: pickNullableString(record, 'avatarUrl', 'avatar_url', 'avatar'),
|
|
74
|
+
...(pickString(record, 'visibility') ? { visibility: pickString(record, 'visibility') } : {}),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export function normalizeAgentStatus(resp) {
|
|
78
|
+
const record = asRecord(resp);
|
|
79
|
+
return {
|
|
80
|
+
agent_id: pickString(record, 'agentId', 'agent_id'),
|
|
81
|
+
status: pickString(record, 'status'),
|
|
82
|
+
...(pickString(record, 'access_token', 'accessToken') ? {
|
|
83
|
+
access_token: pickString(record, 'access_token', 'accessToken'),
|
|
84
|
+
} : {}),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function normalizeCategoryNode(node) {
|
|
88
|
+
const record = asRecord(node);
|
|
89
|
+
return {
|
|
90
|
+
category_id: pickString(record, 'categoryId', 'category_id', 'id'),
|
|
91
|
+
name: pickString(record, 'name'),
|
|
92
|
+
children: pickArray(record, 'children').map(normalizeCategoryNode),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
export function normalizeCategoryTree(resp) {
|
|
96
|
+
const record = asRecord(resp);
|
|
97
|
+
return {
|
|
98
|
+
categories: pickArray(record, 'categories').map(normalizeCategoryNode),
|
|
99
|
+
updated_at: pickString(record, 'updatedAt', 'updated_at'),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
export function normalizeSearchResponse(resp) {
|
|
103
|
+
const record = asRecord(resp);
|
|
104
|
+
const cards = pickArray(record, 'agentCards', 'agent_cards');
|
|
105
|
+
return {
|
|
106
|
+
searchId: pickString(record, 'searchId', 'search_id'),
|
|
107
|
+
agentCards: cards.map(normalizeAgentCard),
|
|
108
|
+
pageNum: pickNumber(record, 1, 'pageNum', 'page_num'),
|
|
109
|
+
pageSize: pickNumber(record, cards.length, 'pageSize', 'page_size'),
|
|
110
|
+
hasNext: pickBoolean(record, false, 'hasNext', 'has_next'),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OASN Agent 状态查询
|
|
3
|
+
*
|
|
4
|
+
* 设计依据:
|
|
5
|
+
* - docs/specs/2026-06-16-oasn-api-module-mapping.md §2.3
|
|
6
|
+
*
|
|
7
|
+
* 注意:register 和 claim 已迁移到 CLI(openclaw-plugin-openagent-cli),
|
|
8
|
+
* 本模块仅保留 getStatus(GET /api/agents/{agent_id})。
|
|
9
|
+
*/
|
|
10
|
+
import type { AgentStatusResult } from '../types.js';
|
|
11
|
+
import type { OasnHttpClient } from './oasn-http.js';
|
|
12
|
+
/**
|
|
13
|
+
* OASN Agent 状态查询模块。
|
|
14
|
+
*/
|
|
15
|
+
export declare class OasnAgentStatus {
|
|
16
|
+
private readonly _http;
|
|
17
|
+
constructor(_http: OasnHttpClient);
|
|
18
|
+
getStatus(agentId: string): Promise<AgentStatusResult>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OASN Agent 状态查询
|
|
3
|
+
*
|
|
4
|
+
* 设计依据:
|
|
5
|
+
* - docs/specs/2026-06-16-oasn-api-module-mapping.md §2.3
|
|
6
|
+
*
|
|
7
|
+
* 注意:register 和 claim 已迁移到 CLI(openclaw-plugin-openagent-cli),
|
|
8
|
+
* 本模块仅保留 getStatus(GET /api/agents/{agent_id})。
|
|
9
|
+
*/
|
|
10
|
+
import { OASN_ENDPOINTS, } from './oasn-types.js';
|
|
11
|
+
import { normalizeAgentStatus } from './oasn-normalize.js';
|
|
12
|
+
/**
|
|
13
|
+
* OASN Agent 状态查询模块。
|
|
14
|
+
*/
|
|
15
|
+
export class OasnAgentStatus {
|
|
16
|
+
_http;
|
|
17
|
+
constructor(_http) {
|
|
18
|
+
this._http = _http;
|
|
19
|
+
}
|
|
20
|
+
async getStatus(agentId) {
|
|
21
|
+
const resp = await this._http.get(OASN_ENDPOINTS.AGENT_STATUS(agentId), { skipRetry: true });
|
|
22
|
+
return normalizeAgentStatus(resp);
|
|
23
|
+
}
|
|
24
|
+
}
|