p-spore 0.1.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 (261) hide show
  1. package/README.md +97 -0
  2. package/dist/agents/agent-manager.d.ts +57 -0
  3. package/dist/agents/agent-manager.d.ts.map +1 -0
  4. package/dist/agents/agent-manager.js +559 -0
  5. package/dist/agents/agent-manager.js.map +1 -0
  6. package/dist/agents/agent-tools.d.ts +4 -0
  7. package/dist/agents/agent-tools.d.ts.map +1 -0
  8. package/dist/agents/agent-tools.js +111 -0
  9. package/dist/agents/agent-tools.js.map +1 -0
  10. package/dist/agents/types.d.ts +52 -0
  11. package/dist/agents/types.d.ts.map +1 -0
  12. package/dist/agents/types.js +2 -0
  13. package/dist/agents/types.js.map +1 -0
  14. package/dist/application/agent-application.d.ts +89 -0
  15. package/dist/application/agent-application.d.ts.map +1 -0
  16. package/dist/application/agent-application.js +542 -0
  17. package/dist/application/agent-application.js.map +1 -0
  18. package/dist/automation/automation-tools.d.ts +4 -0
  19. package/dist/automation/automation-tools.d.ts.map +1 -0
  20. package/dist/automation/automation-tools.js +35 -0
  21. package/dist/automation/automation-tools.js.map +1 -0
  22. package/dist/automation/cron-adapter.d.ts +11 -0
  23. package/dist/automation/cron-adapter.d.ts.map +1 -0
  24. package/dist/automation/cron-adapter.js +48 -0
  25. package/dist/automation/cron-adapter.js.map +1 -0
  26. package/dist/automation/daemon-run-executor.d.ts +18 -0
  27. package/dist/automation/daemon-run-executor.d.ts.map +1 -0
  28. package/dist/automation/daemon-run-executor.js +69 -0
  29. package/dist/automation/daemon-run-executor.js.map +1 -0
  30. package/dist/automation/job-store.d.ts +12 -0
  31. package/dist/automation/job-store.d.ts.map +1 -0
  32. package/dist/automation/job-store.js +185 -0
  33. package/dist/automation/job-store.js.map +1 -0
  34. package/dist/automation/run-ledger.d.ts +25 -0
  35. package/dist/automation/run-ledger.d.ts.map +1 -0
  36. package/dist/automation/run-ledger.js +148 -0
  37. package/dist/automation/run-ledger.js.map +1 -0
  38. package/dist/automation/scheduler.d.ts +36 -0
  39. package/dist/automation/scheduler.d.ts.map +1 -0
  40. package/dist/automation/scheduler.js +300 -0
  41. package/dist/automation/scheduler.js.map +1 -0
  42. package/dist/automation/types.d.ts +81 -0
  43. package/dist/automation/types.d.ts.map +1 -0
  44. package/dist/automation/types.js +2 -0
  45. package/dist/automation/types.js.map +1 -0
  46. package/dist/cli/main.d.ts +4 -0
  47. package/dist/cli/main.d.ts.map +1 -0
  48. package/dist/cli/main.js +768 -0
  49. package/dist/cli/main.js.map +1 -0
  50. package/dist/config/config.d.ts +52 -0
  51. package/dist/config/config.d.ts.map +1 -0
  52. package/dist/config/config.js +345 -0
  53. package/dist/config/config.js.map +1 -0
  54. package/dist/daemon/daemon.d.ts +30 -0
  55. package/dist/daemon/daemon.d.ts.map +1 -0
  56. package/dist/daemon/daemon.js +437 -0
  57. package/dist/daemon/daemon.js.map +1 -0
  58. package/dist/daemon/supervisor.d.ts +18 -0
  59. package/dist/daemon/supervisor.d.ts.map +1 -0
  60. package/dist/daemon/supervisor.js +37 -0
  61. package/dist/daemon/supervisor.js.map +1 -0
  62. package/dist/desktop/daemon-bridge.d.ts +32 -0
  63. package/dist/desktop/daemon-bridge.d.ts.map +1 -0
  64. package/dist/desktop/daemon-bridge.js +204 -0
  65. package/dist/desktop/daemon-bridge.js.map +1 -0
  66. package/dist/desktop/daemon-supervisor.d.ts +2 -0
  67. package/dist/desktop/daemon-supervisor.d.ts.map +1 -0
  68. package/dist/desktop/daemon-supervisor.js +2 -0
  69. package/dist/desktop/daemon-supervisor.js.map +1 -0
  70. package/dist/desktop/ipc-contract.d.ts +153 -0
  71. package/dist/desktop/ipc-contract.d.ts.map +1 -0
  72. package/dist/desktop/ipc-contract.js +219 -0
  73. package/dist/desktop/ipc-contract.js.map +1 -0
  74. package/dist/desktop/main.d.ts +2 -0
  75. package/dist/desktop/main.d.ts.map +1 -0
  76. package/dist/desktop/main.js +594 -0
  77. package/dist/desktop/main.js.map +1 -0
  78. package/dist/desktop/preload.cjs +32 -0
  79. package/dist/desktop/renderer/index.html +284 -0
  80. package/dist/desktop/renderer/renderer.d.ts +2 -0
  81. package/dist/desktop/renderer/renderer.d.ts.map +1 -0
  82. package/dist/desktop/renderer/renderer.js +895 -0
  83. package/dist/desktop/renderer/renderer.js.map +1 -0
  84. package/dist/desktop/renderer/store.d.ts +33 -0
  85. package/dist/desktop/renderer/store.d.ts.map +1 -0
  86. package/dist/desktop/renderer/store.js +144 -0
  87. package/dist/desktop/renderer/store.js.map +1 -0
  88. package/dist/desktop/renderer/styles.css +540 -0
  89. package/dist/desktop/security.d.ts +5 -0
  90. package/dist/desktop/security.d.ts.map +1 -0
  91. package/dist/desktop/security.js +49 -0
  92. package/dist/desktop/security.js.map +1 -0
  93. package/dist/desktop/smoke-fixture.d.ts +3 -0
  94. package/dist/desktop/smoke-fixture.d.ts.map +1 -0
  95. package/dist/desktop/smoke-fixture.js +223 -0
  96. package/dist/desktop/smoke-fixture.js.map +1 -0
  97. package/dist/desktop/validation.d.ts +8 -0
  98. package/dist/desktop/validation.d.ts.map +1 -0
  99. package/dist/desktop/validation.js +341 -0
  100. package/dist/desktop/validation.js.map +1 -0
  101. package/dist/domain/runtime.d.ts +116 -0
  102. package/dist/domain/runtime.d.ts.map +1 -0
  103. package/dist/domain/runtime.js +2 -0
  104. package/dist/domain/runtime.js.map +1 -0
  105. package/dist/gateway/local-gateway.d.ts +23 -0
  106. package/dist/gateway/local-gateway.d.ts.map +1 -0
  107. package/dist/gateway/local-gateway.js +265 -0
  108. package/dist/gateway/local-gateway.js.map +1 -0
  109. package/dist/kernel/loop.d.ts +10 -0
  110. package/dist/kernel/loop.d.ts.map +1 -0
  111. package/dist/kernel/loop.js +41 -0
  112. package/dist/kernel/loop.js.map +1 -0
  113. package/dist/kernel/reducer.d.ts +3 -0
  114. package/dist/kernel/reducer.d.ts.map +1 -0
  115. package/dist/kernel/reducer.js +102 -0
  116. package/dist/kernel/reducer.js.map +1 -0
  117. package/dist/kernel/state.d.ts +93 -0
  118. package/dist/kernel/state.d.ts.map +1 -0
  119. package/dist/kernel/state.js +15 -0
  120. package/dist/kernel/state.js.map +1 -0
  121. package/dist/mcp/mcp-manager.d.ts +55 -0
  122. package/dist/mcp/mcp-manager.d.ts.map +1 -0
  123. package/dist/mcp/mcp-manager.js +591 -0
  124. package/dist/mcp/mcp-manager.js.map +1 -0
  125. package/dist/mcp/mcp-tools.d.ts +4 -0
  126. package/dist/mcp/mcp-tools.d.ts.map +1 -0
  127. package/dist/mcp/mcp-tools.js +53 -0
  128. package/dist/mcp/mcp-tools.js.map +1 -0
  129. package/dist/mcp/types.d.ts +73 -0
  130. package/dist/mcp/types.d.ts.map +1 -0
  131. package/dist/mcp/types.js +2 -0
  132. package/dist/mcp/types.js.map +1 -0
  133. package/dist/memory/bm25.d.ts +14 -0
  134. package/dist/memory/bm25.d.ts.map +1 -0
  135. package/dist/memory/bm25.js +40 -0
  136. package/dist/memory/bm25.js.map +1 -0
  137. package/dist/memory/context-loader.d.ts +26 -0
  138. package/dist/memory/context-loader.d.ts.map +1 -0
  139. package/dist/memory/context-loader.js +97 -0
  140. package/dist/memory/context-loader.js.map +1 -0
  141. package/dist/memory/memory-service.d.ts +30 -0
  142. package/dist/memory/memory-service.d.ts.map +1 -0
  143. package/dist/memory/memory-service.js +133 -0
  144. package/dist/memory/memory-service.js.map +1 -0
  145. package/dist/memory/memory-tools.d.ts +4 -0
  146. package/dist/memory/memory-tools.d.ts.map +1 -0
  147. package/dist/memory/memory-tools.js +49 -0
  148. package/dist/memory/memory-tools.js.map +1 -0
  149. package/dist/memory/search-service.d.ts +38 -0
  150. package/dist/memory/search-service.d.ts.map +1 -0
  151. package/dist/memory/search-service.js +463 -0
  152. package/dist/memory/search-service.js.map +1 -0
  153. package/dist/memory/tokenizer.d.ts +5 -0
  154. package/dist/memory/tokenizer.d.ts.map +1 -0
  155. package/dist/memory/tokenizer.js +42 -0
  156. package/dist/memory/tokenizer.js.map +1 -0
  157. package/dist/notifications/notification-service.d.ts +47 -0
  158. package/dist/notifications/notification-service.d.ts.map +1 -0
  159. package/dist/notifications/notification-service.js +163 -0
  160. package/dist/notifications/notification-service.js.map +1 -0
  161. package/dist/notifications/notification-tools.d.ts +4 -0
  162. package/dist/notifications/notification-tools.d.ts.map +1 -0
  163. package/dist/notifications/notification-tools.js +31 -0
  164. package/dist/notifications/notification-tools.js.map +1 -0
  165. package/dist/pi/pi-runtime-adapter.d.ts +38 -0
  166. package/dist/pi/pi-runtime-adapter.d.ts.map +1 -0
  167. package/dist/pi/pi-runtime-adapter.js +483 -0
  168. package/dist/pi/pi-runtime-adapter.js.map +1 -0
  169. package/dist/plugins/extension-manager.d.ts +32 -0
  170. package/dist/plugins/extension-manager.d.ts.map +1 -0
  171. package/dist/plugins/extension-manager.js +562 -0
  172. package/dist/plugins/extension-manager.js.map +1 -0
  173. package/dist/plugins/resource-inspector.d.ts +18 -0
  174. package/dist/plugins/resource-inspector.d.ts.map +1 -0
  175. package/dist/plugins/resource-inspector.js +386 -0
  176. package/dist/plugins/resource-inspector.js.map +1 -0
  177. package/dist/plugins/types.d.ts +96 -0
  178. package/dist/plugins/types.d.ts.map +1 -0
  179. package/dist/plugins/types.js +2 -0
  180. package/dist/plugins/types.js.map +1 -0
  181. package/dist/policy/approval-service.d.ts +68 -0
  182. package/dist/policy/approval-service.d.ts.map +1 -0
  183. package/dist/policy/approval-service.js +349 -0
  184. package/dist/policy/approval-service.js.map +1 -0
  185. package/dist/policy/policy.d.ts +26 -0
  186. package/dist/policy/policy.d.ts.map +1 -0
  187. package/dist/policy/policy.js +228 -0
  188. package/dist/policy/policy.js.map +1 -0
  189. package/dist/protocol/client.d.ts +25 -0
  190. package/dist/protocol/client.d.ts.map +1 -0
  191. package/dist/protocol/client.js +188 -0
  192. package/dist/protocol/client.js.map +1 -0
  193. package/dist/protocol/errors.d.ts +5 -0
  194. package/dist/protocol/errors.d.ts.map +1 -0
  195. package/dist/protocol/errors.js +9 -0
  196. package/dist/protocol/errors.js.map +1 -0
  197. package/dist/protocol/schema.d.ts +71 -0
  198. package/dist/protocol/schema.d.ts.map +1 -0
  199. package/dist/protocol/schema.js +155 -0
  200. package/dist/protocol/schema.js.map +1 -0
  201. package/dist/settings/llm-settings-service.d.ts +9 -0
  202. package/dist/settings/llm-settings-service.d.ts.map +1 -0
  203. package/dist/settings/llm-settings-service.js +199 -0
  204. package/dist/settings/llm-settings-service.js.map +1 -0
  205. package/dist/settings/llm-settings.d.ts +29 -0
  206. package/dist/settings/llm-settings.d.ts.map +1 -0
  207. package/dist/settings/llm-settings.js +113 -0
  208. package/dist/settings/llm-settings.js.map +1 -0
  209. package/dist/shell/shell-service.d.ts +88 -0
  210. package/dist/shell/shell-service.d.ts.map +1 -0
  211. package/dist/shell/shell-service.js +459 -0
  212. package/dist/shell/shell-service.js.map +1 -0
  213. package/dist/storage/atomic.d.ts +4 -0
  214. package/dist/storage/atomic.d.ts.map +1 -0
  215. package/dist/storage/atomic.js +49 -0
  216. package/dist/storage/atomic.js.map +1 -0
  217. package/dist/storage/daemon-lock.d.ts +17 -0
  218. package/dist/storage/daemon-lock.d.ts.map +1 -0
  219. package/dist/storage/daemon-lock.js +97 -0
  220. package/dist/storage/daemon-lock.js.map +1 -0
  221. package/dist/storage/home.d.ts +29 -0
  222. package/dist/storage/home.d.ts.map +1 -0
  223. package/dist/storage/home.js +108 -0
  224. package/dist/storage/home.js.map +1 -0
  225. package/dist/storage/jsonl.d.ts +2 -0
  226. package/dist/storage/jsonl.d.ts.map +1 -0
  227. package/dist/storage/jsonl.js +45 -0
  228. package/dist/storage/jsonl.js.map +1 -0
  229. package/dist/tools/core-tools.d.ts +16 -0
  230. package/dist/tools/core-tools.d.ts.map +1 -0
  231. package/dist/tools/core-tools.js +283 -0
  232. package/dist/tools/core-tools.js.map +1 -0
  233. package/dist/ui/shared/presentation.d.ts +46 -0
  234. package/dist/ui/shared/presentation.d.ts.map +1 -0
  235. package/dist/ui/shared/presentation.js +81 -0
  236. package/dist/ui/shared/presentation.js.map +1 -0
  237. package/dist/ui/shared/protocol-state.d.ts +30 -0
  238. package/dist/ui/shared/protocol-state.d.ts.map +1 -0
  239. package/dist/ui/shared/protocol-state.js +165 -0
  240. package/dist/ui/shared/protocol-state.js.map +1 -0
  241. package/dist/ui/tui/commands.d.ts +2 -0
  242. package/dist/ui/tui/commands.d.ts.map +1 -0
  243. package/dist/ui/tui/commands.js +33 -0
  244. package/dist/ui/tui/commands.js.map +1 -0
  245. package/dist/ui/tui/store.d.ts +32 -0
  246. package/dist/ui/tui/store.d.ts.map +1 -0
  247. package/dist/ui/tui/store.js +62 -0
  248. package/dist/ui/tui/store.js.map +1 -0
  249. package/dist/ui/tui/tui-app.d.ts +21 -0
  250. package/dist/ui/tui/tui-app.d.ts.map +1 -0
  251. package/dist/ui/tui/tui-app.js +643 -0
  252. package/dist/ui/tui/tui-app.js.map +1 -0
  253. package/dist/ui/tui/view.d.ts +29 -0
  254. package/dist/ui/tui/view.d.ts.map +1 -0
  255. package/dist/ui/tui/view.js +255 -0
  256. package/dist/ui/tui/view.js.map +1 -0
  257. package/dist/version.d.ts +5 -0
  258. package/dist/version.d.ts.map +1 -0
  259. package/dist/version.js +5 -0
  260. package/dist/version.js.map +1 -0
  261. package/package.json +62 -0
@@ -0,0 +1,895 @@
1
+ /// <reference lib="DOM" />
2
+ import { defaultModelFor } from "../../settings/llm-settings.js";
3
+ import { clockTime, conversationIntent, formatBytes, formatTokens, presentSessions, selectedAgent as findSelectedAgent, shortId, terminalAgentStatus, } from "../../ui/shared/presentation.js";
4
+ import { DesktopStore } from "./store.js";
5
+ const store = new DesktopStore();
6
+ const queuedFrames = [];
7
+ let initialized = false;
8
+ let versions;
9
+ let jobs = [];
10
+ let resources = [];
11
+ let inspection;
12
+ let activeApprovalId;
13
+ let renderSequence = 0;
14
+ let settingsOpen = false;
15
+ let savingSettings = false;
16
+ let responsiveMode;
17
+ const THEME_STORAGE_KEY = "pspore.desktop.theme";
18
+ const elements = {
19
+ appShell: byId("app-shell"),
20
+ navigationPanel: byId("navigation-panel"),
21
+ navigationToggle: byId("navigation-toggle"),
22
+ inspectorPanel: byId("inspector-panel"),
23
+ inspectorToggle: byId("inspector-toggle"),
24
+ inspectorClose: byId("inspector-close"),
25
+ inspectorTabs: Array.from(document.querySelectorAll("[data-inspector-tab]")),
26
+ inspectorViews: Array.from(document.querySelectorAll("[data-inspector-panel]")),
27
+ connectionBadge: byId("connection-badge"),
28
+ daemonInstance: byId("daemon-instance"),
29
+ revision: byId("revision-value"),
30
+ usage: byId("usage-value"),
31
+ cache: byId("cache-value"),
32
+ themeToggle: byId("theme-toggle"),
33
+ themeColor: byId("theme-color"),
34
+ refresh: byId("refresh-button"),
35
+ connectionBanner: byId("connection-banner"),
36
+ connectionMessage: byId("connection-message"),
37
+ sessionCount: byId("session-count"),
38
+ sessionList: byId("session-list"),
39
+ agentCompose: byId("agent-compose"),
40
+ agentComposeToggle: byId("agent-compose-toggle"),
41
+ agentComposeClose: byId("agent-compose-close"),
42
+ spawnForm: byId("spawn-form"),
43
+ spawnObjective: byId("spawn-objective"),
44
+ childControls: byId("child-controls"),
45
+ joinAgent: byId("join-agent-button"),
46
+ cancelAgent: byId("cancel-agent-button"),
47
+ inspectForm: byId("inspect-form"),
48
+ stagingName: byId("staging-name"),
49
+ inspectionResult: byId("inspection-result"),
50
+ resourceShortcut: byId("resource-shortcut"),
51
+ versionLine: byId("version-line"),
52
+ runtimeLine: byId("runtime-line"),
53
+ sessionTitle: byId("session-title"),
54
+ sessionStatus: byId("session-status"),
55
+ modelName: byId("model-name"),
56
+ transcript: byId("transcript"),
57
+ notice: byId("notice-line"),
58
+ composerForm: byId("composer-form"),
59
+ composerInput: byId("composer-input"),
60
+ composerMode: byId("composer-mode"),
61
+ compact: byId("compact-button"),
62
+ abort: byId("abort-button"),
63
+ send: byId("send-button"),
64
+ activityCount: byId("activity-count"),
65
+ activityList: byId("activity-list"),
66
+ jobList: byId("job-list"),
67
+ runList: byId("run-list"),
68
+ integrationList: byId("integration-list"),
69
+ notificationList: byId("notification-list"),
70
+ settingsButton: byId("settings-button"),
71
+ settingsOverlay: byId("settings-overlay"),
72
+ settingsDialog: document.querySelector(".settings-dialog") ?? missingElement(".settings-dialog"),
73
+ settingsClose: byId("settings-close"),
74
+ settingsCancel: byId("settings-cancel"),
75
+ settingsForm: byId("settings-form"),
76
+ settingsProvider: byId("settings-provider"),
77
+ settingsProviderHelp: byId("settings-provider-help"),
78
+ settingsModel: byId("settings-model"),
79
+ settingsBaseUrl: byId("settings-base-url"),
80
+ settingsApi: byId("settings-api"),
81
+ settingsThinking: byId("settings-thinking"),
82
+ settingsApiKey: byId("settings-api-key"),
83
+ settingsKeyReveal: byId("settings-key-reveal"),
84
+ settingsClearKey: byId("settings-clear-key"),
85
+ settingsStatus: byId("settings-status"),
86
+ settingsSave: byId("settings-save"),
87
+ approvalOverlay: byId("approval-overlay"),
88
+ approvalTitle: byId("approval-title"),
89
+ approvalSummary: byId("approval-summary"),
90
+ approvalCapability: byId("approval-capability"),
91
+ approvalTarget: byId("approval-target"),
92
+ approvalReasons: byId("approval-reasons"),
93
+ approvalCommand: byId("approval-command"),
94
+ approvalActions: byId("approval-actions"),
95
+ };
96
+ applyTheme(initialTheme());
97
+ syncResponsivePanels(true);
98
+ const unsubscribe = window.pspore.subscribe((frame) => {
99
+ if (!initialized)
100
+ queuedFrames.push(frame);
101
+ else {
102
+ store.applyFrame(frame);
103
+ render();
104
+ }
105
+ });
106
+ window.addEventListener("beforeunload", unsubscribe, { once: true });
107
+ void initialize();
108
+ async function initialize() {
109
+ bindStaticActions();
110
+ try {
111
+ const bootstrap = await window.pspore.bootstrap();
112
+ versions = bootstrap.versions;
113
+ store.applyBootstrap(bootstrap);
114
+ initialized = true;
115
+ for (const frame of queuedFrames.splice(0))
116
+ store.applyFrame(frame);
117
+ render();
118
+ await refreshSideData();
119
+ }
120
+ catch (error) {
121
+ initialized = true;
122
+ store.setNotice(`Desktop initialization failed: ${toError(error).message}`);
123
+ render();
124
+ }
125
+ finally {
126
+ document.documentElement.dataset["ready"] = "true";
127
+ }
128
+ }
129
+ function bindStaticActions() {
130
+ elements.navigationToggle.addEventListener("click", () => {
131
+ setPanelState("navigation", document.documentElement.dataset["navigation"] !== "open");
132
+ });
133
+ elements.inspectorToggle.addEventListener("click", () => {
134
+ setPanelState("inspector", document.documentElement.dataset["inspector"] !== "open");
135
+ });
136
+ elements.inspectorClose.addEventListener("click", () => setPanelState("inspector", false));
137
+ for (const tab of elements.inspectorTabs) {
138
+ tab.addEventListener("click", () => selectInspectorTab(tab.dataset["inspectorTab"]));
139
+ }
140
+ elements.agentComposeToggle.addEventListener("click", () => setAgentCompose(elements.agentCompose.hidden));
141
+ elements.agentComposeClose.addEventListener("click", () => setAgentCompose(false));
142
+ elements.resourceShortcut.addEventListener("click", () => {
143
+ selectInspectorTab("resources");
144
+ setPanelState("inspector", true);
145
+ });
146
+ window.addEventListener("resize", () => syncResponsivePanels(false));
147
+ elements.themeToggle.addEventListener("click", () => {
148
+ const theme = document.documentElement.dataset["theme"] === "light" ? "dark" : "light";
149
+ applyTheme(theme);
150
+ try {
151
+ window.localStorage.setItem(THEME_STORAGE_KEY, theme);
152
+ }
153
+ catch {
154
+ // Theme remains active for this session when local persistence is unavailable.
155
+ }
156
+ });
157
+ elements.refresh.addEventListener("click", () => void refreshSideData());
158
+ elements.settingsButton.addEventListener("click", () => void openSettings());
159
+ elements.settingsClose.addEventListener("click", () => closeSettings());
160
+ elements.settingsCancel.addEventListener("click", () => closeSettings());
161
+ elements.settingsForm.addEventListener("submit", (event) => {
162
+ event.preventDefault();
163
+ void saveSettings();
164
+ });
165
+ elements.settingsProvider.addEventListener("change", () => configureProviderFields(true));
166
+ elements.settingsKeyReveal.addEventListener("click", () => {
167
+ const revealing = elements.settingsApiKey.type === "password";
168
+ elements.settingsApiKey.type = revealing ? "text" : "password";
169
+ elements.settingsKeyReveal.textContent = revealing ? "隐藏" : "显示";
170
+ });
171
+ elements.settingsApiKey.addEventListener("input", () => {
172
+ if (elements.settingsApiKey.value)
173
+ elements.settingsClearKey.checked = false;
174
+ });
175
+ elements.settingsClearKey.addEventListener("change", () => {
176
+ if (elements.settingsClearKey.checked)
177
+ elements.settingsApiKey.value = "";
178
+ });
179
+ elements.settingsOverlay.addEventListener("keydown", (event) => {
180
+ if (event.key === "Escape" && !savingSettings) {
181
+ event.preventDefault();
182
+ closeSettings();
183
+ return;
184
+ }
185
+ trapDialogFocus(event, elements.settingsDialog);
186
+ });
187
+ elements.composerForm.addEventListener("submit", (event) => {
188
+ event.preventDefault();
189
+ void submitComposer();
190
+ });
191
+ elements.composerInput.addEventListener("keydown", (event) => {
192
+ if (event.key === "Enter" && (event.ctrlKey || event.metaKey)) {
193
+ event.preventDefault();
194
+ elements.composerForm.requestSubmit();
195
+ }
196
+ });
197
+ elements.spawnForm.addEventListener("submit", (event) => {
198
+ event.preventDefault();
199
+ const objective = elements.spawnObjective.value.trim();
200
+ if (!objective)
201
+ return;
202
+ void perform("Starting child agent", { action: "agent.spawn", objective }, () => {
203
+ elements.spawnObjective.value = "";
204
+ setAgentCompose(false);
205
+ return refreshSideData();
206
+ });
207
+ });
208
+ elements.abort.addEventListener("click", () => {
209
+ const snapshot = store.snapshot();
210
+ const child = selectedAgent(snapshot);
211
+ const command = child ? { action: "agent.cancel", agentId: child.id } : { action: "abort" };
212
+ void perform(child ? `Cancelling ${child.id}` : "Aborting main session", command);
213
+ });
214
+ elements.compact.addEventListener("click", () => void perform("Compacting main context", { action: "compact" }));
215
+ elements.joinAgent.addEventListener("click", () => {
216
+ const child = selectedAgent(store.snapshot());
217
+ if (child)
218
+ void perform(`Waiting for ${child.id}`, { action: "agent.join", agentIds: [child.id] });
219
+ });
220
+ elements.cancelAgent.addEventListener("click", () => {
221
+ const child = selectedAgent(store.snapshot());
222
+ if (child)
223
+ void perform(`Cancelling ${child.id}`, { action: "agent.cancel", agentId: child.id });
224
+ });
225
+ elements.inspectForm.addEventListener("submit", (event) => {
226
+ event.preventDefault();
227
+ const stagingName = elements.stagingName.value.trim();
228
+ if (!stagingName)
229
+ return;
230
+ void perform("Inspecting staged resource", { action: "resources.inspect", stagingName }, (result) => {
231
+ inspection = isInspection(result) ? result : undefined;
232
+ renderInspection();
233
+ });
234
+ });
235
+ elements.approvalOverlay.addEventListener("keydown", (event) => {
236
+ if (event.key === "Escape") {
237
+ event.preventDefault();
238
+ return;
239
+ }
240
+ trapDialogFocus(event, elements.approvalOverlay);
241
+ });
242
+ }
243
+ function syncResponsivePanels(force) {
244
+ const nextMode = window.innerWidth <= 1_160 ? "narrow" : window.innerWidth <= 1_320 ? "compact" : "wide";
245
+ if (!force && nextMode === responsiveMode)
246
+ return;
247
+ responsiveMode = nextMode;
248
+ setPanelState("navigation", nextMode !== "narrow");
249
+ setPanelState("inspector", false);
250
+ }
251
+ function setPanelState(panel, open) {
252
+ document.documentElement.dataset[panel] = open ? "open" : "closed";
253
+ const button = panel === "navigation" ? elements.navigationToggle : elements.inspectorToggle;
254
+ button.setAttribute("aria-expanded", String(open));
255
+ const label = panel === "navigation"
256
+ ? open ? "收起会话栏" : "展开会话栏"
257
+ : open ? "收起检查器" : "展开检查器";
258
+ button.setAttribute("aria-label", label);
259
+ button.title = label;
260
+ }
261
+ function selectInspectorTab(selected) {
262
+ for (const tab of elements.inspectorTabs) {
263
+ const active = tab.dataset["inspectorTab"] === selected;
264
+ tab.classList.toggle("selected", active);
265
+ tab.setAttribute("aria-selected", String(active));
266
+ tab.tabIndex = active ? 0 : -1;
267
+ }
268
+ for (const view of elements.inspectorViews)
269
+ view.hidden = view.dataset["inspectorPanel"] !== selected;
270
+ }
271
+ function setAgentCompose(open) {
272
+ elements.agentCompose.hidden = !open;
273
+ elements.agentComposeToggle.setAttribute("aria-expanded", String(open));
274
+ if (open)
275
+ requestAnimationFrame(() => elements.spawnObjective.focus());
276
+ }
277
+ async function openSettings() {
278
+ if (!elements.approvalOverlay.hidden)
279
+ return;
280
+ settingsOpen = true;
281
+ elements.settingsOverlay.hidden = false;
282
+ elements.settingsStatus.textContent = "读取设置中…";
283
+ elements.settingsSave.disabled = true;
284
+ updateAppInert();
285
+ requestAnimationFrame(() => elements.settingsClose.focus());
286
+ try {
287
+ const settings = await window.pspore.getSettings();
288
+ populateSettings(settings);
289
+ elements.settingsStatus.textContent = settings.hasApiKey
290
+ ? "已检测到私有 Key;留空会保留当前值。"
291
+ : "尚未通过桌面设置保存 API Key。";
292
+ elements.settingsSave.disabled = false;
293
+ }
294
+ catch (error) {
295
+ elements.settingsStatus.textContent = `读取失败:${toError(error).message}`;
296
+ }
297
+ }
298
+ function populateSettings(settings) {
299
+ elements.settingsProvider.value = settings.provider;
300
+ elements.settingsModel.value = settings.model;
301
+ elements.settingsBaseUrl.value = settings.baseUrl;
302
+ elements.settingsApi.value = settings.api;
303
+ elements.settingsThinking.value = settings.thinking;
304
+ configureProviderFields(false);
305
+ elements.settingsApiKey.value = "";
306
+ elements.settingsApiKey.type = "password";
307
+ elements.settingsApiKey.placeholder = settings.hasApiKey ? "已配置;留空将保留" : "输入 API Key";
308
+ elements.settingsKeyReveal.textContent = "显示";
309
+ elements.settingsClearKey.checked = false;
310
+ }
311
+ async function saveSettings() {
312
+ if (savingSettings)
313
+ return;
314
+ const update = {
315
+ provider: elements.settingsProvider.value,
316
+ model: elements.settingsModel.value.trim(),
317
+ baseUrl: elements.settingsBaseUrl.value.trim(),
318
+ api: elements.settingsApi.value,
319
+ thinking: elements.settingsThinking.value,
320
+ customModel: elements.settingsProvider.value === "custom-openai",
321
+ ...(elements.settingsApiKey.value ? { apiKey: elements.settingsApiKey.value } : {}),
322
+ clearApiKey: elements.settingsClearKey.checked,
323
+ };
324
+ savingSettings = true;
325
+ elements.settingsSave.disabled = true;
326
+ elements.settingsCancel.disabled = true;
327
+ elements.settingsClose.disabled = true;
328
+ elements.settingsStatus.textContent = "正在验证并保存设置…";
329
+ try {
330
+ const result = await window.pspore.saveSettings(update);
331
+ populateSettings(result.settings);
332
+ elements.settingsStatus.textContent = result.message;
333
+ }
334
+ catch (error) {
335
+ elements.settingsStatus.textContent = `保存失败:${toError(error).message}`;
336
+ }
337
+ finally {
338
+ savingSettings = false;
339
+ elements.settingsSave.disabled = false;
340
+ elements.settingsCancel.disabled = false;
341
+ elements.settingsClose.disabled = false;
342
+ }
343
+ }
344
+ function configureProviderFields(resetModel) {
345
+ const provider = elements.settingsProvider.value;
346
+ const custom = provider === "custom-openai";
347
+ if (resetModel)
348
+ elements.settingsModel.value = defaultModelFor(provider);
349
+ elements.settingsBaseUrl.disabled = !custom;
350
+ elements.settingsBaseUrl.required = custom;
351
+ elements.settingsApi.disabled = !custom;
352
+ if (custom) {
353
+ if (elements.settingsApi.value === "provider-default")
354
+ elements.settingsApi.value = "openai-completions";
355
+ elements.settingsProviderHelp.textContent = "使用 OpenAI 兼容接口;必须填写 Base URL,并选择 Chat Completions 或 Responses。";
356
+ elements.settingsBaseUrl.placeholder = "例如 https://gateway.example.com/v1";
357
+ }
358
+ else {
359
+ elements.settingsBaseUrl.value = "";
360
+ elements.settingsApi.value = "provider-default";
361
+ elements.settingsProviderHelp.textContent = provider === "deepseek"
362
+ ? "使用 DeepSeek 内置模型目录与默认接口。"
363
+ : "使用 OpenAI 内置模型目录与默认接口。";
364
+ elements.settingsBaseUrl.placeholder = "选择“自定义 OpenAI”后配置";
365
+ }
366
+ }
367
+ function closeSettings(restoreFocus = true) {
368
+ if (!settingsOpen || savingSettings)
369
+ return;
370
+ settingsOpen = false;
371
+ elements.settingsOverlay.hidden = true;
372
+ elements.settingsApiKey.value = "";
373
+ elements.settingsApiKey.type = "password";
374
+ updateAppInert();
375
+ if (restoreFocus)
376
+ requestAnimationFrame(() => elements.settingsButton.focus());
377
+ }
378
+ function updateAppInert() {
379
+ const inert = settingsOpen || !elements.approvalOverlay.hidden;
380
+ elements.appShell.inert = inert;
381
+ if (inert)
382
+ elements.appShell.setAttribute("aria-hidden", "true");
383
+ else
384
+ elements.appShell.removeAttribute("aria-hidden");
385
+ }
386
+ function trapDialogFocus(event, container) {
387
+ if (event.key !== "Tab")
388
+ return;
389
+ const focusable = Array.from(container.querySelectorAll("button:not(:disabled), input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex='-1'])")).filter((candidate) => candidate.offsetParent !== null);
390
+ const first = focusable[0];
391
+ const last = focusable.at(-1);
392
+ if (!first || !last)
393
+ return;
394
+ if (event.shiftKey && document.activeElement === first) {
395
+ event.preventDefault();
396
+ last.focus();
397
+ }
398
+ else if (!event.shiftKey && document.activeElement === last) {
399
+ event.preventDefault();
400
+ first.focus();
401
+ }
402
+ }
403
+ async function submitComposer() {
404
+ const text = elements.composerInput.value.trim();
405
+ if (!text)
406
+ return;
407
+ const snapshot = store.snapshot();
408
+ const mode = elements.composerMode.value;
409
+ const intent = conversationIntent(snapshot.kernel, snapshot.selectedSessionId, text, mode === "steer" ? "steer" : mode === "follow_up" ? "follow_up" : "prompt");
410
+ let command;
411
+ if (intent.target === "agent") {
412
+ command = {
413
+ action: "agent.send",
414
+ agentId: intent.agentId,
415
+ message: intent.text,
416
+ mode: intent.mode,
417
+ };
418
+ }
419
+ else {
420
+ command = { action: intent.mode, text: intent.text };
421
+ }
422
+ await perform(`Sending to ${intent.target === "agent" ? intent.agentId : "main"}`, command, () => {
423
+ elements.composerInput.value = "";
424
+ elements.composerInput.focus();
425
+ });
426
+ }
427
+ async function perform(pendingNotice, command, onSuccess) {
428
+ store.setNotice(`${pendingNotice}…`);
429
+ render();
430
+ try {
431
+ const result = await window.pspore.dispatch(command);
432
+ store.setNotice(`${pendingNotice}: done`);
433
+ await onSuccess?.(result);
434
+ }
435
+ catch (error) {
436
+ store.setNotice(`${pendingNotice}: ${toError(error).message}`);
437
+ }
438
+ render();
439
+ }
440
+ async function refreshSideData() {
441
+ const snapshot = store.snapshot();
442
+ if (snapshot.connection.phase !== "connected") {
443
+ store.setNotice("Daemon is offline; lists will refresh after reconnect");
444
+ render();
445
+ return;
446
+ }
447
+ store.setNotice("Refreshing jobs and trusted resources…");
448
+ render();
449
+ const results = await Promise.allSettled([
450
+ window.pspore.dispatch({ action: "jobs.list" }),
451
+ window.pspore.dispatch({ action: "resources.list" }),
452
+ window.pspore.dispatch({ action: "agents.list" }),
453
+ window.pspore.dispatch({ action: "mcp.servers" }),
454
+ ]);
455
+ const jobResult = results[0];
456
+ const resourceResult = results[1];
457
+ if (jobResult?.status === "fulfilled" && Array.isArray(jobResult.value)) {
458
+ jobs = jobResult.value.filter(isJobDefinition);
459
+ }
460
+ if (resourceResult?.status === "fulfilled" && Array.isArray(resourceResult.value)) {
461
+ resources = resourceResult.value.filter(isResourceEntry);
462
+ }
463
+ const failures = results.filter((result) => result.status === "rejected");
464
+ store.setNotice(failures.length ? `${failures.length} list refresh request(s) failed` : "Lists refreshed");
465
+ render();
466
+ }
467
+ function render() {
468
+ const snapshot = store.snapshot();
469
+ renderHeader(snapshot);
470
+ renderSessions(snapshot);
471
+ renderConversation(snapshot);
472
+ renderActivities(snapshot.activities);
473
+ renderJobs(snapshot);
474
+ renderIntegrations(snapshot);
475
+ renderNotifications(snapshot);
476
+ renderApproval(snapshot);
477
+ renderInspection();
478
+ renderVersions();
479
+ renderSequence += 1;
480
+ document.documentElement.dataset["renderSequence"] = String(renderSequence);
481
+ document.documentElement.dataset["revision"] = String(snapshot.kernel.revision);
482
+ document.documentElement.dataset["approval"] = Object.keys(snapshot.kernel.approvals).length ? "visible" : "hidden";
483
+ document.documentElement.dataset["connection"] = snapshot.connection.phase;
484
+ }
485
+ function renderHeader(snapshot) {
486
+ const connection = snapshot.connection;
487
+ elements.connectionBadge.className = `connection-badge ${connection.phase}`;
488
+ elements.connectionBadge.replaceChildren(element("i"), text(connection.phase === "connected" ? "ONLINE" : connection.phase.toUpperCase()));
489
+ elements.daemonInstance.textContent = connection.instanceId ? `daemon ${shortId(connection.instanceId)}` : "等待本地守护进程";
490
+ elements.connectionBanner.classList.toggle("visible", connection.phase !== "connected");
491
+ elements.connectionMessage.textContent = friendlyConnectionMessage(connection.message, connection.phase);
492
+ elements.revision.textContent = String(snapshot.kernel.revision);
493
+ const session = snapshot.kernel.sessions[snapshot.selectedSessionId];
494
+ elements.usage.textContent = formatTokens(session?.usage.total ?? 0);
495
+ elements.cache.textContent = formatTokens((session?.usage.cacheRead ?? 0) + (session?.usage.cacheWrite ?? 0));
496
+ elements.notice.textContent = snapshot.notice;
497
+ elements.refresh.disabled = connection.phase !== "connected";
498
+ }
499
+ function renderSessions(snapshot) {
500
+ const sessions = presentSessions(snapshot.kernel, snapshot.selectedSessionId);
501
+ const nodes = sessions.map((session) => sessionButton(session.id, session.title, session.status, session.depth, session.selected, session.active));
502
+ elements.sessionList.replaceChildren(...nodes);
503
+ elements.sessionCount.textContent = String(sessions.length);
504
+ const child = selectedAgent(snapshot);
505
+ elements.childControls.hidden = !child;
506
+ elements.joinAgent.disabled = !child || terminalAgentStatus(child.status) || snapshot.connection.phase !== "connected";
507
+ elements.cancelAgent.disabled = !child || terminalAgentStatus(child.status) || snapshot.connection.phase !== "connected";
508
+ }
509
+ function sessionButton(id, title, status, depth, selected, active) {
510
+ const button = element("button", `session-item ${status}${selected ? " selected" : ""}`);
511
+ button.type = "button";
512
+ button.disabled = !active;
513
+ button.style.paddingLeft = `${10 + depth * 13}px`;
514
+ const copy = element("span", "session-copy");
515
+ const strong = element("strong");
516
+ strong.textContent = id === "main" ? "main" : shortId(id);
517
+ const small = element("small");
518
+ small.textContent = title;
519
+ copy.append(strong, small);
520
+ const depthMark = element("span", "depth-mark");
521
+ depthMark.textContent = depth ? `L${depth}` : "ROOT";
522
+ button.append(element("i", "node-dot"), copy, depthMark);
523
+ button.addEventListener("click", () => {
524
+ try {
525
+ store.selectSession(id);
526
+ render();
527
+ }
528
+ catch (error) {
529
+ store.setNotice(toError(error).message);
530
+ render();
531
+ }
532
+ });
533
+ return button;
534
+ }
535
+ function renderConversation(snapshot) {
536
+ const session = snapshot.kernel.sessions[snapshot.selectedSessionId];
537
+ const child = selectedAgent(snapshot);
538
+ elements.sessionTitle.textContent = child ? `${shortId(child.id)} · ${child.objective}` : "main";
539
+ const status = session?.status ?? (child?.status ?? "idle");
540
+ elements.sessionStatus.textContent = status.toUpperCase();
541
+ elements.sessionStatus.className = `status-chip ${status}`;
542
+ elements.modelName.textContent = session?.model ? `${session.model.provider}/${session.model.model}` : "未配置模型";
543
+ elements.composerInput.placeholder = `向 ${child?.id ?? "main"} 发送消息…`;
544
+ const connected = snapshot.connection.phase === "connected";
545
+ elements.composerInput.disabled = !connected;
546
+ elements.composerMode.disabled = !connected;
547
+ elements.send.disabled = !connected;
548
+ elements.abort.disabled = !connected || (!child && session?.status !== "running");
549
+ elements.compact.disabled = !connected || Boolean(child) || !session;
550
+ const transcript = elements.transcript;
551
+ const pinned = transcript.scrollHeight - transcript.scrollTop - transcript.clientHeight < 120;
552
+ const messages = session?.messages ?? [];
553
+ const content = messages.map((message) => renderMessage(message));
554
+ const streaming = snapshot.streamingText[snapshot.selectedSessionId];
555
+ if (streaming)
556
+ content.push(renderStreamingMessage(streaming));
557
+ if (content.length === 0) {
558
+ const empty = element("div", "empty-transcript");
559
+ const inner = element("div");
560
+ const heading = element("strong");
561
+ heading.textContent = child ? "子 Agent 尚未产生消息" : "本地会话已就绪";
562
+ const body = element("span");
563
+ body.textContent = child ? "可发送 next、steer 或 follow-up 消息。" : "输入任务,或从左侧委派一个有界子任务。";
564
+ inner.append(heading, body);
565
+ empty.append(inner);
566
+ content.push(empty);
567
+ }
568
+ transcript.replaceChildren(...content);
569
+ if (pinned)
570
+ requestAnimationFrame(() => transcript.scrollTo({ top: transcript.scrollHeight }));
571
+ }
572
+ function renderMessage(message) {
573
+ const role = normalizedRole(message.role);
574
+ const article = element("article", `message ${role}`);
575
+ const meta = element("div", "message-meta");
576
+ const label = element("span");
577
+ label.textContent = role === "assistant" ? "pSpore" : role;
578
+ meta.append(element("i", "role-dot"), label);
579
+ if (role === "tool") {
580
+ const details = element("details");
581
+ const summary = element("summary");
582
+ summary.textContent = `Tool output · ${formatBytes(new TextEncoder().encode(message.text).byteLength)} · click to expand`;
583
+ const pre = element("pre");
584
+ pre.textContent = message.text || "(empty output)";
585
+ details.append(summary, pre);
586
+ article.append(meta, details);
587
+ }
588
+ else {
589
+ const body = element("div", "message-body");
590
+ appendRichText(body, message.text || "(empty message)");
591
+ article.append(meta, body);
592
+ }
593
+ return article;
594
+ }
595
+ function renderStreamingMessage(value) {
596
+ const article = element("article", "message assistant streaming");
597
+ const meta = element("div", "message-meta");
598
+ const label = element("span");
599
+ label.textContent = "pSpore · streaming";
600
+ meta.append(element("i", "role-dot"), label);
601
+ const body = element("div", "message-body");
602
+ body.append(text(value), element("i", "stream-cursor"));
603
+ article.append(meta, body);
604
+ return article;
605
+ }
606
+ function appendRichText(container, value) {
607
+ const expression = /```(?:[^\n]*)\n?([\s\S]*?)```/g;
608
+ let cursor = 0;
609
+ for (const match of value.matchAll(expression)) {
610
+ const index = match.index;
611
+ if (index > cursor)
612
+ container.append(text(value.slice(cursor, index)));
613
+ const pre = element("pre");
614
+ pre.textContent = match[1] ?? "";
615
+ container.append(pre);
616
+ cursor = index + match[0].length;
617
+ }
618
+ if (cursor < value.length)
619
+ container.append(text(value.slice(cursor)));
620
+ }
621
+ function renderActivities(activities) {
622
+ elements.activityCount.textContent = String(activities.length);
623
+ const cards = activities.slice(-12).reverse().map((activity) => {
624
+ const card = element("article", "activity-card");
625
+ const header = element("header");
626
+ const title = element("b");
627
+ title.textContent = activity.summary;
628
+ const time = element("time");
629
+ time.textContent = clockTime(activity.ts);
630
+ time.dateTime = activity.ts;
631
+ header.append(title, time);
632
+ card.append(header);
633
+ if (activity.sessionId) {
634
+ const session = element("p");
635
+ session.textContent = `session ${shortId(activity.sessionId)}`;
636
+ card.append(session);
637
+ }
638
+ if (activity.detail) {
639
+ const details = element("details");
640
+ const summary = element("summary");
641
+ summary.textContent = "查看已折叠详情";
642
+ const pre = element("pre", "activity-detail");
643
+ pre.textContent = activity.detail;
644
+ details.append(summary, pre);
645
+ card.append(details);
646
+ }
647
+ return card;
648
+ });
649
+ elements.activityList.replaceChildren(...(cards.length ? cards : [emptyList("暂无活动事件")]));
650
+ }
651
+ function renderJobs(snapshot) {
652
+ const connected = snapshot.connection.phase === "connected";
653
+ const jobNodes = jobs.slice(0, 4).map((job) => {
654
+ const item = denseItem(job.id, job.enabled ? `${job.schedule.cron} · ${job.schedule.timezone}` : "disabled", job.enabled ? "ready" : "disabled");
655
+ const actions = element("div", "item-actions");
656
+ const run = actionButton("立即运行", "ghost-button", () => void perform(`Running ${job.id}`, { action: "job.run", jobId: job.id }));
657
+ run.disabled = !connected || !job.enabled;
658
+ actions.append(run);
659
+ item.append(actions);
660
+ return item;
661
+ });
662
+ elements.jobList.replaceChildren(...(jobNodes.length ? jobNodes : [emptyList("没有已加载的 cron job")]));
663
+ const runs = Object.values(snapshot.kernel.runs)
664
+ .sort((left, right) => right.receivedAt.localeCompare(left.receivedAt))
665
+ .slice(0, 5);
666
+ const runNodes = runs.map((run) => {
667
+ const item = denseItem(run.jobId ?? shortId(run.id), `${run.status} · ${clockTime(run.receivedAt)}`, run.status);
668
+ if (run.status === "queued" || run.status === "running") {
669
+ const actions = element("div", "item-actions");
670
+ const cancel = actionButton("取消运行", "danger-ghost-button", () => void perform(`Cancelling ${run.id}`, { action: "run.cancel", runId: run.id }));
671
+ cancel.disabled = !connected;
672
+ actions.append(cancel);
673
+ item.append(actions);
674
+ }
675
+ return item;
676
+ });
677
+ elements.runList.replaceChildren(...(runNodes.length ? runNodes : [emptyList("暂无运行记录")]));
678
+ }
679
+ function renderIntegrations(snapshot) {
680
+ const connected = snapshot.connection.phase === "connected";
681
+ const nodes = [];
682
+ for (const health of Object.values(snapshot.kernel.mcpServers)) {
683
+ const item = denseItem(`MCP · ${health.serverId}`, `${health.status} · ${health.toolCount} tools`, health.status);
684
+ const actions = element("div", "item-actions");
685
+ const discover = actionButton("发现工具", "ghost-button", () => void perform(`Discovering ${health.serverId}`, { action: "mcp.discover", serverId: health.serverId }));
686
+ const ping = actionButton("Ping", "ghost-button", () => void perform(`Pinging ${health.serverId}`, { action: "mcp.ping", serverId: health.serverId }));
687
+ discover.disabled = !connected || health.status === "disabled" || health.status === "untrusted";
688
+ ping.disabled = discover.disabled;
689
+ actions.append(discover, ping);
690
+ item.append(actions);
691
+ nodes.push(item);
692
+ }
693
+ const catalogByKey = new Map(resources.map((entry) => [`${entry.record.kind}:${entry.record.name}`, entry]));
694
+ for (const health of Object.values(snapshot.kernel.resources)) {
695
+ const item = denseItem(`${health.kind === "skill" ? "Skill" : "Plugin"} · ${health.name}`, `${health.status}${health.restartRequired ? " · restart required" : ""}`, health.status);
696
+ const catalog = catalogByKey.get(`${health.kind}:${health.name}`);
697
+ if (catalog?.record.trusted) {
698
+ const actions = element("div", "item-actions");
699
+ const disable = actionButton("撤销信任", "danger-ghost-button", () => void perform(`Disabling ${health.name}`, {
700
+ action: "resources.disable",
701
+ kind: health.kind,
702
+ name: health.name,
703
+ expectedDigest: health.digest,
704
+ }, refreshSideData));
705
+ disable.disabled = !connected;
706
+ actions.append(disable);
707
+ item.append(actions);
708
+ }
709
+ nodes.push(item);
710
+ }
711
+ elements.integrationList.replaceChildren(...(nodes.length ? nodes : [emptyList("未配置 MCP 或受信资源")]));
712
+ }
713
+ function renderNotifications(snapshot) {
714
+ const nodes = snapshot.kernel.notifications.slice(-5).reverse().map((notification) => {
715
+ const item = denseItem(notification.title, clockTime(notification.ts), notification.level === "error" ? "failed" : notification.level === "warning" ? "running" : "ready");
716
+ const body = element("p");
717
+ body.textContent = notification.body;
718
+ item.append(body);
719
+ return item;
720
+ });
721
+ elements.notificationList.replaceChildren(...(nodes.length ? nodes : [emptyList("暂无通知")]));
722
+ }
723
+ function renderApproval(snapshot) {
724
+ const request = Object.values(snapshot.kernel.approvals).sort((left, right) => left.requestedAt.localeCompare(right.requestedAt))[0];
725
+ if (!request) {
726
+ const previousApproval = activeApprovalId;
727
+ elements.approvalOverlay.hidden = true;
728
+ updateAppInert();
729
+ activeApprovalId = undefined;
730
+ if (previousApproval)
731
+ requestAnimationFrame(() => elements.composerInput.focus());
732
+ return;
733
+ }
734
+ if (settingsOpen && !savingSettings)
735
+ closeSettings(false);
736
+ elements.approvalOverlay.hidden = false;
737
+ updateAppInert();
738
+ elements.approvalTitle.textContent = request.display.summary;
739
+ elements.approvalSummary.textContent = `Agent ${request.agentPath.join(" → ")} 请求执行受保护操作。批准范围会被守护进程记录并强制执行。`;
740
+ elements.approvalCapability.textContent = request.capability;
741
+ elements.approvalTarget.textContent = request.canonicalTarget ?? request.display.cwd ?? "未指定";
742
+ elements.approvalReasons.textContent = request.riskReasons.join(";") || "策略要求确认";
743
+ elements.approvalCommand.hidden = !request.display.command;
744
+ elements.approvalCommand.textContent = request.display.command ?? "";
745
+ const actions = [];
746
+ const denialScope = request.allowedScopes[0] ?? "once";
747
+ actions.push(approvalButton("拒绝", "danger-ghost-button", request, false, denialScope));
748
+ for (const scope of request.allowedScopes) {
749
+ const label = scope === "once" ? "仅此一次" : scope === "session" ? "本会话允许" : "此任务允许";
750
+ actions.push(approvalButton(label, scope === "once" ? "primary-button" : "secondary-button", request, true, scope));
751
+ }
752
+ elements.approvalActions.replaceChildren(...actions);
753
+ if (activeApprovalId !== request.id) {
754
+ activeApprovalId = request.id;
755
+ requestAnimationFrame(() => actions[0]?.focus());
756
+ }
757
+ }
758
+ function approvalButton(label, className, request, approved, scope) {
759
+ return actionButton(label, className, () => void perform(`${approved ? "Approving" : "Denying"} ${request.id}`, {
760
+ action: "approval.resolve",
761
+ approvalId: request.id,
762
+ approved,
763
+ scope,
764
+ }));
765
+ }
766
+ function renderInspection() {
767
+ const result = inspection;
768
+ elements.inspectionResult.hidden = !result;
769
+ if (!result) {
770
+ elements.inspectionResult.replaceChildren();
771
+ return;
772
+ }
773
+ const title = element("b");
774
+ title.textContent = `${result.kind} · ${result.name}@${result.resourceVersion}`;
775
+ const digest = element("code");
776
+ digest.textContent = result.digest;
777
+ const summary = element("p");
778
+ summary.textContent = `${result.files.length} files · ${formatBytes(result.totalBytes)}${result.diagnostics.length ? ` · ${result.diagnostics.join("; ")}` : ""}`;
779
+ const install = actionButton(result.installable ? "信任并安装精确内容" : "不可安装", "secondary-button", () => {
780
+ const stagingName = elements.stagingName.value.trim();
781
+ if (stagingName)
782
+ void perform(`Installing ${result.name}`, { action: "resources.install", stagingName, expectedDigest: result.digest }, async () => {
783
+ inspection = undefined;
784
+ await refreshSideData();
785
+ });
786
+ });
787
+ install.disabled = !result.installable || store.snapshot().connection.phase !== "connected";
788
+ elements.inspectionResult.replaceChildren(title, digest, summary, install);
789
+ }
790
+ function renderVersions() {
791
+ if (!versions)
792
+ return;
793
+ elements.versionLine.textContent = `pSpore ${versions.pspore} · Pi ${versions.pi}`;
794
+ elements.runtimeLine.textContent = `Electron ${versions.electron} · Node ${versions.node}`;
795
+ }
796
+ function selectedAgent(snapshot) {
797
+ return findSelectedAgent(snapshot.kernel, snapshot.selectedSessionId);
798
+ }
799
+ function denseItem(titleValue, subtitleValue, status) {
800
+ const item = element("article", "dense-item");
801
+ const header = element("header");
802
+ const title = element("b");
803
+ const dot = element("i", `state-dot ${safeStatus(status)}`);
804
+ title.append(dot, text(titleValue));
805
+ const small = element("small");
806
+ small.textContent = subtitleValue;
807
+ header.append(title, small);
808
+ item.append(header);
809
+ return item;
810
+ }
811
+ function actionButton(label, className, listener) {
812
+ const button = element("button", className);
813
+ button.type = "button";
814
+ button.textContent = label;
815
+ button.addEventListener("click", listener);
816
+ return button;
817
+ }
818
+ function emptyList(label) {
819
+ const empty = element("div", "empty-list");
820
+ empty.textContent = label;
821
+ return empty;
822
+ }
823
+ function element(tag, className) {
824
+ const result = document.createElement(tag);
825
+ if (className)
826
+ result.className = className;
827
+ return result;
828
+ }
829
+ function text(value) {
830
+ return document.createTextNode(value);
831
+ }
832
+ function byId(id) {
833
+ const value = document.getElementById(id);
834
+ if (!value)
835
+ throw new Error(`Missing desktop element #${id}`);
836
+ return value;
837
+ }
838
+ function missingElement(selector) {
839
+ throw new Error(`Missing desktop element ${selector}`);
840
+ }
841
+ function normalizedRole(role) {
842
+ const normalized = role.toLowerCase();
843
+ if (normalized.includes("tool"))
844
+ return "tool";
845
+ return normalized === "user" ? "user" : "assistant";
846
+ }
847
+ function safeStatus(value) {
848
+ const allowed = new Set(["ready", "active", "succeeded", "running", "connecting", "starting", "failed", "degraded", "blocked"]);
849
+ return allowed.has(value) ? value : "unknown";
850
+ }
851
+ function initialTheme() {
852
+ try {
853
+ const stored = window.localStorage.getItem(THEME_STORAGE_KEY);
854
+ if (stored === "dark" || stored === "light")
855
+ return stored;
856
+ }
857
+ catch {
858
+ // Fall through to the operating-system preference.
859
+ }
860
+ return window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark";
861
+ }
862
+ function applyTheme(theme) {
863
+ document.documentElement.dataset["theme"] = theme;
864
+ const nextLabel = theme === "dark" ? "切换到浅色模式" : "切换到深色模式";
865
+ elements.themeToggle.setAttribute("aria-label", nextLabel);
866
+ elements.themeToggle.title = nextLabel;
867
+ elements.themeColor.content = theme === "dark" ? "#0d100e" : "#f2f2ec";
868
+ }
869
+ function friendlyConnectionMessage(message, phase) {
870
+ if (message?.includes("ENOENT") || message?.includes("daemon.sock")) {
871
+ return "本地守护进程正在启动,连接就绪后会自动恢复…";
872
+ }
873
+ return message ?? (phase === "connecting" ? "正在连接本地守护进程…" : "守护进程已断开,正在重试…");
874
+ }
875
+ function isJobDefinition(value) {
876
+ if (!isRecord(value) || value["version"] !== 1 || typeof value["id"] !== "string" || typeof value["enabled"] !== "boolean")
877
+ return false;
878
+ const schedule = value["schedule"];
879
+ return isRecord(schedule) && typeof schedule["cron"] === "string" && typeof schedule["timezone"] === "string";
880
+ }
881
+ function isResourceEntry(value) {
882
+ if (!isRecord(value) || !isRecord(value["record"]) || !isRecord(value["health"]))
883
+ return false;
884
+ return typeof value["record"]["name"] === "string" && typeof value["record"]["digest"] === "string";
885
+ }
886
+ function isInspection(value) {
887
+ return isRecord(value) && value["version"] === 1 && typeof value["name"] === "string" && typeof value["digest"] === "string" && Array.isArray(value["files"]);
888
+ }
889
+ function isRecord(value) {
890
+ return typeof value === "object" && value !== null && !Array.isArray(value);
891
+ }
892
+ function toError(error) {
893
+ return error instanceof Error ? error : new Error(String(error));
894
+ }
895
+ //# sourceMappingURL=renderer.js.map