pi-studio 0.4.1 → 0.4.2
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/CHANGELOG.md +17 -0
- package/README.md +4 -2
- package/index.ts +728 -27
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `pi-studio` are documented here.
|
|
4
4
|
|
|
5
|
+
## [0.4.2] — 2026-03-03
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- New editor action: **Load from pi editor** to pull the current terminal editor draft into Studio.
|
|
9
|
+
- Optional Studio debug tracing (`?debug=1`) with client/server lifecycle events for request/state/tool diagnostics.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- Footer busy status now reflects Studio-owned and terminal-owned activity phases more clearly (`running`, `tool`, `responding`).
|
|
13
|
+
- Tool activity labels are derived from tool calls/executions with improved command classification for shell workflows (including current/parent directory listings and listing-like `find` commands).
|
|
14
|
+
- Studio request ownership remains sticky during active/agent-busy phases to avoid confusing Studio → Terminal label flips mid-turn.
|
|
15
|
+
- Editor and response preview panes keep previous rendered content visible while a new render is in flight, using a subtle delayed **Updating** indicator instead of replacing content with a loading screen.
|
|
16
|
+
- Footer shortcut hint and run-button tooltip now explicitly document `Cmd/Ctrl+Enter` for **Run editor text**.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- Studio requests are no longer cleared prematurely when assistant messages end with `stopReason: "toolUse"`.
|
|
20
|
+
- Embedded-script activity label normalization now preserves whitespace correctly (fixes corrupted labels caused by escaped regex mismatch).
|
|
21
|
+
|
|
5
22
|
## [0.4.1] — 2026-03-03
|
|
6
23
|
|
|
7
24
|
### Changed
|
package/README.md
CHANGED
|
@@ -49,9 +49,9 @@ Experimental extension for [pi](https://github.com/badlogic/pi-mono) that opens
|
|
|
49
49
|
- Response load helpers:
|
|
50
50
|
- non-critique: **Load response into editor**
|
|
51
51
|
- critique: **Load critique notes into editor** / **Load full critique into editor**
|
|
52
|
-
- File actions: **Save editor as…**, **Save editor**, **Load file content**
|
|
52
|
+
- File/editor actions: **Save editor as…**, **Save editor**, **Load file content**, **Send to pi editor**, **Load from pi editor**
|
|
53
53
|
- View toggles: panel header dropdowns for `Editor (Raw|Preview)` and `Response (Raw|Preview) | Editor (Preview)`
|
|
54
|
-
- **Editor Preview in response pane**: side-by-side source/rendered view (Overleaf-style) — select `Right: Editor (Preview)` to render editor text in the right pane with live
|
|
54
|
+
- **Editor Preview in response pane**: side-by-side source/rendered view (Overleaf-style) — select `Right: Editor (Preview)` to render editor text in the right pane with live updates
|
|
55
55
|
- Preview mode supports MathML equations and Mermaid fenced diagrams
|
|
56
56
|
- **Language-aware syntax highlighting** with selectable language mode:
|
|
57
57
|
- Markdown (default): headings, links, code fences, lists, quotes, inline code
|
|
@@ -67,6 +67,8 @@ Experimental extension for [pi](https://github.com/badlogic/pi-mono) that opens
|
|
|
67
67
|
- **Working directory**: "Set working dir" button for uploaded files — resolves relative image paths and enables "Save editor" for uploaded content
|
|
68
68
|
- **Live theme sync**: changing the pi theme in the terminal updates the studio browser UI automatically (polled every 2 seconds)
|
|
69
69
|
- Separate syntax highlight toggles for editor and response Raw views, with local preference persistence
|
|
70
|
+
- Keyboard shortcuts: `Cmd/Ctrl+Enter` runs **Run editor text** when editor pane is active; `Cmd/Ctrl+Esc` / `F10` toggles focus mode; `Esc` exits focus mode
|
|
71
|
+
- Footer status reflects Studio/terminal activity phases (connecting, ready, submitting, terminal activity)
|
|
70
72
|
- Theme-aware browser UI derived from current pi theme
|
|
71
73
|
- View mode selectors integrated into panel headers for a cleaner layout
|
|
72
74
|
|
package/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { spawn } from "node:child_process";
|
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { readFileSync, statSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http";
|
|
6
|
-
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
6
|
+
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
7
7
|
import { URL } from "node:url";
|
|
8
8
|
import { WebSocketServer, WebSocket, type RawData } from "ws";
|
|
9
9
|
|
|
@@ -11,6 +11,7 @@ type Lens = "writing" | "code";
|
|
|
11
11
|
type RequestedLens = Lens | "auto";
|
|
12
12
|
type StudioRequestKind = "critique" | "annotation" | "direct";
|
|
13
13
|
type StudioSourceKind = "file" | "last-response" | "blank";
|
|
14
|
+
type TerminalActivityPhase = "idle" | "running" | "tool" | "responding";
|
|
14
15
|
|
|
15
16
|
interface StudioServerState {
|
|
16
17
|
server: Server;
|
|
@@ -90,6 +91,11 @@ interface SendToEditorRequestMessage {
|
|
|
90
91
|
content: string;
|
|
91
92
|
}
|
|
92
93
|
|
|
94
|
+
interface GetFromEditorRequestMessage {
|
|
95
|
+
type: "get_from_editor_request";
|
|
96
|
+
requestId: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
93
99
|
type IncomingStudioMessage =
|
|
94
100
|
| HelloMessage
|
|
95
101
|
| PingMessage
|
|
@@ -99,7 +105,8 @@ type IncomingStudioMessage =
|
|
|
99
105
|
| SendRunRequestMessage
|
|
100
106
|
| SaveAsRequestMessage
|
|
101
107
|
| SaveOverRequestMessage
|
|
102
|
-
| SendToEditorRequestMessage
|
|
108
|
+
| SendToEditorRequestMessage
|
|
109
|
+
| GetFromEditorRequestMessage;
|
|
103
110
|
|
|
104
111
|
const REQUEST_TIMEOUT_MS = 5 * 60 * 1000;
|
|
105
112
|
const PREVIEW_RENDER_MAX_CHARS = 400_000;
|
|
@@ -1133,9 +1140,146 @@ function parseIncomingMessage(data: RawData): IncomingStudioMessage | null {
|
|
|
1133
1140
|
};
|
|
1134
1141
|
}
|
|
1135
1142
|
|
|
1143
|
+
if (msg.type === "get_from_editor_request" && typeof msg.requestId === "string") {
|
|
1144
|
+
return {
|
|
1145
|
+
type: "get_from_editor_request",
|
|
1146
|
+
requestId: msg.requestId,
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1136
1150
|
return null;
|
|
1137
1151
|
}
|
|
1138
1152
|
|
|
1153
|
+
function normalizeActivityLabel(label: string): string | null {
|
|
1154
|
+
const compact = String(label || "").replace(/\s+/g, " ").trim();
|
|
1155
|
+
if (!compact) return null;
|
|
1156
|
+
if (compact.length <= 96) return compact;
|
|
1157
|
+
return `${compact.slice(0, 93).trimEnd()}…`;
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
function isGenericToolActivityLabel(label: string | null | undefined): boolean {
|
|
1161
|
+
const normalized = String(label || "").trim().toLowerCase();
|
|
1162
|
+
if (!normalized) return true;
|
|
1163
|
+
return normalized.startsWith("running ")
|
|
1164
|
+
|| normalized === "reading file"
|
|
1165
|
+
|| normalized === "writing file"
|
|
1166
|
+
|| normalized === "editing file";
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
function deriveBashActivityLabel(command: string): string | null {
|
|
1170
|
+
const normalized = String(command || "").trim();
|
|
1171
|
+
if (!normalized) return null;
|
|
1172
|
+
const lower = normalized.toLowerCase();
|
|
1173
|
+
|
|
1174
|
+
const segments = lower
|
|
1175
|
+
.split(/(?:&&|\|\||;|\n)+/g)
|
|
1176
|
+
.map((segment) => segment.trim())
|
|
1177
|
+
.filter((segment) => segment.length > 0);
|
|
1178
|
+
|
|
1179
|
+
let hasPwd = false;
|
|
1180
|
+
let hasLsCurrent = false;
|
|
1181
|
+
let hasLsParent = false;
|
|
1182
|
+
let hasFind = false;
|
|
1183
|
+
let hasFindCurrentListing = false;
|
|
1184
|
+
let hasFindParentListing = false;
|
|
1185
|
+
|
|
1186
|
+
for (const segment of segments) {
|
|
1187
|
+
if (/\bpwd\b/.test(segment)) hasPwd = true;
|
|
1188
|
+
|
|
1189
|
+
if (/\bls\b/.test(segment)) {
|
|
1190
|
+
if (/\.\./.test(segment)) hasLsParent = true;
|
|
1191
|
+
else hasLsCurrent = true;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
if (/\bfind\b/.test(segment)) {
|
|
1195
|
+
hasFind = true;
|
|
1196
|
+
const pathMatch = segment.match(/\bfind\s+([^\s]+)/);
|
|
1197
|
+
const pathToken = pathMatch ? pathMatch[1] : "";
|
|
1198
|
+
const hasSelector = /-(?:name|iname|regex|path|ipath|newer|mtime|mmin|size|user|group)\b/.test(segment);
|
|
1199
|
+
const listingLike = /-maxdepth\s+\d+\b/.test(segment) && !hasSelector;
|
|
1200
|
+
|
|
1201
|
+
if (listingLike) {
|
|
1202
|
+
if (pathToken === ".." || pathToken === "../") {
|
|
1203
|
+
hasFindParentListing = true;
|
|
1204
|
+
} else if (pathToken === "." || pathToken === "./" || pathToken === "") {
|
|
1205
|
+
hasFindCurrentListing = true;
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
const hasCurrentListing = hasLsCurrent || hasFindCurrentListing;
|
|
1212
|
+
const hasParentListing = hasLsParent || hasFindParentListing;
|
|
1213
|
+
|
|
1214
|
+
if (hasCurrentListing && hasParentListing) {
|
|
1215
|
+
return "Listing directory and parent directory files";
|
|
1216
|
+
}
|
|
1217
|
+
if (hasPwd && hasCurrentListing) {
|
|
1218
|
+
return "Listing current directory files";
|
|
1219
|
+
}
|
|
1220
|
+
if (hasParentListing) {
|
|
1221
|
+
return "Listing parent directory files";
|
|
1222
|
+
}
|
|
1223
|
+
if (hasCurrentListing || /\bls\b/.test(lower)) {
|
|
1224
|
+
return "Listing directory files";
|
|
1225
|
+
}
|
|
1226
|
+
if (hasFind || /\bfind\b/.test(lower)) {
|
|
1227
|
+
return "Searching files";
|
|
1228
|
+
}
|
|
1229
|
+
if (/\brg\b/.test(lower) || /\bgrep\b/.test(lower)) {
|
|
1230
|
+
return "Searching text in files";
|
|
1231
|
+
}
|
|
1232
|
+
if (/\bcat\b/.test(lower) || /\bsed\b/.test(lower) || /\bawk\b/.test(lower)) {
|
|
1233
|
+
return "Reading file content";
|
|
1234
|
+
}
|
|
1235
|
+
if (/\bgit\s+status\b/.test(lower)) {
|
|
1236
|
+
return "Checking git status";
|
|
1237
|
+
}
|
|
1238
|
+
if (/\bgit\s+diff\b/.test(lower)) {
|
|
1239
|
+
return "Reviewing git changes";
|
|
1240
|
+
}
|
|
1241
|
+
if (/\bgit\b/.test(lower)) {
|
|
1242
|
+
return "Running git command";
|
|
1243
|
+
}
|
|
1244
|
+
if (/\bnpm\b/.test(lower)) {
|
|
1245
|
+
return "Running npm command";
|
|
1246
|
+
}
|
|
1247
|
+
if (/\bpython3?\b/.test(lower)) {
|
|
1248
|
+
return "Running Python command";
|
|
1249
|
+
}
|
|
1250
|
+
if (/\bnode\b/.test(lower)) {
|
|
1251
|
+
return "Running Node.js command";
|
|
1252
|
+
}
|
|
1253
|
+
return "Running shell command";
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
function deriveToolActivityLabel(toolName: string, args: unknown): string | null {
|
|
1257
|
+
const normalizedTool = String(toolName || "").trim().toLowerCase();
|
|
1258
|
+
const payload = (args && typeof args === "object") ? (args as Record<string, unknown>) : {};
|
|
1259
|
+
|
|
1260
|
+
if (normalizedTool === "bash") {
|
|
1261
|
+
const command = typeof payload.command === "string" ? payload.command : "";
|
|
1262
|
+
return deriveBashActivityLabel(command);
|
|
1263
|
+
}
|
|
1264
|
+
if (normalizedTool === "read") {
|
|
1265
|
+
const path = typeof payload.path === "string" ? payload.path : "";
|
|
1266
|
+
return path ? `Reading ${basename(path)}` : "Reading file";
|
|
1267
|
+
}
|
|
1268
|
+
if (normalizedTool === "write") {
|
|
1269
|
+
const path = typeof payload.path === "string" ? payload.path : "";
|
|
1270
|
+
return path ? `Writing ${basename(path)}` : "Writing file";
|
|
1271
|
+
}
|
|
1272
|
+
if (normalizedTool === "edit") {
|
|
1273
|
+
const path = typeof payload.path === "string" ? payload.path : "";
|
|
1274
|
+
return path ? `Editing ${basename(path)}` : "Editing file";
|
|
1275
|
+
}
|
|
1276
|
+
if (normalizedTool === "find") return "Searching files";
|
|
1277
|
+
if (normalizedTool === "grep") return "Searching text in files";
|
|
1278
|
+
if (normalizedTool === "ls") return "Listing directory files";
|
|
1279
|
+
|
|
1280
|
+
return normalizeActivityLabel(`Running ${normalizedTool || "tool"}`);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1139
1283
|
function isAllowedOrigin(_origin: string | undefined, _port: number): boolean {
|
|
1140
1284
|
// For local-only studio, token auth is the primary guard. In practice,
|
|
1141
1285
|
// browser origin headers can vary (or be omitted) across wrappers/browsers,
|
|
@@ -1696,6 +1840,7 @@ ${cssVarsBlock}
|
|
|
1696
1840
|
}
|
|
1697
1841
|
|
|
1698
1842
|
.panel-scroll {
|
|
1843
|
+
position: relative;
|
|
1699
1844
|
min-height: 0;
|
|
1700
1845
|
overflow: auto;
|
|
1701
1846
|
padding: 12px;
|
|
@@ -1968,6 +2113,19 @@ ${cssVarsBlock}
|
|
|
1968
2113
|
font-style: italic;
|
|
1969
2114
|
}
|
|
1970
2115
|
|
|
2116
|
+
.panel-scroll.preview-pending::after {
|
|
2117
|
+
content: "Updating";
|
|
2118
|
+
position: absolute;
|
|
2119
|
+
top: 10px;
|
|
2120
|
+
right: 12px;
|
|
2121
|
+
color: var(--muted);
|
|
2122
|
+
font-size: 10px;
|
|
2123
|
+
line-height: 1.2;
|
|
2124
|
+
letter-spacing: 0.01em;
|
|
2125
|
+
pointer-events: none;
|
|
2126
|
+
opacity: 0.64;
|
|
2127
|
+
}
|
|
2128
|
+
|
|
1971
2129
|
.preview-error {
|
|
1972
2130
|
color: var(--warn);
|
|
1973
2131
|
margin-bottom: 0.75em;
|
|
@@ -2089,7 +2247,7 @@ ${cssVarsBlock}
|
|
|
2089
2247
|
<span id="syncBadge" class="source-badge sync-badge">No response loaded</span>
|
|
2090
2248
|
</div>
|
|
2091
2249
|
<div class="source-actions">
|
|
2092
|
-
<button id="sendRunBtn" type="button" title="Send editor text directly to the model as-is.">Run editor text</button>
|
|
2250
|
+
<button id="sendRunBtn" type="button" title="Send editor text directly to the model as-is. Shortcut: Cmd/Ctrl+Enter when editor pane is active.">Run editor text</button>
|
|
2093
2251
|
<button id="insertHeaderBtn" type="button" title="Prepends/updates the annotated-reply header in the editor.">Insert annotation header</button>
|
|
2094
2252
|
<select id="lensSelect" aria-label="Critique focus">
|
|
2095
2253
|
<option value="auto" selected>Critique focus: Auto</option>
|
|
@@ -2098,6 +2256,7 @@ ${cssVarsBlock}
|
|
|
2098
2256
|
</select>
|
|
2099
2257
|
<button id="critiqueBtn" type="button">Critique editor text</button>
|
|
2100
2258
|
<button id="sendEditorBtn" type="button">Send to pi editor</button>
|
|
2259
|
+
<button id="getEditorBtn" type="button" title="Load the current terminal editor draft into Studio.">Load from pi editor</button>
|
|
2101
2260
|
<button id="copyDraftBtn" type="button">Copy editor text</button>
|
|
2102
2261
|
<select id="highlightSelect" aria-label="Editor syntax highlighting">
|
|
2103
2262
|
<option value="off">Syntax highlight: Off</option>
|
|
@@ -2175,7 +2334,7 @@ ${cssVarsBlock}
|
|
|
2175
2334
|
|
|
2176
2335
|
<footer>
|
|
2177
2336
|
<span id="status">Booting studio…</span>
|
|
2178
|
-
<span class="shortcut-hint">Focus pane: Cmd/Ctrl+Esc (or F10), Esc to exit</span>
|
|
2337
|
+
<span class="shortcut-hint">Focus pane: Cmd/Ctrl+Esc (or F10), Esc to exit · Run editor text: Cmd/Ctrl+Enter</span>
|
|
2179
2338
|
</footer>
|
|
2180
2339
|
|
|
2181
2340
|
<!-- Defer sanitizer script so studio can boot/connect even if CDN is slow or blocked. -->
|
|
@@ -2235,6 +2394,7 @@ ${cssVarsBlock}
|
|
|
2235
2394
|
const saveAsBtn = document.getElementById("saveAsBtn");
|
|
2236
2395
|
const saveOverBtn = document.getElementById("saveOverBtn");
|
|
2237
2396
|
const sendEditorBtn = document.getElementById("sendEditorBtn");
|
|
2397
|
+
const getEditorBtn = document.getElementById("getEditorBtn");
|
|
2238
2398
|
const sendRunBtn = document.getElementById("sendRunBtn");
|
|
2239
2399
|
const copyDraftBtn = document.getElementById("copyDraftBtn");
|
|
2240
2400
|
const highlightSelect = document.getElementById("highlightSelect");
|
|
@@ -2252,6 +2412,7 @@ ${cssVarsBlock}
|
|
|
2252
2412
|
let statusLevel = "";
|
|
2253
2413
|
let pendingRequestId = null;
|
|
2254
2414
|
let pendingKind = null;
|
|
2415
|
+
let stickyStudioKind = null;
|
|
2255
2416
|
let initialDocumentApplied = false;
|
|
2256
2417
|
let editorView = "markdown";
|
|
2257
2418
|
let rightView = "preview";
|
|
@@ -2265,6 +2426,11 @@ ${cssVarsBlock}
|
|
|
2265
2426
|
let latestResponseNormalized = "";
|
|
2266
2427
|
let latestCritiqueNotes = "";
|
|
2267
2428
|
let latestCritiqueNotesNormalized = "";
|
|
2429
|
+
let agentBusyFromServer = false;
|
|
2430
|
+
let terminalActivityPhase = "idle";
|
|
2431
|
+
let terminalActivityToolName = "";
|
|
2432
|
+
let terminalActivityLabel = "";
|
|
2433
|
+
let lastSpecificToolLabel = "";
|
|
2268
2434
|
let uiBusy = false;
|
|
2269
2435
|
let sourceState = {
|
|
2270
2436
|
source: initialSourceState.source,
|
|
@@ -2317,6 +2483,8 @@ ${cssVarsBlock}
|
|
|
2317
2483
|
const RESPONSE_HIGHLIGHT_MAX_CHARS = 120_000;
|
|
2318
2484
|
const RESPONSE_HIGHLIGHT_STORAGE_KEY = "piStudio.responseHighlightEnabled";
|
|
2319
2485
|
const PREVIEW_INPUT_DEBOUNCE_MS = 0;
|
|
2486
|
+
const PREVIEW_PENDING_BADGE_DELAY_MS = 220;
|
|
2487
|
+
const previewPendingTimers = new WeakMap();
|
|
2320
2488
|
let sourcePreviewRenderTimer = null;
|
|
2321
2489
|
let sourcePreviewRenderNonce = 0;
|
|
2322
2490
|
let responsePreviewRenderNonce = 0;
|
|
@@ -2333,8 +2501,153 @@ ${cssVarsBlock}
|
|
|
2333
2501
|
let mermaidModulePromise = null;
|
|
2334
2502
|
let mermaidInitialized = false;
|
|
2335
2503
|
|
|
2504
|
+
const DEBUG_ENABLED = (() => {
|
|
2505
|
+
try {
|
|
2506
|
+
const query = new URLSearchParams(window.location.search || "");
|
|
2507
|
+
const hash = new URLSearchParams((window.location.hash || "").replace(/^#/, ""));
|
|
2508
|
+
const value = String(query.get("debug") || hash.get("debug") || "").trim().toLowerCase();
|
|
2509
|
+
return value === "1" || value === "true" || value === "yes" || value === "on";
|
|
2510
|
+
} catch {
|
|
2511
|
+
return false;
|
|
2512
|
+
}
|
|
2513
|
+
})();
|
|
2514
|
+
const DEBUG_LOG_MAX = 400;
|
|
2515
|
+
const debugLog = [];
|
|
2516
|
+
|
|
2517
|
+
function debugTrace(eventName, payload) {
|
|
2518
|
+
if (!DEBUG_ENABLED) return;
|
|
2519
|
+
const entry = {
|
|
2520
|
+
ts: Date.now(),
|
|
2521
|
+
event: String(eventName || ""),
|
|
2522
|
+
payload: payload || null,
|
|
2523
|
+
};
|
|
2524
|
+
debugLog.push(entry);
|
|
2525
|
+
if (debugLog.length > DEBUG_LOG_MAX) debugLog.shift();
|
|
2526
|
+
window.__piStudioDebugLog = debugLog.slice();
|
|
2527
|
+
try {
|
|
2528
|
+
console.debug("[pi-studio]", new Date(entry.ts).toISOString(), entry.event, entry.payload);
|
|
2529
|
+
} catch {
|
|
2530
|
+
// ignore console errors
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2534
|
+
function summarizeServerMessage(message) {
|
|
2535
|
+
if (!message || typeof message !== "object") return { type: "invalid" };
|
|
2536
|
+
const summary = {
|
|
2537
|
+
type: typeof message.type === "string" ? message.type : "unknown",
|
|
2538
|
+
};
|
|
2539
|
+
if (typeof message.requestId === "string") summary.requestId = message.requestId;
|
|
2540
|
+
if (typeof message.activeRequestId === "string") summary.activeRequestId = message.activeRequestId;
|
|
2541
|
+
if (typeof message.activeRequestKind === "string") summary.activeRequestKind = message.activeRequestKind;
|
|
2542
|
+
if (typeof message.kind === "string") summary.kind = message.kind;
|
|
2543
|
+
if (typeof message.event === "string") summary.event = message.event;
|
|
2544
|
+
if (typeof message.timestamp === "number") summary.timestamp = message.timestamp;
|
|
2545
|
+
if (typeof message.busy === "boolean") summary.busy = message.busy;
|
|
2546
|
+
if (typeof message.agentBusy === "boolean") summary.agentBusy = message.agentBusy;
|
|
2547
|
+
if (typeof message.terminalPhase === "string") summary.terminalPhase = message.terminalPhase;
|
|
2548
|
+
if (typeof message.terminalToolName === "string") summary.terminalToolName = message.terminalToolName;
|
|
2549
|
+
if (typeof message.terminalActivityLabel === "string") summary.terminalActivityLabel = message.terminalActivityLabel;
|
|
2550
|
+
if (typeof message.stopReason === "string") summary.stopReason = message.stopReason;
|
|
2551
|
+
if (typeof message.markdown === "string") summary.markdownLength = message.markdown.length;
|
|
2552
|
+
if (typeof message.label === "string") summary.label = message.label;
|
|
2553
|
+
if (typeof message.details === "object" && message.details !== null) summary.details = message.details;
|
|
2554
|
+
return summary;
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2336
2557
|
function getIdleStatus() {
|
|
2337
|
-
return "Ready. Edit text, then run
|
|
2558
|
+
return "Ready. Edit, load, or annotate text, then run, save, send to pi editor, or critique.";
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
function normalizeTerminalPhase(phase) {
|
|
2562
|
+
if (phase === "running" || phase === "tool" || phase === "responding") return phase;
|
|
2563
|
+
return "idle";
|
|
2564
|
+
}
|
|
2565
|
+
|
|
2566
|
+
function normalizeActivityLabel(label) {
|
|
2567
|
+
if (typeof label !== "string") return "";
|
|
2568
|
+
return label.replace(/\\s+/g, " ").trim();
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2571
|
+
function isGenericToolLabel(label) {
|
|
2572
|
+
const normalized = normalizeActivityLabel(label).toLowerCase();
|
|
2573
|
+
if (!normalized) return true;
|
|
2574
|
+
return normalized.startsWith("running ")
|
|
2575
|
+
|| normalized === "reading file"
|
|
2576
|
+
|| normalized === "writing file"
|
|
2577
|
+
|| normalized === "editing file";
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
function withEllipsis(text) {
|
|
2581
|
+
const value = String(text || "").trim();
|
|
2582
|
+
if (!value) return "";
|
|
2583
|
+
if (/[….!?]$/.test(value)) return value;
|
|
2584
|
+
return value + "…";
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2587
|
+
function updateTerminalActivityState(phase, toolName, label) {
|
|
2588
|
+
terminalActivityPhase = normalizeTerminalPhase(phase);
|
|
2589
|
+
terminalActivityToolName = typeof toolName === "string" ? toolName.trim() : "";
|
|
2590
|
+
terminalActivityLabel = normalizeActivityLabel(label);
|
|
2591
|
+
|
|
2592
|
+
if (terminalActivityPhase === "tool" && terminalActivityLabel && !isGenericToolLabel(terminalActivityLabel)) {
|
|
2593
|
+
lastSpecificToolLabel = terminalActivityLabel;
|
|
2594
|
+
}
|
|
2595
|
+
if (terminalActivityPhase === "idle") {
|
|
2596
|
+
lastSpecificToolLabel = "";
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
|
|
2600
|
+
function getTerminalBusyStatus() {
|
|
2601
|
+
if (terminalActivityPhase === "tool") {
|
|
2602
|
+
if (terminalActivityLabel) {
|
|
2603
|
+
return "Terminal: " + withEllipsis(terminalActivityLabel);
|
|
2604
|
+
}
|
|
2605
|
+
return terminalActivityToolName
|
|
2606
|
+
? "Terminal: running tool: " + terminalActivityToolName + "…"
|
|
2607
|
+
: "Terminal: running tool…";
|
|
2608
|
+
}
|
|
2609
|
+
if (terminalActivityPhase === "responding") {
|
|
2610
|
+
if (lastSpecificToolLabel) {
|
|
2611
|
+
return "Terminal: " + lastSpecificToolLabel + " (generating response)…";
|
|
2612
|
+
}
|
|
2613
|
+
return "Terminal: generating response…";
|
|
2614
|
+
}
|
|
2615
|
+
if (terminalActivityPhase === "running" && lastSpecificToolLabel) {
|
|
2616
|
+
return "Terminal: " + withEllipsis(lastSpecificToolLabel);
|
|
2617
|
+
}
|
|
2618
|
+
return "Terminal: running…";
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2621
|
+
function getStudioActionLabel(kind) {
|
|
2622
|
+
if (kind === "annotation") return "sending annotated reply";
|
|
2623
|
+
if (kind === "critique") return "running critique";
|
|
2624
|
+
if (kind === "direct") return "running editor text";
|
|
2625
|
+
if (kind === "send_to_editor") return "sending to pi editor";
|
|
2626
|
+
if (kind === "get_from_editor") return "loading from pi editor";
|
|
2627
|
+
if (kind === "save_as" || kind === "save_over") return "saving editor text";
|
|
2628
|
+
return "submitting request";
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2631
|
+
function getStudioBusyStatus(kind) {
|
|
2632
|
+
const action = getStudioActionLabel(kind);
|
|
2633
|
+
if (terminalActivityPhase === "tool") {
|
|
2634
|
+
if (terminalActivityLabel) {
|
|
2635
|
+
return "Studio: " + withEllipsis(terminalActivityLabel);
|
|
2636
|
+
}
|
|
2637
|
+
return terminalActivityToolName
|
|
2638
|
+
? "Studio: " + action + " (tool: " + terminalActivityToolName + ")…"
|
|
2639
|
+
: "Studio: " + action + " (running tool)…";
|
|
2640
|
+
}
|
|
2641
|
+
if (terminalActivityPhase === "responding") {
|
|
2642
|
+
if (lastSpecificToolLabel) {
|
|
2643
|
+
return "Studio: " + lastSpecificToolLabel + " (generating response)…";
|
|
2644
|
+
}
|
|
2645
|
+
return "Studio: " + action + " (generating response)…";
|
|
2646
|
+
}
|
|
2647
|
+
if (terminalActivityPhase === "running" && lastSpecificToolLabel) {
|
|
2648
|
+
return "Studio: " + withEllipsis(lastSpecificToolLabel);
|
|
2649
|
+
}
|
|
2650
|
+
return "Studio: " + action + "…";
|
|
2338
2651
|
}
|
|
2339
2652
|
|
|
2340
2653
|
function renderStatus() {
|
|
@@ -2352,6 +2665,19 @@ ${cssVarsBlock}
|
|
|
2352
2665
|
statusMessage = message;
|
|
2353
2666
|
statusLevel = level || "";
|
|
2354
2667
|
renderStatus();
|
|
2668
|
+
debugTrace("status", {
|
|
2669
|
+
wsState,
|
|
2670
|
+
message: statusMessage,
|
|
2671
|
+
level: statusLevel,
|
|
2672
|
+
pendingRequestId,
|
|
2673
|
+
pendingKind,
|
|
2674
|
+
uiBusy,
|
|
2675
|
+
agentBusyFromServer,
|
|
2676
|
+
terminalPhase: terminalActivityPhase,
|
|
2677
|
+
terminalToolName: terminalActivityToolName,
|
|
2678
|
+
terminalActivityLabel,
|
|
2679
|
+
lastSpecificToolLabel,
|
|
2680
|
+
});
|
|
2355
2681
|
}
|
|
2356
2682
|
|
|
2357
2683
|
renderStatus();
|
|
@@ -2594,6 +2920,48 @@ ${cssVarsBlock}
|
|
|
2594
2920
|
targetEl.appendChild(el);
|
|
2595
2921
|
}
|
|
2596
2922
|
|
|
2923
|
+
function hasMeaningfulPreviewContent(targetEl) {
|
|
2924
|
+
if (!targetEl || typeof targetEl.querySelector !== "function") return false;
|
|
2925
|
+
if (targetEl.querySelector(".preview-loading")) return false;
|
|
2926
|
+
const text = typeof targetEl.textContent === "string" ? targetEl.textContent.trim() : "";
|
|
2927
|
+
return text.length > 0;
|
|
2928
|
+
}
|
|
2929
|
+
|
|
2930
|
+
function beginPreviewRender(targetEl) {
|
|
2931
|
+
if (!targetEl || !targetEl.classList) return;
|
|
2932
|
+
|
|
2933
|
+
const pendingTimer = previewPendingTimers.get(targetEl);
|
|
2934
|
+
if (pendingTimer !== undefined) {
|
|
2935
|
+
window.clearTimeout(pendingTimer);
|
|
2936
|
+
previewPendingTimers.delete(targetEl);
|
|
2937
|
+
}
|
|
2938
|
+
|
|
2939
|
+
if (hasMeaningfulPreviewContent(targetEl)) {
|
|
2940
|
+
targetEl.classList.remove("preview-pending");
|
|
2941
|
+
const timerId = window.setTimeout(() => {
|
|
2942
|
+
previewPendingTimers.delete(targetEl);
|
|
2943
|
+
if (!targetEl || !targetEl.classList) return;
|
|
2944
|
+
if (!hasMeaningfulPreviewContent(targetEl)) return;
|
|
2945
|
+
targetEl.classList.add("preview-pending");
|
|
2946
|
+
}, PREVIEW_PENDING_BADGE_DELAY_MS);
|
|
2947
|
+
previewPendingTimers.set(targetEl, timerId);
|
|
2948
|
+
return;
|
|
2949
|
+
}
|
|
2950
|
+
|
|
2951
|
+
targetEl.classList.remove("preview-pending");
|
|
2952
|
+
targetEl.innerHTML = "<div class='preview-loading'>Rendering preview…</div>";
|
|
2953
|
+
}
|
|
2954
|
+
|
|
2955
|
+
function finishPreviewRender(targetEl) {
|
|
2956
|
+
if (!targetEl || !targetEl.classList) return;
|
|
2957
|
+
const pendingTimer = previewPendingTimers.get(targetEl);
|
|
2958
|
+
if (pendingTimer !== undefined) {
|
|
2959
|
+
window.clearTimeout(pendingTimer);
|
|
2960
|
+
previewPendingTimers.delete(targetEl);
|
|
2961
|
+
}
|
|
2962
|
+
targetEl.classList.remove("preview-pending");
|
|
2963
|
+
}
|
|
2964
|
+
|
|
2597
2965
|
async function getMermaidApi() {
|
|
2598
2966
|
if (mermaidModulePromise) {
|
|
2599
2967
|
return mermaidModulePromise;
|
|
@@ -2739,6 +3107,7 @@ ${cssVarsBlock}
|
|
|
2739
3107
|
if (nonce !== responsePreviewRenderNonce || (rightView !== "preview" && rightView !== "editor-preview")) return;
|
|
2740
3108
|
}
|
|
2741
3109
|
|
|
3110
|
+
finishPreviewRender(targetEl);
|
|
2742
3111
|
targetEl.innerHTML = sanitizeRenderedHtml(renderedHtml, markdown);
|
|
2743
3112
|
await renderMermaidInElement(targetEl);
|
|
2744
3113
|
|
|
@@ -2758,6 +3127,7 @@ ${cssVarsBlock}
|
|
|
2758
3127
|
}
|
|
2759
3128
|
|
|
2760
3129
|
const detail = error && error.message ? error.message : String(error || "unknown error");
|
|
3130
|
+
finishPreviewRender(targetEl);
|
|
2761
3131
|
targetEl.innerHTML = buildPreviewErrorHtml("Preview renderer unavailable (" + detail + "). Showing plain markdown.", markdown);
|
|
2762
3132
|
}
|
|
2763
3133
|
}
|
|
@@ -2766,11 +3136,12 @@ ${cssVarsBlock}
|
|
|
2766
3136
|
if (editorView !== "preview") return;
|
|
2767
3137
|
const text = sourceTextEl.value || "";
|
|
2768
3138
|
if (editorLanguage && editorLanguage !== "markdown" && editorLanguage !== "latex") {
|
|
3139
|
+
finishPreviewRender(sourcePreviewEl);
|
|
2769
3140
|
sourcePreviewEl.innerHTML = "<div class='response-markdown-highlight' style='white-space:pre;font-family:var(--font-mono);font-size:13px;line-height:1.5;padding:16px;overflow:auto;'>" + highlightCode(text, editorLanguage) + "</div>";
|
|
2770
3141
|
return;
|
|
2771
3142
|
}
|
|
2772
3143
|
const nonce = ++sourcePreviewRenderNonce;
|
|
2773
|
-
sourcePreviewEl
|
|
3144
|
+
beginPreviewRender(sourcePreviewEl);
|
|
2774
3145
|
void applyRenderedMarkdown(sourcePreviewEl, text, "source", nonce);
|
|
2775
3146
|
}
|
|
2776
3147
|
|
|
@@ -2825,34 +3196,38 @@ ${cssVarsBlock}
|
|
|
2825
3196
|
if (rightView === "editor-preview") {
|
|
2826
3197
|
const editorText = sourceTextEl.value || "";
|
|
2827
3198
|
if (!editorText.trim()) {
|
|
3199
|
+
finishPreviewRender(critiqueViewEl);
|
|
2828
3200
|
critiqueViewEl.innerHTML = "<pre class='plain-markdown'>Editor is empty.</pre>";
|
|
2829
3201
|
return;
|
|
2830
3202
|
}
|
|
2831
3203
|
if (editorLanguage && editorLanguage !== "markdown" && editorLanguage !== "latex") {
|
|
3204
|
+
finishPreviewRender(critiqueViewEl);
|
|
2832
3205
|
critiqueViewEl.innerHTML = "<div class='response-markdown-highlight' style='white-space:pre;font-family:var(--font-mono);font-size:13px;line-height:1.5;padding:16px;overflow:auto;'>" + highlightCode(editorText, editorLanguage) + "</div>";
|
|
2833
3206
|
return;
|
|
2834
3207
|
}
|
|
2835
3208
|
const nonce = ++responsePreviewRenderNonce;
|
|
2836
|
-
critiqueViewEl
|
|
3209
|
+
beginPreviewRender(critiqueViewEl);
|
|
2837
3210
|
void applyRenderedMarkdown(critiqueViewEl, editorText, "response", nonce);
|
|
2838
3211
|
return;
|
|
2839
3212
|
}
|
|
2840
3213
|
|
|
2841
3214
|
const markdown = latestResponseMarkdown;
|
|
2842
3215
|
if (!markdown || !markdown.trim()) {
|
|
3216
|
+
finishPreviewRender(critiqueViewEl);
|
|
2843
3217
|
critiqueViewEl.innerHTML = "<pre class='plain-markdown'>No response yet. Run editor text or critique editor text.</pre>";
|
|
2844
3218
|
return;
|
|
2845
3219
|
}
|
|
2846
3220
|
|
|
2847
3221
|
if (rightView === "preview") {
|
|
2848
3222
|
const nonce = ++responsePreviewRenderNonce;
|
|
2849
|
-
critiqueViewEl
|
|
3223
|
+
beginPreviewRender(critiqueViewEl);
|
|
2850
3224
|
void applyRenderedMarkdown(critiqueViewEl, markdown, "response", nonce);
|
|
2851
3225
|
return;
|
|
2852
3226
|
}
|
|
2853
3227
|
|
|
2854
3228
|
if (responseHighlightEnabled) {
|
|
2855
3229
|
if (markdown.length > RESPONSE_HIGHLIGHT_MAX_CHARS) {
|
|
3230
|
+
finishPreviewRender(critiqueViewEl);
|
|
2856
3231
|
critiqueViewEl.innerHTML = buildPreviewErrorHtml(
|
|
2857
3232
|
"Response is too large for markdown highlighting. Showing plain markdown.",
|
|
2858
3233
|
markdown,
|
|
@@ -2860,10 +3235,12 @@ ${cssVarsBlock}
|
|
|
2860
3235
|
return;
|
|
2861
3236
|
}
|
|
2862
3237
|
|
|
3238
|
+
finishPreviewRender(critiqueViewEl);
|
|
2863
3239
|
critiqueViewEl.innerHTML = "<div class='response-markdown-highlight'>" + highlightMarkdown(markdown) + "</div>";
|
|
2864
3240
|
return;
|
|
2865
3241
|
}
|
|
2866
3242
|
|
|
3243
|
+
finishPreviewRender(critiqueViewEl);
|
|
2867
3244
|
critiqueViewEl.innerHTML = buildPlainMarkdownHtml(markdown);
|
|
2868
3245
|
}
|
|
2869
3246
|
|
|
@@ -2936,6 +3313,7 @@ ${cssVarsBlock}
|
|
|
2936
3313
|
saveAsBtn.disabled = uiBusy;
|
|
2937
3314
|
saveOverBtn.disabled = uiBusy || !canSaveOver;
|
|
2938
3315
|
sendEditorBtn.disabled = uiBusy;
|
|
3316
|
+
if (getEditorBtn) getEditorBtn.disabled = uiBusy;
|
|
2939
3317
|
sendRunBtn.disabled = uiBusy;
|
|
2940
3318
|
copyDraftBtn.disabled = uiBusy;
|
|
2941
3319
|
if (highlightSelect) highlightSelect.disabled = uiBusy;
|
|
@@ -2981,6 +3359,10 @@ ${cssVarsBlock}
|
|
|
2981
3359
|
sourcePreviewRenderTimer = null;
|
|
2982
3360
|
}
|
|
2983
3361
|
|
|
3362
|
+
if (!showPreview) {
|
|
3363
|
+
finishPreviewRender(sourcePreviewEl);
|
|
3364
|
+
}
|
|
3365
|
+
|
|
2984
3366
|
if (showPreview) {
|
|
2985
3367
|
renderSourcePreview();
|
|
2986
3368
|
}
|
|
@@ -3665,14 +4047,30 @@ ${cssVarsBlock}
|
|
|
3665
4047
|
function handleServerMessage(message) {
|
|
3666
4048
|
if (!message || typeof message !== "object") return;
|
|
3667
4049
|
|
|
4050
|
+
debugTrace("server_message", summarizeServerMessage(message));
|
|
4051
|
+
|
|
4052
|
+
if (message.type === "debug_event") {
|
|
4053
|
+
debugTrace("server_debug_event", summarizeServerMessage(message));
|
|
4054
|
+
return;
|
|
4055
|
+
}
|
|
4056
|
+
|
|
3668
4057
|
if (message.type === "hello_ack") {
|
|
3669
4058
|
const busy = Boolean(message.busy);
|
|
4059
|
+
agentBusyFromServer = Boolean(message.agentBusy);
|
|
4060
|
+
updateTerminalActivityState(message.terminalPhase, message.terminalToolName, message.terminalActivityLabel);
|
|
3670
4061
|
setBusy(busy);
|
|
3671
4062
|
setWsState(busy ? "Submitting" : "Ready");
|
|
3672
|
-
if (message.activeRequestId) {
|
|
3673
|
-
pendingRequestId =
|
|
3674
|
-
|
|
3675
|
-
|
|
4063
|
+
if (typeof message.activeRequestId === "string" && message.activeRequestId.length > 0) {
|
|
4064
|
+
pendingRequestId = message.activeRequestId;
|
|
4065
|
+
if (typeof message.activeRequestKind === "string" && message.activeRequestKind.length > 0) {
|
|
4066
|
+
pendingKind = message.activeRequestKind;
|
|
4067
|
+
} else if (!pendingKind) {
|
|
4068
|
+
pendingKind = "unknown";
|
|
4069
|
+
}
|
|
4070
|
+
stickyStudioKind = pendingKind;
|
|
4071
|
+
} else {
|
|
4072
|
+
pendingRequestId = null;
|
|
4073
|
+
pendingKind = null;
|
|
3676
4074
|
}
|
|
3677
4075
|
|
|
3678
4076
|
let loadedInitialDocument = false;
|
|
@@ -3705,7 +4103,26 @@ ${cssVarsBlock}
|
|
|
3705
4103
|
handleIncomingResponse(lastMarkdown, lastResponseKind, message.lastResponse.timestamp);
|
|
3706
4104
|
}
|
|
3707
4105
|
|
|
3708
|
-
if (
|
|
4106
|
+
if (pendingRequestId) {
|
|
4107
|
+
if (busy) {
|
|
4108
|
+
setStatus(getStudioBusyStatus(pendingKind), "warning");
|
|
4109
|
+
}
|
|
4110
|
+
return;
|
|
4111
|
+
}
|
|
4112
|
+
|
|
4113
|
+
if (busy) {
|
|
4114
|
+
if (agentBusyFromServer && stickyStudioKind) {
|
|
4115
|
+
setStatus(getStudioBusyStatus(stickyStudioKind), "warning");
|
|
4116
|
+
} else if (agentBusyFromServer) {
|
|
4117
|
+
setStatus(getTerminalBusyStatus(), "warning");
|
|
4118
|
+
} else {
|
|
4119
|
+
setStatus("Studio is busy.", "warning");
|
|
4120
|
+
}
|
|
4121
|
+
return;
|
|
4122
|
+
}
|
|
4123
|
+
|
|
4124
|
+
stickyStudioKind = null;
|
|
4125
|
+
if (!loadedInitialDocument) {
|
|
3709
4126
|
refreshResponseUi();
|
|
3710
4127
|
setStatus(getIdleStatus());
|
|
3711
4128
|
}
|
|
@@ -3715,17 +4132,10 @@ ${cssVarsBlock}
|
|
|
3715
4132
|
if (message.type === "request_started") {
|
|
3716
4133
|
pendingRequestId = typeof message.requestId === "string" ? message.requestId : pendingRequestId;
|
|
3717
4134
|
pendingKind = typeof message.kind === "string" ? message.kind : "unknown";
|
|
4135
|
+
stickyStudioKind = pendingKind;
|
|
3718
4136
|
setBusy(true);
|
|
3719
4137
|
setWsState("Submitting");
|
|
3720
|
-
|
|
3721
|
-
setStatus("Sending annotated reply…", "warning");
|
|
3722
|
-
} else if (pendingKind === "critique") {
|
|
3723
|
-
setStatus("Running critique…", "warning");
|
|
3724
|
-
} else if (pendingKind === "direct") {
|
|
3725
|
-
setStatus("Running editor text…", "warning");
|
|
3726
|
-
} else {
|
|
3727
|
-
setStatus("Submitting…", "warning");
|
|
3728
|
-
}
|
|
4138
|
+
setStatus(getStudioBusyStatus(pendingKind), "warning");
|
|
3729
4139
|
return;
|
|
3730
4140
|
}
|
|
3731
4141
|
|
|
@@ -3739,6 +4149,7 @@ ${cssVarsBlock}
|
|
|
3739
4149
|
? message.kind
|
|
3740
4150
|
: (pendingKind === "critique" ? "critique" : "annotation");
|
|
3741
4151
|
|
|
4152
|
+
stickyStudioKind = responseKind;
|
|
3742
4153
|
pendingRequestId = null;
|
|
3743
4154
|
pendingKind = null;
|
|
3744
4155
|
setBusy(false);
|
|
@@ -3810,13 +4221,71 @@ ${cssVarsBlock}
|
|
|
3810
4221
|
return;
|
|
3811
4222
|
}
|
|
3812
4223
|
|
|
4224
|
+
if (message.type === "editor_snapshot") {
|
|
4225
|
+
if (typeof message.requestId === "string" && pendingRequestId && message.requestId !== pendingRequestId) {
|
|
4226
|
+
return;
|
|
4227
|
+
}
|
|
4228
|
+
if (typeof message.requestId === "string" && pendingRequestId === message.requestId) {
|
|
4229
|
+
pendingRequestId = null;
|
|
4230
|
+
pendingKind = null;
|
|
4231
|
+
}
|
|
4232
|
+
|
|
4233
|
+
const content = typeof message.content === "string" ? message.content : "";
|
|
4234
|
+
sourceTextEl.value = content;
|
|
4235
|
+
renderSourcePreview();
|
|
4236
|
+
setSourceState({ source: "pi-editor", label: "pi editor draft", path: null });
|
|
4237
|
+
setBusy(false);
|
|
4238
|
+
setWsState("Ready");
|
|
4239
|
+
setStatus(
|
|
4240
|
+
content.trim()
|
|
4241
|
+
? "Loaded draft from pi editor."
|
|
4242
|
+
: "pi editor is empty. Loaded blank text.",
|
|
4243
|
+
content.trim() ? "success" : "warning",
|
|
4244
|
+
);
|
|
4245
|
+
return;
|
|
4246
|
+
}
|
|
4247
|
+
|
|
3813
4248
|
if (message.type === "studio_state") {
|
|
3814
4249
|
const busy = Boolean(message.busy);
|
|
4250
|
+
agentBusyFromServer = Boolean(message.agentBusy);
|
|
4251
|
+
updateTerminalActivityState(message.terminalPhase, message.terminalToolName, message.terminalActivityLabel);
|
|
4252
|
+
|
|
4253
|
+
if (typeof message.activeRequestId === "string" && message.activeRequestId.length > 0) {
|
|
4254
|
+
pendingRequestId = message.activeRequestId;
|
|
4255
|
+
if (typeof message.activeRequestKind === "string" && message.activeRequestKind.length > 0) {
|
|
4256
|
+
pendingKind = message.activeRequestKind;
|
|
4257
|
+
} else if (!pendingKind) {
|
|
4258
|
+
pendingKind = "unknown";
|
|
4259
|
+
}
|
|
4260
|
+
stickyStudioKind = pendingKind;
|
|
4261
|
+
} else {
|
|
4262
|
+
pendingRequestId = null;
|
|
4263
|
+
pendingKind = null;
|
|
4264
|
+
}
|
|
4265
|
+
|
|
3815
4266
|
setBusy(busy);
|
|
3816
4267
|
setWsState(busy ? "Submitting" : "Ready");
|
|
3817
|
-
|
|
3818
|
-
|
|
4268
|
+
|
|
4269
|
+
if (pendingRequestId) {
|
|
4270
|
+
if (busy) {
|
|
4271
|
+
setStatus(getStudioBusyStatus(pendingKind), "warning");
|
|
4272
|
+
}
|
|
4273
|
+
return;
|
|
3819
4274
|
}
|
|
4275
|
+
|
|
4276
|
+
if (busy) {
|
|
4277
|
+
if (agentBusyFromServer && stickyStudioKind) {
|
|
4278
|
+
setStatus(getStudioBusyStatus(stickyStudioKind), "warning");
|
|
4279
|
+
} else if (agentBusyFromServer) {
|
|
4280
|
+
setStatus(getTerminalBusyStatus(), "warning");
|
|
4281
|
+
} else {
|
|
4282
|
+
setStatus("Studio is busy.", "warning");
|
|
4283
|
+
}
|
|
4284
|
+
return;
|
|
4285
|
+
}
|
|
4286
|
+
|
|
4287
|
+
stickyStudioKind = null;
|
|
4288
|
+
setStatus(getIdleStatus());
|
|
3820
4289
|
return;
|
|
3821
4290
|
}
|
|
3822
4291
|
|
|
@@ -3825,6 +4294,7 @@ ${cssVarsBlock}
|
|
|
3825
4294
|
pendingRequestId = null;
|
|
3826
4295
|
pendingKind = null;
|
|
3827
4296
|
}
|
|
4297
|
+
stickyStudioKind = null;
|
|
3828
4298
|
setBusy(false);
|
|
3829
4299
|
setWsState("Ready");
|
|
3830
4300
|
setStatus(typeof message.message === "string" ? message.message : "Studio is busy.", "warning");
|
|
@@ -3836,6 +4306,7 @@ ${cssVarsBlock}
|
|
|
3836
4306
|
pendingRequestId = null;
|
|
3837
4307
|
pendingKind = null;
|
|
3838
4308
|
}
|
|
4309
|
+
stickyStudioKind = null;
|
|
3839
4310
|
setBusy(false);
|
|
3840
4311
|
setWsState("Ready");
|
|
3841
4312
|
setStatus(typeof message.message === "string" ? message.message : "Request failed.", "error");
|
|
@@ -3870,7 +4341,7 @@ ${cssVarsBlock}
|
|
|
3870
4341
|
}
|
|
3871
4342
|
|
|
3872
4343
|
const wsProtocol = window.location.protocol === "https:" ? "wss" : "ws";
|
|
3873
|
-
const wsUrl = wsProtocol + "://" + window.location.host + "/ws?token=" + encodeURIComponent(token);
|
|
4344
|
+
const wsUrl = wsProtocol + "://" + window.location.host + "/ws?token=" + encodeURIComponent(token) + (DEBUG_ENABLED ? "&debug=1" : "");
|
|
3874
4345
|
|
|
3875
4346
|
setWsState("Connecting");
|
|
3876
4347
|
setStatus("Connecting to Studio server…");
|
|
@@ -3927,8 +4398,10 @@ ${cssVarsBlock}
|
|
|
3927
4398
|
const requestId = makeRequestId();
|
|
3928
4399
|
pendingRequestId = requestId;
|
|
3929
4400
|
pendingKind = kind;
|
|
4401
|
+
stickyStudioKind = kind;
|
|
3930
4402
|
setBusy(true);
|
|
3931
4403
|
setWsState("Submitting");
|
|
4404
|
+
setStatus(getStudioBusyStatus(kind), "warning");
|
|
3932
4405
|
return requestId;
|
|
3933
4406
|
}
|
|
3934
4407
|
|
|
@@ -4233,6 +4706,24 @@ ${cssVarsBlock}
|
|
|
4233
4706
|
}
|
|
4234
4707
|
});
|
|
4235
4708
|
|
|
4709
|
+
if (getEditorBtn) {
|
|
4710
|
+
getEditorBtn.addEventListener("click", () => {
|
|
4711
|
+
const requestId = beginUiAction("get_from_editor");
|
|
4712
|
+
if (!requestId) return;
|
|
4713
|
+
|
|
4714
|
+
const sent = sendMessage({
|
|
4715
|
+
type: "get_from_editor_request",
|
|
4716
|
+
requestId,
|
|
4717
|
+
});
|
|
4718
|
+
|
|
4719
|
+
if (!sent) {
|
|
4720
|
+
pendingRequestId = null;
|
|
4721
|
+
pendingKind = null;
|
|
4722
|
+
setBusy(false);
|
|
4723
|
+
}
|
|
4724
|
+
});
|
|
4725
|
+
}
|
|
4726
|
+
|
|
4236
4727
|
sendRunBtn.addEventListener("click", () => {
|
|
4237
4728
|
const content = sourceTextEl.value;
|
|
4238
4729
|
if (!content.trim()) {
|
|
@@ -4397,6 +4888,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
4397
4888
|
let lastCommandCtx: ExtensionCommandContext | null = null;
|
|
4398
4889
|
let lastThemeVarsJson = "";
|
|
4399
4890
|
let agentBusy = false;
|
|
4891
|
+
let terminalActivityPhase: TerminalActivityPhase = "idle";
|
|
4892
|
+
let terminalActivityToolName: string | null = null;
|
|
4893
|
+
let terminalActivityLabel: string | null = null;
|
|
4894
|
+
let lastSpecificToolActivityLabel: string | null = null;
|
|
4400
4895
|
|
|
4401
4896
|
const isStudioBusy = () => agentBusy || activeRequest !== null;
|
|
4402
4897
|
|
|
@@ -4427,18 +4922,94 @@ export default function (pi: ExtensionAPI) {
|
|
|
4427
4922
|
}
|
|
4428
4923
|
};
|
|
4429
4924
|
|
|
4925
|
+
const emitDebugEvent = (event: string, details?: Record<string, unknown>) => {
|
|
4926
|
+
broadcast({
|
|
4927
|
+
type: "debug_event",
|
|
4928
|
+
event,
|
|
4929
|
+
timestamp: Date.now(),
|
|
4930
|
+
details: details ?? null,
|
|
4931
|
+
});
|
|
4932
|
+
};
|
|
4933
|
+
|
|
4934
|
+
const setTerminalActivity = (phase: TerminalActivityPhase, toolName?: string | null, label?: string | null) => {
|
|
4935
|
+
const nextPhase: TerminalActivityPhase =
|
|
4936
|
+
phase === "running" || phase === "tool" || phase === "responding"
|
|
4937
|
+
? phase
|
|
4938
|
+
: "idle";
|
|
4939
|
+
const nextToolName = nextPhase === "tool" ? (toolName?.trim() || null) : null;
|
|
4940
|
+
const baseLabel = nextPhase === "tool" ? normalizeActivityLabel(label || "") : null;
|
|
4941
|
+
let nextLabel: string | null = null;
|
|
4942
|
+
|
|
4943
|
+
if (nextPhase === "tool") {
|
|
4944
|
+
if (baseLabel && !isGenericToolActivityLabel(baseLabel)) {
|
|
4945
|
+
if (
|
|
4946
|
+
lastSpecificToolActivityLabel
|
|
4947
|
+
&& lastSpecificToolActivityLabel !== baseLabel
|
|
4948
|
+
&& !isGenericToolActivityLabel(lastSpecificToolActivityLabel)
|
|
4949
|
+
) {
|
|
4950
|
+
nextLabel = normalizeActivityLabel(`${lastSpecificToolActivityLabel} → ${baseLabel}`);
|
|
4951
|
+
} else {
|
|
4952
|
+
nextLabel = baseLabel;
|
|
4953
|
+
}
|
|
4954
|
+
lastSpecificToolActivityLabel = baseLabel;
|
|
4955
|
+
} else {
|
|
4956
|
+
nextLabel = baseLabel;
|
|
4957
|
+
}
|
|
4958
|
+
} else {
|
|
4959
|
+
nextLabel = null;
|
|
4960
|
+
if (nextPhase === "idle") {
|
|
4961
|
+
lastSpecificToolActivityLabel = null;
|
|
4962
|
+
}
|
|
4963
|
+
}
|
|
4964
|
+
|
|
4965
|
+
if (
|
|
4966
|
+
terminalActivityPhase === nextPhase
|
|
4967
|
+
&& terminalActivityToolName === nextToolName
|
|
4968
|
+
&& terminalActivityLabel === nextLabel
|
|
4969
|
+
) {
|
|
4970
|
+
return;
|
|
4971
|
+
}
|
|
4972
|
+
terminalActivityPhase = nextPhase;
|
|
4973
|
+
terminalActivityToolName = nextToolName;
|
|
4974
|
+
terminalActivityLabel = nextLabel;
|
|
4975
|
+
emitDebugEvent("terminal_activity", {
|
|
4976
|
+
phase: terminalActivityPhase,
|
|
4977
|
+
toolName: terminalActivityToolName,
|
|
4978
|
+
label: terminalActivityLabel,
|
|
4979
|
+
baseLabel,
|
|
4980
|
+
lastSpecificToolActivityLabel,
|
|
4981
|
+
activeRequestId: activeRequest?.id ?? null,
|
|
4982
|
+
activeRequestKind: activeRequest?.kind ?? null,
|
|
4983
|
+
agentBusy,
|
|
4984
|
+
});
|
|
4985
|
+
broadcastState();
|
|
4986
|
+
};
|
|
4987
|
+
|
|
4430
4988
|
const broadcastState = () => {
|
|
4431
4989
|
broadcast({
|
|
4432
4990
|
type: "studio_state",
|
|
4433
4991
|
busy: isStudioBusy(),
|
|
4992
|
+
agentBusy,
|
|
4993
|
+
terminalPhase: terminalActivityPhase,
|
|
4994
|
+
terminalToolName: terminalActivityToolName,
|
|
4995
|
+
terminalActivityLabel,
|
|
4434
4996
|
activeRequestId: activeRequest?.id ?? null,
|
|
4997
|
+
activeRequestKind: activeRequest?.kind ?? null,
|
|
4435
4998
|
});
|
|
4436
4999
|
};
|
|
4437
5000
|
|
|
4438
5001
|
const clearActiveRequest = (options?: { notify?: string; level?: "info" | "warning" | "error" }) => {
|
|
4439
5002
|
if (!activeRequest) return;
|
|
5003
|
+
const completedRequestId = activeRequest.id;
|
|
5004
|
+
const completedKind = activeRequest.kind;
|
|
4440
5005
|
clearTimeout(activeRequest.timer);
|
|
4441
5006
|
activeRequest = null;
|
|
5007
|
+
emitDebugEvent("clear_active_request", {
|
|
5008
|
+
requestId: completedRequestId,
|
|
5009
|
+
kind: completedKind,
|
|
5010
|
+
notify: options?.notify ?? null,
|
|
5011
|
+
agentBusy,
|
|
5012
|
+
});
|
|
4442
5013
|
broadcastState();
|
|
4443
5014
|
if (options?.notify) {
|
|
4444
5015
|
broadcast({ type: "info", message: options.notify, level: options.level ?? "info" });
|
|
@@ -4446,6 +5017,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
4446
5017
|
};
|
|
4447
5018
|
|
|
4448
5019
|
const beginRequest = (requestId: string, kind: StudioRequestKind): boolean => {
|
|
5020
|
+
emitDebugEvent("begin_request_attempt", {
|
|
5021
|
+
requestId,
|
|
5022
|
+
kind,
|
|
5023
|
+
hasActiveRequest: Boolean(activeRequest),
|
|
5024
|
+
agentBusy,
|
|
5025
|
+
});
|
|
4449
5026
|
if (activeRequest) {
|
|
4450
5027
|
broadcast({ type: "busy", requestId, message: "A studio request is already in progress." });
|
|
4451
5028
|
return false;
|
|
@@ -4457,6 +5034,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
4457
5034
|
|
|
4458
5035
|
const timer = setTimeout(() => {
|
|
4459
5036
|
if (!activeRequest || activeRequest.id !== requestId) return;
|
|
5037
|
+
emitDebugEvent("request_timeout", { requestId, kind });
|
|
4460
5038
|
broadcast({ type: "error", requestId, message: "Studio request timed out. Please try again." });
|
|
4461
5039
|
clearActiveRequest();
|
|
4462
5040
|
}, REQUEST_TIMEOUT_MS);
|
|
@@ -4468,6 +5046,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
4468
5046
|
timer,
|
|
4469
5047
|
};
|
|
4470
5048
|
|
|
5049
|
+
emitDebugEvent("begin_request", { requestId, kind });
|
|
4471
5050
|
broadcast({ type: "request_started", requestId, kind });
|
|
4472
5051
|
broadcastState();
|
|
4473
5052
|
return true;
|
|
@@ -4491,11 +5070,24 @@ export default function (pi: ExtensionAPI) {
|
|
|
4491
5070
|
return;
|
|
4492
5071
|
}
|
|
4493
5072
|
|
|
5073
|
+
emitDebugEvent("studio_message", {
|
|
5074
|
+
type: msg.type,
|
|
5075
|
+
requestId: "requestId" in msg ? msg.requestId : null,
|
|
5076
|
+
activeRequestId: activeRequest?.id ?? null,
|
|
5077
|
+
activeRequestKind: activeRequest?.kind ?? null,
|
|
5078
|
+
agentBusy,
|
|
5079
|
+
});
|
|
5080
|
+
|
|
4494
5081
|
if (msg.type === "hello") {
|
|
4495
5082
|
sendToClient(client, {
|
|
4496
5083
|
type: "hello_ack",
|
|
4497
5084
|
busy: isStudioBusy(),
|
|
5085
|
+
agentBusy,
|
|
5086
|
+
terminalPhase: terminalActivityPhase,
|
|
5087
|
+
terminalToolName: terminalActivityToolName,
|
|
5088
|
+
terminalActivityLabel,
|
|
4498
5089
|
activeRequestId: activeRequest?.id ?? null,
|
|
5090
|
+
activeRequestKind: activeRequest?.kind ?? null,
|
|
4499
5091
|
lastResponse: lastStudioResponse,
|
|
4500
5092
|
initialDocument: initialStudioDocument,
|
|
4501
5093
|
});
|
|
@@ -4727,6 +5319,41 @@ export default function (pi: ExtensionAPI) {
|
|
|
4727
5319
|
}
|
|
4728
5320
|
return;
|
|
4729
5321
|
}
|
|
5322
|
+
|
|
5323
|
+
if (msg.type === "get_from_editor_request") {
|
|
5324
|
+
if (!isValidRequestId(msg.requestId)) {
|
|
5325
|
+
sendToClient(client, { type: "error", requestId: msg.requestId, message: "Invalid request ID." });
|
|
5326
|
+
return;
|
|
5327
|
+
}
|
|
5328
|
+
if (isStudioBusy()) {
|
|
5329
|
+
sendToClient(client, { type: "busy", requestId: msg.requestId, message: "Studio is busy." });
|
|
5330
|
+
return;
|
|
5331
|
+
}
|
|
5332
|
+
if (!lastCommandCtx || !lastCommandCtx.hasUI) {
|
|
5333
|
+
sendToClient(client, {
|
|
5334
|
+
type: "error",
|
|
5335
|
+
requestId: msg.requestId,
|
|
5336
|
+
message: "No interactive pi editor context is available.",
|
|
5337
|
+
});
|
|
5338
|
+
return;
|
|
5339
|
+
}
|
|
5340
|
+
|
|
5341
|
+
try {
|
|
5342
|
+
const content = lastCommandCtx.ui.getEditorText();
|
|
5343
|
+
sendToClient(client, {
|
|
5344
|
+
type: "editor_snapshot",
|
|
5345
|
+
requestId: msg.requestId,
|
|
5346
|
+
content,
|
|
5347
|
+
});
|
|
5348
|
+
} catch (error) {
|
|
5349
|
+
sendToClient(client, {
|
|
5350
|
+
type: "error",
|
|
5351
|
+
requestId: msg.requestId,
|
|
5352
|
+
message: `Failed to read pi editor text: ${error instanceof Error ? error.message : String(error)}`,
|
|
5353
|
+
});
|
|
5354
|
+
}
|
|
5355
|
+
return;
|
|
5356
|
+
}
|
|
4730
5357
|
};
|
|
4731
5358
|
|
|
4732
5359
|
const handleRenderPreviewRequest = async (req: IncomingMessage, res: ServerResponse) => {
|
|
@@ -5004,21 +5631,81 @@ export default function (pi: ExtensionAPI) {
|
|
|
5004
5631
|
|
|
5005
5632
|
pi.on("session_start", async (_event, ctx) => {
|
|
5006
5633
|
hydrateLatestAssistant(ctx.sessionManager.getBranch());
|
|
5634
|
+
agentBusy = false;
|
|
5635
|
+
emitDebugEvent("session_start", { entryCount: ctx.sessionManager.getBranch().length });
|
|
5636
|
+
setTerminalActivity("idle");
|
|
5007
5637
|
});
|
|
5008
5638
|
|
|
5009
5639
|
pi.on("session_switch", async (_event, ctx) => {
|
|
5010
5640
|
clearActiveRequest({ notify: "Session switched. Studio request state cleared.", level: "warning" });
|
|
5011
5641
|
lastCommandCtx = null;
|
|
5012
5642
|
hydrateLatestAssistant(ctx.sessionManager.getBranch());
|
|
5643
|
+
agentBusy = false;
|
|
5644
|
+
emitDebugEvent("session_switch", { entryCount: ctx.sessionManager.getBranch().length });
|
|
5645
|
+
setTerminalActivity("idle");
|
|
5013
5646
|
});
|
|
5014
5647
|
|
|
5015
5648
|
pi.on("agent_start", async () => {
|
|
5016
5649
|
agentBusy = true;
|
|
5017
|
-
|
|
5650
|
+
emitDebugEvent("agent_start", { activeRequestId: activeRequest?.id ?? null, activeRequestKind: activeRequest?.kind ?? null });
|
|
5651
|
+
setTerminalActivity("running");
|
|
5652
|
+
});
|
|
5653
|
+
|
|
5654
|
+
pi.on("tool_call", async (event) => {
|
|
5655
|
+
if (!agentBusy) return;
|
|
5656
|
+
const toolName = typeof event.toolName === "string" ? event.toolName : "";
|
|
5657
|
+
const input = (event as { input?: unknown }).input;
|
|
5658
|
+
const label = deriveToolActivityLabel(toolName, input);
|
|
5659
|
+
emitDebugEvent("tool_call", { toolName, label, activeRequestId: activeRequest?.id ?? null, activeRequestKind: activeRequest?.kind ?? null });
|
|
5660
|
+
setTerminalActivity("tool", toolName, label);
|
|
5661
|
+
});
|
|
5662
|
+
|
|
5663
|
+
pi.on("tool_execution_start", async (event) => {
|
|
5664
|
+
if (!agentBusy) return;
|
|
5665
|
+
const label = deriveToolActivityLabel(event.toolName, event.args);
|
|
5666
|
+
emitDebugEvent("tool_execution_start", { toolName: event.toolName, label, activeRequestId: activeRequest?.id ?? null, activeRequestKind: activeRequest?.kind ?? null });
|
|
5667
|
+
setTerminalActivity("tool", event.toolName, label);
|
|
5668
|
+
});
|
|
5669
|
+
|
|
5670
|
+
pi.on("tool_execution_end", async (event) => {
|
|
5671
|
+
if (!agentBusy) return;
|
|
5672
|
+
emitDebugEvent("tool_execution_end", { toolName: event.toolName, activeRequestId: activeRequest?.id ?? null, activeRequestKind: activeRequest?.kind ?? null });
|
|
5673
|
+
// Keep tool phase visible until the next tool call, assistant response phase,
|
|
5674
|
+
// or agent_end. This avoids tool labels flashing too quickly to read.
|
|
5675
|
+
});
|
|
5676
|
+
|
|
5677
|
+
pi.on("message_start", async (event) => {
|
|
5678
|
+
const role = (event.message as { role?: string } | undefined)?.role;
|
|
5679
|
+
emitDebugEvent("message_start", { role: role ?? "", activeRequestId: activeRequest?.id ?? null, activeRequestKind: activeRequest?.kind ?? null });
|
|
5680
|
+
if (agentBusy && role === "assistant") {
|
|
5681
|
+
setTerminalActivity("responding");
|
|
5682
|
+
}
|
|
5018
5683
|
});
|
|
5019
5684
|
|
|
5020
5685
|
pi.on("message_end", async (event) => {
|
|
5686
|
+
const message = event.message as { stopReason?: string; role?: string };
|
|
5687
|
+
const stopReason = typeof message.stopReason === "string" ? message.stopReason : "";
|
|
5688
|
+
const role = typeof message.role === "string" ? message.role : "";
|
|
5021
5689
|
const markdown = extractAssistantText(event.message);
|
|
5690
|
+
emitDebugEvent("message_end", {
|
|
5691
|
+
role,
|
|
5692
|
+
stopReason,
|
|
5693
|
+
hasMarkdown: Boolean(markdown),
|
|
5694
|
+
markdownLength: markdown ? markdown.length : 0,
|
|
5695
|
+
activeRequestId: activeRequest?.id ?? null,
|
|
5696
|
+
activeRequestKind: activeRequest?.kind ?? null,
|
|
5697
|
+
});
|
|
5698
|
+
|
|
5699
|
+
// Assistant is handing off to tool calls; request is still in progress.
|
|
5700
|
+
if (stopReason === "toolUse") {
|
|
5701
|
+
emitDebugEvent("message_end_tool_use", {
|
|
5702
|
+
role,
|
|
5703
|
+
activeRequestId: activeRequest?.id ?? null,
|
|
5704
|
+
activeRequestKind: activeRequest?.kind ?? null,
|
|
5705
|
+
});
|
|
5706
|
+
return;
|
|
5707
|
+
}
|
|
5708
|
+
|
|
5022
5709
|
if (!markdown) return;
|
|
5023
5710
|
|
|
5024
5711
|
if (activeRequest) {
|
|
@@ -5029,6 +5716,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
5029
5716
|
timestamp: Date.now(),
|
|
5030
5717
|
kind,
|
|
5031
5718
|
};
|
|
5719
|
+
emitDebugEvent("broadcast_response", {
|
|
5720
|
+
requestId,
|
|
5721
|
+
kind,
|
|
5722
|
+
markdownLength: markdown.length,
|
|
5723
|
+
stopReason,
|
|
5724
|
+
});
|
|
5032
5725
|
broadcast({
|
|
5033
5726
|
type: "response",
|
|
5034
5727
|
requestId,
|
|
@@ -5046,6 +5739,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
5046
5739
|
timestamp: Date.now(),
|
|
5047
5740
|
kind: inferredKind,
|
|
5048
5741
|
};
|
|
5742
|
+
emitDebugEvent("broadcast_latest_response", {
|
|
5743
|
+
kind: inferredKind,
|
|
5744
|
+
markdownLength: markdown.length,
|
|
5745
|
+
stopReason,
|
|
5746
|
+
});
|
|
5049
5747
|
broadcast({
|
|
5050
5748
|
type: "latest_response",
|
|
5051
5749
|
kind: inferredKind,
|
|
@@ -5056,7 +5754,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
5056
5754
|
|
|
5057
5755
|
pi.on("agent_end", async () => {
|
|
5058
5756
|
agentBusy = false;
|
|
5059
|
-
|
|
5757
|
+
emitDebugEvent("agent_end", { activeRequestId: activeRequest?.id ?? null, activeRequestKind: activeRequest?.kind ?? null });
|
|
5758
|
+
setTerminalActivity("idle");
|
|
5060
5759
|
if (activeRequest) {
|
|
5061
5760
|
const requestId = activeRequest.id;
|
|
5062
5761
|
broadcast({
|
|
@@ -5070,6 +5769,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
5070
5769
|
|
|
5071
5770
|
pi.on("session_shutdown", async () => {
|
|
5072
5771
|
lastCommandCtx = null;
|
|
5772
|
+
agentBusy = false;
|
|
5773
|
+
setTerminalActivity("idle");
|
|
5073
5774
|
await stopServer();
|
|
5074
5775
|
});
|
|
5075
5776
|
|