@blackbelt-technology/pi-agent-dashboard 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +342 -0
- package/README.md +619 -0
- package/docs/architecture.md +646 -0
- package/package.json +92 -0
- package/packages/extension/package.json +33 -0
- package/packages/extension/src/__tests__/ask-user-tool.test.ts +85 -0
- package/packages/extension/src/__tests__/command-handler.test.ts +712 -0
- package/packages/extension/src/__tests__/connection.test.ts +344 -0
- package/packages/extension/src/__tests__/credentials-updated.test.ts +26 -0
- package/packages/extension/src/__tests__/dev-build.test.ts +79 -0
- package/packages/extension/src/__tests__/event-forwarder.test.ts +89 -0
- package/packages/extension/src/__tests__/git-info.test.ts +112 -0
- package/packages/extension/src/__tests__/git-link-builder.test.ts +102 -0
- package/packages/extension/src/__tests__/openspec-activity-detector.test.ts +232 -0
- package/packages/extension/src/__tests__/openspec-poller.test.ts +119 -0
- package/packages/extension/src/__tests__/process-metrics.test.ts +47 -0
- package/packages/extension/src/__tests__/process-scanner.test.ts +202 -0
- package/packages/extension/src/__tests__/prompt-expander.test.ts +54 -0
- package/packages/extension/src/__tests__/server-auto-start.test.ts +167 -0
- package/packages/extension/src/__tests__/server-launcher.test.ts +44 -0
- package/packages/extension/src/__tests__/server-probe.test.ts +25 -0
- package/packages/extension/src/__tests__/session-switch.test.ts +139 -0
- package/packages/extension/src/__tests__/session-sync.test.ts +55 -0
- package/packages/extension/src/__tests__/source-detector.test.ts +73 -0
- package/packages/extension/src/__tests__/stats-extractor.test.ts +92 -0
- package/packages/extension/src/__tests__/ui-proxy.test.ts +583 -0
- package/packages/extension/src/__tests__/watchdog.test.ts +161 -0
- package/packages/extension/src/ask-user-tool.ts +63 -0
- package/packages/extension/src/bridge-context.ts +64 -0
- package/packages/extension/src/bridge.ts +926 -0
- package/packages/extension/src/command-handler.ts +538 -0
- package/packages/extension/src/connection.ts +204 -0
- package/packages/extension/src/dev-build.ts +39 -0
- package/packages/extension/src/event-forwarder.ts +40 -0
- package/packages/extension/src/flow-event-wiring.ts +102 -0
- package/packages/extension/src/git-info.ts +65 -0
- package/packages/extension/src/git-link-builder.ts +112 -0
- package/packages/extension/src/model-tracker.ts +56 -0
- package/packages/extension/src/pi-env.d.ts +23 -0
- package/packages/extension/src/process-metrics.ts +70 -0
- package/packages/extension/src/process-scanner.ts +396 -0
- package/packages/extension/src/prompt-expander.ts +87 -0
- package/packages/extension/src/provider-register.ts +276 -0
- package/packages/extension/src/server-auto-start.ts +87 -0
- package/packages/extension/src/server-launcher.ts +82 -0
- package/packages/extension/src/server-probe.ts +33 -0
- package/packages/extension/src/session-sync.ts +154 -0
- package/packages/extension/src/source-detector.ts +26 -0
- package/packages/extension/src/ui-proxy.ts +269 -0
- package/packages/extension/tsconfig.json +11 -0
- package/packages/server/package.json +37 -0
- package/packages/server/src/__tests__/auth-plugin.test.ts +117 -0
- package/packages/server/src/__tests__/auth.test.ts +224 -0
- package/packages/server/src/__tests__/auto-attach.test.ts +246 -0
- package/packages/server/src/__tests__/auto-resume.test.ts +135 -0
- package/packages/server/src/__tests__/auto-shutdown.test.ts +136 -0
- package/packages/server/src/__tests__/browse-endpoint.test.ts +104 -0
- package/packages/server/src/__tests__/bulk-archive-handler.test.ts +15 -0
- package/packages/server/src/__tests__/cli-parse.test.ts +73 -0
- package/packages/server/src/__tests__/client-discovery.test.ts +39 -0
- package/packages/server/src/__tests__/config-api.test.ts +104 -0
- package/packages/server/src/__tests__/cors.test.ts +48 -0
- package/packages/server/src/__tests__/directory-service.test.ts +240 -0
- package/packages/server/src/__tests__/editor-detection.test.ts +60 -0
- package/packages/server/src/__tests__/editor-endpoints.test.ts +26 -0
- package/packages/server/src/__tests__/editor-manager.test.ts +73 -0
- package/packages/server/src/__tests__/editor-registry.test.ts +151 -0
- package/packages/server/src/__tests__/event-status-extraction-flow.test.ts +55 -0
- package/packages/server/src/__tests__/event-status-extraction.test.ts +58 -0
- package/packages/server/src/__tests__/extension-register.test.ts +61 -0
- package/packages/server/src/__tests__/file-endpoint.test.ts +49 -0
- package/packages/server/src/__tests__/force-kill-handler.test.ts +109 -0
- package/packages/server/src/__tests__/git-operations.test.ts +251 -0
- package/packages/server/src/__tests__/headless-pid-registry.test.ts +233 -0
- package/packages/server/src/__tests__/headless-shutdown-fallback.test.ts +109 -0
- package/packages/server/src/__tests__/health-endpoint.test.ts +35 -0
- package/packages/server/src/__tests__/heartbeat-ack.test.ts +63 -0
- package/packages/server/src/__tests__/json-store.test.ts +70 -0
- package/packages/server/src/__tests__/localhost-guard.test.ts +149 -0
- package/packages/server/src/__tests__/memory-event-store.test.ts +260 -0
- package/packages/server/src/__tests__/memory-session-manager.test.ts +80 -0
- package/packages/server/src/__tests__/meta-persistence.test.ts +107 -0
- package/packages/server/src/__tests__/migrate-persistence.test.ts +180 -0
- package/packages/server/src/__tests__/npm-search-proxy.test.ts +153 -0
- package/packages/server/src/__tests__/oauth-callback-server.test.ts +165 -0
- package/packages/server/src/__tests__/openspec-archive.test.ts +87 -0
- package/packages/server/src/__tests__/package-manager-wrapper.test.ts +163 -0
- package/packages/server/src/__tests__/package-routes.test.ts +172 -0
- package/packages/server/src/__tests__/pending-fork-registry.test.ts +69 -0
- package/packages/server/src/__tests__/pending-load-manager.test.ts +144 -0
- package/packages/server/src/__tests__/pending-resume-registry.test.ts +130 -0
- package/packages/server/src/__tests__/pi-resource-scanner.test.ts +235 -0
- package/packages/server/src/__tests__/preferences-store.test.ts +108 -0
- package/packages/server/src/__tests__/process-manager.test.ts +184 -0
- package/packages/server/src/__tests__/provider-auth-handlers.test.ts +93 -0
- package/packages/server/src/__tests__/provider-auth-routes.test.ts +143 -0
- package/packages/server/src/__tests__/provider-auth-storage.test.ts +114 -0
- package/packages/server/src/__tests__/resolve-path.test.ts +38 -0
- package/packages/server/src/__tests__/ring-buffer.test.ts +45 -0
- package/packages/server/src/__tests__/server-pid.test.ts +89 -0
- package/packages/server/src/__tests__/session-api.test.ts +244 -0
- package/packages/server/src/__tests__/session-diff.test.ts +138 -0
- package/packages/server/src/__tests__/session-file-dedup.test.ts +102 -0
- package/packages/server/src/__tests__/session-file-reader.test.ts +85 -0
- package/packages/server/src/__tests__/session-lifecycle-logging.test.ts +138 -0
- package/packages/server/src/__tests__/session-order-manager.test.ts +135 -0
- package/packages/server/src/__tests__/session-ordering-integration.test.ts +102 -0
- package/packages/server/src/__tests__/session-scanner.test.ts +199 -0
- package/packages/server/src/__tests__/shutdown-endpoint.test.ts +42 -0
- package/packages/server/src/__tests__/skip-wipe.test.ts +123 -0
- package/packages/server/src/__tests__/sleep-aware-heartbeat.test.ts +126 -0
- package/packages/server/src/__tests__/smoke-integration.test.ts +175 -0
- package/packages/server/src/__tests__/spa-fallback.test.ts +68 -0
- package/packages/server/src/__tests__/subscription-handler.test.ts +155 -0
- package/packages/server/src/__tests__/terminal-gateway.test.ts +61 -0
- package/packages/server/src/__tests__/terminal-manager.test.ts +257 -0
- package/packages/server/src/__tests__/trusted-networks-config.test.ts +84 -0
- package/packages/server/src/__tests__/tunnel.test.ts +206 -0
- package/packages/server/src/__tests__/ws-ping-pong.test.ts +112 -0
- package/packages/server/src/auth-plugin.ts +302 -0
- package/packages/server/src/auth.ts +323 -0
- package/packages/server/src/browse.ts +55 -0
- package/packages/server/src/browser-gateway.ts +495 -0
- package/packages/server/src/browser-handlers/directory-handler.ts +137 -0
- package/packages/server/src/browser-handlers/handler-context.ts +45 -0
- package/packages/server/src/browser-handlers/session-action-handler.ts +271 -0
- package/packages/server/src/browser-handlers/session-meta-handler.ts +95 -0
- package/packages/server/src/browser-handlers/subscription-handler.ts +154 -0
- package/packages/server/src/browser-handlers/terminal-handler.ts +37 -0
- package/packages/server/src/cli.ts +347 -0
- package/packages/server/src/config-api.ts +130 -0
- package/packages/server/src/directory-service.ts +162 -0
- package/packages/server/src/editor-detection.ts +60 -0
- package/packages/server/src/editor-manager.ts +352 -0
- package/packages/server/src/editor-proxy.ts +134 -0
- package/packages/server/src/editor-registry.ts +108 -0
- package/packages/server/src/event-status-extraction.ts +131 -0
- package/packages/server/src/event-wiring.ts +589 -0
- package/packages/server/src/extension-register.ts +92 -0
- package/packages/server/src/git-operations.ts +200 -0
- package/packages/server/src/headless-pid-registry.ts +207 -0
- package/packages/server/src/idle-timer.ts +61 -0
- package/packages/server/src/json-store.ts +32 -0
- package/packages/server/src/localhost-guard.ts +117 -0
- package/packages/server/src/memory-event-store.ts +193 -0
- package/packages/server/src/memory-session-manager.ts +123 -0
- package/packages/server/src/meta-persistence.ts +64 -0
- package/packages/server/src/migrate-persistence.ts +195 -0
- package/packages/server/src/npm-search-proxy.ts +143 -0
- package/packages/server/src/oauth-callback-server.ts +177 -0
- package/packages/server/src/openspec-archive.ts +60 -0
- package/packages/server/src/package-manager-wrapper.ts +200 -0
- package/packages/server/src/pending-fork-registry.ts +53 -0
- package/packages/server/src/pending-load-manager.ts +110 -0
- package/packages/server/src/pending-resume-registry.ts +69 -0
- package/packages/server/src/pi-gateway.ts +419 -0
- package/packages/server/src/pi-resource-scanner.ts +369 -0
- package/packages/server/src/preferences-store.ts +116 -0
- package/packages/server/src/process-manager.ts +311 -0
- package/packages/server/src/provider-auth-handlers.ts +438 -0
- package/packages/server/src/provider-auth-storage.ts +200 -0
- package/packages/server/src/resolve-path.ts +12 -0
- package/packages/server/src/routes/editor-routes.ts +86 -0
- package/packages/server/src/routes/file-routes.ts +116 -0
- package/packages/server/src/routes/git-routes.ts +89 -0
- package/packages/server/src/routes/openspec-routes.ts +99 -0
- package/packages/server/src/routes/package-routes.ts +172 -0
- package/packages/server/src/routes/provider-auth-routes.ts +244 -0
- package/packages/server/src/routes/provider-routes.ts +101 -0
- package/packages/server/src/routes/route-deps.ts +23 -0
- package/packages/server/src/routes/session-routes.ts +91 -0
- package/packages/server/src/routes/system-routes.ts +271 -0
- package/packages/server/src/server-pid.ts +84 -0
- package/packages/server/src/server.ts +554 -0
- package/packages/server/src/session-api.ts +330 -0
- package/packages/server/src/session-bootstrap.ts +80 -0
- package/packages/server/src/session-diff.ts +178 -0
- package/packages/server/src/session-discovery.ts +134 -0
- package/packages/server/src/session-file-reader.ts +135 -0
- package/packages/server/src/session-order-manager.ts +73 -0
- package/packages/server/src/session-scanner.ts +233 -0
- package/packages/server/src/session-stats-reader.ts +99 -0
- package/packages/server/src/terminal-gateway.ts +51 -0
- package/packages/server/src/terminal-manager.ts +241 -0
- package/packages/server/src/tunnel.ts +329 -0
- package/packages/server/tsconfig.json +11 -0
- package/packages/shared/package.json +15 -0
- package/packages/shared/src/__tests__/config.test.ts +358 -0
- package/packages/shared/src/__tests__/deriveChangeState.test.ts +95 -0
- package/packages/shared/src/__tests__/mdns-discovery.test.ts +80 -0
- package/packages/shared/src/__tests__/protocol.test.ts +243 -0
- package/packages/shared/src/__tests__/resolve-jiti.test.ts +17 -0
- package/packages/shared/src/__tests__/server-identity.test.ts +73 -0
- package/packages/shared/src/__tests__/session-meta.test.ts +125 -0
- package/packages/shared/src/archive-types.ts +11 -0
- package/packages/shared/src/browser-protocol.ts +534 -0
- package/packages/shared/src/config.ts +245 -0
- package/packages/shared/src/diff-types.ts +41 -0
- package/packages/shared/src/editor-types.ts +18 -0
- package/packages/shared/src/mdns-discovery.ts +248 -0
- package/packages/shared/src/openspec-activity-detector.ts +109 -0
- package/packages/shared/src/openspec-poller.ts +96 -0
- package/packages/shared/src/protocol.ts +369 -0
- package/packages/shared/src/resolve-jiti.ts +43 -0
- package/packages/shared/src/rest-api.ts +255 -0
- package/packages/shared/src/server-identity.ts +51 -0
- package/packages/shared/src/session-meta.ts +86 -0
- package/packages/shared/src/state-replay.ts +174 -0
- package/packages/shared/src/stats-extractor.ts +54 -0
- package/packages/shared/src/terminal-types.ts +18 -0
- package/packages/shared/src/types.ts +351 -0
- package/packages/shared/tsconfig.json +8 -0
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handles server→extension messages by dispatching to pi API.
|
|
3
|
+
*/
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
6
|
+
import type {
|
|
7
|
+
ServerToExtensionMessage,
|
|
8
|
+
ExtensionToServerMessage,
|
|
9
|
+
} from "@blackbelt-technology/pi-dashboard-shared/protocol.js";
|
|
10
|
+
import { killProcessByPgid } from "./process-scanner.js";
|
|
11
|
+
import type { FileEntry, PiSessionInfo } from "@blackbelt-technology/pi-dashboard-shared/types.js";
|
|
12
|
+
import { filterHiddenCommands } from "./bridge-context.js";
|
|
13
|
+
|
|
14
|
+
/** Escape regex special characters for fd pattern */
|
|
15
|
+
function escapeRegex(value: string): string {
|
|
16
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Search files using fd */
|
|
20
|
+
function searchFiles(cwd: string, query: string): FileEntry[] {
|
|
21
|
+
const args = [
|
|
22
|
+
"--base-directory", cwd,
|
|
23
|
+
"--max-results", "20",
|
|
24
|
+
"--type", "f",
|
|
25
|
+
"--type", "d",
|
|
26
|
+
"--full-path",
|
|
27
|
+
"--hidden",
|
|
28
|
+
"--exclude", ".git",
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
if (query) {
|
|
32
|
+
args.push(escapeRegex(query));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const result = spawnSync("fd", args, {
|
|
37
|
+
encoding: "utf-8",
|
|
38
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
39
|
+
timeout: 5000,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (result.status !== 0 || !result.stdout) {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return result.stdout.trim().split("\n").filter(Boolean).map((line) => {
|
|
47
|
+
const normalized = line.replace(/\\/g, "/");
|
|
48
|
+
const isDirectory = normalized.endsWith("/");
|
|
49
|
+
return { path: normalized, isDirectory };
|
|
50
|
+
});
|
|
51
|
+
} catch {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Parsed result from parseSendPrompt */
|
|
57
|
+
export type ParsedPrompt =
|
|
58
|
+
| { type: "bash"; command: string; excludeFromContext: boolean }
|
|
59
|
+
| { type: "compact"; customInstructions: string | undefined }
|
|
60
|
+
| { type: "model"; provider: string; modelId: string }
|
|
61
|
+
| { type: "shutdown" }
|
|
62
|
+
| { type: "reload" }
|
|
63
|
+
| { type: "new" }
|
|
64
|
+
| { type: "mgmt"; event: string; data: Record<string, unknown> }
|
|
65
|
+
| { type: "slash"; text: string }
|
|
66
|
+
| { type: "passthrough"; text: string };
|
|
67
|
+
|
|
68
|
+
/** pi-flows management commands with known event mappings.
|
|
69
|
+
* These are dispatched via pi.events instead of flow:run.
|
|
70
|
+
* Flow management commands (flows:new, flows:edit, flows:delete) are
|
|
71
|
+
* handled in bridge.ts sessionPrompt callback which passes cachedCtx
|
|
72
|
+
* as fallback context for headless sessions. */
|
|
73
|
+
const MANAGEMENT_COMMAND_EVENTS: Record<string, {
|
|
74
|
+
event: string;
|
|
75
|
+
dataFn: (args: string) => Record<string, unknown>;
|
|
76
|
+
}> = {};
|
|
77
|
+
|
|
78
|
+
/** Parse input text to detect pi internal command prefixes */
|
|
79
|
+
export function parseSendPrompt(text: string): ParsedPrompt {
|
|
80
|
+
// 1. Check !! (must check before !)
|
|
81
|
+
if (text.startsWith("!!")) {
|
|
82
|
+
const command = text.slice(2).trim();
|
|
83
|
+
if (!command) return { type: "passthrough", text };
|
|
84
|
+
return { type: "bash", command, excludeFromContext: true };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 2. Check !
|
|
88
|
+
if (text.startsWith("!")) {
|
|
89
|
+
const command = text.slice(1).trim();
|
|
90
|
+
if (!command) return { type: "passthrough", text };
|
|
91
|
+
return { type: "bash", command, excludeFromContext: false };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 3. Check /compact
|
|
95
|
+
if (text === "/compact" || text.startsWith("/compact ")) {
|
|
96
|
+
const args = text.startsWith("/compact ") ? text.slice(9).trim() : undefined;
|
|
97
|
+
return { type: "compact", customInstructions: args || undefined };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 4. Check /quit and /exit
|
|
101
|
+
if (text === "/quit" || text === "/exit") {
|
|
102
|
+
return { type: "shutdown" };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 4b. Check /reload
|
|
106
|
+
if (text === "/reload") {
|
|
107
|
+
return { type: "reload" };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 4c. Check /new
|
|
111
|
+
if (text === "/new") {
|
|
112
|
+
return { type: "new" };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 4d. Check /model <provider/id>
|
|
116
|
+
if (text.startsWith("/model ")) {
|
|
117
|
+
const modelStr = text.slice(7).trim();
|
|
118
|
+
const slashIdx = modelStr.indexOf("/");
|
|
119
|
+
if (slashIdx > 0) {
|
|
120
|
+
return { type: "model", provider: modelStr.slice(0, slashIdx), modelId: modelStr.slice(slashIdx + 1) };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 5. Check management commands (/flows:new, etc.) with known event mappings
|
|
125
|
+
if (text.startsWith("/") && !text.includes("\n")) {
|
|
126
|
+
const cmdText = text.slice(1);
|
|
127
|
+
const spaceIdx = cmdText.indexOf(" ");
|
|
128
|
+
const cmdName = spaceIdx === -1 ? cmdText : cmdText.slice(0, spaceIdx);
|
|
129
|
+
const cmdArgs = spaceIdx === -1 ? "" : cmdText.slice(spaceIdx + 1);
|
|
130
|
+
const mgmt = MANAGEMENT_COMMAND_EVENTS[cmdName];
|
|
131
|
+
if (mgmt) {
|
|
132
|
+
return { type: "mgmt", event: mgmt.event, data: mgmt.dataFn(cmdArgs) };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 6. Check / prefix (generic slash command)
|
|
137
|
+
if (text.startsWith("/") && !text.includes("\n")) {
|
|
138
|
+
return { type: "slash", text };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 5. Passthrough
|
|
142
|
+
return { type: "passthrough", text };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const BASH_TIMEOUT = 30_000;
|
|
146
|
+
|
|
147
|
+
export interface CommandHandler {
|
|
148
|
+
handle(msg: ServerToExtensionMessage): ExtensionToServerMessage | undefined | Promise<ExtensionToServerMessage | undefined>;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function createCommandHandler(
|
|
152
|
+
pi: ExtensionAPI,
|
|
153
|
+
sessionIdOrGetter: string | (() => string),
|
|
154
|
+
options?: {
|
|
155
|
+
getModelRegistry?: () => any;
|
|
156
|
+
setThinkingLevel?: (level: string) => void;
|
|
157
|
+
getThinkingLevel?: () => string | undefined;
|
|
158
|
+
shutdown?: () => void;
|
|
159
|
+
abort?: () => void;
|
|
160
|
+
getCwd?: () => string;
|
|
161
|
+
/** Callback to send events (e.g., bash_output, command_feedback) back to server */
|
|
162
|
+
eventSink?: (msg: ExtensionToServerMessage) => void;
|
|
163
|
+
/** Trigger context compaction */
|
|
164
|
+
compact?: (options: { customInstructions?: string }) => void;
|
|
165
|
+
/** Trigger session reload (extensions, settings, skills, etc.) */
|
|
166
|
+
reload?: () => void;
|
|
167
|
+
/** Spawn a new session in the same cwd */
|
|
168
|
+
spawnNew?: () => void;
|
|
169
|
+
/** Switch model via pi.setModel() */
|
|
170
|
+
setModel?: (provider: string, modelId: string) => Promise<void>;
|
|
171
|
+
/** Route slash commands through session.prompt() */
|
|
172
|
+
sessionPrompt?: (text: string) => void;
|
|
173
|
+
},
|
|
174
|
+
): CommandHandler {
|
|
175
|
+
const getSessionId = typeof sessionIdOrGetter === "function" ? sessionIdOrGetter : () => sessionIdOrGetter;
|
|
176
|
+
return {
|
|
177
|
+
async handle(msg: ServerToExtensionMessage): Promise<ExtensionToServerMessage | undefined> {
|
|
178
|
+
const sessionId = getSessionId();
|
|
179
|
+
|
|
180
|
+
// Ignore messages for other sessions (skip session-less messages like heartbeat_ack)
|
|
181
|
+
if (msg.sessionId !== undefined && msg.sessionId !== sessionId) {
|
|
182
|
+
console.error(`[dashboard] Ignoring message type=${msg.type} for session ${msg.sessionId}, current session is ${sessionId}`);
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
switch (msg.type) {
|
|
187
|
+
case "send_prompt": {
|
|
188
|
+
const parsed = parseSendPrompt(msg.text);
|
|
189
|
+
|
|
190
|
+
// Route based on parsed command type
|
|
191
|
+
if (parsed.type === "bash") {
|
|
192
|
+
await handleBashCommand(pi, sessionId, parsed.command, parsed.excludeFromContext, options?.eventSink);
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (parsed.type === "compact") {
|
|
197
|
+
await handleCompactCommand(sessionId, parsed.customInstructions, options?.compact, options?.eventSink);
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (parsed.type === "shutdown") {
|
|
202
|
+
if (options?.shutdown) {
|
|
203
|
+
options.shutdown();
|
|
204
|
+
}
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (parsed.type === "reload") {
|
|
209
|
+
if (options?.reload) {
|
|
210
|
+
options.reload();
|
|
211
|
+
}
|
|
212
|
+
options?.eventSink?.({
|
|
213
|
+
type: "event_forward",
|
|
214
|
+
sessionId,
|
|
215
|
+
event: {
|
|
216
|
+
eventType: "command_feedback",
|
|
217
|
+
timestamp: Date.now(),
|
|
218
|
+
data: { command: "/reload", status: "completed" },
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
return undefined;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (parsed.type === "new") {
|
|
225
|
+
if (options?.spawnNew) {
|
|
226
|
+
options.spawnNew();
|
|
227
|
+
}
|
|
228
|
+
options?.eventSink?.({
|
|
229
|
+
type: "event_forward",
|
|
230
|
+
sessionId,
|
|
231
|
+
event: {
|
|
232
|
+
eventType: "command_feedback",
|
|
233
|
+
timestamp: Date.now(),
|
|
234
|
+
data: { command: "/new", status: "completed" },
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
return undefined;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (parsed.type === "model") {
|
|
241
|
+
if (options?.setModel) {
|
|
242
|
+
await options.setModel(parsed.provider, parsed.modelId);
|
|
243
|
+
}
|
|
244
|
+
options?.eventSink?.({
|
|
245
|
+
type: "event_forward",
|
|
246
|
+
sessionId,
|
|
247
|
+
event: {
|
|
248
|
+
eventType: "command_feedback",
|
|
249
|
+
timestamp: Date.now(),
|
|
250
|
+
data: { command: `/model ${parsed.provider}/${parsed.modelId}`, status: "completed" },
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
return undefined;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (parsed.type === "mgmt") {
|
|
257
|
+
// Dispatch management command via pi.events (e.g. flows:new-request)
|
|
258
|
+
if ((pi as any).events) {
|
|
259
|
+
(pi as any).events.emit(parsed.event, parsed.data);
|
|
260
|
+
}
|
|
261
|
+
options?.eventSink?.({
|
|
262
|
+
type: "event_forward",
|
|
263
|
+
sessionId,
|
|
264
|
+
event: {
|
|
265
|
+
eventType: "command_feedback",
|
|
266
|
+
timestamp: Date.now(),
|
|
267
|
+
data: { command: parsed.event, status: "completed" },
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
return undefined;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (parsed.type === "slash") {
|
|
274
|
+
if (options?.sessionPrompt) {
|
|
275
|
+
options.sessionPrompt(parsed.text);
|
|
276
|
+
} else {
|
|
277
|
+
pi.sendUserMessage(parsed.text);
|
|
278
|
+
}
|
|
279
|
+
options?.eventSink?.({
|
|
280
|
+
type: "event_forward",
|
|
281
|
+
sessionId,
|
|
282
|
+
event: {
|
|
283
|
+
eventType: "command_feedback",
|
|
284
|
+
timestamp: Date.now(),
|
|
285
|
+
data: { command: parsed.text, status: "completed" },
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
return undefined;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Passthrough: send as regular user message (with image handling)
|
|
292
|
+
sendUserMessageWithImages(pi, msg.text, msg.images);
|
|
293
|
+
return undefined;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
case "abort":
|
|
297
|
+
if (options?.abort) {
|
|
298
|
+
options.abort();
|
|
299
|
+
}
|
|
300
|
+
return undefined;
|
|
301
|
+
|
|
302
|
+
case "request_commands": {
|
|
303
|
+
const commands = filterHiddenCommands(pi.getCommands());
|
|
304
|
+
// Also send flows list alongside commands
|
|
305
|
+
if (options?.eventSink) {
|
|
306
|
+
const probe: any = {};
|
|
307
|
+
try { pi.events?.emit("flow:list-flows", probe); } catch { /* ignore */ }
|
|
308
|
+
options.eventSink({ type: "flows_list", sessionId, flows: probe.flows ?? [] });
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
type: "commands_list",
|
|
312
|
+
sessionId,
|
|
313
|
+
commands,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
case "list_files": {
|
|
318
|
+
const files = searchFiles(process.cwd(), msg.query);
|
|
319
|
+
return {
|
|
320
|
+
type: "files_list",
|
|
321
|
+
sessionId,
|
|
322
|
+
query: msg.query,
|
|
323
|
+
files,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// openspec_refresh removed — server handles directly via DirectoryService
|
|
328
|
+
|
|
329
|
+
case "rename_session":
|
|
330
|
+
pi.setSessionName(msg.name);
|
|
331
|
+
return {
|
|
332
|
+
type: "session_name_update",
|
|
333
|
+
sessionId,
|
|
334
|
+
name: msg.name,
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
case "request_models": {
|
|
338
|
+
const registry = options?.getModelRegistry?.();
|
|
339
|
+
if (registry) {
|
|
340
|
+
try {
|
|
341
|
+
registry.refresh();
|
|
342
|
+
const models = registry.getAvailable().map((m: any) => ({
|
|
343
|
+
provider: m.provider,
|
|
344
|
+
id: m.id,
|
|
345
|
+
}));
|
|
346
|
+
return { type: "models_list", sessionId, models };
|
|
347
|
+
} catch { /* ignore */ }
|
|
348
|
+
}
|
|
349
|
+
return { type: "models_list", sessionId, models: [] };
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
case "set_thinking_level":
|
|
353
|
+
if (options?.setThinkingLevel) {
|
|
354
|
+
options.setThinkingLevel(msg.level);
|
|
355
|
+
}
|
|
356
|
+
return undefined;
|
|
357
|
+
|
|
358
|
+
case "set_model":
|
|
359
|
+
if (options?.setModel) {
|
|
360
|
+
await options.setModel(msg.provider, msg.modelId);
|
|
361
|
+
}
|
|
362
|
+
return undefined;
|
|
363
|
+
|
|
364
|
+
case "kill_process": {
|
|
365
|
+
const pgid = (msg as { pgid: number }).pgid;
|
|
366
|
+
if (pgid) {
|
|
367
|
+
const killed = killProcessByPgid(pgid);
|
|
368
|
+
console.error(`[dashboard] kill_process pgid=${pgid} result=${killed}`);
|
|
369
|
+
}
|
|
370
|
+
return undefined;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
case "shutdown":
|
|
374
|
+
if (options?.shutdown) {
|
|
375
|
+
options.shutdown();
|
|
376
|
+
}
|
|
377
|
+
return undefined;
|
|
378
|
+
|
|
379
|
+
case "request_state_sync":
|
|
380
|
+
// State sync is handled by the bridge on reconnect
|
|
381
|
+
return undefined;
|
|
382
|
+
|
|
383
|
+
case "request_flows_refresh": {
|
|
384
|
+
// Re-query pi-flows and send updated list
|
|
385
|
+
if (options?.eventSink) {
|
|
386
|
+
const probe: any = {};
|
|
387
|
+
try { pi.events?.emit("flow:list-flows", probe); } catch { /* ignore */ }
|
|
388
|
+
options.eventSink({ type: "flows_list", sessionId, flows: probe.flows ?? [] });
|
|
389
|
+
}
|
|
390
|
+
return undefined;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
case "list_sessions": {
|
|
394
|
+
try {
|
|
395
|
+
// Dynamic import to avoid hard dependency at module load
|
|
396
|
+
const { SessionManager } = await import("@mariozechner/pi-coding-agent") as any;
|
|
397
|
+
const cwd = msg.cwd || options?.getCwd?.() || process.cwd();
|
|
398
|
+
const sessionInfos = await SessionManager.list(cwd);
|
|
399
|
+
const sessions: PiSessionInfo[] = (sessionInfos || []).map((s: any) => ({
|
|
400
|
+
id: s.id,
|
|
401
|
+
path: s.path,
|
|
402
|
+
cwd: s.cwd,
|
|
403
|
+
name: s.name,
|
|
404
|
+
parentSessionPath: s.parentSessionPath,
|
|
405
|
+
created: s.created instanceof Date ? s.created.toISOString() : String(s.created),
|
|
406
|
+
modified: s.modified instanceof Date ? s.modified.toISOString() : String(s.modified),
|
|
407
|
+
messageCount: s.messageCount ?? 0,
|
|
408
|
+
firstMessage: s.firstMessage,
|
|
409
|
+
}));
|
|
410
|
+
return { type: "sessions_list", sessionId, cwd, sessions };
|
|
411
|
+
} catch {
|
|
412
|
+
return { type: "sessions_list", sessionId, cwd: msg.cwd || process.cwd(), sessions: [] };
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
default:
|
|
417
|
+
return undefined;
|
|
418
|
+
}
|
|
419
|
+
},
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/** Send a user message with optional image validation.
|
|
424
|
+
* Uses deliverAs: "followUp" so messages queue properly when the agent is streaming. */
|
|
425
|
+
function sendUserMessageWithImages(
|
|
426
|
+
pi: ExtensionAPI,
|
|
427
|
+
text: string,
|
|
428
|
+
images?: Array<{ type: string; data: string; mimeType: string }>,
|
|
429
|
+
): void {
|
|
430
|
+
const sendOptions = { deliverAs: "followUp" as const };
|
|
431
|
+
if (images && images.length > 0) {
|
|
432
|
+
const validMimeTypes = new Set(["image/jpeg", "image/png", "image/gif", "image/webp"]);
|
|
433
|
+
const validImages = images.filter((img) => {
|
|
434
|
+
if (!img || typeof img !== "object") {
|
|
435
|
+
console.error("[dashboard] Dropping non-object image entry");
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
if (!img.mimeType || typeof img.mimeType !== "string" || !validMimeTypes.has(img.mimeType)) {
|
|
439
|
+
console.error(`[dashboard] Dropping image with invalid mimeType: "${img.mimeType}" (type: ${typeof img.mimeType})`);
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
if (!img.data || typeof img.data !== "string") {
|
|
443
|
+
console.error(`[dashboard] Dropping image with invalid data (type: ${typeof img.data}, length: ${img.data?.length ?? 0})`);
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
return true;
|
|
447
|
+
});
|
|
448
|
+
if (validImages.length > 0) {
|
|
449
|
+
const content = [
|
|
450
|
+
{ type: "text" as const, text },
|
|
451
|
+
...validImages.map((img) => ({
|
|
452
|
+
type: "image" as const,
|
|
453
|
+
data: img.data,
|
|
454
|
+
mimeType: img.mimeType,
|
|
455
|
+
})),
|
|
456
|
+
];
|
|
457
|
+
console.error(`[dashboard] Sending message with ${validImages.length} image(s), mimeTypes: ${validImages.map(i => i.mimeType).join(", ")}`);
|
|
458
|
+
(pi.sendUserMessage as any)(content, sendOptions);
|
|
459
|
+
} else {
|
|
460
|
+
(pi.sendUserMessage as any)(text, sendOptions);
|
|
461
|
+
}
|
|
462
|
+
} else {
|
|
463
|
+
(pi.sendUserMessage as any)(text, sendOptions);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/** Execute a bash command and forward results */
|
|
468
|
+
async function handleBashCommand(
|
|
469
|
+
pi: ExtensionAPI,
|
|
470
|
+
sessionId: string,
|
|
471
|
+
command: string,
|
|
472
|
+
excludeFromContext: boolean,
|
|
473
|
+
eventSink?: (msg: ExtensionToServerMessage) => void,
|
|
474
|
+
): Promise<void> {
|
|
475
|
+
let output = "";
|
|
476
|
+
let exitCode = 0;
|
|
477
|
+
try {
|
|
478
|
+
const result = await pi.exec("sh", ["-c", command], { timeout: BASH_TIMEOUT });
|
|
479
|
+
output = (result.stdout || "") + (result.stderr || "");
|
|
480
|
+
exitCode = result.exitCode ?? 0;
|
|
481
|
+
} catch (err: any) {
|
|
482
|
+
output = err?.message ?? "Command execution failed";
|
|
483
|
+
exitCode = 1;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Forward bash output event
|
|
487
|
+
eventSink?.({
|
|
488
|
+
type: "event_forward",
|
|
489
|
+
sessionId,
|
|
490
|
+
event: {
|
|
491
|
+
eventType: "bash_output",
|
|
492
|
+
timestamp: Date.now(),
|
|
493
|
+
data: { command, output, exitCode, excludeFromContext },
|
|
494
|
+
},
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
// For ! (not !!), also send to LLM
|
|
498
|
+
if (!excludeFromContext) {
|
|
499
|
+
const message = `$ ${command}\n${output}`;
|
|
500
|
+
pi.sendUserMessage(message);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/** Handle /compact command */
|
|
505
|
+
async function handleCompactCommand(
|
|
506
|
+
sessionId: string,
|
|
507
|
+
customInstructions: string | undefined,
|
|
508
|
+
compact?: (options: { customInstructions?: string }) => void,
|
|
509
|
+
eventSink?: (msg: ExtensionToServerMessage) => void,
|
|
510
|
+
): Promise<void> {
|
|
511
|
+
eventSink?.({
|
|
512
|
+
type: "event_forward",
|
|
513
|
+
sessionId,
|
|
514
|
+
event: {
|
|
515
|
+
eventType: "command_feedback",
|
|
516
|
+
timestamp: Date.now(),
|
|
517
|
+
data: { command: "/compact", status: "started" },
|
|
518
|
+
},
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
try {
|
|
522
|
+
if (compact) {
|
|
523
|
+
compact({ customInstructions });
|
|
524
|
+
}
|
|
525
|
+
} catch (err: any) {
|
|
526
|
+
eventSink?.({
|
|
527
|
+
type: "event_forward",
|
|
528
|
+
sessionId,
|
|
529
|
+
event: {
|
|
530
|
+
eventType: "command_feedback",
|
|
531
|
+
timestamp: Date.now(),
|
|
532
|
+
data: { command: "/compact", status: "error", message: err?.message ?? "Compaction failed" },
|
|
533
|
+
},
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// handleLoadSessionEvents removed — server loads sessions directly via DirectoryService
|