@_daniel_jiang/ai-host 0.1.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 +78 -0
- package/dist/ai-host.js +262 -0
- package/dist/ai-host.mjs +1004 -0
- package/index.d.ts +14 -0
- package/package.json +32 -0
- package/src/iframe.ts +751 -0
- package/src/index.ts +410 -0
- package/src/messages.ts +102 -0
- package/src/store.ts +327 -0
- package/src/types.ts +159 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
import { IframeBridge } from './iframe'
|
|
2
|
+
import {
|
|
3
|
+
buildActionLaunchPayload,
|
|
4
|
+
buildActionWidgetRequest,
|
|
5
|
+
clearHostPageStore,
|
|
6
|
+
dismissCurrentPageHint,
|
|
7
|
+
dispatchRegisteredPageRefresh,
|
|
8
|
+
getCurrentPageId,
|
|
9
|
+
getCurrentPagePrimaryAction,
|
|
10
|
+
getRegisteredPages,
|
|
11
|
+
isDirectHostAction,
|
|
12
|
+
registerHostPage,
|
|
13
|
+
serializePageActions,
|
|
14
|
+
setCurrentHostPage,
|
|
15
|
+
setHintUiListener,
|
|
16
|
+
replaceHostContext as applyReplaceHostContextStore,
|
|
17
|
+
getHostContext as readHostContextStore,
|
|
18
|
+
unregisterHostPage,
|
|
19
|
+
} from './store'
|
|
20
|
+
import type {
|
|
21
|
+
BootstrapResult,
|
|
22
|
+
HostInitOptions,
|
|
23
|
+
HostPageContext,
|
|
24
|
+
HostPrefilledContext,
|
|
25
|
+
OpenWorkflowOptions,
|
|
26
|
+
RegisterPageOptions,
|
|
27
|
+
} from './types'
|
|
28
|
+
import type { ChildToParentMessage } from './messages'
|
|
29
|
+
|
|
30
|
+
export type {
|
|
31
|
+
BootstrapResult,
|
|
32
|
+
HostActionKind,
|
|
33
|
+
HostAnchorRef,
|
|
34
|
+
HostHintPlacement,
|
|
35
|
+
HostInitOptions,
|
|
36
|
+
HostPageAction,
|
|
37
|
+
HostPageContext,
|
|
38
|
+
HostPrefilledContext,
|
|
39
|
+
HostPrefilledPatients,
|
|
40
|
+
HostRefreshPayload,
|
|
41
|
+
OpenWorkflowOptions,
|
|
42
|
+
RegisterPageOptions,
|
|
43
|
+
} from './types'
|
|
44
|
+
export { AGENT_HOST_MSG, isAgentHostMessage } from './messages'
|
|
45
|
+
export type { WidgetPayload, SerializedPageAction } from './messages'
|
|
46
|
+
|
|
47
|
+
let _init: HostInitOptions = {}
|
|
48
|
+
let _bridge: IframeBridge | null = null
|
|
49
|
+
let _cachedToken = ''
|
|
50
|
+
let _cachedRefreshToken = ''
|
|
51
|
+
let _initPromise: Promise<void> | null = null
|
|
52
|
+
let _hubRunningCount = 0
|
|
53
|
+
|
|
54
|
+
function resolveAgentOrigin(): string {
|
|
55
|
+
const origin = (_init.agentOrigin || '').replace(/\/$/, '')
|
|
56
|
+
if (!origin) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
'Agent 前端地址未配置:请在 init({ agentOrigin }) 传入,或由宿主 BFF bootstrap 返回 agent_web_origin',
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
return origin
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function applyBootstrap(boot: BootstrapResult) {
|
|
65
|
+
if (boot.agent_web_origin) {
|
|
66
|
+
_init.agentOrigin = boot.agent_web_origin.replace(/\/$/, '')
|
|
67
|
+
}
|
|
68
|
+
if (boot.agent_token) {
|
|
69
|
+
_cachedToken = boot.agent_token
|
|
70
|
+
}
|
|
71
|
+
if (boot.refresh_token) {
|
|
72
|
+
_cachedRefreshToken = boot.refresh_token
|
|
73
|
+
}
|
|
74
|
+
_init.onBootstrap?.(boot)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function ensureToken(): Promise<string> {
|
|
78
|
+
if (_init.getToken) {
|
|
79
|
+
_cachedToken = await _init.getToken()
|
|
80
|
+
return _cachedToken
|
|
81
|
+
}
|
|
82
|
+
if (_cachedToken) return _cachedToken
|
|
83
|
+
if (_init.bootstrap) {
|
|
84
|
+
const boot = await _init.bootstrap()
|
|
85
|
+
applyBootstrap(boot)
|
|
86
|
+
if (_cachedToken) return _cachedToken
|
|
87
|
+
}
|
|
88
|
+
throw new Error('未连接 Agent:请配置 bootstrap 或 getToken')
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function refreshLauncherHint() {
|
|
92
|
+
if (!_init.agentOrigin) return
|
|
93
|
+
try {
|
|
94
|
+
ensureBridge().updateLauncherHint(getCurrentPagePrimaryAction())
|
|
95
|
+
} catch {
|
|
96
|
+
/* agentOrigin 尚未就绪 */
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function ensureBridge(): IframeBridge {
|
|
101
|
+
const origin = resolveAgentOrigin()
|
|
102
|
+
if (!_bridge) {
|
|
103
|
+
setHintUiListener(refreshLauncherHint)
|
|
104
|
+
_bridge = new IframeBridge(origin, handleChildMessage, {
|
|
105
|
+
onHintUse: (pageId, actionId) => {
|
|
106
|
+
void launchPageAction(pageId, actionId)
|
|
107
|
+
},
|
|
108
|
+
onHintDismiss: () => {
|
|
109
|
+
dismissCurrentPageHint()
|
|
110
|
+
refreshLauncherHint()
|
|
111
|
+
},
|
|
112
|
+
})
|
|
113
|
+
if (_init.showHub !== false) {
|
|
114
|
+
_bridge.mountHub()
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return _bridge
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function syncHubIframeState() {
|
|
121
|
+
if (!_bridge || !_init.agentOrigin) return
|
|
122
|
+
for (const { pageId, actions } of getRegisteredPages()) {
|
|
123
|
+
_bridge.sendToHub({
|
|
124
|
+
channel: 'agent-host',
|
|
125
|
+
type: 'register-page',
|
|
126
|
+
pageId,
|
|
127
|
+
actions: serializePageActions(actions),
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
_bridge.sendToHub({
|
|
131
|
+
channel: 'agent-host',
|
|
132
|
+
type: 'set-page',
|
|
133
|
+
pageId: getCurrentPageId(),
|
|
134
|
+
})
|
|
135
|
+
refreshLauncherHint()
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function handleChildMessage(msg: ChildToParentMessage) {
|
|
139
|
+
if (msg.type === 'ready' && msg.surface === 'hub') {
|
|
140
|
+
void ensureToken()
|
|
141
|
+
.then((token) => {
|
|
142
|
+
_bridge?.sendToHub({
|
|
143
|
+
channel: 'agent-host',
|
|
144
|
+
type: 'init',
|
|
145
|
+
token,
|
|
146
|
+
refreshToken: _cachedRefreshToken || undefined,
|
|
147
|
+
})
|
|
148
|
+
syncHubIframeState()
|
|
149
|
+
})
|
|
150
|
+
.catch(() => {})
|
|
151
|
+
}
|
|
152
|
+
if (msg.type === 'started') {
|
|
153
|
+
_bridge?.closeWidget()
|
|
154
|
+
_bridge?.openHubPanel()
|
|
155
|
+
_bridge?.sendToHub({
|
|
156
|
+
channel: 'agent-host',
|
|
157
|
+
type: 'task-started',
|
|
158
|
+
taskId: msg.taskId,
|
|
159
|
+
sessionId: msg.sessionId,
|
|
160
|
+
workflow: msg.workflow,
|
|
161
|
+
label: msg.label || msg.workflow,
|
|
162
|
+
formValues: msg.formValues,
|
|
163
|
+
prefilled: msg.prefilled,
|
|
164
|
+
hostContext: msg.hostContext,
|
|
165
|
+
hubContinue: msg.hubContinue,
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
if (msg.type === 'complete') {
|
|
169
|
+
// 成功回填;失败/取消也刷新,便于宿主关掉 OCR loading 等 UI
|
|
170
|
+
if (msg.status === 'success' || msg.status === 'failed' || msg.status === 'cancelled') {
|
|
171
|
+
const refreshKey = msg.hostContext && (msg.hostContext as { refresh?: unknown }).refresh
|
|
172
|
+
dispatchRegisteredPageRefresh(refreshKey, {
|
|
173
|
+
taskId: msg.taskId,
|
|
174
|
+
result: msg.result,
|
|
175
|
+
hostContext: msg.hostContext,
|
|
176
|
+
apply: msg.apply,
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
_init.onTaskComplete?.({
|
|
180
|
+
taskId: msg.taskId,
|
|
181
|
+
status: msg.status,
|
|
182
|
+
hostContext: msg.hostContext,
|
|
183
|
+
result: msg.result,
|
|
184
|
+
apply: msg.apply,
|
|
185
|
+
})
|
|
186
|
+
_bridge?.closeWidget()
|
|
187
|
+
}
|
|
188
|
+
if (msg.type === 'close') {
|
|
189
|
+
_bridge?.closeWidget()
|
|
190
|
+
}
|
|
191
|
+
if (msg.type === 'open-widget') {
|
|
192
|
+
void openWidgetIframe(msg.payload)
|
|
193
|
+
}
|
|
194
|
+
if (msg.type === 'open-action') {
|
|
195
|
+
void launchPageAction(msg.pageId, msg.actionId)
|
|
196
|
+
}
|
|
197
|
+
if (msg.type === 'hub-stats') {
|
|
198
|
+
_hubRunningCount = msg.running
|
|
199
|
+
_bridge?.updateLauncherStats(msg.running, msg.waiting)
|
|
200
|
+
refreshLauncherHint()
|
|
201
|
+
}
|
|
202
|
+
if (msg.type === 'request-host-context') {
|
|
203
|
+
void respondHostContextRequest()
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async function respondHostContextRequest() {
|
|
208
|
+
if (!_bridge || !_init.agentOrigin) return
|
|
209
|
+
let raw: HostPageContext = {}
|
|
210
|
+
try {
|
|
211
|
+
if (_init.getHostContext) {
|
|
212
|
+
raw = (await _init.getHostContext()) || {}
|
|
213
|
+
} else {
|
|
214
|
+
raw = readHostContextStore()
|
|
215
|
+
}
|
|
216
|
+
} catch {
|
|
217
|
+
raw = readHostContextStore()
|
|
218
|
+
}
|
|
219
|
+
const context = applyReplaceHostContextStore(raw)
|
|
220
|
+
_bridge.sendToHub({
|
|
221
|
+
channel: 'agent-host',
|
|
222
|
+
type: 'set-host-context',
|
|
223
|
+
context,
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function launchPageAction(pageId: string, actionId: string) {
|
|
228
|
+
const packed = buildActionLaunchPayload(pageId, actionId)
|
|
229
|
+
if (!packed) return
|
|
230
|
+
if (isDirectHostAction(packed.action)) {
|
|
231
|
+
await startDirectRun(packed)
|
|
232
|
+
return
|
|
233
|
+
}
|
|
234
|
+
const req = buildActionWidgetRequest(pageId, actionId)
|
|
235
|
+
if (req) await openWidgetIframe(req)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async function startDirectRun(packed: NonNullable<ReturnType<typeof buildActionLaunchPayload>>) {
|
|
239
|
+
await ensureInitialized()
|
|
240
|
+
if (packed.action.workflow && !packed.action.skill && _init.prepareOpenWidget) {
|
|
241
|
+
await _init.prepareOpenWidget()
|
|
242
|
+
}
|
|
243
|
+
const token = await ensureToken()
|
|
244
|
+
const bridge = ensureBridge()
|
|
245
|
+
bridge.openHubPanel()
|
|
246
|
+
bridge.sendToHub({
|
|
247
|
+
channel: 'agent-host',
|
|
248
|
+
type: 'init',
|
|
249
|
+
token,
|
|
250
|
+
refreshToken: _cachedRefreshToken || undefined,
|
|
251
|
+
})
|
|
252
|
+
bridge.sendToHub({
|
|
253
|
+
channel: 'agent-host',
|
|
254
|
+
type: 'run-direct',
|
|
255
|
+
workflow: packed.action.workflow || packed.action.skill || '',
|
|
256
|
+
title: packed.action.label,
|
|
257
|
+
host: packed.action.host || _init.host,
|
|
258
|
+
skill: packed.action.skill,
|
|
259
|
+
noTools: packed.action.noTools === true,
|
|
260
|
+
prefilled: packed.prefilled,
|
|
261
|
+
hostContext: packed.hostContext,
|
|
262
|
+
})
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async function openWidgetIframe(payload: OpenWorkflowOptions & { workflow: string }) {
|
|
266
|
+
await ensureInitialized()
|
|
267
|
+
if (_init.prepareOpenWidget) {
|
|
268
|
+
await _init.prepareOpenWidget()
|
|
269
|
+
}
|
|
270
|
+
const bridge = ensureBridge()
|
|
271
|
+
const token = await ensureToken()
|
|
272
|
+
bridge.openWidget(
|
|
273
|
+
{
|
|
274
|
+
workflow: payload.workflow,
|
|
275
|
+
title: payload.title || payload.label,
|
|
276
|
+
host: payload.host || _init.host,
|
|
277
|
+
prefilled: payload.prefilled,
|
|
278
|
+
hostContext: payload.hostContext,
|
|
279
|
+
sessionId: payload.sessionId,
|
|
280
|
+
preferSandboxFiles: payload.preferSandboxFiles,
|
|
281
|
+
},
|
|
282
|
+
token,
|
|
283
|
+
_cachedRefreshToken || undefined,
|
|
284
|
+
)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async function ensureInitialized() {
|
|
288
|
+
if (_initPromise) await _initPromise
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async function runInit(opts: HostInitOptions) {
|
|
292
|
+
_init = { showHub: true, ...opts }
|
|
293
|
+
if (_init.bootstrap) {
|
|
294
|
+
const boot = await _init.bootstrap()
|
|
295
|
+
applyBootstrap(boot)
|
|
296
|
+
}
|
|
297
|
+
// 配置了 bootstrap 时必须有 agent_web_origin,禁止回退到宿主 origin(避免 Hub 指到 EDC 自己)
|
|
298
|
+
if (!_init.agentOrigin) {
|
|
299
|
+
return
|
|
300
|
+
}
|
|
301
|
+
if (_init.showHub !== false && typeof document !== 'undefined') {
|
|
302
|
+
try {
|
|
303
|
+
ensureBridge()
|
|
304
|
+
const token = await ensureToken()
|
|
305
|
+
_bridge?.sendToHub({
|
|
306
|
+
channel: 'agent-host',
|
|
307
|
+
type: 'init',
|
|
308
|
+
token,
|
|
309
|
+
refreshToken: _cachedRefreshToken || undefined,
|
|
310
|
+
})
|
|
311
|
+
} catch {
|
|
312
|
+
/* 未登录或 bootstrap 失败:不挂 Hub */
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export const AgentHost = {
|
|
318
|
+
async init(opts: HostInitOptions): Promise<void> {
|
|
319
|
+
if (_initPromise) await _initPromise
|
|
320
|
+
_initPromise = runInit(opts).finally(() => {
|
|
321
|
+
_initPromise = null
|
|
322
|
+
})
|
|
323
|
+
await _initPromise
|
|
324
|
+
},
|
|
325
|
+
|
|
326
|
+
async getToken(): Promise<string> {
|
|
327
|
+
await ensureInitialized()
|
|
328
|
+
return ensureToken()
|
|
329
|
+
},
|
|
330
|
+
|
|
331
|
+
async registerPage(
|
|
332
|
+
pageId: string,
|
|
333
|
+
actions: Parameters<typeof registerHostPage>[1],
|
|
334
|
+
options: RegisterPageOptions = {},
|
|
335
|
+
) {
|
|
336
|
+
await ensureInitialized()
|
|
337
|
+
registerHostPage(pageId, actions, options)
|
|
338
|
+
if (_init.agentOrigin) {
|
|
339
|
+
ensureBridge().sendToHub({
|
|
340
|
+
channel: 'agent-host',
|
|
341
|
+
type: 'register-page',
|
|
342
|
+
pageId,
|
|
343
|
+
actions: serializePageActions(actions),
|
|
344
|
+
})
|
|
345
|
+
}
|
|
346
|
+
refreshLauncherHint()
|
|
347
|
+
},
|
|
348
|
+
|
|
349
|
+
/** 最近一次按需回写的宿主上下文(系统 / 名称 / 描述 / 菜单) */
|
|
350
|
+
getHostContext() {
|
|
351
|
+
return readHostContextStore()
|
|
352
|
+
},
|
|
353
|
+
|
|
354
|
+
async unregisterPage(pageId: string) {
|
|
355
|
+
await ensureInitialized()
|
|
356
|
+
unregisterHostPage(pageId)
|
|
357
|
+
if (_init.agentOrigin) {
|
|
358
|
+
ensureBridge().sendToHub({ channel: 'agent-host', type: 'unregister-page', pageId })
|
|
359
|
+
ensureBridge().sendToHub({ channel: 'agent-host', type: 'set-page', pageId: null })
|
|
360
|
+
}
|
|
361
|
+
refreshLauncherHint()
|
|
362
|
+
},
|
|
363
|
+
|
|
364
|
+
async setCurrentPage(pageId: string | null) {
|
|
365
|
+
await ensureInitialized()
|
|
366
|
+
setCurrentHostPage(pageId)
|
|
367
|
+
if (_init.agentOrigin) {
|
|
368
|
+
ensureBridge().sendToHub({ channel: 'agent-host', type: 'set-page', pageId })
|
|
369
|
+
}
|
|
370
|
+
refreshLauncherHint()
|
|
371
|
+
},
|
|
372
|
+
|
|
373
|
+
async openAction(pageId: string, actionId: string) {
|
|
374
|
+
const packed = buildActionLaunchPayload(pageId, actionId)
|
|
375
|
+
if (!packed) throw new Error('当前操作不可用')
|
|
376
|
+
await launchPageAction(pageId, actionId)
|
|
377
|
+
},
|
|
378
|
+
|
|
379
|
+
async openWorkflow(workflowName: string, opts: OpenWorkflowOptions = {}) {
|
|
380
|
+
await openWidgetIframe({
|
|
381
|
+
workflow: workflowName,
|
|
382
|
+
...opts,
|
|
383
|
+
host: opts.host || _init.host,
|
|
384
|
+
title: opts.title || opts.label || workflowName,
|
|
385
|
+
})
|
|
386
|
+
},
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* 兼容保留。场景能否启动由宿主 `action.enabled` 判断,SDK 不识别具体 workflow/skill slug。
|
|
390
|
+
*/
|
|
391
|
+
canOpen(_workflowName?: string, _opts?: { prefilled?: HostPrefilledContext & Record<string, unknown> }): boolean {
|
|
392
|
+
return true
|
|
393
|
+
},
|
|
394
|
+
|
|
395
|
+
destroy() {
|
|
396
|
+
_bridge?.destroy()
|
|
397
|
+
_bridge = null
|
|
398
|
+
_cachedToken = ''
|
|
399
|
+
_cachedRefreshToken = ''
|
|
400
|
+
_init = {}
|
|
401
|
+
_initPromise = null
|
|
402
|
+
_hubRunningCount = 0
|
|
403
|
+
setHintUiListener(null)
|
|
404
|
+
clearHostPageStore()
|
|
405
|
+
},
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (typeof window !== 'undefined') {
|
|
409
|
+
;(window as unknown as { AgentHost: typeof AgentHost }).AgentHost = AgentHost
|
|
410
|
+
}
|
package/src/messages.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/** postMessage 契约:宿主页面 ↔ Agent 嵌入 iframe */
|
|
2
|
+
|
|
3
|
+
export const AGENT_HOST_MSG = 'agent-host' as const
|
|
4
|
+
|
|
5
|
+
export type WidgetPayload = {
|
|
6
|
+
workflow: string
|
|
7
|
+
title?: string
|
|
8
|
+
host?: string
|
|
9
|
+
prefilled?: Record<string, unknown>
|
|
10
|
+
hostContext?: Record<string, unknown>
|
|
11
|
+
/** 复用已有会话沙盒(续聊工作流) */
|
|
12
|
+
sessionId?: string
|
|
13
|
+
/** 文件字段优先从会话沙盒选择,而非本地上传 */
|
|
14
|
+
preferSandboxFiles?: boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type SerializedPageAction = {
|
|
18
|
+
id: string
|
|
19
|
+
workflow: string
|
|
20
|
+
label: string
|
|
21
|
+
host?: string
|
|
22
|
+
hint?: string
|
|
23
|
+
hostContext?: Record<string, unknown>
|
|
24
|
+
prefilled?: Record<string, unknown>
|
|
25
|
+
/** registerPage 时在宿主侧求值,供 iframe hint 展示/点击判断 */
|
|
26
|
+
enabled?: boolean
|
|
27
|
+
/** 缺省视为 widget;direct 由 Hub 无弹窗启动 */
|
|
28
|
+
kind?: 'widget' | 'direct'
|
|
29
|
+
skill?: string
|
|
30
|
+
noTools?: boolean
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type ParentToChildMessage =
|
|
34
|
+
| { channel: typeof AGENT_HOST_MSG; type: 'init'; token: string; refreshToken?: string }
|
|
35
|
+
| { channel: typeof AGENT_HOST_MSG; type: 'register-page'; pageId: string; actions: SerializedPageAction[] }
|
|
36
|
+
| { channel: typeof AGENT_HOST_MSG; type: 'unregister-page'; pageId: string }
|
|
37
|
+
| { channel: typeof AGENT_HOST_MSG; type: 'set-page'; pageId: string | null }
|
|
38
|
+
/** 宿主对 request-host-context 的回写(整份上下文) */
|
|
39
|
+
| { channel: typeof AGENT_HOST_MSG; type: 'set-host-context'; context: Record<string, unknown> }
|
|
40
|
+
| { channel: typeof AGENT_HOST_MSG; type: 'open-widget'; payload: WidgetPayload }
|
|
41
|
+
| { channel: typeof AGENT_HOST_MSG; type: 'open-hub-panel' }
|
|
42
|
+
| { channel: typeof AGENT_HOST_MSG; type: 'close-hub-panel' }
|
|
43
|
+
| {
|
|
44
|
+
channel: typeof AGENT_HOST_MSG
|
|
45
|
+
type: 'run-direct'
|
|
46
|
+
workflow: string
|
|
47
|
+
title?: string
|
|
48
|
+
host?: string
|
|
49
|
+
skill?: string
|
|
50
|
+
noTools?: boolean
|
|
51
|
+
prefilled?: Record<string, unknown>
|
|
52
|
+
hostContext?: Record<string, unknown>
|
|
53
|
+
}
|
|
54
|
+
| {
|
|
55
|
+
channel: typeof AGENT_HOST_MSG
|
|
56
|
+
type: 'task-started'
|
|
57
|
+
taskId: string
|
|
58
|
+
sessionId?: string
|
|
59
|
+
workflow: string
|
|
60
|
+
label?: string
|
|
61
|
+
formValues?: Record<string, unknown>
|
|
62
|
+
prefilled?: Record<string, unknown>
|
|
63
|
+
hostContext?: Record<string, unknown>
|
|
64
|
+
hubContinue?: boolean
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export type ChildToParentMessage =
|
|
68
|
+
| { channel: typeof AGENT_HOST_MSG; type: 'ready'; surface: 'hub' | 'widget' }
|
|
69
|
+
| { channel: typeof AGENT_HOST_MSG; type: 'hub-panel-close' }
|
|
70
|
+
| { channel: typeof AGENT_HOST_MSG; type: 'hub-panel-width'; expanded: boolean }
|
|
71
|
+
| { channel: typeof AGENT_HOST_MSG; type: 'hub-stats'; running: number; waiting: number; done: number }
|
|
72
|
+
| { channel: typeof AGENT_HOST_MSG; type: 'started'; taskId: string; sessionId?: string; workflow: string; label?: string; formValues?: Record<string, unknown>; prefilled?: Record<string, unknown>; hostContext?: Record<string, unknown>; hubContinue?: boolean }
|
|
73
|
+
| {
|
|
74
|
+
channel: typeof AGENT_HOST_MSG
|
|
75
|
+
type: 'complete'
|
|
76
|
+
taskId: string
|
|
77
|
+
status: string
|
|
78
|
+
hostContext?: Record<string, unknown>
|
|
79
|
+
result?: string
|
|
80
|
+
apply?: Record<string, unknown>
|
|
81
|
+
}
|
|
82
|
+
| { channel: typeof AGENT_HOST_MSG; type: 'close' }
|
|
83
|
+
| { channel: typeof AGENT_HOST_MSG; type: 'open-widget'; payload: WidgetPayload }
|
|
84
|
+
| { channel: typeof AGENT_HOST_MSG; type: 'open-action'; pageId: string; actionId: string }
|
|
85
|
+
| { channel: typeof AGENT_HOST_MSG; type: 'widget-resize'; height: number }
|
|
86
|
+
/** Hub 按需向宿主索取当前上下文(配合 init.getHostContext) */
|
|
87
|
+
| { channel: typeof AGENT_HOST_MSG; type: 'request-host-context' }
|
|
88
|
+
|
|
89
|
+
export function isAgentHostMessage(data: unknown): data is ParentToChildMessage | ChildToParentMessage {
|
|
90
|
+
return (
|
|
91
|
+
typeof data === 'object' &&
|
|
92
|
+
data !== null &&
|
|
93
|
+
(data as { channel?: string }).channel === AGENT_HOST_MSG &&
|
|
94
|
+
typeof (data as { type?: string }).type === 'string'
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** postMessage 仅接受 plain object;剥离 Vue reactive / 函数等不可克隆值 */
|
|
99
|
+
export function cloneForPostMessage<T>(value: T): T {
|
|
100
|
+
if (value === undefined || value === null) return value
|
|
101
|
+
return JSON.parse(JSON.stringify(value)) as T
|
|
102
|
+
}
|