@allin-ai/agent-client 0.1.0 → 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 +145 -34
  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.0",
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",
@@ -22,21 +18,93 @@
22
18
  "agent-client",
23
19
  "cli"
24
20
  ],
25
- "main": "./src/index.ts",
21
+ "main": "./dist/index.js",
26
22
  "exports": {
27
- ".": "./src/index.ts",
28
- "./wire": "./src/wire.ts",
29
- "./hub": "./src/hub.ts",
30
- "./logger": "./src/logger.ts",
31
- "./paths": "./src/paths.ts",
32
- "./config": "./src/config.ts",
33
- "./credentials": "./src/credentials.ts",
34
- "./control": "./src/control.ts",
35
- "./runtime": "./src/runtime/index.ts",
36
- "./plugins": "./src/plugins/manager.ts",
37
- "./cli": "./src/cli/commands.ts",
38
- "./service/launchd": "./src/service/launchd.ts",
39
- "./service/systemd": "./src/service/systemd.ts"
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "import": "./dist/index.js",
26
+ "default": "./dist/index.js"
27
+ },
28
+ "./protocol": {
29
+ "types": "./dist/protocol/index.d.ts",
30
+ "import": "./dist/protocol/index.js",
31
+ "default": "./dist/protocol/index.js"
32
+ },
33
+ "./wire": {
34
+ "types": "./dist/wire.d.ts",
35
+ "import": "./dist/wire.js",
36
+ "default": "./dist/wire.js"
37
+ },
38
+ "./hub": {
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"
52
+ },
53
+ "./logger": {
54
+ "types": "./dist/logger.d.ts",
55
+ "import": "./dist/logger.js",
56
+ "default": "./dist/logger.js"
57
+ },
58
+ "./paths": {
59
+ "types": "./dist/paths.d.ts",
60
+ "import": "./dist/paths.js",
61
+ "default": "./dist/paths.js"
62
+ },
63
+ "./config": {
64
+ "types": "./dist/config.d.ts",
65
+ "import": "./dist/config.js",
66
+ "default": "./dist/config.js"
67
+ },
68
+ "./credentials": {
69
+ "types": "./dist/credentials.d.ts",
70
+ "import": "./dist/credentials.js",
71
+ "default": "./dist/credentials.js"
72
+ },
73
+ "./control": {
74
+ "types": "./dist/control.d.ts",
75
+ "import": "./dist/control.js",
76
+ "default": "./dist/control.js"
77
+ },
78
+ "./runtime": {
79
+ "types": "./dist/runtime/index.d.ts",
80
+ "import": "./dist/runtime/index.js",
81
+ "default": "./dist/runtime/index.js"
82
+ },
83
+ "./plugins": {
84
+ "types": "./dist/plugins/manager.d.ts",
85
+ "import": "./dist/plugins/manager.js",
86
+ "default": "./dist/plugins/manager.js"
87
+ },
88
+ "./cli": {
89
+ "types": "./dist/cli/commands.d.ts",
90
+ "import": "./dist/cli/commands.js",
91
+ "default": "./dist/cli/commands.js"
92
+ },
93
+ "./service/launchd": {
94
+ "types": "./dist/service/launchd.d.ts",
95
+ "import": "./dist/service/launchd.js",
96
+ "default": "./dist/service/launchd.js"
97
+ },
98
+ "./service/systemd": {
99
+ "types": "./dist/service/systemd.d.ts",
100
+ "import": "./dist/service/systemd.js",
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"
107
+ }
40
108
  },
41
109
  "publishConfig": {
42
110
  "exports": {
@@ -45,15 +113,30 @@
45
113
  "import": "./dist/index.js",
46
114
  "default": "./dist/index.js"
47
115
  },
116
+ "./protocol": {
117
+ "types": "./dist/protocol/index.d.ts",
118
+ "import": "./dist/protocol/index.js",
119
+ "default": "./dist/protocol/index.js"
120
+ },
48
121
  "./wire": {
49
122
  "types": "./dist/wire.d.ts",
50
123
  "import": "./dist/wire.js",
51
124
  "default": "./dist/wire.js"
52
125
  },
53
126
  "./hub": {
54
- "types": "./dist/hub.d.ts",
55
- "import": "./dist/hub.js",
56
- "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"
57
140
  },
58
141
  "./logger": {
59
142
  "types": "./dist/logger.d.ts",
@@ -104,37 +187,65 @@
104
187
  "types": "./dist/service/systemd.d.ts",
105
188
  "import": "./dist/service/systemd.js",
106
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"
107
195
  }
108
196
  },
109
197
  "main": "./dist/index.js",
110
198
  "types": "./dist/index.d.ts",
111
- "access": "public",
112
- "registry": "https://registry.npmjs.org/"
199
+ "access": "public"
113
200
  },
114
201
  "bin": {
115
202
  "allinai-agent": "./bin/allinai-agent"
116
203
  },
117
204
  "files": [
118
205
  "bin",
119
- "dist"
206
+ "dist",
207
+ "web"
120
208
  ],
121
209
  "scripts": {
122
210
  "build": "node scripts/build.mjs",
123
- "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'",
124
213
  "typecheck": "tsc --noEmit",
125
- "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'"
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"
126
217
  },
127
218
  "dependencies": {
219
+ "ws": "^8.18.0"
220
+ },
221
+ "peerDependencies": {
128
222
  "@anthropic-ai/claude-agent-sdk": "^0.3.168",
129
223
  "@earendil-works/pi-coding-agent": "0.85.1",
130
- "@openai/codex-sdk": "^0.142.4",
131
- "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
+ }
132
236
  },
133
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",
134
242
  "@types/ws": "^8.5.14",
135
- "typescript": "^5.8.3"
243
+ "tsx": "^4.20.3",
244
+ "typescript": "^5.8.3",
245
+ "ws": "^8.18.0"
136
246
  },
137
247
  "engines": {
138
- "node": ">=22.19.0"
139
- }
248
+ "node": ">=22.18.0"
249
+ },
250
+ "types": "./dist/index.d.ts"
140
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
+ }