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.
- package/README.md +97 -0
- package/dist/agents/agent-manager.d.ts +57 -0
- package/dist/agents/agent-manager.d.ts.map +1 -0
- package/dist/agents/agent-manager.js +559 -0
- package/dist/agents/agent-manager.js.map +1 -0
- package/dist/agents/agent-tools.d.ts +4 -0
- package/dist/agents/agent-tools.d.ts.map +1 -0
- package/dist/agents/agent-tools.js +111 -0
- package/dist/agents/agent-tools.js.map +1 -0
- package/dist/agents/types.d.ts +52 -0
- package/dist/agents/types.d.ts.map +1 -0
- package/dist/agents/types.js +2 -0
- package/dist/agents/types.js.map +1 -0
- package/dist/application/agent-application.d.ts +89 -0
- package/dist/application/agent-application.d.ts.map +1 -0
- package/dist/application/agent-application.js +542 -0
- package/dist/application/agent-application.js.map +1 -0
- package/dist/automation/automation-tools.d.ts +4 -0
- package/dist/automation/automation-tools.d.ts.map +1 -0
- package/dist/automation/automation-tools.js +35 -0
- package/dist/automation/automation-tools.js.map +1 -0
- package/dist/automation/cron-adapter.d.ts +11 -0
- package/dist/automation/cron-adapter.d.ts.map +1 -0
- package/dist/automation/cron-adapter.js +48 -0
- package/dist/automation/cron-adapter.js.map +1 -0
- package/dist/automation/daemon-run-executor.d.ts +18 -0
- package/dist/automation/daemon-run-executor.d.ts.map +1 -0
- package/dist/automation/daemon-run-executor.js +69 -0
- package/dist/automation/daemon-run-executor.js.map +1 -0
- package/dist/automation/job-store.d.ts +12 -0
- package/dist/automation/job-store.d.ts.map +1 -0
- package/dist/automation/job-store.js +185 -0
- package/dist/automation/job-store.js.map +1 -0
- package/dist/automation/run-ledger.d.ts +25 -0
- package/dist/automation/run-ledger.d.ts.map +1 -0
- package/dist/automation/run-ledger.js +148 -0
- package/dist/automation/run-ledger.js.map +1 -0
- package/dist/automation/scheduler.d.ts +36 -0
- package/dist/automation/scheduler.d.ts.map +1 -0
- package/dist/automation/scheduler.js +300 -0
- package/dist/automation/scheduler.js.map +1 -0
- package/dist/automation/types.d.ts +81 -0
- package/dist/automation/types.d.ts.map +1 -0
- package/dist/automation/types.js +2 -0
- package/dist/automation/types.js.map +1 -0
- package/dist/cli/main.d.ts +4 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/main.js +768 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/config/config.d.ts +52 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +345 -0
- package/dist/config/config.js.map +1 -0
- package/dist/daemon/daemon.d.ts +30 -0
- package/dist/daemon/daemon.d.ts.map +1 -0
- package/dist/daemon/daemon.js +437 -0
- package/dist/daemon/daemon.js.map +1 -0
- package/dist/daemon/supervisor.d.ts +18 -0
- package/dist/daemon/supervisor.d.ts.map +1 -0
- package/dist/daemon/supervisor.js +37 -0
- package/dist/daemon/supervisor.js.map +1 -0
- package/dist/desktop/daemon-bridge.d.ts +32 -0
- package/dist/desktop/daemon-bridge.d.ts.map +1 -0
- package/dist/desktop/daemon-bridge.js +204 -0
- package/dist/desktop/daemon-bridge.js.map +1 -0
- package/dist/desktop/daemon-supervisor.d.ts +2 -0
- package/dist/desktop/daemon-supervisor.d.ts.map +1 -0
- package/dist/desktop/daemon-supervisor.js +2 -0
- package/dist/desktop/daemon-supervisor.js.map +1 -0
- package/dist/desktop/ipc-contract.d.ts +153 -0
- package/dist/desktop/ipc-contract.d.ts.map +1 -0
- package/dist/desktop/ipc-contract.js +219 -0
- package/dist/desktop/ipc-contract.js.map +1 -0
- package/dist/desktop/main.d.ts +2 -0
- package/dist/desktop/main.d.ts.map +1 -0
- package/dist/desktop/main.js +594 -0
- package/dist/desktop/main.js.map +1 -0
- package/dist/desktop/preload.cjs +32 -0
- package/dist/desktop/renderer/index.html +284 -0
- package/dist/desktop/renderer/renderer.d.ts +2 -0
- package/dist/desktop/renderer/renderer.d.ts.map +1 -0
- package/dist/desktop/renderer/renderer.js +895 -0
- package/dist/desktop/renderer/renderer.js.map +1 -0
- package/dist/desktop/renderer/store.d.ts +33 -0
- package/dist/desktop/renderer/store.d.ts.map +1 -0
- package/dist/desktop/renderer/store.js +144 -0
- package/dist/desktop/renderer/store.js.map +1 -0
- package/dist/desktop/renderer/styles.css +540 -0
- package/dist/desktop/security.d.ts +5 -0
- package/dist/desktop/security.d.ts.map +1 -0
- package/dist/desktop/security.js +49 -0
- package/dist/desktop/security.js.map +1 -0
- package/dist/desktop/smoke-fixture.d.ts +3 -0
- package/dist/desktop/smoke-fixture.d.ts.map +1 -0
- package/dist/desktop/smoke-fixture.js +223 -0
- package/dist/desktop/smoke-fixture.js.map +1 -0
- package/dist/desktop/validation.d.ts +8 -0
- package/dist/desktop/validation.d.ts.map +1 -0
- package/dist/desktop/validation.js +341 -0
- package/dist/desktop/validation.js.map +1 -0
- package/dist/domain/runtime.d.ts +116 -0
- package/dist/domain/runtime.d.ts.map +1 -0
- package/dist/domain/runtime.js +2 -0
- package/dist/domain/runtime.js.map +1 -0
- package/dist/gateway/local-gateway.d.ts +23 -0
- package/dist/gateway/local-gateway.d.ts.map +1 -0
- package/dist/gateway/local-gateway.js +265 -0
- package/dist/gateway/local-gateway.js.map +1 -0
- package/dist/kernel/loop.d.ts +10 -0
- package/dist/kernel/loop.d.ts.map +1 -0
- package/dist/kernel/loop.js +41 -0
- package/dist/kernel/loop.js.map +1 -0
- package/dist/kernel/reducer.d.ts +3 -0
- package/dist/kernel/reducer.d.ts.map +1 -0
- package/dist/kernel/reducer.js +102 -0
- package/dist/kernel/reducer.js.map +1 -0
- package/dist/kernel/state.d.ts +93 -0
- package/dist/kernel/state.d.ts.map +1 -0
- package/dist/kernel/state.js +15 -0
- package/dist/kernel/state.js.map +1 -0
- package/dist/mcp/mcp-manager.d.ts +55 -0
- package/dist/mcp/mcp-manager.d.ts.map +1 -0
- package/dist/mcp/mcp-manager.js +591 -0
- package/dist/mcp/mcp-manager.js.map +1 -0
- package/dist/mcp/mcp-tools.d.ts +4 -0
- package/dist/mcp/mcp-tools.d.ts.map +1 -0
- package/dist/mcp/mcp-tools.js +53 -0
- package/dist/mcp/mcp-tools.js.map +1 -0
- package/dist/mcp/types.d.ts +73 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +2 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/memory/bm25.d.ts +14 -0
- package/dist/memory/bm25.d.ts.map +1 -0
- package/dist/memory/bm25.js +40 -0
- package/dist/memory/bm25.js.map +1 -0
- package/dist/memory/context-loader.d.ts +26 -0
- package/dist/memory/context-loader.d.ts.map +1 -0
- package/dist/memory/context-loader.js +97 -0
- package/dist/memory/context-loader.js.map +1 -0
- package/dist/memory/memory-service.d.ts +30 -0
- package/dist/memory/memory-service.d.ts.map +1 -0
- package/dist/memory/memory-service.js +133 -0
- package/dist/memory/memory-service.js.map +1 -0
- package/dist/memory/memory-tools.d.ts +4 -0
- package/dist/memory/memory-tools.d.ts.map +1 -0
- package/dist/memory/memory-tools.js +49 -0
- package/dist/memory/memory-tools.js.map +1 -0
- package/dist/memory/search-service.d.ts +38 -0
- package/dist/memory/search-service.d.ts.map +1 -0
- package/dist/memory/search-service.js +463 -0
- package/dist/memory/search-service.js.map +1 -0
- package/dist/memory/tokenizer.d.ts +5 -0
- package/dist/memory/tokenizer.d.ts.map +1 -0
- package/dist/memory/tokenizer.js +42 -0
- package/dist/memory/tokenizer.js.map +1 -0
- package/dist/notifications/notification-service.d.ts +47 -0
- package/dist/notifications/notification-service.d.ts.map +1 -0
- package/dist/notifications/notification-service.js +163 -0
- package/dist/notifications/notification-service.js.map +1 -0
- package/dist/notifications/notification-tools.d.ts +4 -0
- package/dist/notifications/notification-tools.d.ts.map +1 -0
- package/dist/notifications/notification-tools.js +31 -0
- package/dist/notifications/notification-tools.js.map +1 -0
- package/dist/pi/pi-runtime-adapter.d.ts +38 -0
- package/dist/pi/pi-runtime-adapter.d.ts.map +1 -0
- package/dist/pi/pi-runtime-adapter.js +483 -0
- package/dist/pi/pi-runtime-adapter.js.map +1 -0
- package/dist/plugins/extension-manager.d.ts +32 -0
- package/dist/plugins/extension-manager.d.ts.map +1 -0
- package/dist/plugins/extension-manager.js +562 -0
- package/dist/plugins/extension-manager.js.map +1 -0
- package/dist/plugins/resource-inspector.d.ts +18 -0
- package/dist/plugins/resource-inspector.d.ts.map +1 -0
- package/dist/plugins/resource-inspector.js +386 -0
- package/dist/plugins/resource-inspector.js.map +1 -0
- package/dist/plugins/types.d.ts +96 -0
- package/dist/plugins/types.d.ts.map +1 -0
- package/dist/plugins/types.js +2 -0
- package/dist/plugins/types.js.map +1 -0
- package/dist/policy/approval-service.d.ts +68 -0
- package/dist/policy/approval-service.d.ts.map +1 -0
- package/dist/policy/approval-service.js +349 -0
- package/dist/policy/approval-service.js.map +1 -0
- package/dist/policy/policy.d.ts +26 -0
- package/dist/policy/policy.d.ts.map +1 -0
- package/dist/policy/policy.js +228 -0
- package/dist/policy/policy.js.map +1 -0
- package/dist/protocol/client.d.ts +25 -0
- package/dist/protocol/client.d.ts.map +1 -0
- package/dist/protocol/client.js +188 -0
- package/dist/protocol/client.js.map +1 -0
- package/dist/protocol/errors.d.ts +5 -0
- package/dist/protocol/errors.d.ts.map +1 -0
- package/dist/protocol/errors.js +9 -0
- package/dist/protocol/errors.js.map +1 -0
- package/dist/protocol/schema.d.ts +71 -0
- package/dist/protocol/schema.d.ts.map +1 -0
- package/dist/protocol/schema.js +155 -0
- package/dist/protocol/schema.js.map +1 -0
- package/dist/settings/llm-settings-service.d.ts +9 -0
- package/dist/settings/llm-settings-service.d.ts.map +1 -0
- package/dist/settings/llm-settings-service.js +199 -0
- package/dist/settings/llm-settings-service.js.map +1 -0
- package/dist/settings/llm-settings.d.ts +29 -0
- package/dist/settings/llm-settings.d.ts.map +1 -0
- package/dist/settings/llm-settings.js +113 -0
- package/dist/settings/llm-settings.js.map +1 -0
- package/dist/shell/shell-service.d.ts +88 -0
- package/dist/shell/shell-service.d.ts.map +1 -0
- package/dist/shell/shell-service.js +459 -0
- package/dist/shell/shell-service.js.map +1 -0
- package/dist/storage/atomic.d.ts +4 -0
- package/dist/storage/atomic.d.ts.map +1 -0
- package/dist/storage/atomic.js +49 -0
- package/dist/storage/atomic.js.map +1 -0
- package/dist/storage/daemon-lock.d.ts +17 -0
- package/dist/storage/daemon-lock.d.ts.map +1 -0
- package/dist/storage/daemon-lock.js +97 -0
- package/dist/storage/daemon-lock.js.map +1 -0
- package/dist/storage/home.d.ts +29 -0
- package/dist/storage/home.d.ts.map +1 -0
- package/dist/storage/home.js +108 -0
- package/dist/storage/home.js.map +1 -0
- package/dist/storage/jsonl.d.ts +2 -0
- package/dist/storage/jsonl.d.ts.map +1 -0
- package/dist/storage/jsonl.js +45 -0
- package/dist/storage/jsonl.js.map +1 -0
- package/dist/tools/core-tools.d.ts +16 -0
- package/dist/tools/core-tools.d.ts.map +1 -0
- package/dist/tools/core-tools.js +283 -0
- package/dist/tools/core-tools.js.map +1 -0
- package/dist/ui/shared/presentation.d.ts +46 -0
- package/dist/ui/shared/presentation.d.ts.map +1 -0
- package/dist/ui/shared/presentation.js +81 -0
- package/dist/ui/shared/presentation.js.map +1 -0
- package/dist/ui/shared/protocol-state.d.ts +30 -0
- package/dist/ui/shared/protocol-state.d.ts.map +1 -0
- package/dist/ui/shared/protocol-state.js +165 -0
- package/dist/ui/shared/protocol-state.js.map +1 -0
- package/dist/ui/tui/commands.d.ts +2 -0
- package/dist/ui/tui/commands.d.ts.map +1 -0
- package/dist/ui/tui/commands.js +33 -0
- package/dist/ui/tui/commands.js.map +1 -0
- package/dist/ui/tui/store.d.ts +32 -0
- package/dist/ui/tui/store.d.ts.map +1 -0
- package/dist/ui/tui/store.js +62 -0
- package/dist/ui/tui/store.js.map +1 -0
- package/dist/ui/tui/tui-app.d.ts +21 -0
- package/dist/ui/tui/tui-app.d.ts.map +1 -0
- package/dist/ui/tui/tui-app.js +643 -0
- package/dist/ui/tui/tui-app.js.map +1 -0
- package/dist/ui/tui/view.d.ts +29 -0
- package/dist/ui/tui/view.d.ts.map +1 -0
- package/dist/ui/tui/view.js +255 -0
- package/dist/ui/tui/view.js.map +1 -0
- package/dist/version.d.ts +5 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- 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
|