@leeoohoo/ui-apps-devkit 0.1.2 → 0.1.3

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.
Files changed (61) hide show
  1. package/README.md +61 -62
  2. package/bin/chatos-uiapp.js +3 -4
  3. package/package.json +23 -23
  4. package/src/cli.js +53 -53
  5. package/src/commands/dev.js +14 -14
  6. package/src/commands/init.js +129 -129
  7. package/src/commands/install.js +45 -45
  8. package/src/commands/pack.js +72 -72
  9. package/src/commands/validate.js +90 -138
  10. package/src/lib/args.js +49 -49
  11. package/src/lib/config.js +29 -29
  12. package/src/lib/fs.js +78 -78
  13. package/src/lib/path-boundary.js +16 -16
  14. package/src/lib/plugin.js +45 -45
  15. package/src/lib/template.js +172 -172
  16. package/src/sandbox/server.js +1204 -1028
  17. package/templates/basic/README.md +63 -65
  18. package/templates/basic/chatos.config.json +5 -5
  19. package/templates/basic/docs/CHATOS_UI_APPS_AI_CONTRIBUTIONS.md +209 -211
  20. package/templates/basic/docs/CHATOS_UI_APPS_BACKEND_PROTOCOL.md +73 -73
  21. package/templates/basic/docs/CHATOS_UI_APPS_HOST_API.md +136 -136
  22. package/templates/basic/docs/CHATOS_UI_APPS_OVERVIEW.md +106 -106
  23. package/templates/basic/docs/CHATOS_UI_APPS_PLUGIN_MANIFEST.md +239 -239
  24. package/templates/basic/docs/CHATOS_UI_APPS_STYLE_GUIDE.md +95 -95
  25. package/templates/basic/docs/CHATOS_UI_APPS_TROUBLESHOOTING.md +40 -40
  26. package/templates/basic/docs/CHATOS_UI_PROMPTS_PROTOCOL.md +392 -392
  27. package/templates/basic/plugin/apps/app/compact.mjs +41 -41
  28. package/templates/basic/plugin/apps/app/index.mjs +287 -287
  29. package/templates/basic/plugin/apps/app/mcp-prompt.en.md +7 -7
  30. package/templates/basic/plugin/apps/app/mcp-prompt.zh.md +7 -7
  31. package/templates/basic/plugin/apps/app/mcp-server.mjs +15 -15
  32. package/templates/basic/plugin/backend/index.mjs +37 -37
  33. package/templates/basic/template.json +7 -7
  34. package/templates/notepad/README.md +38 -44
  35. package/templates/notepad/chatos.config.json +4 -4
  36. package/templates/notepad/docs/CHATOS_UI_APPS_AI_CONTRIBUTIONS.md +209 -211
  37. package/templates/notepad/docs/CHATOS_UI_APPS_BACKEND_PROTOCOL.md +73 -73
  38. package/templates/notepad/docs/CHATOS_UI_APPS_HOST_API.md +136 -136
  39. package/templates/notepad/docs/CHATOS_UI_APPS_OVERVIEW.md +106 -106
  40. package/templates/notepad/docs/CHATOS_UI_APPS_PLUGIN_MANIFEST.md +239 -239
  41. package/templates/notepad/docs/CHATOS_UI_APPS_STYLE_GUIDE.md +95 -95
  42. package/templates/notepad/docs/CHATOS_UI_APPS_TROUBLESHOOTING.md +40 -40
  43. package/templates/notepad/docs/CHATOS_UI_PROMPTS_PROTOCOL.md +392 -392
  44. package/templates/notepad/plugin/apps/app/api.mjs +30 -30
  45. package/templates/notepad/plugin/apps/app/compact.mjs +41 -41
  46. package/templates/notepad/plugin/apps/app/dom.mjs +14 -14
  47. package/templates/notepad/plugin/apps/app/ds-tree.mjs +35 -35
  48. package/templates/notepad/plugin/apps/app/index.mjs +1056 -1056
  49. package/templates/notepad/plugin/apps/app/layers.mjs +338 -338
  50. package/templates/notepad/plugin/apps/app/markdown.mjs +120 -120
  51. package/templates/notepad/plugin/apps/app/mcp-prompt.en.md +22 -22
  52. package/templates/notepad/plugin/apps/app/mcp-prompt.zh.md +22 -22
  53. package/templates/notepad/plugin/apps/app/mcp-server.mjs +199 -199
  54. package/templates/notepad/plugin/apps/app/styles.mjs +355 -355
  55. package/templates/notepad/plugin/apps/app/tags.mjs +21 -21
  56. package/templates/notepad/plugin/apps/app/ui.mjs +280 -280
  57. package/templates/notepad/plugin/backend/index.mjs +99 -99
  58. package/templates/notepad/plugin/plugin.json +23 -23
  59. package/templates/notepad/plugin/shared/notepad-paths.mjs +39 -39
  60. package/templates/notepad/plugin/shared/notepad-store.mjs +765 -765
  61. package/templates/notepad/template.json +8 -8
@@ -1,41 +1,41 @@
1
- export function mount({ container, host }) {
2
- if (!container) throw new Error('container is required');
3
-
4
- const ctx = typeof host?.context?.get === 'function' ? host.context.get() : {};
5
- const root = document.createElement('div');
6
- root.style.height = '100%';
7
- root.style.boxSizing = 'border-box';
8
- root.style.padding = '16px';
9
- root.style.display = 'flex';
10
- root.style.flexDirection = 'column';
11
- root.style.gap = '10px';
12
-
13
- const title = document.createElement('div');
14
- title.textContent = 'Compact View';
15
- title.style.fontWeight = '700';
16
- title.style.fontSize = '15px';
17
-
18
- const meta = document.createElement('div');
19
- meta.style.fontSize = '12px';
20
- meta.style.opacity = '0.72';
21
- meta.textContent = `${ctx?.pluginId || ''}:${ctx?.appId || ''} · compact`;
22
-
23
- const desc = document.createElement('div');
24
- desc.style.fontSize = '13px';
25
- desc.style.opacity = '0.82';
26
- desc.textContent = 'Provide a lightweight UI here for side drawers or split views.';
27
-
28
- root.appendChild(title);
29
- root.appendChild(meta);
30
- root.appendChild(desc);
31
-
32
- container.appendChild(root);
33
-
34
- return () => {
35
- try {
36
- container.textContent = '';
37
- } catch {
38
- // ignore
39
- }
40
- };
41
- }
1
+ export function mount({ container, host }) {
2
+ if (!container) throw new Error('container is required');
3
+
4
+ const ctx = typeof host?.context?.get === 'function' ? host.context.get() : {};
5
+ const root = document.createElement('div');
6
+ root.style.height = '100%';
7
+ root.style.boxSizing = 'border-box';
8
+ root.style.padding = '16px';
9
+ root.style.display = 'flex';
10
+ root.style.flexDirection = 'column';
11
+ root.style.gap = '10px';
12
+
13
+ const title = document.createElement('div');
14
+ title.textContent = 'Compact View';
15
+ title.style.fontWeight = '700';
16
+ title.style.fontSize = '15px';
17
+
18
+ const meta = document.createElement('div');
19
+ meta.style.fontSize = '12px';
20
+ meta.style.opacity = '0.72';
21
+ meta.textContent = `${ctx?.pluginId || ''}:${ctx?.appId || ''} · compact`;
22
+
23
+ const desc = document.createElement('div');
24
+ desc.style.fontSize = '13px';
25
+ desc.style.opacity = '0.82';
26
+ desc.textContent = 'Provide a lightweight UI here for side drawers or split views.';
27
+
28
+ root.appendChild(title);
29
+ root.appendChild(meta);
30
+ root.appendChild(desc);
31
+
32
+ container.appendChild(root);
33
+
34
+ return () => {
35
+ try {
36
+ container.textContent = '';
37
+ } catch {
38
+ // ignore
39
+ }
40
+ };
41
+ }
@@ -1,287 +1,287 @@
1
- export function mount({ container, host, slots }) {
2
- if (!container) throw new Error('container is required');
3
- if (!host || typeof host !== 'object') throw new Error('host is required');
4
-
5
- const headerSlot =
6
- slots?.header && typeof slots.header === 'object' && typeof slots.header.appendChild === 'function' ? slots.header : null;
7
-
8
- const ctx = typeof host?.context?.get === 'function' ? host.context.get() : { pluginId: '', appId: '', theme: 'light' };
9
- const bridgeEnabled = Boolean(ctx?.bridge?.enabled);
10
- const appMetaPrefix = `${ctx?.pluginId || ''}:${ctx?.appId || ''}`;
11
-
12
- const root = document.createElement('div');
13
- root.style.height = '100%';
14
- root.style.boxSizing = 'border-box';
15
- root.style.padding = '14px';
16
- root.style.display = 'flex';
17
- root.style.flexDirection = 'column';
18
- root.style.gap = '12px';
19
-
20
- const title = document.createElement('div');
21
- title.textContent = '__PLUGIN_NAME__ · __APP_ID__';
22
- title.style.fontWeight = '800';
23
-
24
- const meta = document.createElement('div');
25
- meta.style.fontSize = '12px';
26
- meta.style.opacity = '0.75';
27
- const renderMeta = (theme) => {
28
- meta.textContent = `${appMetaPrefix} · theme=${theme || 'light'} · bridge=${bridgeEnabled ? 'enabled' : 'disabled'}`;
29
- };
30
- renderMeta(ctx?.theme || 'light');
31
-
32
- const header = document.createElement('div');
33
- header.appendChild(title);
34
- header.appendChild(meta);
35
-
36
- const applyTheme = (theme) => {
37
- const nextTheme = theme || 'light';
38
- root.dataset.theme = nextTheme;
39
- renderMeta(nextTheme);
40
- };
41
- applyTheme(ctx?.theme || 'light');
42
-
43
- const body = document.createElement('div');
44
- body.style.display = 'grid';
45
- body.style.gridTemplateColumns = '320px 1fr';
46
- body.style.gap = '12px';
47
- body.style.flex = '1';
48
- body.style.minHeight = '0';
49
-
50
- const actions = document.createElement('div');
51
- actions.style.border = '1px solid var(--ds-panel-border, rgba(0,0,0,0.12))';
52
- actions.style.borderRadius = '14px';
53
- actions.style.padding = '12px';
54
- actions.style.display = 'grid';
55
- actions.style.gap = '10px';
56
-
57
- const input = document.createElement('textarea');
58
- input.placeholder = '输入要发送给 ChatOS / 后端 LLM 的内容…';
59
- input.style.width = '100%';
60
- input.style.minHeight = '86px';
61
- input.style.boxSizing = 'border-box';
62
- input.style.borderRadius = '12px';
63
- input.style.border = '1px solid var(--ds-panel-border, rgba(0,0,0,0.14))';
64
- input.style.background = 'var(--ds-subtle-bg, rgba(0,0,0,0.04))';
65
- input.style.padding = '10px 10px';
66
- input.style.resize = 'vertical';
67
- input.style.outline = 'none';
68
-
69
- const log = document.createElement('pre');
70
- log.style.border = '1px solid var(--ds-panel-border, rgba(0,0,0,0.12))';
71
- log.style.borderRadius = '14px';
72
- log.style.padding = '12px';
73
- log.style.margin = '0';
74
- log.style.overflow = 'auto';
75
- log.style.minHeight = '0';
76
-
77
- const appendLog = (type, payload) => {
78
- const ts = new Date().toISOString();
79
- log.textContent += `[${ts}] ${type}${payload !== undefined ? ` ${JSON.stringify(payload, null, 2)}` : ''}\n`;
80
- log.scrollTop = log.scrollHeight;
81
- };
82
-
83
- const mkBtn = (label) => {
84
- const btn = document.createElement('button');
85
- btn.type = 'button';
86
- btn.textContent = label;
87
- btn.style.padding = '9px 10px';
88
- btn.style.borderRadius = '12px';
89
- btn.style.border = '1px solid var(--ds-panel-border, rgba(0,0,0,0.14))';
90
- btn.style.background = 'var(--ds-subtle-bg, rgba(0,0,0,0.04))';
91
- btn.style.cursor = 'pointer';
92
- btn.style.fontWeight = '650';
93
- return btn;
94
- };
95
-
96
- const run = async (label, fn) => {
97
- try {
98
- const res = await fn();
99
- appendLog(label, res);
100
- return res;
101
- } catch (e) {
102
- appendLog(`${label}.error`, { message: e?.message || String(e) });
103
- return null;
104
- }
105
- };
106
-
107
- const btnPing = mkBtn('backend.invoke("ping")');
108
- btnPing.addEventListener('click', () => run('backend.invoke', () => host.backend.invoke('ping', { hello: 'world' })));
109
-
110
- const btnLlmComplete = mkBtn('backend.invoke("llmComplete")');
111
- btnLlmComplete.addEventListener('click', async () => {
112
- const text = String(input.value || '').trim();
113
- if (!text) return;
114
- btnLlmComplete.disabled = true;
115
- try {
116
- const res = await run('backend.invoke.llmComplete', () => host.backend.invoke('llmComplete', { input: text }));
117
- if (res?.content) {
118
- log.textContent += `\n[sandbox llm]\n${String(res.content)}\n`;
119
- log.scrollTop = log.scrollHeight;
120
- }
121
- } finally {
122
- btnLlmComplete.disabled = false;
123
- }
124
- });
125
-
126
- const btnPrompt = mkBtn('uiPrompts.request(kv)');
127
- btnPrompt.addEventListener('click', async () => {
128
- try {
129
- const res = await host.uiPrompts.request({
130
- prompt: {
131
- kind: 'kv',
132
- title: '需要你补充信息',
133
- message: '填写后点 Submit',
134
- fields: [
135
- { key: 'name', label: '姓名', placeholder: '请输入', required: true },
136
- { key: 'note', label: '备注', placeholder: '可选', multiline: true },
137
- ],
138
- },
139
- });
140
- appendLog('uiPrompts.request', res);
141
- host.uiPrompts.open();
142
- } catch (e) {
143
- appendLog('uiPrompts.request.error', { message: e?.message || String(e) });
144
- }
145
- });
146
-
147
- let activeSessionId = '';
148
- let chatUnsub = null;
149
- let themeUnsub = null;
150
-
151
- if (typeof host?.theme?.onChange === 'function') {
152
- themeUnsub = host.theme.onChange((theme) => applyTheme(theme));
153
- }
154
-
155
- const ensureSession = async () => {
156
- const agents = await run('chat.agents.list', () => host.chat.agents.list());
157
- let agentId = agents?.agents?.[0]?.id || '';
158
- if (!agentId) {
159
- const ensured = await run('chat.agents.ensureDefault', () => host.chat.agents.ensureDefault());
160
- agentId = ensured?.agent?.id || ensured?.agents?.[0]?.id || '';
161
- }
162
- if (!agentId) throw new Error('no agentId');
163
-
164
- const res = await run('chat.sessions.ensureDefault', () => host.chat.sessions.ensureDefault({ agentId }));
165
- activeSessionId = res?.session?.id || '';
166
- if (activeSessionId) appendLog('activeSessionId', activeSessionId);
167
- return activeSessionId;
168
- };
169
-
170
- const btnEnsureSession = mkBtn('chat.sessions.ensureDefault');
171
- btnEnsureSession.addEventListener('click', () => ensureSession().catch((e) => appendLog('chat.ensureSession.error', { message: e?.message || String(e) })));
172
-
173
- const btnSend = mkBtn('chat.send');
174
- btnSend.addEventListener('click', async () => {
175
- const text = String(input.value || '').trim();
176
- if (!text) return;
177
- btnSend.disabled = true;
178
- try {
179
- if (!activeSessionId) await ensureSession();
180
- if (!activeSessionId) throw new Error('no sessionId');
181
- await run('chat.send', () => host.chat.send({ sessionId: activeSessionId, text }));
182
- input.value = '';
183
- } finally {
184
- btnSend.disabled = false;
185
- }
186
- });
187
-
188
- const btnSub = mkBtn('chat.events.subscribe');
189
- btnSub.addEventListener('click', async () => {
190
- if (!activeSessionId) await ensureSession();
191
- if (!activeSessionId) {
192
- appendLog('chat.events.subscribe.error', { message: 'no sessionId' });
193
- return;
194
- }
195
- if (chatUnsub) {
196
- try {
197
- chatUnsub();
198
- } catch {
199
- // ignore
200
- }
201
- chatUnsub = null;
202
- }
203
- try {
204
- chatUnsub = host.chat.events.subscribe({ sessionId: activeSessionId }, (payload) => {
205
- appendLog('chat.event', payload);
206
- if (payload?.type === 'assistant_delta' && payload?.delta) {
207
- log.textContent += payload.delta;
208
- log.scrollTop = log.scrollHeight;
209
- }
210
- });
211
- appendLog('chat.events.subscribe', { ok: true });
212
- } catch (e) {
213
- appendLog('chat.events.subscribe.error', { message: e?.message || String(e) });
214
- }
215
- });
216
-
217
- const btnUnsub = mkBtn('chat.events.unsubscribe');
218
- btnUnsub.addEventListener('click', () => {
219
- if (chatUnsub) {
220
- try {
221
- chatUnsub();
222
- } catch {
223
- // ignore
224
- }
225
- chatUnsub = null;
226
- appendLog('chat.events.unsubscribe', { ok: true });
227
- return;
228
- }
229
- run('chat.events.unsubscribe', () => host.chat.events.unsubscribe());
230
- });
231
-
232
- actions.appendChild(btnPing);
233
- actions.appendChild(btnLlmComplete);
234
- actions.appendChild(btnPrompt);
235
- actions.appendChild(input);
236
- actions.appendChild(btnEnsureSession);
237
- actions.appendChild(btnSend);
238
- actions.appendChild(btnSub);
239
- actions.appendChild(btnUnsub);
240
-
241
- body.appendChild(actions);
242
- body.appendChild(log);
243
-
244
- root.appendChild(body);
245
-
246
- if (headerSlot) {
247
- try {
248
- headerSlot.textContent = '';
249
- headerSlot.appendChild(header);
250
- } catch {
251
- root.prepend(header);
252
- }
253
- } else {
254
- root.prepend(header);
255
- }
256
-
257
- try {
258
- container.textContent = '';
259
- } catch {
260
- // ignore
261
- }
262
- container.appendChild(root);
263
-
264
- return () => {
265
- if (chatUnsub) {
266
- try {
267
- chatUnsub();
268
- } catch {
269
- // ignore
270
- }
271
- chatUnsub = null;
272
- }
273
- if (themeUnsub) {
274
- try {
275
- themeUnsub();
276
- } catch {
277
- // ignore
278
- }
279
- themeUnsub = null;
280
- }
281
- try {
282
- container.textContent = '';
283
- } catch {
284
- // ignore
285
- }
286
- };
287
- }
1
+ export function mount({ container, host, slots }) {
2
+ if (!container) throw new Error('container is required');
3
+ if (!host || typeof host !== 'object') throw new Error('host is required');
4
+
5
+ const headerSlot =
6
+ slots?.header && typeof slots.header === 'object' && typeof slots.header.appendChild === 'function' ? slots.header : null;
7
+
8
+ const ctx = typeof host?.context?.get === 'function' ? host.context.get() : { pluginId: '', appId: '', theme: 'light' };
9
+ const bridgeEnabled = Boolean(ctx?.bridge?.enabled);
10
+ const appMetaPrefix = `${ctx?.pluginId || ''}:${ctx?.appId || ''}`;
11
+
12
+ const root = document.createElement('div');
13
+ root.style.height = '100%';
14
+ root.style.boxSizing = 'border-box';
15
+ root.style.padding = '14px';
16
+ root.style.display = 'flex';
17
+ root.style.flexDirection = 'column';
18
+ root.style.gap = '12px';
19
+
20
+ const title = document.createElement('div');
21
+ title.textContent = '__PLUGIN_NAME__ · __APP_ID__';
22
+ title.style.fontWeight = '800';
23
+
24
+ const meta = document.createElement('div');
25
+ meta.style.fontSize = '12px';
26
+ meta.style.opacity = '0.75';
27
+ const renderMeta = (theme) => {
28
+ meta.textContent = `${appMetaPrefix} · theme=${theme || 'light'} · bridge=${bridgeEnabled ? 'enabled' : 'disabled'}`;
29
+ };
30
+ renderMeta(ctx?.theme || 'light');
31
+
32
+ const header = document.createElement('div');
33
+ header.appendChild(title);
34
+ header.appendChild(meta);
35
+
36
+ const applyTheme = (theme) => {
37
+ const nextTheme = theme || 'light';
38
+ root.dataset.theme = nextTheme;
39
+ renderMeta(nextTheme);
40
+ };
41
+ applyTheme(ctx?.theme || 'light');
42
+
43
+ const body = document.createElement('div');
44
+ body.style.display = 'grid';
45
+ body.style.gridTemplateColumns = '320px 1fr';
46
+ body.style.gap = '12px';
47
+ body.style.flex = '1';
48
+ body.style.minHeight = '0';
49
+
50
+ const actions = document.createElement('div');
51
+ actions.style.border = '1px solid var(--ds-panel-border, rgba(0,0,0,0.12))';
52
+ actions.style.borderRadius = '14px';
53
+ actions.style.padding = '12px';
54
+ actions.style.display = 'grid';
55
+ actions.style.gap = '10px';
56
+
57
+ const input = document.createElement('textarea');
58
+ input.placeholder = '输入要发送给 ChatOS / 后端 LLM 的内容…';
59
+ input.style.width = '100%';
60
+ input.style.minHeight = '86px';
61
+ input.style.boxSizing = 'border-box';
62
+ input.style.borderRadius = '12px';
63
+ input.style.border = '1px solid var(--ds-panel-border, rgba(0,0,0,0.14))';
64
+ input.style.background = 'var(--ds-subtle-bg, rgba(0,0,0,0.04))';
65
+ input.style.padding = '10px 10px';
66
+ input.style.resize = 'vertical';
67
+ input.style.outline = 'none';
68
+
69
+ const log = document.createElement('pre');
70
+ log.style.border = '1px solid var(--ds-panel-border, rgba(0,0,0,0.12))';
71
+ log.style.borderRadius = '14px';
72
+ log.style.padding = '12px';
73
+ log.style.margin = '0';
74
+ log.style.overflow = 'auto';
75
+ log.style.minHeight = '0';
76
+
77
+ const appendLog = (type, payload) => {
78
+ const ts = new Date().toISOString();
79
+ log.textContent += `[${ts}] ${type}${payload !== undefined ? ` ${JSON.stringify(payload, null, 2)}` : ''}\n`;
80
+ log.scrollTop = log.scrollHeight;
81
+ };
82
+
83
+ const mkBtn = (label) => {
84
+ const btn = document.createElement('button');
85
+ btn.type = 'button';
86
+ btn.textContent = label;
87
+ btn.style.padding = '9px 10px';
88
+ btn.style.borderRadius = '12px';
89
+ btn.style.border = '1px solid var(--ds-panel-border, rgba(0,0,0,0.14))';
90
+ btn.style.background = 'var(--ds-subtle-bg, rgba(0,0,0,0.04))';
91
+ btn.style.cursor = 'pointer';
92
+ btn.style.fontWeight = '650';
93
+ return btn;
94
+ };
95
+
96
+ const run = async (label, fn) => {
97
+ try {
98
+ const res = await fn();
99
+ appendLog(label, res);
100
+ return res;
101
+ } catch (e) {
102
+ appendLog(`${label}.error`, { message: e?.message || String(e) });
103
+ return null;
104
+ }
105
+ };
106
+
107
+ const btnPing = mkBtn('backend.invoke("ping")');
108
+ btnPing.addEventListener('click', () => run('backend.invoke', () => host.backend.invoke('ping', { hello: 'world' })));
109
+
110
+ const btnLlmComplete = mkBtn('backend.invoke("llmComplete")');
111
+ btnLlmComplete.addEventListener('click', async () => {
112
+ const text = String(input.value || '').trim();
113
+ if (!text) return;
114
+ btnLlmComplete.disabled = true;
115
+ try {
116
+ const res = await run('backend.invoke.llmComplete', () => host.backend.invoke('llmComplete', { input: text }));
117
+ if (res?.content) {
118
+ log.textContent += `\n[sandbox llm]\n${String(res.content)}\n`;
119
+ log.scrollTop = log.scrollHeight;
120
+ }
121
+ } finally {
122
+ btnLlmComplete.disabled = false;
123
+ }
124
+ });
125
+
126
+ const btnPrompt = mkBtn('uiPrompts.request(kv)');
127
+ btnPrompt.addEventListener('click', async () => {
128
+ try {
129
+ const res = await host.uiPrompts.request({
130
+ prompt: {
131
+ kind: 'kv',
132
+ title: '需要你补充信息',
133
+ message: '填写后点 Submit',
134
+ fields: [
135
+ { key: 'name', label: '姓名', placeholder: '请输入', required: true },
136
+ { key: 'note', label: '备注', placeholder: '可选', multiline: true },
137
+ ],
138
+ },
139
+ });
140
+ appendLog('uiPrompts.request', res);
141
+ host.uiPrompts.open();
142
+ } catch (e) {
143
+ appendLog('uiPrompts.request.error', { message: e?.message || String(e) });
144
+ }
145
+ });
146
+
147
+ let activeSessionId = '';
148
+ let chatUnsub = null;
149
+ let themeUnsub = null;
150
+
151
+ if (typeof host?.theme?.onChange === 'function') {
152
+ themeUnsub = host.theme.onChange((theme) => applyTheme(theme));
153
+ }
154
+
155
+ const ensureSession = async () => {
156
+ const agents = await run('chat.agents.list', () => host.chat.agents.list());
157
+ let agentId = agents?.agents?.[0]?.id || '';
158
+ if (!agentId) {
159
+ const ensured = await run('chat.agents.ensureDefault', () => host.chat.agents.ensureDefault());
160
+ agentId = ensured?.agent?.id || ensured?.agents?.[0]?.id || '';
161
+ }
162
+ if (!agentId) throw new Error('no agentId');
163
+
164
+ const res = await run('chat.sessions.ensureDefault', () => host.chat.sessions.ensureDefault({ agentId }));
165
+ activeSessionId = res?.session?.id || '';
166
+ if (activeSessionId) appendLog('activeSessionId', activeSessionId);
167
+ return activeSessionId;
168
+ };
169
+
170
+ const btnEnsureSession = mkBtn('chat.sessions.ensureDefault');
171
+ btnEnsureSession.addEventListener('click', () => ensureSession().catch((e) => appendLog('chat.ensureSession.error', { message: e?.message || String(e) })));
172
+
173
+ const btnSend = mkBtn('chat.send');
174
+ btnSend.addEventListener('click', async () => {
175
+ const text = String(input.value || '').trim();
176
+ if (!text) return;
177
+ btnSend.disabled = true;
178
+ try {
179
+ if (!activeSessionId) await ensureSession();
180
+ if (!activeSessionId) throw new Error('no sessionId');
181
+ await run('chat.send', () => host.chat.send({ sessionId: activeSessionId, text }));
182
+ input.value = '';
183
+ } finally {
184
+ btnSend.disabled = false;
185
+ }
186
+ });
187
+
188
+ const btnSub = mkBtn('chat.events.subscribe');
189
+ btnSub.addEventListener('click', async () => {
190
+ if (!activeSessionId) await ensureSession();
191
+ if (!activeSessionId) {
192
+ appendLog('chat.events.subscribe.error', { message: 'no sessionId' });
193
+ return;
194
+ }
195
+ if (chatUnsub) {
196
+ try {
197
+ chatUnsub();
198
+ } catch {
199
+ // ignore
200
+ }
201
+ chatUnsub = null;
202
+ }
203
+ try {
204
+ chatUnsub = host.chat.events.subscribe({ sessionId: activeSessionId }, (payload) => {
205
+ appendLog('chat.event', payload);
206
+ if (payload?.type === 'assistant_delta' && payload?.delta) {
207
+ log.textContent += payload.delta;
208
+ log.scrollTop = log.scrollHeight;
209
+ }
210
+ });
211
+ appendLog('chat.events.subscribe', { ok: true });
212
+ } catch (e) {
213
+ appendLog('chat.events.subscribe.error', { message: e?.message || String(e) });
214
+ }
215
+ });
216
+
217
+ const btnUnsub = mkBtn('chat.events.unsubscribe');
218
+ btnUnsub.addEventListener('click', () => {
219
+ if (chatUnsub) {
220
+ try {
221
+ chatUnsub();
222
+ } catch {
223
+ // ignore
224
+ }
225
+ chatUnsub = null;
226
+ appendLog('chat.events.unsubscribe', { ok: true });
227
+ return;
228
+ }
229
+ run('chat.events.unsubscribe', () => host.chat.events.unsubscribe());
230
+ });
231
+
232
+ actions.appendChild(btnPing);
233
+ actions.appendChild(btnLlmComplete);
234
+ actions.appendChild(btnPrompt);
235
+ actions.appendChild(input);
236
+ actions.appendChild(btnEnsureSession);
237
+ actions.appendChild(btnSend);
238
+ actions.appendChild(btnSub);
239
+ actions.appendChild(btnUnsub);
240
+
241
+ body.appendChild(actions);
242
+ body.appendChild(log);
243
+
244
+ root.appendChild(body);
245
+
246
+ if (headerSlot) {
247
+ try {
248
+ headerSlot.textContent = '';
249
+ headerSlot.appendChild(header);
250
+ } catch {
251
+ root.prepend(header);
252
+ }
253
+ } else {
254
+ root.prepend(header);
255
+ }
256
+
257
+ try {
258
+ container.textContent = '';
259
+ } catch {
260
+ // ignore
261
+ }
262
+ container.appendChild(root);
263
+
264
+ return () => {
265
+ if (chatUnsub) {
266
+ try {
267
+ chatUnsub();
268
+ } catch {
269
+ // ignore
270
+ }
271
+ chatUnsub = null;
272
+ }
273
+ if (themeUnsub) {
274
+ try {
275
+ themeUnsub();
276
+ } catch {
277
+ // ignore
278
+ }
279
+ themeUnsub = null;
280
+ }
281
+ try {
282
+ container.textContent = '';
283
+ } catch {
284
+ // ignore
285
+ }
286
+ };
287
+ }
@@ -1,7 +1,7 @@
1
- # __PLUGIN_NAME__ · MCP Prompt (EN)
2
-
3
- You are a tool assistant for a ChatOS UI App.
4
-
5
- - MCP Server: `__PLUGIN_ID__.__APP_ID__`
6
- - Prefer using its MCP tools when relevant.
7
-
1
+ # __PLUGIN_NAME__ · MCP Prompt (EN)
2
+
3
+ You are a tool assistant for a ChatOS UI App.
4
+
5
+ - MCP Server: `__PLUGIN_ID__.__APP_ID__`
6
+ - Prefer using its MCP tools when relevant.
7
+