@allin-ai/agent-client 0.1.1 → 0.2.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.
Files changed (146) hide show
  1. package/README.md +75 -42
  2. package/dist/cli/commands.d.ts +9 -0
  3. package/dist/cli/commands.d.ts.map +1 -1
  4. package/dist/cli/commands.js +244 -7
  5. package/dist/cli/commands.js.map +1 -1
  6. package/dist/client/index.d.ts +7 -0
  7. package/dist/client/index.d.ts.map +1 -0
  8. package/dist/client/index.js +6 -0
  9. package/dist/client/index.js.map +1 -0
  10. package/dist/client/state-store.d.ts +1 -1
  11. package/dist/client/state-store.d.ts.map +1 -1
  12. package/dist/client/state-store.js +1 -1
  13. package/dist/client/state-store.js.map +1 -1
  14. package/dist/client/supervisor.d.ts +37 -1
  15. package/dist/client/supervisor.d.ts.map +1 -1
  16. package/dist/client/supervisor.js +159 -10
  17. package/dist/client/supervisor.js.map +1 -1
  18. package/dist/client/transport.d.ts +5 -1
  19. package/dist/client/transport.d.ts.map +1 -1
  20. package/dist/client/types.d.ts +6 -64
  21. package/dist/client/types.d.ts.map +1 -1
  22. package/dist/client/types.js +9 -12
  23. package/dist/client/types.js.map +1 -1
  24. package/dist/client/wire.d.ts +2 -31
  25. package/dist/client/wire.d.ts.map +1 -1
  26. package/dist/client/wire.js +2 -301
  27. package/dist/client/wire.js.map +1 -1
  28. package/dist/client/ws-transport.d.ts +5 -1
  29. package/dist/client/ws-transport.d.ts.map +1 -1
  30. package/dist/client/ws-transport.js +30 -6
  31. package/dist/client/ws-transport.js.map +1 -1
  32. package/dist/config.d.ts +10 -1
  33. package/dist/config.d.ts.map +1 -1
  34. package/dist/config.js +30 -1
  35. package/dist/config.js.map +1 -1
  36. package/dist/control.d.ts +6 -0
  37. package/dist/control.d.ts.map +1 -1
  38. package/dist/control.js +25 -0
  39. package/dist/control.js.map +1 -1
  40. package/dist/demo/index.d.ts +3 -0
  41. package/dist/demo/index.d.ts.map +1 -0
  42. package/dist/demo/index.js +2 -0
  43. package/dist/demo/index.js.map +1 -0
  44. package/dist/demo/observable-store.d.ts +55 -0
  45. package/dist/demo/observable-store.d.ts.map +1 -0
  46. package/dist/demo/observable-store.js +88 -0
  47. package/dist/demo/observable-store.js.map +1 -0
  48. package/dist/demo/projection.d.ts +23 -0
  49. package/dist/demo/projection.d.ts.map +1 -0
  50. package/dist/demo/projection.js +47 -0
  51. package/dist/demo/projection.js.map +1 -0
  52. package/dist/demo/site.d.ts +30 -0
  53. package/dist/demo/site.d.ts.map +1 -0
  54. package/dist/demo/site.js +330 -0
  55. package/dist/demo/site.js.map +1 -0
  56. package/dist/demo/static.d.ts +5 -0
  57. package/dist/demo/static.d.ts.map +1 -0
  58. package/dist/demo/static.js +53 -0
  59. package/dist/demo/static.js.map +1 -0
  60. package/dist/demo/token-registry.d.ts +20 -0
  61. package/dist/demo/token-registry.d.ts.map +1 -0
  62. package/dist/demo/token-registry.js +34 -0
  63. package/dist/demo/token-registry.js.map +1 -0
  64. package/dist/demo/types.d.ts +8 -0
  65. package/dist/demo/types.d.ts.map +1 -0
  66. package/dist/demo/types.js +2 -0
  67. package/dist/demo/types.js.map +1 -0
  68. package/dist/hub/agent-hub.d.ts +4 -0
  69. package/dist/hub/agent-hub.d.ts.map +1 -0
  70. package/dist/hub/agent-hub.js +352 -0
  71. package/dist/hub/agent-hub.js.map +1 -0
  72. package/dist/hub/index.d.ts +3 -0
  73. package/dist/hub/index.d.ts.map +1 -0
  74. package/dist/hub/index.js +2 -0
  75. package/dist/hub/index.js.map +1 -0
  76. package/dist/hub/testkit/index.d.ts +4 -0
  77. package/dist/hub/testkit/index.d.ts.map +1 -0
  78. package/dist/hub/testkit/index.js +4 -0
  79. package/dist/hub/testkit/index.js.map +1 -0
  80. package/dist/hub/testkit/memory-hub.d.ts +23 -0
  81. package/dist/hub/testkit/memory-hub.d.ts.map +1 -0
  82. package/dist/hub/testkit/memory-hub.js +33 -0
  83. package/dist/hub/testkit/memory-hub.js.map +1 -0
  84. package/dist/hub/testkit/memory-store.d.ts +62 -0
  85. package/dist/hub/testkit/memory-store.d.ts.map +1 -0
  86. package/dist/hub/testkit/memory-store.js +186 -0
  87. package/dist/hub/testkit/memory-store.js.map +1 -0
  88. package/dist/hub/types.d.ts +118 -0
  89. package/dist/hub/types.d.ts.map +1 -0
  90. package/dist/hub/types.js +2 -0
  91. package/dist/hub/types.js.map +1 -0
  92. package/dist/hub.d.ts +2 -82
  93. package/dist/hub.d.ts.map +1 -1
  94. package/dist/hub.js +2 -411
  95. package/dist/hub.js.map +1 -1
  96. package/dist/index.d.ts +3 -4
  97. package/dist/index.d.ts.map +1 -1
  98. package/dist/index.js +4 -4
  99. package/dist/index.js.map +1 -1
  100. package/dist/login.d.ts +22 -0
  101. package/dist/login.d.ts.map +1 -0
  102. package/dist/login.js +94 -0
  103. package/dist/login.js.map +1 -0
  104. package/dist/protocol/index.d.ts +3 -0
  105. package/dist/protocol/index.d.ts.map +1 -0
  106. package/dist/protocol/index.js +3 -0
  107. package/dist/protocol/index.js.map +1 -0
  108. package/dist/protocol/types.d.ts +111 -0
  109. package/dist/protocol/types.d.ts.map +1 -0
  110. package/dist/protocol/types.js +13 -0
  111. package/dist/protocol/types.js.map +1 -0
  112. package/dist/protocol/wire.d.ts +25 -0
  113. package/dist/protocol/wire.d.ts.map +1 -0
  114. package/dist/protocol/wire.js +337 -0
  115. package/dist/protocol/wire.js.map +1 -0
  116. package/dist/runtime/claude.d.ts +32 -9
  117. package/dist/runtime/claude.d.ts.map +1 -1
  118. package/dist/runtime/claude.js +18 -2
  119. package/dist/runtime/claude.js.map +1 -1
  120. package/dist/runtime/codex.d.ts +12 -0
  121. package/dist/runtime/codex.d.ts.map +1 -1
  122. package/dist/runtime/codex.js +31 -6
  123. package/dist/runtime/codex.js.map +1 -1
  124. package/dist/runtime/index.d.ts +4 -4
  125. package/dist/runtime/index.d.ts.map +1 -1
  126. package/dist/runtime/index.js +1 -1
  127. package/dist/runtime/index.js.map +1 -1
  128. package/dist/runtime/pi.d.ts +25 -2
  129. package/dist/runtime/pi.d.ts.map +1 -1
  130. package/dist/runtime/pi.js +20 -3
  131. package/dist/runtime/pi.js.map +1 -1
  132. package/dist/runtime/registry.d.ts +9 -4
  133. package/dist/runtime/registry.d.ts.map +1 -1
  134. package/dist/runtime/registry.js +7 -7
  135. package/dist/runtime/registry.js.map +1 -1
  136. package/dist/runtime/runner-child.js +48 -4
  137. package/dist/runtime/runner-child.js.map +1 -1
  138. package/dist/wire.d.ts +2 -23
  139. package/dist/wire.d.ts.map +1 -1
  140. package/dist/wire.js +2 -6
  141. package/dist/wire.js.map +1 -1
  142. package/package.json +80 -24
  143. package/web/app.js +253 -0
  144. package/web/index.html +421 -0
  145. package/web/login.html +85 -0
  146. package/web/style.css +484 -0
package/package.json CHANGED
@@ -1,18 +1,14 @@
1
1
  {
2
2
  "name": "@allin-ai/agent-client",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Standalone persistent Agent Client: connect to a Hub, durably execute local platform agents and policy-gated capabilities, and report state upstream.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "git+https://github.com/df007df/AllInAI.git",
10
- "directory": "packages/allinai-agent-client"
9
+ "url": "git+https://github.com/df007df/allinai-agentkit.git"
11
10
  },
12
- "bugs": {
13
- "url": "https://github.com/df007df/AllInAI/issues"
14
- },
15
- "homepage": "https://github.com/df007df/AllInAI/tree/main/packages/allinai-agent-client#readme",
11
+ "private": false,
16
12
  "keywords": [
17
13
  "agent",
18
14
  "sdk",
@@ -29,15 +25,30 @@
29
25
  "import": "./dist/index.js",
30
26
  "default": "./dist/index.js"
31
27
  },
28
+ "./protocol": {
29
+ "types": "./dist/protocol/index.d.ts",
30
+ "import": "./dist/protocol/index.js",
31
+ "default": "./dist/protocol/index.js"
32
+ },
32
33
  "./wire": {
33
34
  "types": "./dist/wire.d.ts",
34
35
  "import": "./dist/wire.js",
35
36
  "default": "./dist/wire.js"
36
37
  },
37
38
  "./hub": {
38
- "types": "./dist/hub.d.ts",
39
- "import": "./dist/hub.js",
40
- "default": "./dist/hub.js"
39
+ "types": "./dist/hub/index.d.ts",
40
+ "import": "./dist/hub/index.js",
41
+ "default": "./dist/hub/index.js"
42
+ },
43
+ "./hub/testkit": {
44
+ "types": "./dist/hub/testkit/index.d.ts",
45
+ "import": "./dist/hub/testkit/index.js",
46
+ "default": "./dist/hub/testkit/index.js"
47
+ },
48
+ "./client": {
49
+ "types": "./dist/client/index.d.ts",
50
+ "import": "./dist/client/index.js",
51
+ "default": "./dist/client/index.js"
41
52
  },
42
53
  "./logger": {
43
54
  "types": "./dist/logger.d.ts",
@@ -88,6 +99,11 @@
88
99
  "types": "./dist/service/systemd.d.ts",
89
100
  "import": "./dist/service/systemd.js",
90
101
  "default": "./dist/service/systemd.js"
102
+ },
103
+ "./demo": {
104
+ "types": "./dist/demo/index.d.ts",
105
+ "import": "./dist/demo/index.js",
106
+ "default": "./dist/demo/index.js"
91
107
  }
92
108
  },
93
109
  "publishConfig": {
@@ -97,15 +113,30 @@
97
113
  "import": "./dist/index.js",
98
114
  "default": "./dist/index.js"
99
115
  },
116
+ "./protocol": {
117
+ "types": "./dist/protocol/index.d.ts",
118
+ "import": "./dist/protocol/index.js",
119
+ "default": "./dist/protocol/index.js"
120
+ },
100
121
  "./wire": {
101
122
  "types": "./dist/wire.d.ts",
102
123
  "import": "./dist/wire.js",
103
124
  "default": "./dist/wire.js"
104
125
  },
105
126
  "./hub": {
106
- "types": "./dist/hub.d.ts",
107
- "import": "./dist/hub.js",
108
- "default": "./dist/hub.js"
127
+ "types": "./dist/hub/index.d.ts",
128
+ "import": "./dist/hub/index.js",
129
+ "default": "./dist/hub/index.js"
130
+ },
131
+ "./hub/testkit": {
132
+ "types": "./dist/hub/testkit/index.d.ts",
133
+ "import": "./dist/hub/testkit/index.js",
134
+ "default": "./dist/hub/testkit/index.js"
135
+ },
136
+ "./client": {
137
+ "types": "./dist/client/index.d.ts",
138
+ "import": "./dist/client/index.js",
139
+ "default": "./dist/client/index.js"
109
140
  },
110
141
  "./logger": {
111
142
  "types": "./dist/logger.d.ts",
@@ -156,40 +187,65 @@
156
187
  "types": "./dist/service/systemd.d.ts",
157
188
  "import": "./dist/service/systemd.js",
158
189
  "default": "./dist/service/systemd.js"
190
+ },
191
+ "./demo": {
192
+ "types": "./dist/demo/index.d.ts",
193
+ "import": "./dist/demo/index.js",
194
+ "default": "./dist/demo/index.js"
159
195
  }
160
196
  },
161
197
  "main": "./dist/index.js",
162
198
  "types": "./dist/index.d.ts",
163
- "access": "public",
164
- "registry": "https://registry.npmjs.org/"
199
+ "access": "public"
165
200
  },
166
201
  "bin": {
167
202
  "allinai-agent": "./bin/allinai-agent"
168
203
  },
169
204
  "files": [
170
205
  "bin",
171
- "dist"
206
+ "dist",
207
+ "web"
172
208
  ],
173
209
  "scripts": {
174
210
  "build": "node scripts/build.mjs",
175
- "test": "cd ../.. && TSX_TSCONFIG_PATH=packages/allinai-agent-client/tsconfig.json pnpm exec tsx --test 'packages/allinai-agent-client/src/**/*.test.ts'",
211
+ "demo": "node bin/allinai-agent demo",
212
+ "test": "tsx --tsconfig ./tsconfig.json --test 'src/**/*.test.ts'",
176
213
  "typecheck": "tsc --noEmit",
177
- "prepublishOnly": "pnpm run build && cd ../.. && TSX_TSCONFIG_PATH=packages/allinai-agent-client/tsconfig.json pnpm exec tsx --test 'packages/allinai-agent-client/src/**/*.test.ts'",
178
- "prepack": "node scripts/publish-manifest.mjs prepare",
179
- "postpack": "node scripts/publish-manifest.mjs restore"
214
+ "prepack": "node scripts/build.mjs && node scripts/publish-manifest.mjs prepare",
215
+ "postpack": "node scripts/publish-manifest.mjs restore",
216
+ "verify:artifact": "node scripts/verify-package-artifact.mjs"
180
217
  },
181
218
  "dependencies": {
219
+ "ws": "^8.18.0"
220
+ },
221
+ "peerDependencies": {
182
222
  "@anthropic-ai/claude-agent-sdk": "^0.3.168",
183
223
  "@earendil-works/pi-coding-agent": "0.85.1",
184
- "@openai/codex-sdk": "^0.142.4",
185
- "ws": "^8.18.0"
224
+ "@openai/codex-sdk": "^0.142.4"
225
+ },
226
+ "peerDependenciesMeta": {
227
+ "@anthropic-ai/claude-agent-sdk": {
228
+ "optional": true
229
+ },
230
+ "@earendil-works/pi-coding-agent": {
231
+ "optional": true
232
+ },
233
+ "@openai/codex-sdk": {
234
+ "optional": true
235
+ }
186
236
  },
187
237
  "devDependencies": {
238
+ "@anthropic-ai/claude-agent-sdk": "^0.3.168",
239
+ "@earendil-works/pi-coding-agent": "0.85.1",
240
+ "@openai/codex-sdk": "^0.142.4",
241
+ "@types/node": "^22.15.3",
188
242
  "@types/ws": "^8.5.14",
189
- "typescript": "^5.8.3"
243
+ "tsx": "^4.20.3",
244
+ "typescript": "^5.8.3",
245
+ "ws": "^8.18.0"
190
246
  },
191
247
  "engines": {
192
- "node": ">=22.19.0"
248
+ "node": ">=22.18.0"
193
249
  },
194
250
  "types": "./dist/index.d.ts"
195
251
  }
package/web/app.js ADDED
@@ -0,0 +1,253 @@
1
+ const clientList = document.getElementById("client-list");
2
+ const offerClient = document.getElementById("offer-client");
3
+ const offerRuntime = document.getElementById("offer-runtime");
4
+ const offerProject = document.getElementById("offer-project");
5
+ const offerPrompt = document.getElementById("offer-prompt");
6
+ const offerSend = document.getElementById("offer-send");
7
+ const offerResult = document.getElementById("offer-result");
8
+ const pluginClient = document.getElementById("plugin-client");
9
+ const pluginUrl = document.getElementById("plugin-url");
10
+ const pluginId = document.getElementById("plugin-id");
11
+ const pluginSyncBtn = document.getElementById("plugin-sync");
12
+ const pluginResult = document.getElementById("plugin-result");
13
+ const inventoryClient = document.getElementById("inventory-client");
14
+ const inventoryQueryBtn = document.getElementById("inventory-query");
15
+ const inventoryResult = document.getElementById("inventory-result");
16
+ const inventoryPlatforms = document.getElementById("inventory-platforms");
17
+ const inventoryPlugins = document.getElementById("inventory-plugins");
18
+ let lastInventoryClient = null;
19
+ const timeline = document.getElementById("timeline");
20
+ const sseStatus = document.getElementById("sse-status");
21
+
22
+ const ONLINE_WINDOW_MS = 30_000;
23
+ const clients = new Map(); // clientId -> lastSeen
24
+
25
+ function renderClients() {
26
+ clientList.replaceChildren();
27
+ offerClient.replaceChildren();
28
+ if (clients.size === 0) {
29
+ const empty = document.createElement("li");
30
+ empty.className = "empty";
31
+ empty.textContent = "等待接入。先在终端执行左侧 login 与 daemon。";
32
+ clientList.append(empty);
33
+ return;
34
+ }
35
+ for (const [clientId, lastSeen] of clients) {
36
+ const online = Date.now() - lastSeen < ONLINE_WINDOW_MS;
37
+ const item = document.createElement("li");
38
+ const name = document.createElement("span");
39
+ name.textContent = clientId;
40
+ const state = document.createElement("span");
41
+ state.className = online ? "state-on" : "state-off";
42
+ state.textContent = online ? "在线" : "心跳超时";
43
+ item.append(name, state);
44
+ clientList.append(item);
45
+ const option = document.createElement("option");
46
+ option.value = clientId;
47
+ option.textContent = clientId;
48
+ offerClient.append(option);
49
+ pluginClient.append(option.cloneNode(true));
50
+ inventoryClient.append(option.cloneNode(true));
51
+ }
52
+ }
53
+
54
+ function appendTimeline(entry) {
55
+ const item = document.createElement("li");
56
+ const time = document.createElement("span");
57
+ time.className = "t";
58
+ time.textContent = `[${new Date(entry.at ?? Date.now()).toLocaleTimeString()}] `;
59
+ item.append(time, document.createTextNode(entry.kind));
60
+ if (entry.clientId) item.append(document.createTextNode(` · ${entry.clientId}`));
61
+ if (entry.targetClientId)
62
+ item.append(document.createTextNode(` → ${entry.targetClientId}`));
63
+ timeline.prepend(item);
64
+ while (timeline.children.length > 200) timeline.lastChild.remove();
65
+ }
66
+
67
+ offerSend.onclick = async () => {
68
+ const clientId = offerClient.value;
69
+ const prompt = offerPrompt.value.trim();
70
+ const project = offerProject.value.trim();
71
+ offerResult.hidden = true;
72
+ if (!clientId || !prompt) {
73
+ offerResult.textContent = "请选择 client 并输入 prompt";
74
+ offerResult.hidden = false;
75
+ return;
76
+ }
77
+ try {
78
+ const response = await fetch("/api/demo/offers", {
79
+ method: "POST",
80
+ headers: { "content-type": "application/json" },
81
+ body: JSON.stringify({
82
+ clientId,
83
+ prompt,
84
+ runtime: offerRuntime.value,
85
+ ...(project ? { project } : {}),
86
+ }),
87
+ });
88
+ const payload = await response.json();
89
+ if (!response.ok) throw new Error(payload.error ?? response.status);
90
+ offerResult.textContent = `已入队 offer ${payload.offerId}(runtime=${offerRuntime.value}${project ? `,project=${project}` : ""})`;
91
+ } catch (error) {
92
+ offerResult.textContent = `派发失败:${error.message}`;
93
+ }
94
+ offerResult.hidden = false;
95
+ };
96
+
97
+ pluginSyncBtn.onclick = async () => {
98
+ const clientId = pluginClient.value;
99
+ const gitUrl = pluginUrl.value.trim();
100
+ const id = pluginId.value.trim();
101
+ pluginResult.hidden = true;
102
+ if (!clientId || !gitUrl || !id) {
103
+ pluginResult.textContent = "请选择 client 并填写插件 ID 与 Git URL";
104
+ pluginResult.hidden = false;
105
+ return;
106
+ }
107
+ try {
108
+ const response = await fetch("/api/demo/plugins/sync", {
109
+ method: "POST",
110
+ headers: { "content-type": "application/json" },
111
+ body: JSON.stringify({
112
+ clientId,
113
+ plugins: [{ id, gitUrl, enabled: true }],
114
+ }),
115
+ });
116
+ const payload = await response.json();
117
+ if (!response.ok) throw new Error(payload.error ?? response.status);
118
+ pluginResult.textContent = payload.delivered
119
+ ? "已推送 plugin.sync,等待 client 回执(见时间线 plugin.acknowledged)"
120
+ : "client 当前不在线,未推送";
121
+ } catch (error) {
122
+ pluginResult.textContent = `推送失败:${error.message}`;
123
+ }
124
+ pluginResult.hidden = false;
125
+ };
126
+
127
+ inventoryQueryBtn.onclick = async () => {
128
+ const clientId = inventoryClient.value;
129
+ inventoryResult.hidden = true;
130
+ if (!clientId) {
131
+ inventoryResult.textContent = "请选择 client";
132
+ inventoryResult.hidden = false;
133
+ return;
134
+ }
135
+ try {
136
+ const response = await fetch("/api/demo/inventory/query", {
137
+ method: "POST",
138
+ headers: { "content-type": "application/json" },
139
+ body: JSON.stringify({ clientId }),
140
+ });
141
+ const payload = await response.json();
142
+ if (!response.ok) throw new Error(payload.error ?? response.status);
143
+ inventoryResult.textContent = payload.delivered
144
+ ? "已请求上报,等待 client 回传清单…"
145
+ : "client 当前不在线,未请求";
146
+ } catch (error) {
147
+ inventoryResult.textContent = `请求失败:${error.message}`;
148
+ }
149
+ inventoryResult.hidden = false;
150
+ };
151
+
152
+ async function loadInventory(clientId) {
153
+ const response = await fetch(`/api/demo/inventory/${encodeURIComponent(clientId)}`);
154
+ if (!response.ok) return null;
155
+ return response.json();
156
+ }
157
+
158
+ function renderInventory(report) {
159
+ if (!report) {
160
+ inventoryPlatforms.hidden = true;
161
+ inventoryPlugins.hidden = true;
162
+ inventoryResult.textContent = "该 client 尚未上报清单";
163
+ inventoryResult.hidden = false;
164
+ return;
165
+ }
166
+ inventoryResult.hidden = true;
167
+ const pBody = inventoryPlatforms.querySelector("tbody");
168
+ pBody.replaceChildren(
169
+ ...report.platforms.map((p) => {
170
+ const tr = document.createElement("tr");
171
+ if (!p.installed) tr.className = "row-muted";
172
+ tr.append(
173
+ cell(p.platform), cell(p.installed ? "✓" : "✗"),
174
+ cell(p.version ?? "-"), cell(p.reason ?? ""),
175
+ );
176
+ return tr;
177
+ }),
178
+ );
179
+ inventoryPlatforms.hidden = false;
180
+ const jBody = inventoryPlugins.querySelector("tbody");
181
+ jBody.replaceChildren(
182
+ ...report.plugins.map((j) => {
183
+ const tr = document.createElement("tr");
184
+ if (j.status === "failed") tr.className = "row-failed";
185
+ tr.append(
186
+ cell(j.id), cell(j.status), cell(j.enabled ? "✓" : "✗"),
187
+ cell(j.resolvedCommit === "unresolved" ? "unresolved" : j.resolvedCommit.slice(0, 7)),
188
+ cell(j.ref ?? "-"),
189
+ );
190
+ return tr;
191
+ }),
192
+ );
193
+ inventoryPlugins.hidden = false;
194
+ }
195
+
196
+ function cell(text) {
197
+ const td = document.createElement("td");
198
+ td.textContent = String(text ?? "");
199
+ return td;
200
+ }
201
+
202
+ function setSseState(state, label) {
203
+ sseStatus.dataset.state = state;
204
+ sseStatus.textContent = label;
205
+ }
206
+
207
+ const source = new EventSource("/api/demo/observe");
208
+ source.onopen = () => setSseState("online", "观测流已连接");
209
+ source.onerror = () => setSseState("offline", "观测流已断开,自动重连中");
210
+
211
+ source.addEventListener("snapshot", (event) => {
212
+ const snapshot = JSON.parse(event.data);
213
+ if (snapshot.warning) {
214
+ const node = document.getElementById("host-warning");
215
+ node.textContent = snapshot.warning;
216
+ node.hidden = false;
217
+ }
218
+ for (const client of snapshot.clients ?? []) {
219
+ clients.set(client.clientId, client.lastSeen);
220
+ }
221
+ renderClients();
222
+ });
223
+ source.addEventListener("observation", (event) => {
224
+ const observation = JSON.parse(event.data);
225
+ if ("clientId" in observation && observation.clientId) {
226
+ clients.set(observation.clientId, Date.now());
227
+ }
228
+ if (observation.kind === "inventory.recorded") {
229
+ const clientId = observation.clientId;
230
+ lastInventoryClient = clientId;
231
+ if (clientId === inventoryClient.value || !inventoryClient.value) {
232
+ loadInventory(clientId).then((report) => {
233
+ if (report) renderInventory(report);
234
+ });
235
+ }
236
+ }
237
+ appendTimeline(observation);
238
+ renderClients();
239
+ });
240
+ setInterval(renderClients, 5_000);
241
+
242
+ /* 终端块复制按钮 */
243
+ const copyBtn = document.querySelector("[data-copy]");
244
+ if (copyBtn) {
245
+ copyBtn.onclick = async () => {
246
+ const code = document.querySelector(".term-body code");
247
+ await navigator.clipboard.writeText(code.textContent);
248
+ copyBtn.textContent = "已复制";
249
+ setTimeout(() => {
250
+ copyBtn.textContent = "复制全部";
251
+ }, 1500);
252
+ };
253
+ }