@leeoohoo/ui-apps-devkit 0.1.0 → 0.1.2

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