agentworkshop 0.7.65 → 0.7.66
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/.output/nitro.json +1 -1
- package/.output/public/_nuxt/{DAfWhONg.js → 1NcBJMyL.js} +3 -3
- package/.output/public/_nuxt/{CKE_kTL92.js → Bb4REys92.js} +1 -1
- package/.output/public/_nuxt/{CVUMEGa1.js → Bnf04goH.js} +1 -1
- package/.output/public/_nuxt/{Cuw2--X2.js → CJPIo2E7.js} +1 -1
- package/.output/public/_nuxt/{CkYk8bj02.js → Cfy5Jn8v2.js} +1 -1
- package/.output/public/_nuxt/{DSWuWYx_2.js → DBERjwK92.js} +1 -1
- package/.output/public/_nuxt/DG0esyZR.js +1 -0
- package/.output/public/_nuxt/{B5RD2ZST.js → DPP0W5Ni.js} +1 -1
- package/.output/public/_nuxt/{DqT2_Jtw2.js → DxJFBokW2.js} +1 -1
- package/.output/public/_nuxt/{Cnf1PFhB2.js → GKiBVVYe2.js} +1 -1
- package/.output/public/_nuxt/{Dc5NeCDq.js → H13l21fR.js} +1 -1
- package/.output/public/_nuxt/{Cdw4juXw.js → XErFZOU9.js} +1 -1
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/8bf65244-eb00-45c7-be23-9dcd7b917ef3.json +1 -0
- package/.output/public/_nuxt/{D-z6N1Cz.js → kYyvM13M.js} +1 -1
- package/.output/public/_nuxt/{B0w8DZ4C.js → t54AYjsC.js} +1 -1
- package/.output/server/chunks/_/nitro.mjs +1331 -1015
- package/.output/server/chunks/_/nitro.mjs.map +1 -1
- package/.output/server/chunks/build/useWorkshopWs-CM-Ywk4h.mjs.map +1 -1
- package/.output/server/chunks/routes/renderer.mjs +1 -1
- package/.output/server/chunks/virtual/precomputed.mjs +1 -1
- package/.output/server/package.json +1 -1
- package/README-zh.md +8 -6
- package/README.md +7 -5
- package/app/stores/workshop/notifications.ts +22 -1
- package/config.yml +1 -1
- package/docs/cli.en.md +2 -2
- package/docs/cli.md +2 -2
- package/docs/plugins.en.md +1 -1
- package/docs/plugins.md +1 -1
- package/docs/sdk.en.md +1 -1
- package/docs/sdk.md +1 -1
- package/package.json +1 -1
- package/server/plugins-builtin/diag-bridge/client.mjs +230 -132
- package/server/plugins-builtin/diag-bridge/i18n.json +45 -15
- package/server/plugins-builtin/rag-bridge/client.mjs +182 -108
- package/server/plugins-builtin/rag-bridge/i18n.json +84 -60
- package/server/services/workshop/agents/host-tool-bridge/dispatch.ts +5 -0
- package/server/services/workshop/agents/industrial/aml-dataset-tools.ts +14 -0
- package/server/services/workshop/agents/industrial/dcw-judge-tools.ts +7 -2
- package/server/services/workshop/agents/industrial/line-tools.ts +16 -3
- package/server/services/workshop/agents/industrial/optimization-tools.ts +10 -0
- package/server/services/workshop/agents/industrial/tool-verify.ts +373 -0
- package/server/services/workshop/agents/industrial/twin-tools.ts +5 -0
- package/server/services/workshop/runtime/manager/channel-template-constants.ts +20 -0
- package/server/services/workshop/runtime/manager/channel-templates.ts +3 -12
- package/.output/public/_nuxt/DIDbAMv5.js +0 -1
- package/.output/public/_nuxt/builds/meta/874575e6-2606-4f09-a59b-724158b3aba2.json +0 -1
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tool-verify —— 工业工具调用的统一预检(参数形状 + 引用归属)。
|
|
3
|
+
*
|
|
4
|
+
* 背景(2026-10-17 审计):dispatch 层此前只有产线级 grant 复核(guardIndustrialToolGrant)
|
|
5
|
+
* 与 AML profile 门控,**没有对调用参数做「最小形状 + 引用归属」的集中校验**;逐工具
|
|
6
|
+
* 自校验覆盖面不齐——dcw_journal.recipe_id、aml_dataset_stats/job_*、aml_training_plan_*、
|
|
7
|
+
* optimization_explore.target_node_id、line_status.line_id 等一度可被任意 id 命中(跨线读;
|
|
8
|
+
* 见逐工具审计表)。本模块把这些判定收敛为单一口径:任何工业工具调用在分发给 handler
|
|
9
|
+
* **之前**必须过此闸门。
|
|
10
|
+
*
|
|
11
|
+
* 判定规则(与既有逐工具校验的合法集合对齐,不松不紧):
|
|
12
|
+
* - **参数形状**:声明的必填引用(atLeastOne)缺失 → 结构化拒绝(带 hint 说明该传什么);
|
|
13
|
+
* 引用参数类型不是字符串 → 拒绝。
|
|
14
|
+
* - **引用归属**:从调用者(agent)当前绑定面计算合法引用面,引用不在面内 → 拒绝,
|
|
15
|
+
* 并把「你的绑定面 + 下一步动作」写进回包,Agent 能据此自我纠正:
|
|
16
|
+
* nodeFace = agent 绑定节点(dcw/daq/配方参数节点)
|
|
17
|
+
* ∪ 频道绑线数采节点(v28 只读扩权,与 daqTargetsOf 同源)
|
|
18
|
+
* ∪ 绑定配方所在产线的节点(配方扩展参数场景,与 recipe 面语义一致)
|
|
19
|
+
* lineFace = agentOpsScope 可读线(节点授权线 ∪ 配方绑定线 ∪ 频道绑线)
|
|
20
|
+
* recipeFace = 绑定配方 ∪ 可读线上的配方(与 recipe_versions 口径一致)
|
|
21
|
+
* chainFace = dataset/job/model/plan/scene:创建者本人 或 其 lineId ∈ lineFace
|
|
22
|
+
* - 逐工具自带的更严校验(requireDcwBinding / daqTargetsOf / assertRecipeOperation /
|
|
23
|
+
* mesDatasetVisible …)原样保留:本层只拦「任何合法路径都到不了」的引用,不放宽任何
|
|
24
|
+
* 既有门槛(defense in depth);授权性拒绝入册 audit(level=warn),供 /logs 追溯。
|
|
25
|
+
* - 本层自身故障 → fail-closed(与 grant-guard 同纪律:不因实现缺陷放行)。
|
|
26
|
+
*/
|
|
27
|
+
import type { HostToolResult } from '../host-tool-bridge/types'
|
|
28
|
+
import { AppError } from '@/server/utils/errors'
|
|
29
|
+
import { getAgentNodeBindingRepo } from '../node-bindings.repo'
|
|
30
|
+
import { channelBoundLineId } from './ops-tools'
|
|
31
|
+
import { getDaqNodeRepo } from '../../daq/daq-node.repo'
|
|
32
|
+
import { getDcwController } from '../../dcw/dcw-controller'
|
|
33
|
+
import { getAmlRuntime } from '../../aml/runtime'
|
|
34
|
+
import { getTrainingPlan } from '../../aml/twin/training-plans'
|
|
35
|
+
import { createTwinRepo } from '../../aml/twin/repo'
|
|
36
|
+
import { recordOps } from '../../ops/ops'
|
|
37
|
+
|
|
38
|
+
/** 一次调用解析一次的引用面(校验与拒绝文案共用,避免重复查询) */
|
|
39
|
+
export interface ToolRefFaces {
|
|
40
|
+
agentId: string
|
|
41
|
+
/** 绑定面节点:dcw+daq 绑定节点 ∪ 绑定配方参数节点 */
|
|
42
|
+
boundNodes: Set<string>
|
|
43
|
+
/** 仅数采绑定(dcw 工具只认 dcw 绑定,读取面只认 daq 绑定——分面保留) */
|
|
44
|
+
daqBoundNodes: Set<string>
|
|
45
|
+
/** 绑定的配方(kind='recipe' 的绑定,nodeId 字段存 recipeId) */
|
|
46
|
+
boundRecipes: Set<string>
|
|
47
|
+
/** 绑定配方所在产线(配方扩展参数场景的合法节点线) */
|
|
48
|
+
recipeLines: Set<string>
|
|
49
|
+
/** 可读产线 = agentOpsScope 语义(节点授权线 ∪ 配方绑定线 ∪ 频道绑线) */
|
|
50
|
+
readableLines: Set<string>
|
|
51
|
+
/** 频道绑定产线(未绑定为空串) */
|
|
52
|
+
channelLineId: string
|
|
53
|
+
/** 频道绑线下的数采节点(v28 只读扩权面) */
|
|
54
|
+
channelLineNodes: Set<string>
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** 解析调用者的合法引用面(只读,不缓存——绑定可在调用间隙被用户改写) */
|
|
58
|
+
export function resolveRefFaces(agentId: string): ToolRefFaces {
|
|
59
|
+
const bindings = getAgentNodeBindingRepo().byAgent(agentId)
|
|
60
|
+
const boundNodes = new Set<string>()
|
|
61
|
+
const daqBoundNodes = new Set<string>()
|
|
62
|
+
const boundRecipes = new Set<string>()
|
|
63
|
+
const recipeLines = new Set<string>()
|
|
64
|
+
const readableLines = new Set<string>()
|
|
65
|
+
for (const b of bindings) {
|
|
66
|
+
if (b.kind === 'recipe') {
|
|
67
|
+
boundRecipes.add(b.nodeId)
|
|
68
|
+
const r = getDcwController().listRecipes().find(x => x.id === b.nodeId)
|
|
69
|
+
if (r?.lineId) {
|
|
70
|
+
recipeLines.add(r.lineId)
|
|
71
|
+
readableLines.add(r.lineId)
|
|
72
|
+
}
|
|
73
|
+
for (const p of (r?.params ?? [])) boundNodes.add(p.nodeId)
|
|
74
|
+
continue
|
|
75
|
+
}
|
|
76
|
+
boundNodes.add(b.nodeId)
|
|
77
|
+
if (b.kind === 'daq') daqBoundNodes.add(b.nodeId)
|
|
78
|
+
const n = b.kind === 'dcw' ? getDcwController().byId(b.nodeId) : getDaqNodeRepo().byId(b.nodeId)
|
|
79
|
+
if (n?.lineId) readableLines.add(n.lineId)
|
|
80
|
+
}
|
|
81
|
+
const channelLineId = channelBoundLineId(agentId) ?? ''
|
|
82
|
+
if (channelLineId) readableLines.add(channelLineId)
|
|
83
|
+
const channelLineNodes = new Set<string>()
|
|
84
|
+
if (channelLineId) {
|
|
85
|
+
for (const n of getDaqNodeRepo().all()) if (n.lineId === channelLineId) channelLineNodes.add(n.id)
|
|
86
|
+
}
|
|
87
|
+
return { agentId, boundNodes, daqBoundNodes, boundRecipes, recipeLines, readableLines, channelLineId, channelLineNodes }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ===== 归属判定 =====
|
|
91
|
+
|
|
92
|
+
function nodeRow(id: string): { lineId: string, name: string } | null {
|
|
93
|
+
const dcw = getDcwController().byId(id)
|
|
94
|
+
if (dcw) return { lineId: dcw.lineId ?? '', name: dcw.name }
|
|
95
|
+
const daq = getDaqNodeRepo().byId(id)
|
|
96
|
+
if (daq) return { lineId: daq.lineId ?? '', name: daq.name }
|
|
97
|
+
return null
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function nodeAllowed(faces: ToolRefFaces, id: string): boolean {
|
|
101
|
+
if (faces.boundNodes.has(id) || faces.channelLineNodes.has(id)) return true
|
|
102
|
+
if (faces.recipeLines.size === 0) return false
|
|
103
|
+
const row = nodeRow(id)
|
|
104
|
+
return !!row?.lineId && faces.recipeLines.has(row.lineId)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** 列表化面信息(拒绝文案用;空面显式写「(无)」) */
|
|
108
|
+
function listOf(set: Set<string>): string {
|
|
109
|
+
return set.size > 0 ? [...set].join(', ') : '(无)'
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function deny(toolName: string, args: Record<string, unknown>, heads: string[], faces: ToolRefFaces | null, hint: string, ref?: { param: string, value: string, lineId?: string }): HostToolResult {
|
|
113
|
+
// 授权性拒绝入册(level=warn;形状类拒绝只有 heads 无 ref,不入册避免噪音)
|
|
114
|
+
if (ref && faces) {
|
|
115
|
+
recordOps({
|
|
116
|
+
actor: faces.agentId,
|
|
117
|
+
actorName: faces.agentId,
|
|
118
|
+
actorKind: 'agent',
|
|
119
|
+
action: 'tool.call.denied',
|
|
120
|
+
kind: 'system',
|
|
121
|
+
level: 'warn',
|
|
122
|
+
targetKind: 'tool',
|
|
123
|
+
targetId: toolName,
|
|
124
|
+
summary: `工具调用被拒(引用校验):${ref.param}=${ref.value} 不在授权面`,
|
|
125
|
+
detail: { tool: toolName, param: ref.param, value: ref.value, args },
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
return { text: `${heads.join('\n')}\n${hint}`, isError: true }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** node 引用拒绝文案:点名参数、列出绑定面、给下一步 */
|
|
132
|
+
function denyNode(faces: ToolRefFaces, toolName: string, args: Record<string, unknown>, param: string, id: string, exists: boolean): HostToolResult {
|
|
133
|
+
const row = exists ? nodeRow(id) : null
|
|
134
|
+
const where = row ? `(节点「${row.name}」${row.lineId ? `,归属产线 ${row.lineId}` : ''})` : '(节点不存在或已删除)'
|
|
135
|
+
const faceLine = `你的绑定节点:${listOf(faces.boundNodes)}${faces.channelLineNodes.size > 0 ? `;频道绑线「${faces.channelLineId}」只读数采节点:${listOf(faces.channelLineNodes)}` : ''}`
|
|
136
|
+
const hint = exists
|
|
137
|
+
? `该节点不在你的绑定范围内。${faceLine}。如需访问,请让用户或 lead 在数字孪生界面为你绑定该节点(可用 my_industrial_nodes 查看现有绑定);或改用上述范围内的节点重试。`
|
|
138
|
+
: `请核对节点 id(先用 my_industrial_nodes / line_context 查看在线对象);节点被删除时原绑定会失效,需要重新绑定。`
|
|
139
|
+
return deny(toolName, args, [
|
|
140
|
+
`工具调用被拒(引用校验,${toolName}):${param} = 「${id}」不在你的授权范围内${where}。`,
|
|
141
|
+
], faces, hint, { param, value: id, lineId: row?.lineId })
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function denyLine(faces: ToolRefFaces, toolName: string, args: Record<string, unknown>, param: string, id: string): HostToolResult {
|
|
145
|
+
const exists = getDcwController().listLines().some(l => l.id === id)
|
|
146
|
+
const hint = `你的可读产线:${listOf(faces.readableLines)}${faces.channelLineId ? `(其中「${faces.channelLineId}」来自频道绑线,只读)` : ''}。如需访问该产线,请让用户授予对应产线权限(权限管理),或把当前频道绑定到该产线;也可以改用上述可读产线重试。`
|
|
147
|
+
return deny(toolName, args, [
|
|
148
|
+
`工具调用被拒(引用校验,${toolName}):${param} = 「${id}」${exists ? '' : '(产线不存在)'}不在你的可读范围内。`,
|
|
149
|
+
], faces, hint, { param, value: id })
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function denyRecipe(faces: ToolRefFaces, toolName: string, args: Record<string, unknown>, param: string, id: string): HostToolResult {
|
|
153
|
+
const r = getDcwController().listRecipes().find(x => x.id === id)
|
|
154
|
+
const where = r ? `(配方「${r.name}」,归属产线 ${r.lineId || '未分配'})` : '(配方不存在或已删除)'
|
|
155
|
+
const hint = `你绑定的配方:${listOf(faces.boundRecipes)};可读产线上的配方也可读版本史/日志。配方操作(update/apply/trial/rollback)必须先由用户或 lead 把该配方绑定给你(绑定后该配方参数节点同时进入你的操作面)。`
|
|
156
|
+
return deny(toolName, args, [
|
|
157
|
+
`工具调用被拒(引用校验,${toolName}):${param} = 「${id}」不在你可读/可操作的配方范围内${where}。`,
|
|
158
|
+
], faces, hint, { param, value: id })
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** 链式引用(dataset/job/model/plan/scene):经谱系回溯到产线,按「创建者本人 ∪ 可读线」判定 */
|
|
162
|
+
function chainDeny(faces: ToolRefFaces, toolName: string, args: Record<string, unknown>, param: string, id: string, kindLabel: string, lineId: string): HostToolResult {
|
|
163
|
+
const hint = `该${kindLabel}不属于你可读的产线,也不是由你创建。你的可读产线:${listOf(faces.readableLines)}${faces.channelLineId ? `(含频道绑线「${faces.channelLineId}」)` : ''}。如需跨线使用,请让用户把频道绑定到目标产线或为你的 Agent 绑定该产线的节点;也可以在自己的产线上重建${kindLabel}。`
|
|
164
|
+
return deny(toolName, args, [
|
|
165
|
+
`工具调用被拒(引用校验,${toolName}):${param} = 「${id}」越出你的数据范围(谱系产线 ${lineId || '未知'})。`,
|
|
166
|
+
], faces, hint, { param, value: id, lineId })
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
type ChainKind = 'dataset' | 'job' | 'model' | 'plan' | 'scene'
|
|
170
|
+
|
|
171
|
+
/** 链式解析:返回 null 表示「无法判定/对象不存在」——放行给工具自身兜底(存在性文案更准确) */
|
|
172
|
+
function chainLineOf(kind: ChainKind, id: string, args: Record<string, unknown>): { lineId: string, createdBy: string } | null {
|
|
173
|
+
const rt = getAmlRuntime()
|
|
174
|
+
if (kind === 'dataset') {
|
|
175
|
+
const row = rt.repo.dataset.get(id)
|
|
176
|
+
return row ? { lineId: row.lineId, createdBy: row.createdBy } : null
|
|
177
|
+
}
|
|
178
|
+
if (kind === 'job') {
|
|
179
|
+
const job = rt.repo.job.get(id)
|
|
180
|
+
if (!job) return null
|
|
181
|
+
const ds = rt.repo.dataset.get(job.datasetId)
|
|
182
|
+
return { lineId: ds?.lineId ?? '', createdBy: job.agentId ?? '' }
|
|
183
|
+
}
|
|
184
|
+
if (kind === 'model') {
|
|
185
|
+
const m = rt.repo.model.get(id)
|
|
186
|
+
return m ? { lineId: m.lineId, createdBy: m.createdBy } : null
|
|
187
|
+
}
|
|
188
|
+
if (kind === 'plan') {
|
|
189
|
+
const p = getTrainingPlan(id)
|
|
190
|
+
return p ? { lineId: p.lineId, createdBy: '' } : null
|
|
191
|
+
}
|
|
192
|
+
// scene:需带版本(scene_id 多版本并存);取不到记录 → 交工具自身
|
|
193
|
+
const version = String(args.scene_version ?? '').trim()
|
|
194
|
+
if (!version) return null
|
|
195
|
+
const rec = createTwinRepo(rt.db).getScene(id, version)
|
|
196
|
+
return rec ? { lineId: rec.lineId, createdBy: rec.createdBy } : null
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ===== 工具声明表 =====
|
|
200
|
+
|
|
201
|
+
interface VerifySpec {
|
|
202
|
+
/** 必填引用:至少提供一个(hint 给 Agent 该传什么) */
|
|
203
|
+
atLeastOne?: Array<{ params: string[], hint: string }>
|
|
204
|
+
/** 浅层字符串引用参数 → 判定类别 */
|
|
205
|
+
refs?: Array<{ param: string, kind: 'node' | 'line' | 'recipe' }>
|
|
206
|
+
/** 浅层节点 id 数组参数 */
|
|
207
|
+
nodeLists?: string[]
|
|
208
|
+
/** 深路径节点引用(如 'params[].node_id' / 'packages[].params[].node_id') */
|
|
209
|
+
deepNodeRefs?: string[]
|
|
210
|
+
/** 链式引用(dataset_id/job_id/model_id/plan_id/scene_id) */
|
|
211
|
+
chained?: Array<{ param: string, kind: ChainKind }>
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** 覆盖:全部「接收引用型参数」的工业工具。未列入 = 无引用参数或已由 v2 禁用桩自答。 */
|
|
215
|
+
const VERIFY_SPECS: Record<string, VerifySpec> = {
|
|
216
|
+
// 数控/数采面(v2 禁用桩 handler 自答替代路径,无需预检)
|
|
217
|
+
dcw_control: {},
|
|
218
|
+
param_control: {},
|
|
219
|
+
dcw_rollback: {},
|
|
220
|
+
daq_query: { refs: [{ param: 'node_id', kind: 'node' }, { param: 'line_id', kind: 'line' }] },
|
|
221
|
+
daq_frames: { refs: [{ param: 'node_id', kind: 'node' }, { param: 'line_id', kind: 'line' }] },
|
|
222
|
+
daq_export: { refs: [{ param: 'line_id', kind: 'line' }], nodeLists: ['node_ids'] },
|
|
223
|
+
dcw_judge: { atLeastOne: [{ params: ['record_id'], hint: 'record_id 必填(优化记录 id,opt-*);配方面链路判定不用 dcw_judge,达标用 recipe_update 固化、劣化用 recipe_rollback。' }] },
|
|
224
|
+
dcw_journal: {
|
|
225
|
+
atLeastOne: [{ params: ['node_id', 'recipe_id'], hint: 'node_id 或 recipe_id 至少提供一个:查某节点的参数变更史传 node_id;查配方面优化记录传 recipe_id(全节点不落权限面,不提供)。' }],
|
|
226
|
+
refs: [{ param: 'node_id', kind: 'node' }, { param: 'recipe_id', kind: 'recipe' }],
|
|
227
|
+
},
|
|
228
|
+
// 判定/日志/配方/前哨面
|
|
229
|
+
ops_log: { refs: [{ param: 'line_id', kind: 'line' }, { param: 'node_id', kind: 'node' }] },
|
|
230
|
+
recipe_log: { refs: [{ param: 'line_id', kind: 'line' }] },
|
|
231
|
+
line_context: { refs: [{ param: 'line_id', kind: 'line' }] },
|
|
232
|
+
line_status: { refs: [{ param: 'line_id', kind: 'line' }] },
|
|
233
|
+
line_start: { refs: [{ param: 'recipe_id', kind: 'recipe' }] },
|
|
234
|
+
recipe_versions: { refs: [{ param: 'recipe_id', kind: 'recipe' }] },
|
|
235
|
+
recipe_update: { refs: [{ param: 'recipe_id', kind: 'recipe' }], deepNodeRefs: ['params[].node_id'] },
|
|
236
|
+
recipe_rollback: { refs: [{ param: 'recipe_id', kind: 'recipe' }] },
|
|
237
|
+
recipe_trial: { refs: [{ param: 'recipe_id', kind: 'recipe' }], deepNodeRefs: ['params[].node_id'] },
|
|
238
|
+
recipe_apply: { refs: [{ param: 'recipe_id', kind: 'recipe' }] },
|
|
239
|
+
recipe_propose: { refs: [{ param: 'recipe_id', kind: 'recipe' }], deepNodeRefs: ['packages[].params[].node_id'] },
|
|
240
|
+
// 分析与经验面
|
|
241
|
+
analysis_save: { refs: [{ param: 'line_id', kind: 'line' }, { param: 'recipe_id', kind: 'recipe' }] },
|
|
242
|
+
analysis_query: { refs: [{ param: 'line_id', kind: 'line' }] },
|
|
243
|
+
aml_activity: { refs: [{ param: 'line_id', kind: 'line' }] },
|
|
244
|
+
exp_collect: { refs: [{ param: 'line_id', kind: 'line' }, { param: 'lineId', kind: 'line' }] },
|
|
245
|
+
// 寻优/孪生面
|
|
246
|
+
optimization_explore: { refs: [{ param: 'control_node_id', kind: 'node' }, { param: 'target_node_id', kind: 'node' }] },
|
|
247
|
+
twin_scene_compile: { refs: [{ param: 'line_id', kind: 'line' }, { param: 'recipe_id', kind: 'recipe' }], nodeLists: ['node_ids'] },
|
|
248
|
+
twin_scene_freeze: { chained: [{ param: 'scene_id', kind: 'scene' }] },
|
|
249
|
+
twin_calibration_request: { refs: [{ param: 'line_id', kind: 'line' }, { param: 'recipe_id', kind: 'recipe' }] },
|
|
250
|
+
twin_gate_evaluate: { chained: [{ param: 'model_id', kind: 'model' }] },
|
|
251
|
+
// AML 面
|
|
252
|
+
aml_dataset_build: { refs: [{ param: 'line_id', kind: 'line' }, { param: 'recipe_id', kind: 'recipe' }], deepNodeRefs: ['nodes[].node_id'] },
|
|
253
|
+
aml_dataset_stats: { chained: [{ param: 'dataset_id', kind: 'dataset' }] },
|
|
254
|
+
aml_leaderboard: { chained: [{ param: 'dataset_id', kind: 'dataset' }] },
|
|
255
|
+
aml_job_submit: { chained: [{ param: 'dataset_id', kind: 'dataset' }] },
|
|
256
|
+
aml_job_status: { chained: [{ param: 'job_id', kind: 'job' }] },
|
|
257
|
+
aml_job_logs: { chained: [{ param: 'job_id', kind: 'job' }] },
|
|
258
|
+
aml_job_cancel: { chained: [{ param: 'job_id', kind: 'job' }] },
|
|
259
|
+
aml_model_promote: { chained: [{ param: 'model_id', kind: 'model' }] },
|
|
260
|
+
aml_training_plan_create: { refs: [{ param: 'line_id', kind: 'line' }, { param: 'recipe_id', kind: 'recipe' }] },
|
|
261
|
+
aml_training_plan_train: { chained: [{ param: 'plan_id', kind: 'plan' }] },
|
|
262
|
+
// MES 面(ids 为 MES 数控节点,归属判定与 mes-controller 可见面同源)
|
|
263
|
+
mes_fetch: { nodeLists: ['ids'] },
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** 深路径取值:'params[].node_id' / 'packages[].params[].node_id' */
|
|
267
|
+
function collectDeep(args: unknown, dotted: string): string[] {
|
|
268
|
+
const segs = dotted.split('.')
|
|
269
|
+
const out: string[] = []
|
|
270
|
+
const walk = (cur: unknown, i: number): void => {
|
|
271
|
+
if (i >= segs.length) {
|
|
272
|
+
const s = String(cur ?? '').trim()
|
|
273
|
+
if (s) out.push(s)
|
|
274
|
+
return
|
|
275
|
+
}
|
|
276
|
+
const seg = segs[i]!
|
|
277
|
+
const isList = seg.endsWith('[]')
|
|
278
|
+
const key = isList ? seg.slice(0, -2) : seg
|
|
279
|
+
const val = (cur as Record<string, unknown> | null | undefined)?.[key]
|
|
280
|
+
if (isList) {
|
|
281
|
+
if (Array.isArray(val)) for (const item of val) walk(item, i + 1)
|
|
282
|
+
}
|
|
283
|
+
else walk(val, i + 1)
|
|
284
|
+
}
|
|
285
|
+
walk(args, 0)
|
|
286
|
+
return out
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* 工业工具调用统一预检。返回 null = 通过;返回结果 = 拒绝(可直接回给 Agent 的 isError 文本)。
|
|
291
|
+
* 调用点:dispatch.ts 工业工具族分流处(grant 复核之后、handler 之前)。
|
|
292
|
+
*/
|
|
293
|
+
export function verifyIndustrialToolCall(agentId: string, toolName: string, args: Record<string, unknown>): HostToolResult | null {
|
|
294
|
+
const spec = VERIFY_SPECS[toolName]
|
|
295
|
+
if (!spec) return null
|
|
296
|
+
try {
|
|
297
|
+
// 1) 必填引用(atLeastOne):形状类拒绝,不入册
|
|
298
|
+
for (const need of spec.atLeastOne ?? []) {
|
|
299
|
+
const present = need.params.some(p => String(args[p] ?? '').trim())
|
|
300
|
+
if (!present) {
|
|
301
|
+
return deny(toolName, args, [`工具调用被拒(参数校验,${toolName}):${need.hint}`], null, '参数补齐后重试。')
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
// 2) 类型形状:引用参数必须是字符串(ID 而非对象/数组)
|
|
305
|
+
for (const ref of spec.refs ?? []) {
|
|
306
|
+
const v = args[ref.param]
|
|
307
|
+
if (v != null && typeof v !== 'string') {
|
|
308
|
+
return deny(toolName, args, [`工具调用被拒(参数校验,${toolName}):${ref.param} 必须是字符串 ID,收到 ${Array.isArray(v) ? '数组' : typeof v}。`], null, '请按工具描述传单个对象 ID 字符串。')
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
for (const key of [...spec.nodeLists ?? [], ...spec.deepNodeRefs ?? []]) {
|
|
312
|
+
const v = key.includes('[]') ? undefined : args[key]
|
|
313
|
+
if (v != null && !key.includes('[]') && !Array.isArray(v)) {
|
|
314
|
+
return deny(toolName, args, [`工具调用被拒(参数校验,${toolName}):${key} 必须是 ID 数组。`], null, '请传字符串数组(如 ["dn-xxx","dw-yyy"])。')
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
// 3) 引用归属
|
|
318
|
+
const faces = resolveRefFaces(agentId)
|
|
319
|
+
for (const ref of spec.refs ?? []) {
|
|
320
|
+
const id = String(args[ref.param] ?? '').trim()
|
|
321
|
+
if (!id) continue
|
|
322
|
+
if (ref.kind === 'node') {
|
|
323
|
+
const exists = nodeRow(id) !== null
|
|
324
|
+
if (!exists || !nodeAllowed(faces, id)) return denyNode(faces, toolName, args, ref.param, id, exists)
|
|
325
|
+
}
|
|
326
|
+
else if (ref.kind === 'line') {
|
|
327
|
+
if (!faces.readableLines.has(id)) return denyLine(faces, toolName, args, ref.param, id)
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
const exists = getDcwController().listRecipes().some(x => x.id === id)
|
|
331
|
+
const onReadable = exists && (faces.readableLines.size > 0) && getDcwController().listRecipes().some(x => x.id === id && x.lineId && faces.readableLines.has(x.lineId))
|
|
332
|
+
if (!faces.boundRecipes.has(id) && !onReadable) return denyRecipe(faces, toolName, args, ref.param, id)
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
for (const key of spec.nodeLists ?? []) {
|
|
336
|
+
const list = args[key]
|
|
337
|
+
if (!Array.isArray(list)) continue
|
|
338
|
+
for (const raw of list) {
|
|
339
|
+
const id = String(raw ?? '').trim()
|
|
340
|
+
if (!id) continue
|
|
341
|
+
const exists = nodeRow(id) !== null
|
|
342
|
+
if (!exists || !nodeAllowed(faces, id)) return denyNode(faces, toolName, args, key, id, exists)
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
for (const path of spec.deepNodeRefs ?? []) {
|
|
346
|
+
// 深路径的 param 名用于文案:取最后一段
|
|
347
|
+
const leaf = path.split('.').pop() ?? path
|
|
348
|
+
for (const id of collectDeep(args, path)) {
|
|
349
|
+
const exists = nodeRow(id) !== null
|
|
350
|
+
if (!exists || !nodeAllowed(faces, id)) return denyNode(faces, toolName, args, leaf, id, exists)
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
// 4) 链式引用(谱系回溯;取不到谱系记录 → 放行给工具自身兜底)
|
|
354
|
+
for (const chain of spec.chained ?? []) {
|
|
355
|
+
const id = String(args[chain.param] ?? '').trim()
|
|
356
|
+
if (!id) continue
|
|
357
|
+
const row = chainLineOf(chain.kind, id, args)
|
|
358
|
+
if (!row) continue
|
|
359
|
+
const mine = row.createdBy === agentId
|
|
360
|
+
const readable = !!row.lineId && faces.readableLines.has(row.lineId)
|
|
361
|
+
if (!mine && !readable) {
|
|
362
|
+
const label = { dataset: '数据集', job: '训练作业', model: '模型', plan: '建模任务', scene: '场景' }[chain.kind]
|
|
363
|
+
return chainDeny(faces, toolName, args, chain.param, id, label, row.lineId)
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return null
|
|
367
|
+
}
|
|
368
|
+
catch (err) {
|
|
369
|
+
if (err instanceof AppError) return { text: `工具调用被拒(引用校验,${toolName}):${err.message}`, isError: true }
|
|
370
|
+
// 预检自身故障 → fail-closed(与 grant-guard 同纪律)
|
|
371
|
+
return { text: `工具调用被拒(引用校验服务异常,${toolName}):${err instanceof Error ? err.message : String(err)}。请稍后重试;若持续出现请联系管理员。`, isError: true }
|
|
372
|
+
}
|
|
373
|
+
}
|
|
@@ -285,6 +285,11 @@ export async function toolTwinSceneCompile(agentId: string, args: Record<string,
|
|
|
285
285
|
const lineId = String(args.line_id ?? '').trim()
|
|
286
286
|
if (!sceneId || !lineId) return fail('scene_id 和 line_id 必填。')
|
|
287
287
|
const nodes = boundSceneNodes(agentId).filter(node => !args.node_ids || (Array.isArray(args.node_ids) && args.node_ids.map(String).includes(node.nodeId)))
|
|
288
|
+
// 2026-10-17 补强:声明产线必须与(过滤后)绑定节点归属一致——防把他线合同挂到本机绑定面上
|
|
289
|
+
const nodeLines = new Set(nodes.map(n => String(n.lineId ?? '')).filter(Boolean))
|
|
290
|
+
if (nodeLines.size === 1 && !nodeLines.has(lineId)) {
|
|
291
|
+
return fail(`line_id=${lineId} 与绑定节点归属产线(${[...nodeLines].join(', ')})不一致:SceneContract 的产线必须来自你绑定节点所在产线。请改正 line_id,或先让 lead/用户把目标产线节点绑定给你。`)
|
|
292
|
+
}
|
|
288
293
|
const draft = compileSceneDraft({ sceneId, sceneVersion: String(args.scene_version ?? '0.1.0-draft'), lineId, productId: args.product_id ? String(args.product_id) : undefined, recipeId: args.recipe_id ? String(args.recipe_id) : undefined, prompt: args.prompt ? String(args.prompt) : undefined, createdBy: agentId, nodes, physicsProfileId: args.physics_profile_id ? String(args.physics_profile_id) : undefined, objectiveProfileIds: Array.isArray(args.objective_profile_ids) ? args.objective_profile_ids.map(String) : undefined, freeze: false })
|
|
289
294
|
const repo = createTwinRepo(getAmlRuntime().db)
|
|
290
295
|
repo.upsertScene(draft, agentId)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Channel 模板的纯常量(KB 集成名单 + 作业段提示词)。
|
|
3
|
+
*
|
|
4
|
+
* 单独成模块的原因:ManagerChannelTemplates 继承 ManagerTeams,模块图会拉入 nitro
|
|
5
|
+
* `#imports`(server/utils/config);纯常量不应因此无法被单测直接 import。
|
|
6
|
+
* channel-templates 侧保留同名 re-export,生产调用点不变。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** 携带「知识库集成」选项的模板:实例化时可显式启停 rag-bridge(开 → 注入 KB 作业段提示词);产线 Co-Pilot 两观察面频道(经验/诊断)的经验与诊断沉淀均以 KB 为唯一持久层,必入本名单 */
|
|
10
|
+
export const KNOWLEDGE_BASE_TEMPLATE_IDS = new Set(['chtpl-aml-optimization-default', 'chtpl-generic-optimize-default', 'chtpl-exp-miner-default', 'chtpl-line-doctor-default'])
|
|
11
|
+
|
|
12
|
+
/** 知识库集成开启时追加到频道场景提示词的作业段(教全员:先检索、后行动、再沉淀;知识调优工程师为主消费者) */
|
|
13
|
+
export const KNOWLEDGE_BASE_PROMPT_SECTION = `
|
|
14
|
+
|
|
15
|
+
## 知识库集成(已启用 rag-bridge)
|
|
16
|
+
本频道接入了 rag-knowledge 知识库,优化作业遵循「先检索、后行动、再沉淀」:
|
|
17
|
+
1. 优化启动前,lead 与知识调优工程师先用 kb_agent(mode=sync) 检索知识库中与本场景相关的**数据分析结论**与**物理机理知识**(检索词带上产线/产品/关键物理量,如「注塑 保压 克重 波动」「烘箱温度 膜厚 滞后」);把检索所得的机理关系、历史阈值与经验教训作为探索方向、步长与安全边界的依据;
|
|
18
|
+
2. 知识调优工程师是知识库的主要消费者与沉淀者:调优方案必须注明依据哪条知识;优化过程中若对某个现象拿不准,先查知识库再动手;worker 也可各自检索,但入库/沉淀由 lead 统一收口(可交知识调优工程师执行);
|
|
19
|
+
3. 优化闭环收口后(目标达标或 judge 判定 keep),用 kb_agent(mode=async) 把本次经验沉淀入知识库:标题、类别(工艺经验/数据分析/故障案例)、问题、解决方案、关键数据(带单位的参数调整↔响应变化)。异步提交后无需等待,可继续汇报;入库结果用 kb_agent_status 查询;
|
|
20
|
+
4. 若本频道同时启用深度诊断(diag-bridge):数据判读拿不准或质量异常时,先用 diag_run 发起根因诊断(异步,提交即返 task_id),完成后按 diag_status 返回的指引把报告经验经 kb_agent 入库,再把结论用于调优决策。`
|
|
@@ -12,19 +12,10 @@ import { parseJson } from '../../db/database'
|
|
|
12
12
|
import { setHybridChannelProfile } from '../../aml/twin/channel-profile'
|
|
13
13
|
import { defaultInjectionScene } from '../../aml/twin/physics-runtime'
|
|
14
14
|
import { getChannelPluginsRepo } from '../../db/channel-plugins.repo'
|
|
15
|
+
import { KNOWLEDGE_BASE_PROMPT_SECTION, KNOWLEDGE_BASE_TEMPLATE_IDS } from './channel-template-constants'
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
export
|
|
18
|
-
|
|
19
|
-
/** 知识库集成开启时追加到频道场景提示词的作业段(教全员:先检索、后行动、再沉淀;知识调优工程师为主消费者) */
|
|
20
|
-
export const KNOWLEDGE_BASE_PROMPT_SECTION = `
|
|
21
|
-
|
|
22
|
-
## 知识库集成(已启用 rag-bridge)
|
|
23
|
-
本频道接入了 rag-knowledge 知识库,优化作业遵循「先检索、后行动、再沉淀」:
|
|
24
|
-
1. 优化启动前,lead 与知识调优工程师先用 kb_agent(mode=sync) 检索知识库中与本场景相关的**数据分析结论**与**物理机理知识**(检索词带上产线/产品/关键物理量,如「注塑 保压 克重 波动」「烘箱温度 膜厚 滞后」);把检索所得的机理关系、历史阈值与经验教训作为探索方向、步长与安全边界的依据;
|
|
25
|
-
2. 知识调优工程师是知识库的主要消费者与沉淀者:调优方案必须注明依据哪条知识;优化过程中若对某个现象拿不准,先查知识库再动手;worker 也可各自检索,但入库/沉淀由 lead 统一收口(可交知识调优工程师执行);
|
|
26
|
-
3. 优化闭环收口后(目标达标或 judge 判定 keep),用 kb_agent(mode=async) 把本次经验沉淀入知识库:标题、类别(工艺经验/数据分析/故障案例)、问题、解决方案、关键数据(带单位的参数调整↔响应变化)。异步提交后无需等待,可继续汇报;入库结果用 kb_agent_status 查询;
|
|
27
|
-
4. 若本频道同时启用深度诊断(diag-bridge):数据判读拿不准或质量异常时,先用 diag_run 发起根因诊断(异步,提交即返 task_id),完成后按 diag_status 返回的指引把报告经验经 kb_agent 入库,再把结论用于调优决策。`
|
|
17
|
+
// 常量本体已下沉到纯模块(避免单测 import 触发 nitro #imports);此处保持原导出面不变
|
|
18
|
+
export { KNOWLEDGE_BASE_PROMPT_SECTION, KNOWLEDGE_BASE_TEMPLATE_IDS } from './channel-template-constants'
|
|
28
19
|
|
|
29
20
|
export abstract class ManagerChannelTemplates extends ManagerTeams {
|
|
30
21
|
/** 可见性感知 Channel 模板列表:普通用户 = 本人 + public(含内置);admin = 全量 */
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{v as e}from"./CavpkQb0.js";import{P as t,R as n,et as r}from"./ByMj6e7s.js";import{i}from"./S799kNo8.js";import{t as a}from"./RAqsw632.js";import{t as o}from"./36oeS8_R.js";import{t as s}from"./Do0dCE8C2.js";import{r as c}from"./ClEbKcuS.js";import{t as l}from"./Ch1P2mIf.js";import{t as u}from"./JgheoP3e.js";import{t as d}from"./P0EcfD3j.js";var f=50,p=10,m=2e3,h=i(`workshop.notifications`,()=>{let t=e([]),n=e(0),r=e(null),i=e(new Set),a=e(!1),o=e(null),u=e(0),d=e(!1),h=null;function g(e=5e3){d.value=!0,h&&clearTimeout(h),h=setTimeout(()=>{d.value=!1,h=null},e)}function _(){d.value=!1,h&&=(clearTimeout(h),null)}function v(e){if(!e)return;let t=i.value;if(t.has(e)||t.add(e),t.size>m){let e=[...t].slice(-Math.floor(m/2));i.value=new Set(e)}}function y(e){let n=t.value,r=0;for(;r<n.length&&n[r].createdAt>=e.createdAt;)r++;n.splice(r,0,e)}function b(e,r={}){if(!e?.id)return!1;let a=t.value.findIndex(t=>t.id===e.id);return a>=0?(t.value[a]={...t.value[a],...e},!1):e.eventId&&i.value.has(e.eventId)?!1:(v(e.eventId),y(e),e.readAt||(n.value+=1),x(e),r.live&&(o.value=e,u.value+=1),!0)}function x(e){let t=r.value;(!t||e.createdAt>t.createdAt||e.createdAt===t.createdAt&&e.id>t.id)&&(r.value={createdAt:e.createdAt,id:e.id})}function S(e){if(e.type===`notification.created`){b(e.payload,{live:!d.value});return}e.type===`notification.read`&&C(e.payload)}function C(e){let r=e.readAt||c();for(let n of t.value)n.readAt||e.id&&n.id!==e.id||!e.id&&e.channelId&&n.channelId!==e.channelId||(n.readAt=r);n.value=t.value.filter(e=>!e.readAt).length}async function w(e){let t=s().token,n={limit:String(f)};e&&(n.cursor=`${e.createdAt}|${e.id}`);let r=Object.entries(n).map(([e,t])=>`${e}=${encodeURIComponent(t)}`).join(`&`),i=await l(`/api/workshop/notifications?${r}`,{headers:t?{authorization:`Bearer ${t}`}:{}});if(i.code!==0||!i.data)throw Error(i.message??`通知拉取失败`);return i.data}let T=null,E=0;async function D(e){if(T)return T;let t=2e3-(Date.now()-E);return t>0&&await new Promise(e=>setTimeout(e,t)),E=Date.now(),T=(async()=>{try{await e()}finally{T=null}})(),T}async function O(){typeof window>`u`||!s().token||await D(async()=>{let e=await w(null);t.value=[...e.notifications??[]];for(let e of t.value)v(e.eventId);n.value=e.unreadCount??t.value.filter(e=>!e.readAt).length;let i=t.value[0];r.value=i?{createdAt:i.createdAt,id:i.id}:null,a.value=!0}).catch(()=>{})}async function k(){if(typeof window>`u`||!s().token)return 0;if(!a.value)return await O(),0;let e=0;try{return await D(async()=>{for(let t=0;t<p;t+=1){let t=await w(r.value),i=t.notifications??[];for(let t of i)b(t)&&(e+=1);if(t.nextCursor&&(r.value=t.nextCursor),n.value=t.unreadCount??n.value,i.length<f||!t.nextCursor)return}}),e}catch{return e}}function A(e){e&&(!r.value||e.createdAt>r.value.createdAt||e.createdAt===r.value.createdAt&&e.id>r.value.id)&&(r.value=e)}async function j(e){let r=s().token,i=await l(`/api/workshop/notifications/read`,{method:`POST`,body:{...e},headers:r?{authorization:`Bearer ${r}`}:{}});if(i.code!==0||!i.data)throw Error(i.message??`标记已读失败`);let a=c();for(let n of t.value)n.readAt||e.id&&n.id!==e.id||!e.id&&!e.all&&e.channelId&&n.channelId!==e.channelId||(n.readAt=a);return n.value=i.data.unreadCount??t.value.filter(e=>!e.readAt).length,i.data.count??0}function M(){t.value=[],n.value=0,r.value=null,i.value=new Set,a.value=!1,o.value=null,_()}return{items:t,unreadCount:n,cursor:r,seenEventIds:i,loaded:a,liveItem:o,liveSeq:u,replaying:d,beginReplay:g,endReplay:_,applyEnvelope:S,applyRead:C,upsertNotification:b,loadSnapshot:O,backfill:k,applyReplayCursor:A,markRead:j,clear:M}}),g=45e3,_=9e4,v=i(`workshop.connection`,{state:()=>({state:`closed`,cursors:{},lastDataAt:0,pendingReplay:!1,lastError:``,retryCount:0})}),y=class{onEvent;onStateChange;onPendingReplay;onDataRecovered;ws=null;retry=0;closedByUser=!1;subscribed=new Set;refCounts=new Map;lastSeqByChannel=new Map;lastFrameAt=0;lastDataAt=0;userToken=``;constructor(e,t,n=()=>{},r=()=>{}){this.onEvent=e,this.onStateChange=t,this.onPendingReplay=n,this.onDataRecovered=r}updateCursor(e,t){Number.isFinite(t)&&this.lastSeqByChannel.set(e,t)}url(){return typeof location>`u`?`ws://localhost/api/workshop/ws`:`${location.protocol===`https:`?`wss`:`ws`}://${location.host}/api/workshop/ws${this.userToken?`?token=${encodeURIComponent(this.userToken)}`:``}`}connect(){if(typeof window>`u`)return;this.onStateChange(`connecting`,this.retry);let e=new WebSocket(this.url());this.ws=e,this.lastFrameAt=Date.now(),this.lastDataAt=Date.now(),e.onopen=()=>{this.retry=0,this.onStateChange(`open`,0);for(let e of this.subscribed)this.sendSub(e,this.lastSeqByChannel.get(e))},e.onmessage=e=>{this.lastFrameAt=Date.now();try{let t=JSON.parse(e.data);t.type!==`pong`&&(this.lastDataAt=Date.now(),this.onDataRecovered()),this.onEvent(t)}catch{}},e.onclose=()=>{if(this.ws=null,this.closedByUser){this.onStateChange(`closed`,0);return}this.onStateChange(`closed`,this.retry),this.subscribed.size>0&&this.onPendingReplay();let e=Math.min(1e3*2**this.retry,1e4);this.retry+=1,setTimeout(()=>{this.closedByUser||this.connect()},e)},e.onerror=()=>{}}checkStale(){let e=this.ws;!e||e.readyState!==WebSocket.OPEN||(Date.now()-this.lastFrameAt>g||Date.now()-this.lastDataAt>_)&&e.close()}sendSub(e,t){let n={type:`sub`,channelId:e,token:this.userToken};typeof t==`number`&&(n.lastSeq=t),this.ws?.send(JSON.stringify(n))}subscribe(e,t){let n=this.refCounts.get(e)??0;this.refCounts.set(e,n+1),!(n>0)&&(this.subscribed.add(e),typeof t==`number`&&this.lastSeqByChannel.set(e,t),this.ws?.readyState===WebSocket.OPEN?this.sendSub(e,this.lastSeqByChannel.get(e)):this.ws||this.connect())}ensureConnected(){typeof window>`u`||this.ws||this.connect()}unsubscribe(e){let t=this.refCounts.get(e)??0;t<=1?(this.refCounts.delete(e),this.subscribed.delete(e),this.ws?.readyState===WebSocket.OPEN&&this.ws?.send(JSON.stringify({type:`unsub`,channelId:e}))):this.refCounts.set(e,t-1)}refCount(e){return this.refCounts.get(e)??0}ping(){try{this.ws?.send(JSON.stringify({type:`ping`}))}catch{try{this.ws?.close()}catch{}}}close(){this.closedByUser=!0,this.ws?.close()}},b={silent:0,status:1,attention:2,terminal:3};function x(e){switch(e.type){case`error`:return`attention`;case`a2a.artifact`:case`agent.message`:return`terminal`;case`task.status`:{let t=String(e.payload.state??``);return t===`COMPLETED`||t===`FAILED`||t===`CANCELED`?`terminal`:t===`WAITING`?`attention`:`status`}case`a2a.message`:return(e.payload.metadata??{})[`x-aw-require-reply`]===`true`?`attention`:`status`;case`agent.delta`:case`agent.status.message`:case`task.progress`:case`memory.saved`:return`silent`;default:return`status`}}function S(e){let t=`silent`;for(let n of e){let e=x(n);b[e]>b[t]&&(t=e)}return t}var C=5e3,w=[`agent.delta`],T={all:null,messages:[`agent.message`,`agent.delta`,`agent.status.message`,`a2a.message`],tasks:[`task.status`,`task.progress`,`a2a.artifact`],team:[`agent.member`,`task.status`],errors:[`error`],key:null};function E(e,t,n,r){if(e.type===`channel.snapshot`)return!1;if(t===`key`){let t=x(e);return t===`attention`||t===`terminal`}return n&&!n.includes(e.type)?!1:r&&e.type!==`task.status`&&e.type!==`task.progress`?e.agentId===r||e.type===`a2a.message`&&e.payload.metadata?.[`x-aw-target-agent`]===r:!0}function D(e,t,n,r){return e.filter(e=>E(e,t,n,r))}var O=Object.freeze([]),k=new Map;function A(e,t){let n=0,r=e.length;for(;n<r;){let i=n+r>>>1;(e[i]?.seq??0)>t?r=i:n=i+1}return n}var j=()=>({lastSeq:0,items:[],consumed:new Set,byAgent:new Map});function M(e,t){let n=t.agentId;if(!n)return;let r=e.byAgent.get(n);r?r.push(t):e.byAgent.set(n,[t])}function N(e,t){for(let n of t){let t=e.byAgent.get(n.agentId??``);if(!t)continue;let r=t.indexOf(n);r>=0&&t.splice(r,1),t.length===0&&e.byAgent.delete(n.agentId??``)}}function P(e){e.byAgent.clear();for(let t of e.items)M(e,t)}function F(e,t,n){let r=0;for(let i of t)typeof i.seq!=`number`||i.seq>n||e.consumed.has(i.seq)||(e.items.push(i),e.consumed.add(i.seq),r+=1);if(r>0){e.items.sort((e,t)=>e.seq-t.seq);let t=e.items.length-C;t>0&&e.items.splice(0,t),P(e)}return r}var I=i(`workshop.events`,{state:()=>({rings:{},filters:{},focusAgents:{},laneLoaded:{}}),getters:{ring(e){return t=>e.rings[t]??j()},timeline(e){return t=>{let n=e.rings[t];if(!n)return O;let r=e.filters[t]??`all`,i=e.focusAgents[t]??null,a=n.items,o=T[r],s=a.length>0?a[a.length-1]?.seq??0:-1,c=k.get(t);if(!c||c.filter!==r||c.focus!==i||a.length===0&&c.result.length>0||c.lastSeq>s){let e=D(a,r,o,i);return k.set(t,{filter:r,focus:i,lastSeq:s,result:e}),e}let l=a[0]?.seq??0,u=c.result;if(u.length>0&&(u[0]?.seq??0)<l){let e=0;for(;e<u.length&&(u[e]?.seq??0)<l;)e++;u.splice(0,e)}if(s>c.lastSeq){let e=A(a,c.lastSeq);for(let t=e;t<a.length;t++){let e=a[t];E(e,r,o,i)&&u.push(e)}c.lastSeq=s}return u}},agentEvents(e){return(t,n)=>n?e.rings[t]?.byAgent.get(n)??O:O},lastSeq(e){return t=>e.rings[t]?.lastSeq??0}},actions:{ingest(e){if(!e.channelId)return;let t=this.rings[e.channelId]??j();if(e.type===`channel.snapshot`){this.rings[e.channelId]={lastSeq:e.seq,items:[],consumed:new Set([e.seq]),byAgent:new Map};return}if(typeof e.seq==`number`&&e.seq>t.lastSeq){if(t.consumed.add(e.seq),e.type===`agent.delta`){let n=t.items[t.items.length-1];if(n&&n.type===`agent.delta`&&n.agentId===e.agentId&&(n.taskId??null)===(e.taskId??null)&&n){let r=n.payload.delta;n.payload.delta=r+e.payload.delta,t.lastSeq=e.seq,this.rings[e.channelId]=t;return}}t.items.push(e),M(t,e),t.items.length>C&&N(t,t.items.splice(0,t.items.length-C)),t.lastSeq=e.seq,t.consumed.size>2e4&&(t.consumed=new Set(t.items.map(e=>e.seq))),this.rings[e.channelId]=t}},setFilter(e,t){this.filters[e]=t},async loadHistory(e,t=200){if(typeof window>`u`)return;let n=this.rings[e]??j();try{let r=await l(`/api/workshop/channels/${e}/events`,{params:{limit:t,excludeTypes:w.join(`,`)},headers:{authorization:`Bearer ${s().token}`}});if(r.code!==0||!r.data?.items?.length)return;F(n,r.data.items,n.lastSeq),this.rings[e]=n}catch{}},async loadEarlier(e,t=200){if(typeof window>`u`)return!1;let n=this.rings[e];if(!n)return!1;let r=n.items.length>0?n.items[0]?.seq??0:n.lastSeq;if(r<=1)return!1;try{let i=await l(`/api/workshop/channels/${e}/events`,{params:{limit:t,beforeSeq:Math.max(1,r-1),excludeTypes:w.join(`,`)},headers:{authorization:`Bearer ${s().token}`}});if(i.code!==0||!i.data?.items?.length)return!1;let a=F(n,i.data.items,n.lastSeq);return a>0&&(this.rings[e]=n),a>0&&i.data.items.length>=t}catch{return!1}},async loadLaneHistory(e,t,n=200){if(typeof window>`u`||this.laneLoaded[e]?.has(t))return;(this.laneLoaded[e]??=new Set).add(t);for(let t=0;t<20&&this.lastSeq(e)===0;t++)await new Promise(e=>setTimeout(e,150));if(this.lastSeq(e)===0){this.laneLoaded[e]?.delete(t);return}let r=this.rings[e];try{let i=await l(`/api/workshop/channels/${e}/events`,{params:{limit:n,agentId:t,excludeTypes:w.join(`,`)},headers:{authorization:`Bearer ${s().token}`}});i.code===0&&i.data?.items?.length&&(F(r,i.data.items,r.lastSeq),this.rings[e]=r)}catch{this.laneLoaded[e]?.delete(t)}},async loadLaneEarlier(e,t,n=200){if(typeof window>`u`)return!1;let r=this.rings[e];if(!r)return!1;let i=0;for(let e of r.items)if(e.agentId===t){i=e.seq;break}if(i<=1)return!1;try{let a=await l(`/api/workshop/channels/${e}/events`,{params:{limit:n,agentId:t,beforeSeq:i-1,excludeTypes:w.join(`,`)},headers:{authorization:`Bearer ${s().token}`}});if(a.code!==0||!a.data?.items?.length)return!1;let o=F(r,a.data.items,r.lastSeq);return o>0&&(this.rings[e]=r),o>0&&a.data.items.length>=n}catch{return!1}},setFocusAgent(e,t){this.focusAgents[e]=t},clear(e){Reflect.deleteProperty(this.rings,e),Reflect.deleteProperty(this.laneLoaded,e)}}});function L(e){return e===`COMPLETED`||e===`FAILED`||e===`CANCELED`}function R(){return{activeRootId:null,queuedRoots:[],completedRoots:0,activeRootCount:0,queuedRootCount:0}}function z(e){let t=e.filter(e=>!e.parentId&&e.rootQueueSeq!=null).sort((e,t)=>(e.rootQueueSeq??0)-(t.rootQueueSeq??0)||String(e.createdAt).localeCompare(String(t.createdAt))),n=t.filter(e=>!L(e.state));return{activeRootId:n[0]?.id??null,queuedRoots:n.slice(1).map((e,t)=>({taskId:e.id,title:e.title,position:t+2,state:e.state,createdAt:e.createdAt})),completedRoots:t.length-n.length,activeRootCount:+(n.length>0),queuedRootCount:Math.max(0,n.length-1)}}var B=i(`workshop.entities`,{state:()=>({channels:{},agents:{},tasks:{},rootQueues:{},refreshing:{},refreshingAgents:{}}),getters:{agentById(e){return(t,n)=>e.agents[t]?.find(e=>e.agentId===n)},agentName(e){return(t,n)=>n?e.agents[t]?.find(e=>e.agentId===n)?.name??n.slice(0,8):`system`},taskTitle(e){return(t,n)=>n?e.tasks[t]?.find(e=>e.id===n)?.title??n.slice(0,8):``},rootTasks(e){return t=>(e.tasks[t]??[]).filter(e=>!e.parentId)},taskById(e){return(t,n)=>e.tasks[t]?.find(e=>e.id===n)},busyCount(e){return t=>(e.agents[t]??[]).filter(e=>e.state===`busy`).length},rootQueue(e){return t=>e.rootQueues[t]??R()},activeRootId(e){return t=>e.rootQueues[t]?.activeRootId??(e.tasks[t]??[]).filter(e=>!e.parentId&&!L(e.state))[0]?.id??null}},actions:{applySnapshot(e){let{channelId:t}=e;this.channels[t]={...e.channel,loadedAt:Date.now()},this.agents[t]=e.agents.map(e=>({...e,modelRef:e.config?.modelRef??e.modelRef??null})),this.tasks[t]=e.tasks.map(e=>this.toTaskView(e)),this.rootQueues[t]=e.rootQueue??z(e.tasks)},applyEvent(e){let t=e.channelId;switch(e.type){case`agent.status`:{let n=this.agents[t]??[],r=e.payload,i={agentId:r.agentId,state:r.state,currentTaskId:r.currentTaskId??null,currentTaskTitle:r.currentTaskTitle??null,currentTaskProgress:r.currentTaskProgress??null,queued:r.queued??r.queuedCount??0,completed:r.completed??r.completedCount??0,...r.context===void 0?{}:{context:r.context},...r.supervision?{supervision:r.supervision}:{},...r.continuity?{continuity:r.continuity}:{}},a=n.findIndex(e=>e.agentId===i.agentId);if(a>=0)n[a]={...n[a],...i};else{let e={agentId:i.agentId,state:i.state,currentTaskId:i.currentTaskId??null,queued:i.queued,completed:i.completed,name:i.agentId.slice(0,8),role:`worker`,harness:`-`,...r.supervision?{supervision:r.supervision}:{},...r.continuity?{continuity:r.continuity}:{}};n.push(e),this.refreshAgents(t)}this.agents[t]=[...n];break}case`agent.member`:{let n=e.payload,r=this.agents[t]??[],i=r.findIndex(e=>e.agentId===n.agentId);if(n.op===`added`)i<0&&r.push({agentId:n.agentId,name:n.name,role:n.role,harness:n.harness,enabled:n.enabled,config:n.config,modelRef:n.config?.modelRef??null,state:`idle`,currentTaskId:null,queued:0,completed:0});else if(n.op===`updated`){if(i>=0){let e=r[i],t=n.config??e.config;r[i]={...e,name:n.name??e.name,role:n.role??e.role,harness:n.harness??e.harness,enabled:n.enabled??e.enabled,config:t,modelRef:t?.modelRef??e.modelRef??null}}}else n.op===`removed`&&i>=0&&r.splice(i,1);this.agents[t]=[...r];break}case`task.status`:{let n=e.payload,r=this.tasks[t]??[],i=r.findIndex(e=>e.id===n.taskId);if(i>=0){let e=r[i];r[i]={...e,state:n.state,assigneeId:n.assigneeId??e.assigneeId,title:n.title??e.title,parentId:n.parentId??e.parentId,rootQueueSeq:n.rootQueueSeq??e.rootQueueSeq,progress:Math.max(e.progress,n.progress??0),routeReason:n.routeReason??e.routeReason,closeReason:n.closeReason??e.closeReason,deadlineAt:n.deadlineAt??e.deadlineAt,retryCount:n.retryCount??e.retryCount,createdAt:n.createdAt??e.createdAt,artifacts:Math.max(e.artifacts,n.artifacts??0)}}else{let e={id:n.taskId,title:n.title??n.taskId.slice(0,8),state:n.state,progress:n.progress??0,assigneeId:n.assigneeId??``,artifacts:n.artifacts??0};n.parentId&&(e.parentId=n.parentId),n.rootQueueSeq!=null&&(e.rootQueueSeq=n.rootQueueSeq),n.routeReason&&(e.routeReason=n.routeReason),n.closeReason&&(e.closeReason=n.closeReason),n.deadlineAt&&(e.deadlineAt=n.deadlineAt),n.retryCount!=null&&(e.retryCount=n.retryCount),n.createdAt&&(e.createdAt=n.createdAt),r.push(e),n.title===void 0&&this.refreshTasks(t)}this.tasks[t]=[...r];break}case`task.progress`:{let n=e.payload,r=this.tasks[t]??[],i=r.findIndex(e=>e.id===n.taskId),a=i>=0?r[i]:void 0;a&&(r[i]={...a,progress:n.progress}),this.tasks[t]=[...r];break}case`root.queue`:this.rootQueues[t]=e.payload;break;case`a2a.artifact`:{let n=e.payload;if(!n.taskId)break;let r=this.tasks[t]??[],i=r.findIndex(e=>e.id===n.taskId),a=i>=0?r[i]:void 0;a&&(r[i]={...a,artifacts:a.artifacts+1}),this.tasks[t]=[...r];break}}},toTaskView(e){return{id:e.id,parentId:e.parentId,rootQueueSeq:e.rootQueueSeq,title:e.title,state:e.state,progress:e.progress,assigneeId:e.assigneeId,artifacts:e.artifacts.length,routeReason:e.routeReason,sourceChatMessageId:e.sourceChatMessageId,sourceChatDeliveryId:e.sourceChatDeliveryId,retryCount:e.retryCount,closeReason:e.closeReason,deadlineAt:e.deadlineAt,createdAt:e.createdAt,updatedAt:e.updatedAt}},refreshChannel(e,t){this.applySnapshot(t)},refreshAgents(e){this.refreshingAgents[e]||(this.refreshingAgents[e]=!0,l(`/api/workshop/channels/${e}/agents`,{headers:{authorization:`Bearer ${s().token}`}}).then(t=>{let n=t.data??[],r=[...this.agents[e]??[]];for(let e of n){let t=r.findIndex(t=>t.agentId===e.id);t>=0?r[t]={...r[t],name:e.name,role:e.role,harness:e.harness,config:e.config}:r.push({agentId:e.id,name:e.name,role:e.role,harness:e.harness,config:e.config,state:`idle`,currentTaskId:null,queued:0,completed:0})}this.agents[e]=r}).catch(()=>{}).finally(()=>{this.refreshingAgents[e]=!1}))},refreshTasks(e){this.refreshing[e]||(this.refreshing[e]=!0,l(`/api/workshop/channels/${e}/tasks`,{headers:{authorization:`Bearer ${s().token}`}}).then(t=>{let n=t.data??[],r=this.tasks[e]??[],i=new Map(r.map(e=>[e.id,e])),a=new Set(n.map(e=>e.id)),o=n.map(e=>{let t=i.get(e.id),n=this.toTaskView(e);return t&&t.progress>n.progress?{...n,progress:t.progress}:n});for(let e of r)a.has(e.id)||o.push(e);this.tasks[e]=o}).catch(()=>{}).finally(()=>{this.refreshing[e]=!1}))}}});function V(e){let t=e??{},n=t.response?.data?.code??t.data?.code;return n==null?``:String(n)}function H(e){let t=e??{},n=t.response?.data?.message??t.data?.message;return typeof n==`string`&&n.trim()?n.trim():u(e)}function U(e,t){let n=e.length;for(let r=e.length-1;r>=0;r--){if(e[r].createdAt<=t.createdAt){n=r+1;break}n=r}e.splice(n,0,t)}var W=i(`workshop.chat`,()=>{let t=e({}),n=e({}),r=e({}),i=e({}),a=e({}),o=e(new Set),u=e({}),d=e({}),f=e(null);function p(e){return t.value[e]??=[]}function m(e,t){let n=t||e.channelId;if(!n||!e?.id)return!1;let r=p(n),i=r.findIndex(t=>t.id===e.id);return i>=0?(r[i]={...r[i],...e},!1):(U(r,e),!0)}function h(e){let t=e.channelId;if(!t||!e.deliveryId)return;let n=i.value[t]??={};n[e.deliveryId]={...n[e.deliveryId],...e}}function g(e){switch(e.type){case`chat.message`:m(e.payload,e.channelId||void 0);break;case`chat.delivery.status`:h(e.payload);break;case`chat.member`:{let t=e.payload,r=e.channelId;if(!r||!t?.userId)break;let i=n.value[r]??=[],a=i.findIndex(e=>e.userId===t.userId),o={userId:t.userId,displayName:t.displayName??null,role:t.role,status:t.status,joinedAt:t.joinedAt,leftAt:t.leftAt??null};a>=0?i[a]=o:i.push(o);break}case`chat.settings`:{let t=e.payload,n=t?.id||e.channelId;n&&(r.value[n]=t);break}}}function _(e,t,i){if(e){t?.channel&&(r.value[e]=t.channel),Array.isArray(t?.members)&&(n.value[e]=t.members.map(e=>({...e})));for(let n of t?.chatMessages??[])m(n,e);t?.permissions?a.value[e]=t.permissions:i!==void 0&&(a.value[e]=i??null),o.value.add(e)}}async function v(e,t={}){let n=s().token,r=t.query?`?${Object.entries(t.query).filter(([,e])=>e!==void 0&&e!==``).map(([e,t])=>`${e}=${encodeURIComponent(String(t))}`).join(`&`)}`:``,i=await l(`${e}${r}`,{method:t.method??`GET`,body:t.body,headers:n?{authorization:`Bearer ${n}`}:{}});if(i.code!==0||i.data==null)throw Error(i.message??`群聊请求失败`);return i.data}async function y(e,t={}){if(typeof window>`u`||!e)return!1;let n=t.limit??50;d.value[e]=!0;try{let r=await v(`/api/workshop/channels/${e}/chat/messages`,{query:{before:t.before,limit:n}});for(let t of r.messages??[])m(t,e);r.permissions&&(a.value[e]=r.permissions),o.value.add(e);let i=(r.messages?.length??0)>=n;return u.value[e]=i,i}finally{d.value[e]=!1}}async function b(e){if(!(typeof window>`u`||!e))try{let t=await v(`/api/workshop/channels/${e}/members`);n.value[e]=t.members??[],t.permissions&&(a.value[e]=t.permissions)}catch{}}async function x(e){if(typeof window>`u`||!e)return null;try{let t=await v(`/api/workshop/channels/${e}/chat/permissions`);return t.channel&&(r.value[e]=t.channel),a.value[e]=t.permissions,t.permissions}catch{return a.value[e]=null,null}}async function S(e){let t=await v(`/api/workshop/channels/${e.channelId}/chat/messages`,{method:`POST`,body:{text:e.text,mentions:e.mentions??[],replyToId:e.replyToId??null,clientMessageId:e.clientMessageId}});t.message&&m(t.message,e.channelId);for(let n of t.deliveries??[])h({deliveryId:n.deliveryId,chatMessageId:t.message?.id??``,channelId:e.channelId,targetAgentId:n.agentId,mailboxMessageId:null,status:n.status,updatedAt:c()});return t}async function C(e){let t=await v(`/api/workshop/channels/${e}/members/join`,{method:`POST`,body:{}});return t.permissions&&(a.value[e]=t.permissions),await Promise.all([b(e).catch(()=>{}),y(e).catch(()=>{})]),t.status}async function w(e){await v(`/api/workshop/channels/${e}/members/leave`,{method:`POST`,body:{}}),await x(e)}async function T(e,t){await v(`/api/workshop/channels/${e}/members/${encodeURIComponent(t)}`,{method:`DELETE`}),await b(e)}async function E(e,t){await v(`/api/workshop/channels/${e}/members/${encodeURIComponent(t)}/approve`,{method:`POST`,body:{}}),await b(e)}async function D(e,t){let n=r.value[e]?.version,i=await v(`/api/workshop/channels/${e}`,{method:`PATCH`,body:{...t,version:n}}),a=r.value[e];r.value[e]={id:i.id??e,name:i.name??a?.name??``,description:i.description??a?.description??``,visibility:i.visibility??a?.visibility??`private`,joinPolicy:i.joinPolicy??a?.joinPolicy??`owner_approve`,approvalPolicy:i.approvalPolicy??a?.approvalPolicy??`owner_only`,chatEnabled:i.chatEnabled??a?.chatEnabled??0,version:i.version??a?.version??0,legacy:i.ownerUserId===void 0?a?.legacy??!1:i.ownerUserId===null},await x(e)}function O(e){f.value=e}function k(){f.value=null}function A(e){let t=i.value[e];return t?Object.values(t):[]}function j(e,t){return A(e).filter(e=>e.chatMessageId===t)}function M(e){Reflect.deleteProperty(t.value,e),Reflect.deleteProperty(n.value,e),Reflect.deleteProperty(i.value,e),o.value.delete(e)}return{messages:t,members:n,settings:r,deliveries:i,permissions:a,loadedChannels:o,hasMoreHistory:u,loadingHistory:d,replyTarget:f,applyEnvelope:g,applySnapshot:_,loadHistory:y,loadMembers:b,loadPermissions:x,send:S,join:C,leave:w,removeMember:T,approveMember:E,updateSettings:D,setReplyTarget:O,clearReplyTarget:k,deliveriesOf:j,deliveriesOfChannel:A,clear:M}}),G=globalThis;G.__workshopWsHeartbeat??=null,G.__workshopWsHeartbeatUsers??=0,G.__workshopWsNotifHosts??=0,G.__workshopWsNotifSubscribed??=!1;function K(e,t){if(!e.channelId||typeof e.seq!=`number`||e.seq<=0||e.type===`channel.snapshot`)return!1;let n=t.get(e.channelId)??0;return e.seq<=n||(t.set(e.channelId,e.seq),!1)}function q(){let e=v(),i=s(),c=o(),l=G.__workshopWs??null,u=new Map;if(!l){let t=new y(n=>{let r=I(),i=B(),a=W(),o=d(),s=h();if(!K(n,u)){if(n.channelId&&typeof n.seq==`number`&&n.seq>0&&(e.pendingReplay&&=!1),n.type===`channel.snapshot`&&n.channelId){u.set(n.channelId,typeof n.seq==`number`?n.seq:0),e.pendingReplay=!1,r.ingest(n),i.applyEvent(n),i.applySnapshot(n.payload);let t=n.payload;a.applySnapshot(n.channelId,t.chat??null,t.permissions),r.loadHistory(n.channelId)}else r.ingest(n),i.applyEvent(n),typeof n.type==`string`&&n.type.startsWith(`chat.`)&&a.applyEnvelope(n);(n.type===`hitl.request`||n.type===`hitl.resolved`)&&o.applyEnvelope(n),(n.type===`notification.created`||n.type===`notification.read`)&&s.applyEnvelope(n),n.type===`notification.snapshot`&&(s.applyReplayCursor(n.payload?.nextCursor??null),s.endReplay()),typeof n.seq==`number`&&n.seq>0&&n.channelId&&(e.cursors[n.channelId]=n.seq,t.updateCursor(n.channelId,n.seq)),c.emit(n)}},(t,n)=>{e.state=t,e.retryCount=n,t===`open`&&h().backfill()},()=>{e.pendingReplay=!0},()=>{e.lastDataAt=Date.now();let t=h();G.__workshopWsNotifSubscribed&&p(),t.backfill()});l=t,G.__workshopWs=l}let f=e=>{let t=l?.ws;if(!(!t||t.readyState!==WebSocket.OPEN))try{t.send(JSON.stringify(e))}catch{}},p=()=>{let e=h(),t=e.cursor;e.beginReplay(),f(t?{type:`subNotifications`,cursor:t}:{type:`subNotifications`})};return r(()=>i.token,e=>{let t=h();l instanceof y&&(l.userToken=e),e?((G.__workshopWsNotifHosts??0)>0&&(G.__workshopWsNotifSubscribed=!0,p()),t.loadSnapshot()):(G.__workshopWsNotifSubscribed&&(G.__workshopWsNotifSubscribed=!1,f({type:`unsubNotifications`})),t.clear())},{immediate:!0}),n(()=>{G.__workshopWsHeartbeatUsers=(G.__workshopWsHeartbeatUsers??0)+1,!G.__workshopWsHeartbeat&&(G.__workshopWsHeartbeat=a(()=>{l?.ping(),l?.checkStale()},15e3))}),t(()=>{let e=(G.__workshopWsHeartbeatUsers??1)-1;G.__workshopWsHeartbeatUsers=e>0?e:0,e<=0&&G.__workshopWsHeartbeat&&(clearInterval(G.__workshopWsHeartbeat),G.__workshopWsHeartbeat=null)}),{subscribe:e=>{let t=I().lastSeq(e);l?.subscribe(e,t>0?t:void 0)},unsubscribe:e=>{l&&(l.unsubscribe(e),l.refCount(e)===0&&I().clear(e))},ensureConnected:()=>{l?.ensureConnected()},hostNotifications:()=>{let e=h();G.__workshopWsNotifHosts=(G.__workshopWsNotifHosts??0)+1,!G.__workshopWsNotifSubscribed&&i.token&&(G.__workshopWsNotifSubscribed=!0,p(),e.loadSnapshot());let t=!1;return()=>{if(t)return;t=!0;let e=(G.__workshopWsNotifHosts??1)-1;G.__workshopWsNotifHosts=e>0?e:0,e<=0&&(G.__workshopWsNotifSubscribed=!1,i.token&&f({type:`unsubNotifications`}))}},conn:e}}export{B as a,v as c,W as i,h as l,V as n,I as o,H as r,S as s,q as t};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"id":"874575e6-2606-4f09-a59b-724158b3aba2","timestamp":1792045282999,"prerendered":[]}
|