agentworkshop 0.2.2 → 0.3.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/package.json CHANGED
@@ -1,8 +1,23 @@
1
1
  {
2
2
  "name": "agentworkshop",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "AgentWorkShop 软件开发系统 - 基于 Nuxt 4 的配置驱动运行范式",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./sdk/index.d.mts",
9
+ "default": "./sdk/index.mjs"
10
+ },
11
+ "./sdk": {
12
+ "types": "./sdk/index.d.mts",
13
+ "default": "./sdk/index.mjs"
14
+ },
15
+ "./sdk/client": {
16
+ "types": "./sdk/client.d.mts",
17
+ "default": "./sdk/client.mjs"
18
+ },
19
+ "./package.json": "./package.json"
20
+ },
6
21
  "bin": {
7
22
  "aw": "bin/aw.mjs",
8
23
  "agentworkshop": "bin/aw.mjs"
@@ -60,6 +60,19 @@ const devHost = String(eff.effective['server.host'] ?? '0.0.0.0')
60
60
  const portSource = eff.sources['server.dev.port']
61
61
  console.log(`[config] 开发端口 -> ${devPort} (source: ${portSource}${hasPort ? ', CLI 显式覆盖生效' : ''})`)
62
62
 
63
+ // 端口注入 worker 环境链(PORT):CLI 显式值优先,否则引擎有效值 —— 与实际监听端口一致。
64
+ // 插件宿主(经 PORT env)自环调用 ctx.api 依赖此值;注意 nuxt dev 会自行覆盖 PORT,
65
+ // 因此必须在重入之前于顶层 guard spawn 时就位 —— 见文件头部 guard 块的 env 透传。
66
+ const cliPort = (() => {
67
+ const i = rest.indexOf('--port')
68
+ if (i >= 0 && rest[i + 1]) return rest[i + 1]
69
+ const eq = rest.find(a => a.startsWith('--port='))
70
+ return eq ? eq.slice(7) : null
71
+ })()
72
+ const effectivePort = cliPort ?? devPort
73
+ process.env.PORT = String(effectivePort)
74
+ console.log(`[config] worker PORT env -> ${effectivePort}`)
75
+
63
76
  const inject = [...rest]
64
77
  if (!hasPort) inject.push('--port', String(devPort))
65
78
  if (!hasHost) inject.push('--host', devHost)
package/sdk/api.mjs ADDED
@@ -0,0 +1,101 @@
1
+ // ============================================================
2
+ // AgentWorkShop SDK — 平台 REST 客户端
3
+ // ------------------------------------------------------------
4
+ // SDK 作为项目服务的 client:对平台 REST 面(/api/**)的类型化轻封装。
5
+ // 三个使用形态:
6
+ // 1) 插件内: ctx.api.lines.list() —— 宿主注入(自环 origin)
7
+ // 2) 外部脚本: createPlatformClient({ baseUrl, token }).daq.nodes()
8
+ // 3) 浏览器: createPlatformClient({ baseUrl: '' }) 同源相对路径
9
+ // 资源方法返回原始 JSON body(data 字段已解包);4xx/5xx 抛错(含 status)。
10
+ // ============================================================
11
+
12
+ export function createPlatformClient({ baseUrl = '', token, logger, timeoutMs = 10000 } = {}) {
13
+ // baseUrl 支持函数(延迟解析):插件宿主的监听端口在 nitro listen 钩子后才确定
14
+ const resolveBase = () => String(typeof baseUrl === 'function' ? baseUrl() : (baseUrl ?? '')).replace(/\/+$/, '')
15
+ let authToken = token ?? null
16
+
17
+ async function call(method, path, body, opt = {}) {
18
+ const base = resolveBase()
19
+ const headers = { accept: 'application/json', ...(opt.headers ?? {}) }
20
+ if (authToken) headers.authorization = `Bearer ${authToken}`
21
+ if (body !== undefined) headers['content-type'] = 'application/json'
22
+ const res = await fetch(`${base}${path}`, {
23
+ method,
24
+ headers,
25
+ body: body !== undefined ? JSON.stringify(body) : undefined,
26
+ signal: AbortSignal.timeout(opt.timeoutMs ?? timeoutMs),
27
+ })
28
+ const json = await res.json().catch(() => null)
29
+ if (!res.ok) {
30
+ const err = new Error(json?.message ?? `HTTP ${res.status} ${path}`)
31
+ err.status = res.status
32
+ err.body = json
33
+ if (logger) logger.warn(`API ${method} ${path} → ${res.status}`)
34
+ throw err
35
+ }
36
+ // 平台统一信封 { code, message, data } → 解包 data;非信封原样返回
37
+ return json && typeof json === 'object' && 'data' in json ? json.data : json
38
+ }
39
+
40
+ const resource = root => ({
41
+ list: (query = {}) => call('GET', `${root}${toQuery(query)}`),
42
+ get: id => call('GET', `${root}/${id}`),
43
+ create: body => call('POST', root, body),
44
+ update: (id, patch) => call('PATCH', `${root}/${id}`, patch),
45
+ remove: id => call('DELETE', `${root}/${id}`),
46
+ })
47
+
48
+ const client = {
49
+ /** 底层调用(任意平台路径;自动带 token 与信封解包) */
50
+ call,
51
+ get: (path, query = {}) => call('GET', `${path}${toQuery(query)}`),
52
+ post: (path, body) => call('POST', path, body),
53
+ patch: (path, body) => call('PATCH', path, body),
54
+ delete: path => call('DELETE', path),
55
+ setToken: (t) => {
56
+ authToken = t ?? null
57
+ return client
58
+ },
59
+ /** 平台健康(免鉴权) */
60
+ ping: () => call('GET', '/api/plugins/manifest'),
61
+
62
+ // ---- 业务资源面 ----
63
+ users: {
64
+ ...resource('/api/users'),
65
+ login: (email, password) => call('POST', '/api/users/login', { email, password }),
66
+ me: () => call('GET', '/api/users/me'),
67
+ },
68
+ lines: {
69
+ ...resource('/api/workshop/dcw/lines'),
70
+ start: (id, recipeId = '') => call('POST', `/api/workshop/dcw/lines/${id}/start`, { recipeId }),
71
+ stop: id => call('POST', `/api/workshop/dcw/lines/${id}/stop`),
72
+ },
73
+ products: resource('/api/workshop/dcw/products'),
74
+ recipes: resource('/api/workshop/dcw/recipes'),
75
+ dcwNodes: resource('/api/workshop/dcw'),
76
+ daqNodes: {
77
+ ...resource('/api/workshop/daq'),
78
+ alarms: () => call('GET', '/api/workshop/daq/alarms'),
79
+ },
80
+ templates: {
81
+ daq: () => call('GET', '/api/workshop/daq').then(d => d?.templates ?? []),
82
+ dcw: () => call('GET', '/api/workshop/dcw').then(d => d?.templates ?? []),
83
+ },
84
+ twins: resource('/api/workshop/device-twins'),
85
+ teams: resource('/api/workshop/teams'),
86
+ agents: resource('/api/workshop/agents'),
87
+ channels: resource('/api/workshop/channels'),
88
+ plugins: {
89
+ manifest: () => call('GET', '/api/plugins/manifest'),
90
+ },
91
+ }
92
+ return client
93
+ }
94
+
95
+ function toQuery(query) {
96
+ const entries = Object.entries(query ?? {}).filter(([, v]) => v !== undefined && v !== null && v !== '')
97
+ if (!entries.length) return ''
98
+ return `?${new URLSearchParams(Object.fromEntries(entries.map(([k, v]) => [k, String(v)]))).toString()}`
99
+ }
100
+
101
+ export default createPlatformClient
package/sdk/client.mjs CHANGED
@@ -5,16 +5,18 @@
5
5
  // export function setup(ctx) { ctx.on('daq.reading', d => { ... }) }
6
6
  // ctx 形态(浏览器侧,自包含、零框架依赖):
7
7
  // ctx.name / ctx.sdkVersion
8
- // ctx.hooks 客户端本地 HookBus(client:init / event:* / page:change)
9
- // ctx.on(type, fn) scene 实时事件订阅(与 WS 同源;'*' 通配)
8
+ // ctx.hooks 客户端本地 HookBus(client:init / event:* / page:change / client:destroy)
9
+ // ctx.on(type, fn) scene 实时事件订阅(与 WS 同源;'*' 通配)——自动登记 pagehide 回收
10
+ // ctx.fetch(path, …) 同源平台 API 助手(JSON;自动解信封 data)
10
11
  // ctx.el(tag, attrs, children) DOM 助手(挂到任意面板/宿主节点)
11
12
  // ctx.mount(selector|el, node) 挂载节点(缺失时挂 body 角落)
12
- // ctx.log 前缀 console
13
13
  // ctx.root 插件 UI 挂载点(懒创建,自动附加到 body,#aw-plugin-<name>)
14
+ // ctx.log 前缀 console
15
+ // ctx.dispose() 卸载:清空挂载点 + 回收订阅 + 广播 client:destroy(pagehide 自动触发)
14
16
  // ============================================================
15
17
  import { HookBus } from './hooks.mjs'
16
18
 
17
- export const CLIENT_SDK_VERSION = '0.2.2'
19
+ export const CLIENT_SDK_VERSION = '0.3.0'
18
20
 
19
21
  function el(tag, attrs = {}, children = []) {
20
22
  const node = document.createElement(tag)
@@ -30,16 +32,36 @@ function el(tag, attrs = {}, children = []) {
30
32
  return node
31
33
  }
32
34
 
33
- export function createClientContext({ name, eventBridge }) {
35
+ export function createClientContext({ name, eventBridge, baseUrl = '' }) {
34
36
  const hooks = new HookBus({ name: `client:${name}`, onError: err => console.warn(`[aw-plugin:${name}]`, err) })
37
+ const disposables = []
35
38
  let rootEl = null
39
+ let disposed = false
36
40
 
37
41
  const ctx = {
38
42
  name,
39
43
  sdkVersion: CLIENT_SDK_VERSION,
40
44
  hooks,
41
- on: (type, fn) => (type === 'event:*' ? hooks.on('*', fn) : hooks.on(`event:${type}`, fn)),
42
- off: (type, fn) => (type === 'event:*' ? hooks.off('*', fn) : hooks.off(`event:${type}`, fn)),
45
+ on: (type, fn) => {
46
+ const off = type === 'event:*' ? hooks.on('*', fn) : hooks.on(`event:${type}`, fn)
47
+ disposables.push(off)
48
+ return off
49
+ },
50
+ /** 同源平台 API(JSON;自动解 {data} 信封;非 2xx 抛错) */
51
+ fetch: async (path, opt = {}) => {
52
+ const res = await fetch(`${baseUrl}${path}`, {
53
+ headers: { accept: 'application/json', ...(opt.body !== undefined ? { 'content-type': 'application/json' } : {}), ...(opt.headers ?? {}) },
54
+ method: opt.method ?? (opt.body !== undefined ? 'POST' : 'GET'),
55
+ body: opt.body !== undefined ? JSON.stringify(opt.body) : undefined,
56
+ })
57
+ const json = await res.json().catch(() => null)
58
+ if (!res.ok) {
59
+ const err = new Error(json?.message ?? `HTTP ${res.status} ${path}`)
60
+ err.status = res.status
61
+ throw err
62
+ }
63
+ return json && typeof json === 'object' && 'data' in json ? json.data : json
64
+ },
43
65
  el,
44
66
  log: {
45
67
  info: (...a) => console.info(`[aw-plugin:${name}]`, ...a),
@@ -61,13 +83,35 @@ export function createClientContext({ name, eventBridge }) {
61
83
  ;(host ?? ctx.root()).append(node)
62
84
  return node
63
85
  },
86
+ /** 卸载:回收订阅 + 清空挂载点 + 广播 client:destroy(幂等) */
87
+ dispose: () => {
88
+ if (disposed) return
89
+ disposed = true
90
+ void hooks.emit('client:destroy', { name })
91
+ for (const d of disposables.splice(0)) {
92
+ try {
93
+ d()
94
+ }
95
+ catch { /* 单个回收失败不阻断 */ }
96
+ }
97
+ rootEl?.remove()
98
+ rootEl = null
99
+ },
64
100
  }
65
101
 
66
102
  // scene 事件桥 → ctx.hooks(event:<type> 与 '*' 均可订阅)
67
103
  if (eventBridge) {
68
- eventBridge((type, payload) => {
104
+ const offBridge = eventBridge((type, payload) => {
69
105
  void hooks.emit(`event:${type}`, payload)
70
106
  })
107
+ disposables.push(offBridge)
108
+ }
109
+
110
+ // 页面卸载自动回收(pagehide 覆盖 bfcache 场景)
111
+ if (typeof document !== 'undefined') {
112
+ document.addEventListener('visibilitychange', () => {
113
+ if (document.visibilityState === 'hidden') ctx.dispose()
114
+ }, { once: true })
71
115
  }
72
116
 
73
117
  return ctx
package/sdk/context.mjs CHANGED
@@ -1,22 +1,26 @@
1
1
  // ============================================================
2
2
  // AgentWorkShop SDK — 服务端插件上下文工厂(宿主调用;插件经 setup(ctx) 获得)
3
3
  // ------------------------------------------------------------
4
- // ctx 形态:
5
- // ctx.name / ctx.scope('home'|'project') / ctx.dir 身份与目录
6
- // ctx.hooks HookBus 生命周期钩子(on/once/off/emit)
7
- // ctx.logger 前缀日志(info/warn/error/debug) — 插件名自动前缀
8
- // ctx.config { get(key), all() } — 有效配置只读(四层引擎)
9
- // ctx.dataDir / ctx.kv { get,set,all,bump } 插件私有持久化(json 落配置根 data/plugins/<name>)
10
- // ctx.route(method, path, handler) — 注册插件 API → /api/plugins/<name><path>
11
- // ctx.http { get,post } 带超时 fetch — 对外请求( http/https)
12
- // ctx.events { on(type,fn) } — scene 实时事件订阅(event:* 桥的糖)
13
- // ctx.paths { home, configRoot, dataDir } — 配置根信息
4
+ // ctx 形态(运行时完整变量面):
5
+ // 身份 ctx.name / ctx.scope('home'|'project') / ctx.dir / ctx.sdkVersion
6
+ // 钩子 ctx.hooks HookBus(生命周期 + event:*) [SDK]
7
+ // 日志 ctx.logger { debug, info, warn, error } 插件名前缀 [SDK]
8
+ // 配置 ctx.config { get(key), all(), onChange(fn) } [SDK]
9
+ // 存储 ctx.kv { get,set,all,bump } 内存态+防抖落盘 [SDK]
10
+ // 定时 ctx.timer { setInterval, setTimeout } 服务关闭自动回收 [SDK]
11
+ // 清理 ctx.onDispose(fn) / ctx.subscriptions [SDK]
12
+ // 路由 ctx.route(method, path, handler) /api/plugins/<name>… [SDK]
13
+ // 平台 ctx.api 平台 REST 客户端(lines/daq/dcw/twins/teams…) [SDK]
14
+ // 网络 ctx.http { get, post } 带超时 fetch(仅 http/https) [SDK]
15
+ // 事件 ctx.events { on(type,fn), off } scene 实时事件 [SDK]
16
+ // 路径 ctx.paths { home, configRoot, dataDir } [SDK]
14
17
  // ============================================================
15
18
  import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync } from 'node:fs'
16
- import { dirname, join, resolve } from 'node:path'
19
+ import { join, resolve } from 'node:path'
17
20
  import { HookBus } from './hooks.mjs'
21
+ import { createPlatformClient } from './api.mjs'
18
22
 
19
- export const SDK_VERSION = '0.2.2'
23
+ export const SDK_VERSION = '0.3.0'
20
24
 
21
25
  /** 允许的对外请求协议守卫(拒绝 file:/data: 等;宿主/插件同守此规则) */
22
26
  function safeUrl(raw, timeoutMs = 8000) {
@@ -28,7 +32,7 @@ function safeUrl(raw, timeoutMs = 8000) {
28
32
 
29
33
  /**
30
34
  * 创建服务端插件上下文。
31
- * @param {{ name, scope, dir, hooks, logger, config, paths, emitter }} opts 宿主装配
35
+ * @param {{ name, scope, dir, hooks, logger, config, paths, emitter, onDispose, selfOrigin }} opts 宿主装配
32
36
  */
33
37
  export function createPluginContext(opts) {
34
38
  const { name, scope, dir, hooks, config, paths, emitter } = opts
@@ -41,6 +45,29 @@ export function createPluginContext(opts) {
41
45
  ...(opts.logger ?? {}),
42
46
  }
43
47
 
48
+ // ---- 订阅回收(VSCode subscriptions 范式):服务关闭时宿主逐个调用 ----
49
+ const disposables = []
50
+ const onDispose = (fn) => {
51
+ if (typeof fn === 'function') disposables.push(fn)
52
+ return fn
53
+ }
54
+
55
+ // ---- 定时器:自动登记回收,杜绝插件定时器泄漏 ----
56
+ const timer = {
57
+ setInterval: (fn, ms, ...rest) => {
58
+ const id = setInterval(fn, ms, ...rest)
59
+ if (typeof id === 'object' && id !== null && 'unref' in id) id.unref?.()
60
+ onDispose(() => clearInterval(id))
61
+ return id
62
+ },
63
+ setTimeout: (fn, ms, ...rest) => {
64
+ const id = setTimeout(fn, ms, ...rest)
65
+ if (typeof id === 'object' && id !== null && 'unref' in id) id.unref?.()
66
+ onDispose(() => clearTimeout(id))
67
+ return id
68
+ },
69
+ }
70
+
44
71
  // ---- 插件私有持久化:内存态为准 + 200ms 防抖落盘 —— 高频钩子(daq:sample)
45
72
  // 与低频钩子(line:stop)并发时无 read-modify-write 竞态(JS 同步内存操作原子) ----
46
73
  const kvDir = join(paths.dataDir, 'plugins', name)
@@ -77,6 +104,8 @@ export function createPluginContext(opts) {
77
104
  config: {
78
105
  get: key => config?.effective?.[key],
79
106
  all: () => ({ ...config?.effective }),
107
+ /** 运行时覆盖变更订阅(runtime-settings.json 变化;宿主 fs.watch 驱动) */
108
+ onChange: fn => hooks.on('config:changed', fn),
80
109
  },
81
110
  paths: { ...paths },
82
111
  dataDir: kvDir,
@@ -94,7 +123,21 @@ export function createPluginContext(opts) {
94
123
  return kvState[key]
95
124
  },
96
125
  },
126
+ timer,
127
+ onDispose,
128
+ /** 订阅式清理对象({ dispose(){} })集中登记 */
129
+ subscriptions: {
130
+ add: (d) => {
131
+ disposables.push(typeof d === 'function' ? d : (...a) => d.dispose?.(...a))
132
+ return d
133
+ },
134
+ },
97
135
  route: (method, path, handler) => emitter?.registerRoute(name, method, path, handler),
136
+ /** 平台 REST 客户端(自环 origin 延迟解析;鉴权端点请 ctx.api.setToken(token)) */
137
+ api: createPlatformClient({
138
+ baseUrl: typeof opts.selfOrigin === 'function' ? opts.selfOrigin : () => opts.selfOrigin,
139
+ logger,
140
+ }),
98
141
  http: {
99
142
  get: (url, opts2 = {}) => fetch(url, { ...safeUrl(url, opts2.timeoutMs), ...opts2 }),
100
143
  post: (url, body, opts2 = {}) => fetch(url, {
@@ -166,8 +209,6 @@ export function pluginKvExists(dataDir, name) {
166
209
  return existsSync(join(resolve(dataDir), 'plugins', name, 'kv.json'))
167
210
  }
168
211
 
169
- export { dirname }
170
-
171
212
  export default {
172
213
  SDK_VERSION,
173
214
  HookBus,
@@ -0,0 +1,135 @@
1
+ // AgentWorkShop SDK 类型声明(agentworkshop/sdk)
2
+ import type { HookBus } from './hooks.mjs'
3
+
4
+ export declare const SDK_VERSION: string
5
+
6
+ /** 生命周期钩子总线:异步串行、错误隔离、'*' 通配、连续失败自动熔断 */
7
+ export declare class HookBus {
8
+ constructor(opts?: { name?: string, onError?: (err: Error, meta?: { bus?: string, type?: string, fails?: number }) => void })
9
+ on(type: string, fn: (payload: any) => any): () => void
10
+ once(type: string, fn: (payload: any) => any): () => void
11
+ off(type: string, fn: (payload: any) => any): void
12
+ emit(type: string, payload?: any): Promise<any>
13
+ readonly size: number
14
+ }
15
+
16
+ export interface PluginLogger {
17
+ debug(...args: unknown[]): void
18
+ info(...args: unknown[]): void
19
+ warn(...args: unknown[]): void
20
+ error(...args: unknown[]): void
21
+ }
22
+
23
+ export interface PluginKv {
24
+ get(key: string): any
25
+ set(key: string, value: any): any
26
+ all(): Record<string, any>
27
+ bump(key: string, by?: number): number
28
+ }
29
+
30
+ export interface PluginHttp {
31
+ get(url: string, opts?: { timeoutMs?: number, headers?: Record<string, string> }): Promise<Response>
32
+ post(url: string, body?: unknown, opts?: { timeoutMs?: number, headers?: Record<string, string> }): Promise<Response>
33
+ }
34
+
35
+ /** 平台 REST 客户端(SDK 作为项目服务 client 的门面) */
36
+ export interface PlatformClient {
37
+ call<T = any>(method: string, path: string, body?: unknown): Promise<T>
38
+ get<T = any>(path: string, query?: Record<string, unknown>): Promise<T>
39
+ post<T = any>(path: string, body?: unknown): Promise<T>
40
+ patch<T = any>(path: string, body?: unknown): Promise<T>
41
+ delete<T = any>(path: string): Promise<T>
42
+ setToken(token: string | null): PlatformClient
43
+ ping(): Promise<any>
44
+ users: { list(q?: any): Promise<any>, get(id: string): Promise<any>, create(b: any): Promise<any>, login(email: string, password: string): Promise<any>, me(): Promise<any> }
45
+ lines: { list(q?: any): Promise<any>, get(id: string): Promise<any>, create(b: any): Promise<any>, update(id: string, p: any): Promise<any>, remove(id: string): Promise<any>, start(id: string, recipeId?: string): Promise<any>, stop(id: string): Promise<any> }
46
+ products: { list(q?: any): Promise<any>, create(b: any): Promise<any> }
47
+ recipes: { list(q?: any): Promise<any>, create(b: any): Promise<any> }
48
+ dcwNodes: { list(q?: any): Promise<any>, create(b: any): Promise<any> }
49
+ daqNodes: { list(q?: any): Promise<any>, create(b: any): Promise<any>, alarms(): Promise<any> }
50
+ templates: { daq(): Promise<any[]>, dcw(): Promise<any[]> }
51
+ twins: { list(q?: any): Promise<any>, create(b: any): Promise<any> }
52
+ teams: { list(q?: any): Promise<any>, create(b: any): Promise<any> }
53
+ agents: { list(q?: any): Promise<any>, create(b: any): Promise<any> }
54
+ channels: { list(q?: any): Promise<any> }
55
+ plugins: { manifest(): Promise<any> }
56
+ }
57
+
58
+ export interface PluginContext {
59
+ name: string
60
+ scope: 'project' | 'user'
61
+ dir: string
62
+ sdkVersion: string
63
+ hooks: HookBus
64
+ logger: PluginLogger
65
+ config: {
66
+ get(key: string): any
67
+ all(): Record<string, any>
68
+ onChange(fn: (payload?: { at: string }) => any): () => void
69
+ }
70
+ paths: { home: string, configRoot: string, dataDir: string }
71
+ dataDir: string
72
+ kv: PluginKv
73
+ timer: {
74
+ setInterval(fn: (...args: any[]) => void, ms: number, ...rest: any[]): NodeJS.Timeout
75
+ setTimeout(fn: (...args: any[]) => void, ms: number, ...rest: any[]): NodeJS.Timeout
76
+ }
77
+ onDispose(fn: () => any): () => void
78
+ subscriptions: { add(d: { dispose(): any } | (() => any)): any }
79
+ route(method: string, path: string, handler: (event: any) => any): boolean
80
+ api: PlatformClient
81
+ http: PluginHttp
82
+ events: { on(type: string, fn: (payload: any) => any): () => void, off(type: string, fn: (payload: any) => any): void }
83
+ }
84
+
85
+ export interface PluginRouteDef {
86
+ method?: string
87
+ path: string
88
+ handler: (event: any) => any
89
+ }
90
+
91
+ export interface PluginDef {
92
+ name: string
93
+ version?: string
94
+ description?: string
95
+ setup?(ctx: PluginContext): void | Promise<void>
96
+ client?: string
97
+ routes?: PluginRouteDef[]
98
+ }
99
+
100
+ /** 显式糖:类型化定义插件(宿主同样接受裸对象导出) */
101
+ export declare function definePlugin(def: PluginDef): PluginDef
102
+
103
+ export declare function createPluginContext(opts: Record<string, any>): PluginContext
104
+ export declare function createRouteTable(): {
105
+ register(name: string, method: string, path: string, handler: (event: any) => any): boolean
106
+ resolve(name: string, method: string, path: string): ((event: any) => any) | null
107
+ byPlugin(name: string): Array<{ method: string, path: string }>
108
+ readonly size: number
109
+ }
110
+ export declare function validatePluginModule(mod: any, source: string): { ok: boolean, def?: PluginDef, error?: string }
111
+ export declare function pluginKvExists(dataDir: string, name: string): boolean
112
+
113
+ export declare function createPlatformClient(opts?: { baseUrl?: string, token?: string, logger?: PluginLogger, timeoutMs?: number }): PlatformClient
114
+
115
+ export declare const LIFECYCLE_EVENTS: readonly ['plugin:host:init', 'config:changed', 'event:*', 'daq:sample', 'dcw:write', 'line:start', 'line:stop', 'server:close']
116
+ export declare const CLIENT_EVENTS: readonly ['client:init', 'event:*', 'page:change', 'client:destroy']
117
+
118
+ /** 客户端插件上下文(sdk/client.mjs) */
119
+ export declare const CLIENT_SDK_VERSION: string
120
+ export interface ClientContext {
121
+ name: string
122
+ sdkVersion: string
123
+ hooks: HookBus
124
+ on(type: string, fn: (payload: any) => any): () => void
125
+ fetch<T = any>(path: string, opt?: { method?: string, body?: unknown, headers?: Record<string, string> }): Promise<T>
126
+ el(tag: string, attrs?: Record<string, unknown>, children?: Array<Node | string>): HTMLElement
127
+ mount(target: string | Element, node: Node): Node
128
+ root(): HTMLElement
129
+ log: PluginLogger
130
+ dispose(): void
131
+ }
132
+ export declare function createClientContext(opts: { name: string, eventBridge?: (fn: (type: string, payload: any) => void) => (() => void), baseUrl?: string }): ClientContext
133
+
134
+ declare const _default: Record<string, unknown>
135
+ export default _default
package/sdk/index.mjs CHANGED
@@ -15,29 +15,15 @@
15
15
  // ============================================================
16
16
  import { SDK_VERSION, definePlugin, createPluginContext, createRouteTable, validatePluginModule, pluginKvExists } from './context.mjs'
17
17
  import { HookBus } from './hooks.mjs'
18
+ import { createPlatformClient } from './api.mjs'
18
19
  import { CLIENT_SDK_VERSION, createClientContext } from './client.mjs'
20
+ import { LIFECYCLE_EVENTS, CLIENT_EVENTS } from './lifecycle.mjs'
19
21
 
20
22
  export { SDK_VERSION, definePlugin, createPluginContext, createRouteTable, validatePluginModule, pluginKvExists } from './context.mjs'
21
23
  export { HookBus } from './hooks.mjs'
24
+ export { createPlatformClient } from './api.mjs'
22
25
  export { CLIENT_SDK_VERSION, createClientContext } from './client.mjs'
23
-
24
- /** 服务端生命周期事件清单(宿主触发;文档见 docs/plugins.md) */
25
- export const LIFECYCLE_EVENTS = Object.freeze([
26
- 'plugin:host:init',
27
- 'event:*',
28
- 'daq:sample',
29
- 'dcw:write',
30
- 'line:start',
31
- 'line:stop',
32
- 'server:close',
33
- ])
34
-
35
- /** 客户端生命周期事件清单 */
36
- export const CLIENT_EVENTS = Object.freeze([
37
- 'client:init',
38
- 'event:*',
39
- 'page:change',
40
- ])
26
+ export { LIFECYCLE_EVENTS, CLIENT_EVENTS } from './lifecycle.mjs'
41
27
 
42
28
  export default {
43
29
  SDK_VERSION,
@@ -47,6 +33,7 @@ export default {
47
33
  createRouteTable,
48
34
  validatePluginModule,
49
35
  pluginKvExists,
36
+ createPlatformClient,
50
37
  CLIENT_SDK_VERSION,
51
38
  createClientContext,
52
39
  LIFECYCLE_EVENTS,
@@ -0,0 +1,25 @@
1
+ // ============================================================
2
+ // AgentWorkShop SDK — 生命周期事件清单(宿主触发;单一事实源)
3
+ // ============================================================
4
+
5
+ /** 服务端生命周期事件清单(宿主触发;文档见 docs/plugins.md) */
6
+ export const LIFECYCLE_EVENTS = Object.freeze([
7
+ 'plugin:host:init',
8
+ 'config:changed',
9
+ 'event:*',
10
+ 'daq:sample',
11
+ 'dcw:write',
12
+ 'line:start',
13
+ 'line:stop',
14
+ 'server:close',
15
+ ])
16
+
17
+ /** 客户端生命周期事件清单 */
18
+ export const CLIENT_EVENTS = Object.freeze([
19
+ 'client:init',
20
+ 'event:*',
21
+ 'page:change',
22
+ 'client:destroy',
23
+ ])
24
+
25
+ export default { LIFECYCLE_EVENTS, CLIENT_EVENTS }
@@ -3,10 +3,10 @@
3
3
  * 插件经 ctx.route(method, path, handler) 注册;handler(event) 返回值由 nitro 序列化。
4
4
  * 鉴权由插件自行处理(v1 不强制;可经 resolveUser 复用业务鉴权)。
5
5
  */
6
- import { defineEventHandler, createError } from 'h3'
6
+ import { defineEventHandler, createError, readBody } from 'h3'
7
7
  import { getPluginHost } from '@/server/services/workshop/plugins/host.mjs'
8
8
 
9
- export default defineEventHandler((event) => {
9
+ export default defineEventHandler(async (event) => {
10
10
  const host = getPluginHost()
11
11
  if (!host) throw createError({ statusCode: 503, statusMessage: 'plugin host not ready' })
12
12
  const name = String(event.context.params?.name ?? '')
@@ -15,5 +15,8 @@ export default defineEventHandler((event) => {
15
15
  if (!handler) {
16
16
  throw createError({ statusCode: 404, statusMessage: `plugin route not found: ${event.method} /api/plugins/${name}${path}` })
17
17
  }
18
+ // 预读 body 挂到 event(插件 handler 无 h3 导入能力,经 event.awBody 消费)
19
+ const awBody = await readBody(event).catch(() => undefined)
20
+ ;(event as Record<string, unknown>).awBody = awBody
18
21
  return handler(event)
19
22
  })