agentworkshop 0.3.0 → 0.4.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/LICENSE +91 -0
- package/README-zh.md +431 -424
- package/README.md +10 -3
- package/app/components/AppSidebar.vue +1 -0
- package/app/pages/plugins/index.vue +258 -0
- package/app/plugins/aw-plugins.client.ts +81 -48
- package/cli/commands/plugin.mjs +57 -7
- package/docs/plugins.md +133 -63
- package/docs/sdk.md +265 -0
- package/i18n/locales/en.ts +17 -0
- package/i18n/locales/zh-CN.ts +17 -0
- package/package.json +4 -2
- package/scripts/_dbg-docs-site-shot.mjs +33 -0
- package/scripts/home-bootstrap.mjs +26 -1
- package/sdk/client.mjs +6 -2
- package/sdk/examples/line-sentinel/client.mjs +31 -0
- package/sdk/examples/line-sentinel/index.mjs +102 -0
- package/sdk/examples/ops-notifier/client.mjs +23 -0
- package/sdk/examples/ops-notifier/index.mjs +34 -0
- package/server/api/workshop/plugins/[name]/disable.post.ts +19 -0
- package/server/api/workshop/plugins/[name]/enable.post.ts +19 -0
- package/server/api/workshop/plugins/[name]/index.get.ts +14 -0
- package/server/api/workshop/plugins/index.get.ts +17 -0
- package/server/data/daqs.json +68 -46
- package/server/data/dcw-lines.json +7 -0
- package/server/data/dcw-products.json +7 -0
- package/server/data/dcw-recipes.json +20 -0
- package/server/data/dcw-rollback.json +1 -1
- package/server/data/dcw-runs.json +25 -18
- package/server/data/dcws.json +21 -0
- package/server/data/line-runs.json +30 -20
- package/server/services/workshop/plugins/host.mjs +177 -49
|
@@ -1866,5 +1866,25 @@
|
|
|
1866
1866
|
"lastGoodRunId": null,
|
|
1867
1867
|
"createdAt": "2026-09-02T04:20:58.370Z",
|
|
1868
1868
|
"updatedAt": "2026-09-02T04:20:58.370Z"
|
|
1869
|
+
},
|
|
1870
|
+
{
|
|
1871
|
+
"id": "rc-21479838",
|
|
1872
|
+
"productId": "pd-d919d4bf",
|
|
1873
|
+
"lineId": "ln-f223008a",
|
|
1874
|
+
"name": "配方-mtjo000v",
|
|
1875
|
+
"description": "",
|
|
1876
|
+
"params": [
|
|
1877
|
+
{
|
|
1878
|
+
"nodeId": "dw-6ab87477",
|
|
1879
|
+
"templateRef": "temp-sp",
|
|
1880
|
+
"value": 120
|
|
1881
|
+
}
|
|
1882
|
+
],
|
|
1883
|
+
"daqWindows": [],
|
|
1884
|
+
"version": 1,
|
|
1885
|
+
"paramsHistory": [],
|
|
1886
|
+
"lastGoodRunId": null,
|
|
1887
|
+
"createdAt": "2026-09-02T05:37:46.125Z",
|
|
1888
|
+
"updatedAt": "2026-09-02T05:37:46.125Z"
|
|
1869
1889
|
}
|
|
1870
1890
|
]
|
|
@@ -1,22 +1,4 @@
|
|
|
1
1
|
[
|
|
2
|
-
{
|
|
3
|
-
"id": "rr-127c6247",
|
|
4
|
-
"recipeId": "rc-28c5aeb8",
|
|
5
|
-
"recipeName": "终审配方A",
|
|
6
|
-
"productId": "pd-54f429af",
|
|
7
|
-
"lineId": "ln-c5145881",
|
|
8
|
-
"startedAt": "2026-08-29T15:05:46.202Z",
|
|
9
|
-
"endedAt": "2026-08-29T15:05:58.337Z",
|
|
10
|
-
"results": [
|
|
11
|
-
{
|
|
12
|
-
"templateRef": "dcw-temp-sp",
|
|
13
|
-
"nodeId": "dw-c5912d51",
|
|
14
|
-
"ok": true,
|
|
15
|
-
"message": "写入并回读一致:182 → raw 1280,回读 182(标定后物理值 182)",
|
|
16
|
-
"value": 182
|
|
17
|
-
}
|
|
18
|
-
]
|
|
19
|
-
},
|
|
20
2
|
{
|
|
21
3
|
"id": "rr-a5f58b1a",
|
|
22
4
|
"recipeId": "rc-7511c1bb",
|
|
@@ -4706,5 +4688,30 @@
|
|
|
4706
4688
|
"value": 120
|
|
4707
4689
|
}
|
|
4708
4690
|
]
|
|
4691
|
+
},
|
|
4692
|
+
{
|
|
4693
|
+
"id": "rr-06caf6f4",
|
|
4694
|
+
"recipeId": "rc-21479838",
|
|
4695
|
+
"recipeName": "配方-mtjo000v",
|
|
4696
|
+
"productId": "pd-d919d4bf",
|
|
4697
|
+
"lineId": "ln-f223008a",
|
|
4698
|
+
"startedAt": "2026-09-02T05:37:50.743Z",
|
|
4699
|
+
"endedAt": null,
|
|
4700
|
+
"results": [
|
|
4701
|
+
{
|
|
4702
|
+
"templateRef": "temp-sp",
|
|
4703
|
+
"nodeId": "dw-6ab87477",
|
|
4704
|
+
"ok": false,
|
|
4705
|
+
"message": "设定值 120℃ 越出工艺安全量程 [150, 200] ℃",
|
|
4706
|
+
"value": 120
|
|
4707
|
+
}
|
|
4708
|
+
],
|
|
4709
|
+
"paramsSnapshot": [
|
|
4710
|
+
{
|
|
4711
|
+
"nodeId": "dw-6ab87477",
|
|
4712
|
+
"templateRef": "temp-sp",
|
|
4713
|
+
"value": 120
|
|
4714
|
+
}
|
|
4715
|
+
]
|
|
4709
4716
|
}
|
|
4710
4717
|
]
|
package/server/data/dcws.json
CHANGED
|
@@ -1963,5 +1963,26 @@
|
|
|
1963
1963
|
"state": "idle",
|
|
1964
1964
|
"lastError": null,
|
|
1965
1965
|
"createdAt": "2026-09-02T04:20:58.320Z"
|
|
1966
|
+
},
|
|
1967
|
+
{
|
|
1968
|
+
"id": "dw-6ab87477",
|
|
1969
|
+
"templateRef": "temp-sp",
|
|
1970
|
+
"name": "温控",
|
|
1971
|
+
"driver": "mock",
|
|
1972
|
+
"enabled": true,
|
|
1973
|
+
"holdIntervalMs": null,
|
|
1974
|
+
"unit": "℃",
|
|
1975
|
+
"decimals": 1,
|
|
1976
|
+
"min": 150,
|
|
1977
|
+
"max": 200,
|
|
1978
|
+
"deviceBindingId": null,
|
|
1979
|
+
"driverConfig": {},
|
|
1980
|
+
"lineId": "ln-f223008a",
|
|
1981
|
+
"value": null,
|
|
1982
|
+
"lastAckAt": null,
|
|
1983
|
+
"lastWriteAt": null,
|
|
1984
|
+
"state": "idle",
|
|
1985
|
+
"lastError": null,
|
|
1986
|
+
"createdAt": "2026-09-02T05:37:46.098Z"
|
|
1966
1987
|
}
|
|
1967
1988
|
]
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"productId": "pd-3ad54846",
|
|
8
8
|
"productName": "光学膜 BOPP",
|
|
9
9
|
"startedAt": "2026-08-31T16:20:13.707Z",
|
|
10
|
-
"taggedSamples":
|
|
10
|
+
"taggedSamples": 29766
|
|
11
11
|
},
|
|
12
12
|
{
|
|
13
13
|
"lineId": "ln-c377a6bf",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"productId": "pd-7e69e0ee",
|
|
18
18
|
"productName": "警告验证产品-k2xh",
|
|
19
19
|
"startedAt": "2026-09-01T03:23:18.233Z",
|
|
20
|
-
"taggedSamples":
|
|
20
|
+
"taggedSamples": 11693
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
23
|
"lineId": "ln-9224d3d7",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"productId": "pd-910c0fa8",
|
|
28
28
|
"productName": "新协议产品-9xhn",
|
|
29
29
|
"startedAt": "2026-09-01T05:59:24.062Z",
|
|
30
|
-
"taggedSamples":
|
|
30
|
+
"taggedSamples": 14666
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
"lineId": "ln-386c53ed",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"productId": "pd-2d4f9357",
|
|
38
38
|
"productName": "新协议产品-6ofk",
|
|
39
39
|
"startedAt": "2026-09-01T06:05:53.063Z",
|
|
40
|
-
"taggedSamples":
|
|
40
|
+
"taggedSamples": 14956
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
"lineId": "ln-5957f6db",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"productId": "pd-a3ed5047",
|
|
48
48
|
"productName": "新协议产品-x4hd",
|
|
49
49
|
"startedAt": "2026-09-01T06:09:02.148Z",
|
|
50
|
-
"taggedSamples":
|
|
50
|
+
"taggedSamples": 14542
|
|
51
51
|
},
|
|
52
52
|
{
|
|
53
53
|
"lineId": "ln-9ec42f08",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"productId": "pd-5de729c0",
|
|
58
58
|
"productName": "新协议产品-teti",
|
|
59
59
|
"startedAt": "2026-09-01T06:13:44.315Z",
|
|
60
|
-
"taggedSamples":
|
|
60
|
+
"taggedSamples": 14521
|
|
61
61
|
},
|
|
62
62
|
{
|
|
63
63
|
"lineId": "ln-69e37924",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"productId": "pd-45eb8d43",
|
|
68
68
|
"productName": "新协议产品-fg8t",
|
|
69
69
|
"startedAt": "2026-09-01T06:16:47.469Z",
|
|
70
|
-
"taggedSamples":
|
|
70
|
+
"taggedSamples": 14554
|
|
71
71
|
},
|
|
72
72
|
{
|
|
73
73
|
"lineId": "ln-c269e692",
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"productId": "pd-b3bd0191",
|
|
78
78
|
"productName": "新协议产品-w2sp",
|
|
79
79
|
"startedAt": "2026-09-01T06:56:05.893Z",
|
|
80
|
-
"taggedSamples":
|
|
80
|
+
"taggedSamples": 14605
|
|
81
81
|
},
|
|
82
82
|
{
|
|
83
83
|
"lineId": "ln-229d1637",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"productId": "pd-06b896f9",
|
|
88
88
|
"productName": "新协议产品-lp4h",
|
|
89
89
|
"startedAt": "2026-09-01T06:57:29.288Z",
|
|
90
|
-
"taggedSamples":
|
|
90
|
+
"taggedSamples": 14477
|
|
91
91
|
},
|
|
92
92
|
{
|
|
93
93
|
"lineId": "ln-9546f91f",
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
"productId": "pd-53e55078",
|
|
98
98
|
"productName": "新协议产品-jri6",
|
|
99
99
|
"startedAt": "2026-09-01T07:00:25.059Z",
|
|
100
|
-
"taggedSamples":
|
|
100
|
+
"taggedSamples": 13900
|
|
101
101
|
},
|
|
102
102
|
{
|
|
103
103
|
"lineId": "ln-087aad5b",
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
"productId": "pd-e7e969af",
|
|
108
108
|
"productName": "新协议产品-4c2d",
|
|
109
109
|
"startedAt": "2026-09-01T07:09:07.307Z",
|
|
110
|
-
"taggedSamples":
|
|
110
|
+
"taggedSamples": 12891
|
|
111
111
|
},
|
|
112
112
|
{
|
|
113
113
|
"lineId": "ln-97d44f8a",
|
|
@@ -117,7 +117,7 @@
|
|
|
117
117
|
"productId": "pd-28a3ab2c",
|
|
118
118
|
"productName": "新协议产品-b2ni",
|
|
119
119
|
"startedAt": "2026-09-01T07:13:44.912Z",
|
|
120
|
-
"taggedSamples":
|
|
120
|
+
"taggedSamples": 12740
|
|
121
121
|
},
|
|
122
122
|
{
|
|
123
123
|
"lineId": "ln-a9551770",
|
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
"productId": "pd-0c6a64c8",
|
|
128
128
|
"productName": "新协议产品-uwkf",
|
|
129
129
|
"startedAt": "2026-09-01T07:15:40.113Z",
|
|
130
|
-
"taggedSamples":
|
|
130
|
+
"taggedSamples": 12385
|
|
131
131
|
},
|
|
132
132
|
{
|
|
133
133
|
"lineId": "ln-844ad82f",
|
|
@@ -137,7 +137,7 @@
|
|
|
137
137
|
"productId": "pd-6932b24f",
|
|
138
138
|
"productName": "新协议产品-kk6l",
|
|
139
139
|
"startedAt": "2026-09-01T09:06:55.728Z",
|
|
140
|
-
"taggedSamples":
|
|
140
|
+
"taggedSamples": 10540
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
143
|
"lineId": "ln-af002514",
|
|
@@ -147,7 +147,7 @@
|
|
|
147
147
|
"productId": "pd-6348a595",
|
|
148
148
|
"productName": "0.8mm 光学膜",
|
|
149
149
|
"startedAt": "2026-09-01T14:06:28.766Z",
|
|
150
|
-
"taggedSamples":
|
|
150
|
+
"taggedSamples": 956
|
|
151
151
|
},
|
|
152
152
|
{
|
|
153
153
|
"lineId": "ln-0eccf6ba",
|
|
@@ -157,7 +157,7 @@
|
|
|
157
157
|
"productId": "pd-4b71be77",
|
|
158
158
|
"productName": "哨兵产品-mtjku36w",
|
|
159
159
|
"startedAt": "2026-09-02T04:09:11.482Z",
|
|
160
|
-
"taggedSamples":
|
|
160
|
+
"taggedSamples": 6
|
|
161
161
|
},
|
|
162
162
|
{
|
|
163
163
|
"lineId": "ln-e984246e",
|
|
@@ -167,7 +167,7 @@
|
|
|
167
167
|
"productId": "pd-bb1e0bed",
|
|
168
168
|
"productName": "产品C-mtjkwyhu",
|
|
169
169
|
"startedAt": "2026-09-02T04:11:26.583Z",
|
|
170
|
-
"taggedSamples":
|
|
170
|
+
"taggedSamples": 5
|
|
171
171
|
},
|
|
172
172
|
{
|
|
173
173
|
"lineId": "ln-1fe0b5bc",
|
|
@@ -177,7 +177,7 @@
|
|
|
177
177
|
"productId": "pd-9f2d9507",
|
|
178
178
|
"productName": "产品D-mtjkzcfr",
|
|
179
179
|
"startedAt": "2026-09-02T04:13:17.902Z",
|
|
180
|
-
"taggedSamples":
|
|
180
|
+
"taggedSamples": 4
|
|
181
181
|
},
|
|
182
182
|
{
|
|
183
183
|
"lineId": "ln-d826473e",
|
|
@@ -187,7 +187,7 @@
|
|
|
187
187
|
"productId": "pd-d230255e",
|
|
188
188
|
"productName": "产品E-mtjl43pv",
|
|
189
189
|
"startedAt": "2026-09-02T04:16:59.891Z",
|
|
190
|
-
"taggedSamples":
|
|
190
|
+
"taggedSamples": 3
|
|
191
191
|
},
|
|
192
192
|
{
|
|
193
193
|
"lineId": "ln-156abb71",
|
|
@@ -197,7 +197,7 @@
|
|
|
197
197
|
"productId": "pd-6065b4ba",
|
|
198
198
|
"productName": "产品F-mtjl60qf",
|
|
199
199
|
"startedAt": "2026-09-02T04:18:29.340Z",
|
|
200
|
-
"taggedSamples":
|
|
200
|
+
"taggedSamples": 2
|
|
201
201
|
},
|
|
202
202
|
{
|
|
203
203
|
"lineId": "ln-bac43927",
|
|
@@ -207,6 +207,16 @@
|
|
|
207
207
|
"productId": "pd-52f54e33",
|
|
208
208
|
"productName": "产品G-mtjl98nl",
|
|
209
209
|
"startedAt": "2026-09-02T04:20:59.682Z",
|
|
210
|
+
"taggedSamples": 1
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"lineId": "ln-f223008a",
|
|
214
|
+
"runId": "rr-06caf6f4",
|
|
215
|
+
"recipeId": "rc-21479838",
|
|
216
|
+
"recipeName": "配方-mtjo000v",
|
|
217
|
+
"productId": "pd-d919d4bf",
|
|
218
|
+
"productName": "产品-mtjo000v",
|
|
219
|
+
"startedAt": "2026-09-02T05:37:50.743Z",
|
|
210
220
|
"taggedSamples": 0
|
|
211
221
|
}
|
|
212
222
|
]
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// ============================================================
|
|
2
|
-
// AgentWorkShop 插件宿主 —— 发现 / 装载 /
|
|
2
|
+
// AgentWorkShop 插件宿主 —— 发现 / 装载 / 启停状态 / 热重载 / 路由表
|
|
3
3
|
// ------------------------------------------------------------
|
|
4
4
|
// 目录(与 aw commands 同哲学):
|
|
5
5
|
// project: <repo>/.AgentWorkShop/plugins/<name>/index.mjs
|
|
@@ -7,10 +7,15 @@
|
|
|
7
7
|
// 契约:入口导出普通对象 { name, version?, description?, setup(ctx)?,
|
|
8
8
|
// client?: './client.mjs', routes?: [{method,path,handler}] }
|
|
9
9
|
// —— ctx 由宿主注入,插件运行时零导入依赖(sdk/ 供类型与显式糖)。
|
|
10
|
+
//
|
|
11
|
+
// 启停状态机:配置根 plugins-state.json { version, updatedAt, disabled: string[] }
|
|
12
|
+
// · 装载时跳过 disabled 插件(manifest 仍可见,enabled:false)
|
|
13
|
+
// · 状态文件变化(fs.watch)→ 热重载:全部 dispose/解绑 → 重新装载
|
|
14
|
+
// · CLI(aw plugin enable/disable) 与 Web 设置页均只写状态文件,服务自感知
|
|
10
15
|
// 错误隔离:单插件装载/执行失败记入 failures,绝不拖垮主服务。
|
|
11
16
|
// ============================================================
|
|
12
|
-
import { existsSync, readdirSync, readFileSync, watch } from 'node:fs'
|
|
13
|
-
import { join, resolve } from 'node:path'
|
|
17
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, watch, writeFileSync } from 'node:fs'
|
|
18
|
+
import { dirname, join, resolve } from 'node:path'
|
|
14
19
|
import { pathToFileURL } from 'node:url'
|
|
15
20
|
import { HookBus, createPluginContext, createRouteTable, validatePluginModule } from '@/sdk/index.mjs'
|
|
16
21
|
|
|
@@ -18,6 +23,7 @@ const g = globalThis
|
|
|
18
23
|
|
|
19
24
|
function log() {
|
|
20
25
|
return {
|
|
26
|
+
debug: (...a) => console.log('[aw-plugins][debug]', ...a),
|
|
21
27
|
info: (...a) => console.log('[aw-plugins]', ...a),
|
|
22
28
|
warn: (...a) => console.warn('[aw-plugins]', ...a),
|
|
23
29
|
error: (...a) => console.error('[aw-plugins]', ...a),
|
|
@@ -35,6 +41,7 @@ function modePaths(cwd) {
|
|
|
35
41
|
return {
|
|
36
42
|
projectDir: isRepo ? join(cwd, '.AgentWorkShop', 'plugins') : null,
|
|
37
43
|
userDir: join(process.env.AW_HOME && String(process.env.AW_HOME).trim() ? String(process.env.AW_HOME).trim() : defaultHome(), 'plugins'),
|
|
44
|
+
homeDir: process.env.AW_HOME && String(process.env.AW_HOME).trim() ? String(process.env.AW_HOME).trim() : defaultHome(),
|
|
38
45
|
}
|
|
39
46
|
}
|
|
40
47
|
|
|
@@ -42,6 +49,29 @@ function pathToUrl(p) {
|
|
|
42
49
|
return pathToFileURL(resolve(p)).href
|
|
43
50
|
}
|
|
44
51
|
|
|
52
|
+
// ---- 启停状态(单一事实源:<配置根>/plugins-state.json;CLI/Web/宿主三方读写) ----
|
|
53
|
+
export function statePathFor(homeDir) {
|
|
54
|
+
return join(homeDir, 'plugins-state.json')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function readDisabledSet(homeDir) {
|
|
58
|
+
try {
|
|
59
|
+
const j = JSON.parse(readFileSync(statePathFor(homeDir), 'utf8'))
|
|
60
|
+
return new Set(Array.isArray(j.disabled) ? j.disabled : [])
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return new Set()
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function writeDisabledSet(homeDir, disabled) {
|
|
68
|
+
const p = statePathFor(homeDir)
|
|
69
|
+
mkdirSync(dirname(p), { recursive: true })
|
|
70
|
+
const tmp = `${p}.${process.pid}.tmp`
|
|
71
|
+
writeFileSync(tmp, `${JSON.stringify({ version: 1, updatedAt: new Date().toISOString(), disabled: [...disabled] }, null, 2)}\n`, 'utf8')
|
|
72
|
+
renameSync(tmp, p)
|
|
73
|
+
}
|
|
74
|
+
|
|
45
75
|
/** 发现两个作用域下的插件入口(project 同名覆盖 user) */
|
|
46
76
|
export function discoverPluginDirs(cwd = process.cwd()) {
|
|
47
77
|
const { projectDir, userDir } = modePaths(cwd)
|
|
@@ -62,12 +92,11 @@ export function discoverPluginDirs(cwd = process.cwd()) {
|
|
|
62
92
|
|
|
63
93
|
/**
|
|
64
94
|
* 装载插件宿主(idempotent;nitro 启动期调用一次)。
|
|
65
|
-
* 装载 = 动态 import 入口 → 形态校验 → createPluginContext → setup(ctx)
|
|
66
|
-
* → 收集 routes/client → emit plugin:host:init
|
|
67
95
|
*/
|
|
68
96
|
export async function initPluginHost({ cwd = process.cwd(), packageRoot } = {}) {
|
|
69
97
|
if (g.__awPluginHost) return g.__awPluginHost
|
|
70
98
|
const logger = log()
|
|
99
|
+
const homeDir = modePaths(cwd).homeDir
|
|
71
100
|
const host = {
|
|
72
101
|
bus: new HookBus({
|
|
73
102
|
name: 'aw-plugins',
|
|
@@ -75,17 +104,20 @@ export async function initPluginHost({ cwd = process.cwd(), packageRoot } = {})
|
|
|
75
104
|
}),
|
|
76
105
|
routes: createRouteTable(),
|
|
77
106
|
plugins: new Map(),
|
|
78
|
-
disposables: new Map(), // name → fn[](
|
|
107
|
+
disposables: new Map(), // name → fn[](ctx.onDispose 登记)
|
|
108
|
+
hookOffs: new Map(), // name → off[](ctx.hooks.on 登记;热重载时解绑)
|
|
79
109
|
failures: [],
|
|
80
110
|
initedAt: null,
|
|
111
|
+
cwd,
|
|
112
|
+
packageRoot,
|
|
81
113
|
logger,
|
|
82
114
|
}
|
|
83
115
|
g.__awPluginHost = host
|
|
84
116
|
|
|
85
|
-
// 有效配置(
|
|
117
|
+
// 有效配置(只读面;引擎/模式解析从运行根动态加载)
|
|
86
118
|
let config = null
|
|
87
119
|
let settingsPath = null
|
|
88
|
-
let paths = { home:
|
|
120
|
+
let paths = { home: homeDir, configRoot: join(cwd, '.AgentWorkShop'), dataDir: join(cwd, '.AgentWorkShop', 'data') }
|
|
89
121
|
try {
|
|
90
122
|
const homeMod = await import(pathToUrl(join(cwd, 'shared', 'config', 'home.mjs')))
|
|
91
123
|
const rm = homeMod.resolveRunMode({ cwd, packageRoot, env: process.env })
|
|
@@ -102,8 +134,9 @@ export async function initPluginHost({ cwd = process.cwd(), packageRoot } = {})
|
|
|
102
134
|
host.logger.warn('配置引擎加载降级(插件 ctx.config 将为空):', err?.message)
|
|
103
135
|
}
|
|
104
136
|
host.config = config
|
|
105
|
-
|
|
106
|
-
//
|
|
137
|
+
|
|
138
|
+
// 自环 origin:PORT env(prod:start.mjs 注入 / dev:dev-guard 注入 CLI 显式值)权威;
|
|
139
|
+
// nitro listen 钩子兜底回填
|
|
107
140
|
const argPort = (() => {
|
|
108
141
|
const argv = process.argv
|
|
109
142
|
const i = argv.indexOf('--port')
|
|
@@ -117,38 +150,21 @@ export async function initPluginHost({ cwd = process.cwd(), packageRoot } = {})
|
|
|
117
150
|
host.logger.info(`自环 origin 就绪: ${selfOrigin}`)
|
|
118
151
|
}
|
|
119
152
|
host.selfOrigin = () => selfOrigin
|
|
120
|
-
host.logger.info(`自环 origin: ${selfOrigin}
|
|
153
|
+
host.logger.info(`自环 origin: ${selfOrigin}`)
|
|
121
154
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
watch(settingsPath, () => {
|
|
127
|
-
clearTimeout(debounce)
|
|
128
|
-
debounce = setTimeout(async () => {
|
|
129
|
-
try {
|
|
130
|
-
if (host.config && existsSync(settingsPath)) {
|
|
131
|
-
const homeMod = await import(pathToUrl(join(cwd, 'shared', 'config', 'home.mjs')))
|
|
132
|
-
const rm = homeMod.resolveRunMode({ cwd, packageRoot, env: process.env })
|
|
133
|
-
const engine = await import(pathToUrl(join((rm.mode === 'repo' ? rm.root : packageRoot ?? rm.root) ?? cwd, 'shared', 'config', 'engine.mjs')))
|
|
134
|
-
const fresh = engine.loadEffective({ configPath: rm.configPath, settingsPath: rm.settingsPath, env: process.env })
|
|
135
|
-
Object.assign(host.config.effective, fresh.effective)
|
|
136
|
-
host.config.sources = fresh.sources
|
|
137
|
-
}
|
|
138
|
-
await host.bus.emit('config:changed', { at: new Date().toISOString() })
|
|
139
|
-
host.logger.info('配置变更已广播(config:changed)')
|
|
140
|
-
}
|
|
141
|
-
catch (err) {
|
|
142
|
-
host.logger.warn('config:changed 处理失败:', err?.message)
|
|
143
|
-
}
|
|
144
|
-
}, 300)
|
|
145
|
-
})
|
|
146
|
-
}
|
|
147
|
-
catch { /* fs.watch 不可用时插件可经轮询自行感知 */ }
|
|
148
|
-
}
|
|
155
|
+
await loadAllPlugins(host, { config, settingsPath, paths })
|
|
156
|
+
ensureStateWatcher(host)
|
|
157
|
+
return host
|
|
158
|
+
}
|
|
149
159
|
|
|
150
|
-
|
|
151
|
-
|
|
160
|
+
/** 装载/重载全部插件(跳过 disabled;manifest 仍可见) */
|
|
161
|
+
async function loadAllPlugins(host, { config, paths }) {
|
|
162
|
+
const homeDir = modePaths(host.cwd).homeDir
|
|
163
|
+
const disabled = readDisabledSet(homeDir)
|
|
164
|
+
host.disabledSet = disabled
|
|
165
|
+
|
|
166
|
+
const entries = discoverPluginDirs(host.cwd)
|
|
167
|
+
if (entries.length) host.logger.info(`发现 ${entries.length} 个插件(停用 ${disabled.size}),开始装载 ...`)
|
|
152
168
|
|
|
153
169
|
for (const { dir, scope } of entries) {
|
|
154
170
|
const entry = join(dir, 'index.mjs')
|
|
@@ -159,6 +175,22 @@ export async function initPluginHost({ cwd = process.cwd(), packageRoot } = {})
|
|
|
159
175
|
const def = check.def
|
|
160
176
|
|
|
161
177
|
if (host.plugins.has(def.name)) throw new Error(`插件重名(后装载者跳过): ${def.name}`)
|
|
178
|
+
if (disabled.has(def.name)) {
|
|
179
|
+
host.plugins.set(def.name, {
|
|
180
|
+
name: def.name,
|
|
181
|
+
version: String(def.version ?? '0.0.0'),
|
|
182
|
+
description: String(def.description ?? ''),
|
|
183
|
+
scope,
|
|
184
|
+
dir,
|
|
185
|
+
entry,
|
|
186
|
+
clientPath: def.client ? resolve(dir, def.client) : null,
|
|
187
|
+
routes: [],
|
|
188
|
+
enabled: false,
|
|
189
|
+
error: null,
|
|
190
|
+
})
|
|
191
|
+
host.logger.info(`⊘ 跳过(已停用) [${scope}] ${def.name}`)
|
|
192
|
+
continue
|
|
193
|
+
}
|
|
162
194
|
|
|
163
195
|
const rec = {
|
|
164
196
|
name: def.name,
|
|
@@ -169,6 +201,8 @@ export async function initPluginHost({ cwd = process.cwd(), packageRoot } = {})
|
|
|
169
201
|
entry,
|
|
170
202
|
clientPath: def.client ? resolve(dir, def.client) : null,
|
|
171
203
|
routes: [],
|
|
204
|
+
enabled: true,
|
|
205
|
+
error: null,
|
|
172
206
|
}
|
|
173
207
|
|
|
174
208
|
const emitter = {
|
|
@@ -178,17 +212,33 @@ export async function initPluginHost({ cwd = process.cwd(), packageRoot } = {})
|
|
|
178
212
|
},
|
|
179
213
|
}
|
|
180
214
|
const perPluginDisposables = []
|
|
215
|
+
const hookOffs = []
|
|
181
216
|
host.disposables.set(def.name, perPluginDisposables)
|
|
217
|
+
host.hookOffs.set(def.name, hookOffs)
|
|
218
|
+
const scopedHooks = {
|
|
219
|
+
on: (t, fn) => {
|
|
220
|
+
const off = host.bus.on(t, fn)
|
|
221
|
+
hookOffs.push(off)
|
|
222
|
+
return off
|
|
223
|
+
},
|
|
224
|
+
once: (t, fn) => {
|
|
225
|
+
const off = host.bus.once(t, fn)
|
|
226
|
+
hookOffs.push(off)
|
|
227
|
+
return off
|
|
228
|
+
},
|
|
229
|
+
off: (t, fn) => host.bus.off(t, fn),
|
|
230
|
+
emit: (t, p) => host.bus.emit(t, p),
|
|
231
|
+
}
|
|
182
232
|
const ctx = createPluginContext({
|
|
183
233
|
name: def.name,
|
|
184
234
|
scope,
|
|
185
235
|
dir,
|
|
186
|
-
hooks:
|
|
236
|
+
hooks: scopedHooks,
|
|
187
237
|
logger: {
|
|
188
|
-
debug: (...a) => logger.info(`[${def.name}][debug]`, ...a),
|
|
189
|
-
info: (...a) => logger.info(`[${def.name}]`, ...a),
|
|
190
|
-
warn: (...a) => logger.warn(`[${def.name}]`, ...a),
|
|
191
|
-
error: (...a) => logger.error(`[${def.name}]`, ...a),
|
|
238
|
+
debug: (...a) => host.logger.info(`[${def.name}][debug]`, ...a),
|
|
239
|
+
info: (...a) => host.logger.info(`[${def.name}]`, ...a),
|
|
240
|
+
warn: (...a) => host.logger.warn(`[${def.name}]`, ...a),
|
|
241
|
+
error: (...a) => host.logger.error(`[${def.name}]`, ...a),
|
|
192
242
|
},
|
|
193
243
|
config,
|
|
194
244
|
paths,
|
|
@@ -197,10 +247,9 @@ export async function initPluginHost({ cwd = process.cwd(), packageRoot } = {})
|
|
|
197
247
|
perPluginDisposables.push(fn)
|
|
198
248
|
return fn
|
|
199
249
|
},
|
|
200
|
-
selfOrigin: host
|
|
250
|
+
selfOrigin: () => selfOriginRef(host),
|
|
201
251
|
})
|
|
202
252
|
|
|
203
|
-
// 声明式 routes + setup 内 ctx.route() 两种形态都支持
|
|
204
253
|
for (const r of (Array.isArray(def.routes) ? def.routes : [])) {
|
|
205
254
|
emitter.registerRoute(def.name, r.method ?? 'GET', r.path, r.handler)
|
|
206
255
|
}
|
|
@@ -217,10 +266,87 @@ export async function initPluginHost({ cwd = process.cwd(), packageRoot } = {})
|
|
|
217
266
|
|
|
218
267
|
host.initedAt = new Date().toISOString()
|
|
219
268
|
await host.bus.emit('plugin:host:init', { plugins: [...host.plugins.keys()], failures: host.failures.length })
|
|
220
|
-
host.logger.info(`装载完成: ${host.plugins.size}
|
|
269
|
+
host.logger.info(`装载完成: ${host.plugins.size} 个(含停用)/ ${host.failures.length} 失败 / 活跃路由 ${host.routes.size} 条`)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function selfOriginRef(host) {
|
|
273
|
+
return host.selfOrigin()
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** 热重载:全部 dispose/解绑 → 重新装载(跳过停用)→ 广播 plugins.reloaded(并发合并) */
|
|
277
|
+
export async function reloadPluginHost() {
|
|
278
|
+
const host = getPluginHost()
|
|
279
|
+
if (!host) return null
|
|
280
|
+
if (host.reloadInFlight) return host.reloadInFlight
|
|
281
|
+
host.reloadInFlight = doReload(host).finally(() => {
|
|
282
|
+
host.reloadInFlight = null
|
|
283
|
+
})
|
|
284
|
+
return host.reloadInFlight
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async function doReload(host) {
|
|
288
|
+
host.logger.info('热重载插件宿主 ...')
|
|
289
|
+
for (const [name, list] of host.disposables ?? []) {
|
|
290
|
+
for (const fn of list.splice(0)) {
|
|
291
|
+
try {
|
|
292
|
+
await fn()
|
|
293
|
+
}
|
|
294
|
+
catch (err) {
|
|
295
|
+
host.logger.warn(`[${name}] onDispose 失败:`, err?.message)
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
for (const offs of (host.hookOffs ?? new Map()).values()) {
|
|
300
|
+
for (const off of offs.splice(0)) {
|
|
301
|
+
try {
|
|
302
|
+
off()
|
|
303
|
+
}
|
|
304
|
+
catch { /* 解绑失败忽略 */ }
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
host.plugins.clear()
|
|
308
|
+
host.routes = createRouteTable()
|
|
309
|
+
host.failures = []
|
|
310
|
+
await loadAllPlugins(host, { config: host.config, settingsPath: null, paths: { home: modePaths(host.cwd).homeDir, configRoot: join(host.cwd, '.AgentWorkShop'), dataDir: join(host.cwd, '.AgentWorkShop', 'data') } })
|
|
311
|
+
await host.bus.emit('plugins:reloaded', { plugins: pluginManifest() })
|
|
312
|
+
try {
|
|
313
|
+
const m = await import('@/server/services/workshop/scene-events')
|
|
314
|
+
m.broadcastSceneEvent('plugins.reloaded', { plugins: pluginManifest() })
|
|
315
|
+
}
|
|
316
|
+
catch { /* 广播失败不影响重载 */ }
|
|
221
317
|
return host
|
|
222
318
|
}
|
|
223
319
|
|
|
320
|
+
/** 启停单插件(写状态文件;调用方随后 reloadPluginHost 或由 state watcher 触发) */
|
|
321
|
+
export function setPluginEnabled(name, enabled) {
|
|
322
|
+
const host = getPluginHost()
|
|
323
|
+
const homeDir = modePaths(host?.cwd ?? process.cwd()).homeDir
|
|
324
|
+
const set = readDisabledSet(homeDir)
|
|
325
|
+
if (enabled) set.delete(name)
|
|
326
|
+
else set.add(name)
|
|
327
|
+
writeDisabledSet(homeDir, set)
|
|
328
|
+
return [...set]
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** 状态文件监视 → 热重载(CLI/Web 只写文件,服务自感知) */
|
|
332
|
+
function ensureStateWatcher(host) {
|
|
333
|
+
const statePath = statePathFor(modePaths(host.cwd).homeDir)
|
|
334
|
+
try {
|
|
335
|
+
mkdirSync(dirname(statePath), { recursive: true })
|
|
336
|
+
if (!existsSync(statePath)) writeDisabledSet(modePaths(host.cwd).homeDir, new Set())
|
|
337
|
+
let debounce = null
|
|
338
|
+
watch(statePath, () => {
|
|
339
|
+
clearTimeout(debounce)
|
|
340
|
+
debounce = setTimeout(() => {
|
|
341
|
+
void reloadPluginHost()
|
|
342
|
+
}, 400)
|
|
343
|
+
})
|
|
344
|
+
}
|
|
345
|
+
catch (err) {
|
|
346
|
+
host.logger.warn('状态文件监视不可用(启停需手动重启):', err?.message)
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
224
350
|
/** 单例访问(未初始化返回 null —— 桥接点据此快速 no-op) */
|
|
225
351
|
export function getPluginHost() {
|
|
226
352
|
return g.__awPluginHost ?? null
|
|
@@ -256,7 +382,7 @@ export function readClientScript(name) {
|
|
|
256
382
|
return { status: 200, code: readFileSync(rec.clientPath, 'utf8'), contentType: 'text/javascript; charset=utf-8' }
|
|
257
383
|
}
|
|
258
384
|
|
|
259
|
-
/** 清单(
|
|
385
|
+
/** 清单(非敏感只读;含启停状态与路由) */
|
|
260
386
|
export function pluginManifest() {
|
|
261
387
|
const host = getPluginHost()
|
|
262
388
|
if (!host) return []
|
|
@@ -265,8 +391,10 @@ export function pluginManifest() {
|
|
|
265
391
|
version: r.version,
|
|
266
392
|
description: r.description,
|
|
267
393
|
scope: r.scope,
|
|
394
|
+
enabled: r.enabled !== false,
|
|
268
395
|
hasClient: Boolean(r.clientPath),
|
|
269
396
|
routes: host.routes.byPlugin(r.name),
|
|
397
|
+
error: r.error,
|
|
270
398
|
}))
|
|
271
399
|
}
|
|
272
400
|
|