pi-studio 0.5.10 → 0.5.12
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 +10 -0
- package/index.ts +410 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,16 @@ All notable changes to `pi-studio` are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.5.12] — 2026-03-15
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Studio now has a `Load git diff` button that loads the current git changes (staged + unstaged tracked changes plus untracked text files) into the editor from the current Studio context and sets the editor language to `diff`.
|
|
11
|
+
|
|
12
|
+
## [0.5.11] — 2026-03-15
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- Studio tabs now show a title attention marker like `● Response ready` or `● Critique ready` when a Studio-started model request finishes while the tab is unfocused, and clear that marker when the tab regains focus or the next Studio request starts.
|
|
16
|
+
|
|
7
17
|
## [0.5.10] — 2026-03-14
|
|
8
18
|
|
|
9
19
|
### Fixed
|
package/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionCommandContext, SessionEntry, Theme } from "@mariozechner/pi-coding-agent";
|
|
2
|
-
import { spawn } from "node:child_process";
|
|
2
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { readFileSync, statSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
@@ -120,6 +120,13 @@ interface GetFromEditorRequestMessage {
|
|
|
120
120
|
requestId: string;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
interface LoadGitDiffRequestMessage {
|
|
124
|
+
type: "load_git_diff_request";
|
|
125
|
+
requestId: string;
|
|
126
|
+
sourcePath?: string;
|
|
127
|
+
resourceDir?: string;
|
|
128
|
+
}
|
|
129
|
+
|
|
123
130
|
interface CancelRequestMessage {
|
|
124
131
|
type: "cancel_request";
|
|
125
132
|
requestId: string;
|
|
@@ -137,6 +144,7 @@ type IncomingStudioMessage =
|
|
|
137
144
|
| SaveOverRequestMessage
|
|
138
145
|
| SendToEditorRequestMessage
|
|
139
146
|
| GetFromEditorRequestMessage
|
|
147
|
+
| LoadGitDiffRequestMessage
|
|
140
148
|
| CancelRequestMessage;
|
|
141
149
|
|
|
142
150
|
const REQUEST_TIMEOUT_MS = 5 * 60 * 1000;
|
|
@@ -904,6 +912,207 @@ function writeStudioFile(pathArg: string, cwd: string, content: string):
|
|
|
904
912
|
}
|
|
905
913
|
}
|
|
906
914
|
|
|
915
|
+
function splitStudioGitPathOutput(output: string): string[] {
|
|
916
|
+
return output
|
|
917
|
+
.split(/\r?\n/)
|
|
918
|
+
.map((line) => line.trim())
|
|
919
|
+
.filter((line) => line.length > 0);
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
function formatStudioGitSpawnFailure(
|
|
923
|
+
result: { stdout?: string | Buffer | null; stderr?: string | Buffer | null },
|
|
924
|
+
args: string[],
|
|
925
|
+
): string {
|
|
926
|
+
const stderr = typeof result.stderr === "string"
|
|
927
|
+
? result.stderr.trim()
|
|
928
|
+
: (result.stderr ? result.stderr.toString("utf-8").trim() : "");
|
|
929
|
+
const stdout = typeof result.stdout === "string"
|
|
930
|
+
? result.stdout.trim()
|
|
931
|
+
: (result.stdout ? result.stdout.toString("utf-8").trim() : "");
|
|
932
|
+
return stderr || stdout || `git ${args.join(" ")} failed`;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
function readStudioTextFileIfPossible(path: string): string | null {
|
|
936
|
+
try {
|
|
937
|
+
const buf = readFileSync(path);
|
|
938
|
+
const sample = buf.subarray(0, 8192);
|
|
939
|
+
let nulCount = 0;
|
|
940
|
+
let controlCount = 0;
|
|
941
|
+
for (let i = 0; i < sample.length; i++) {
|
|
942
|
+
const b = sample[i];
|
|
943
|
+
if (b === 0x00) nulCount += 1;
|
|
944
|
+
else if (b < 0x08 || (b > 0x0D && b < 0x20 && b !== 0x1B)) controlCount += 1;
|
|
945
|
+
}
|
|
946
|
+
if (nulCount > 0 || (sample.length > 0 && controlCount / sample.length > 0.1)) {
|
|
947
|
+
return null;
|
|
948
|
+
}
|
|
949
|
+
return buf.toString("utf-8").replace(/\r\n/g, "\n");
|
|
950
|
+
} catch {
|
|
951
|
+
return null;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
function buildStudioSyntheticNewFileDiff(filePath: string, content: string): string {
|
|
956
|
+
const lines = content.split("\n");
|
|
957
|
+
if (lines.length > 0 && lines[lines.length - 1] === "") {
|
|
958
|
+
lines.pop();
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
const diffLines = [
|
|
962
|
+
`diff --git a/${filePath} b/${filePath}`,
|
|
963
|
+
"new file mode 100644",
|
|
964
|
+
"--- /dev/null",
|
|
965
|
+
`+++ b/${filePath}`,
|
|
966
|
+
`@@ -0,0 +1,${lines.length} @@`,
|
|
967
|
+
];
|
|
968
|
+
|
|
969
|
+
if (lines.length > 0) {
|
|
970
|
+
diffLines.push(lines.map((line) => `+${line}`).join("\n"));
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
return diffLines.join("\n");
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
function resolveStudioGitDiffBaseDir(sourcePath: string | undefined, resourceDir: string | undefined, fallbackCwd: string): string {
|
|
977
|
+
const source = typeof sourcePath === "string" ? sourcePath.trim() : "";
|
|
978
|
+
if (source) {
|
|
979
|
+
return dirname(source);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
const resource = typeof resourceDir === "string" ? resourceDir.trim() : "";
|
|
983
|
+
if (resource) {
|
|
984
|
+
return isAbsolute(resource) ? resource : resolve(fallbackCwd, resource);
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
return fallbackCwd;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
function readStudioGitDiff(baseDir: string):
|
|
991
|
+
| { ok: true; text: string; label: string }
|
|
992
|
+
| { ok: false; level: "info" | "warning" | "error"; message: string } {
|
|
993
|
+
const repoRootArgs = ["rev-parse", "--show-toplevel"];
|
|
994
|
+
const repoRootResult = spawnSync("git", repoRootArgs, {
|
|
995
|
+
cwd: baseDir,
|
|
996
|
+
encoding: "utf-8",
|
|
997
|
+
});
|
|
998
|
+
if (repoRootResult.status !== 0) {
|
|
999
|
+
return {
|
|
1000
|
+
ok: false,
|
|
1001
|
+
level: "warning",
|
|
1002
|
+
message: "No git repository found for the current Studio context.",
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
const repoRoot = repoRootResult.stdout.trim();
|
|
1006
|
+
|
|
1007
|
+
const hasHead = spawnSync("git", ["rev-parse", "--verify", "HEAD"], {
|
|
1008
|
+
cwd: repoRoot,
|
|
1009
|
+
encoding: "utf-8",
|
|
1010
|
+
}).status === 0;
|
|
1011
|
+
|
|
1012
|
+
const untrackedArgs = ["ls-files", "--others", "--exclude-standard"];
|
|
1013
|
+
const untrackedResult = spawnSync("git", untrackedArgs, {
|
|
1014
|
+
cwd: repoRoot,
|
|
1015
|
+
encoding: "utf-8",
|
|
1016
|
+
});
|
|
1017
|
+
if (untrackedResult.status !== 0) {
|
|
1018
|
+
return {
|
|
1019
|
+
ok: false,
|
|
1020
|
+
level: "error",
|
|
1021
|
+
message: `Failed to list untracked files: ${formatStudioGitSpawnFailure(untrackedResult, untrackedArgs)}`,
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
const untrackedPaths = splitStudioGitPathOutput(untrackedResult.stdout ?? "").sort();
|
|
1025
|
+
|
|
1026
|
+
let diffOutput = "";
|
|
1027
|
+
let statSummary = "";
|
|
1028
|
+
let currentTreeFileCount = 0;
|
|
1029
|
+
|
|
1030
|
+
if (hasHead) {
|
|
1031
|
+
const diffArgs = ["diff", "HEAD", "--unified=3", "--find-renames", "--no-color", "--"];
|
|
1032
|
+
const diffResult = spawnSync("git", diffArgs, {
|
|
1033
|
+
cwd: repoRoot,
|
|
1034
|
+
encoding: "utf-8",
|
|
1035
|
+
});
|
|
1036
|
+
if (diffResult.status !== 0) {
|
|
1037
|
+
return {
|
|
1038
|
+
ok: false,
|
|
1039
|
+
level: "error",
|
|
1040
|
+
message: `Failed to collect git diff: ${formatStudioGitSpawnFailure(diffResult, diffArgs)}`,
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
diffOutput = diffResult.stdout ?? "";
|
|
1044
|
+
|
|
1045
|
+
const statArgs = ["diff", "HEAD", "--stat", "--find-renames", "--no-color", "--"];
|
|
1046
|
+
const statResult = spawnSync("git", statArgs, {
|
|
1047
|
+
cwd: repoRoot,
|
|
1048
|
+
encoding: "utf-8",
|
|
1049
|
+
});
|
|
1050
|
+
if (statResult.status === 0) {
|
|
1051
|
+
const statLines = splitStudioGitPathOutput(statResult.stdout ?? "");
|
|
1052
|
+
statSummary = statLines.length > 0 ? (statLines[statLines.length - 1] ?? "") : "";
|
|
1053
|
+
}
|
|
1054
|
+
} else {
|
|
1055
|
+
const trackedArgs = ["ls-files", "--cached"];
|
|
1056
|
+
const trackedResult = spawnSync("git", trackedArgs, {
|
|
1057
|
+
cwd: repoRoot,
|
|
1058
|
+
encoding: "utf-8",
|
|
1059
|
+
});
|
|
1060
|
+
if (trackedResult.status !== 0) {
|
|
1061
|
+
return {
|
|
1062
|
+
ok: false,
|
|
1063
|
+
level: "error",
|
|
1064
|
+
message: `Failed to inspect tracked files: ${formatStudioGitSpawnFailure(trackedResult, trackedArgs)}`,
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
const trackedPaths = splitStudioGitPathOutput(trackedResult.stdout ?? "");
|
|
1069
|
+
const currentTreePaths = Array.from(new Set([...trackedPaths, ...untrackedPaths])).sort();
|
|
1070
|
+
currentTreeFileCount = currentTreePaths.length;
|
|
1071
|
+
diffOutput = currentTreePaths
|
|
1072
|
+
.map((filePath) => {
|
|
1073
|
+
const content = readStudioTextFileIfPossible(join(repoRoot, filePath));
|
|
1074
|
+
if (content == null) return "";
|
|
1075
|
+
return buildStudioSyntheticNewFileDiff(filePath, content);
|
|
1076
|
+
})
|
|
1077
|
+
.filter((section) => section.length > 0)
|
|
1078
|
+
.join("\n\n");
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
const untrackedSections = hasHead
|
|
1082
|
+
? untrackedPaths
|
|
1083
|
+
.map((filePath) => {
|
|
1084
|
+
const content = readStudioTextFileIfPossible(join(repoRoot, filePath));
|
|
1085
|
+
if (content == null) return "";
|
|
1086
|
+
return buildStudioSyntheticNewFileDiff(filePath, content);
|
|
1087
|
+
})
|
|
1088
|
+
.filter((section) => section.length > 0)
|
|
1089
|
+
: [];
|
|
1090
|
+
|
|
1091
|
+
const fullDiff = [diffOutput.trimEnd(), ...untrackedSections].filter(Boolean).join("\n\n");
|
|
1092
|
+
if (!fullDiff.trim()) {
|
|
1093
|
+
return {
|
|
1094
|
+
ok: false,
|
|
1095
|
+
level: "info",
|
|
1096
|
+
message: "No uncommitted git changes to load.",
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
const summaryParts: string[] = [];
|
|
1101
|
+
if (hasHead && statSummary) {
|
|
1102
|
+
summaryParts.push(statSummary);
|
|
1103
|
+
}
|
|
1104
|
+
if (!hasHead && currentTreeFileCount > 0) {
|
|
1105
|
+
summaryParts.push(`${currentTreeFileCount} file${currentTreeFileCount === 1 ? "" : "s"} in current tree`);
|
|
1106
|
+
}
|
|
1107
|
+
if (untrackedPaths.length > 0) {
|
|
1108
|
+
summaryParts.push(`${untrackedPaths.length} untracked file${untrackedPaths.length === 1 ? "" : "s"}`);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
const labelBase = hasHead ? "git diff HEAD" : "git diff (no commits yet)";
|
|
1112
|
+
const label = summaryParts.length > 0 ? `${labelBase} (${summaryParts.join(", ")})` : labelBase;
|
|
1113
|
+
return { ok: true, text: fullDiff, label };
|
|
1114
|
+
}
|
|
1115
|
+
|
|
907
1116
|
function readLocalPackageMetadata(): { name: string; version: string } | null {
|
|
908
1117
|
try {
|
|
909
1118
|
const raw = readFileSync(new URL("./package.json", import.meta.url), "utf-8");
|
|
@@ -1904,6 +2113,20 @@ function parseIncomingMessage(data: RawData): IncomingStudioMessage | null {
|
|
|
1904
2113
|
};
|
|
1905
2114
|
}
|
|
1906
2115
|
|
|
2116
|
+
if (
|
|
2117
|
+
msg.type === "load_git_diff_request"
|
|
2118
|
+
&& typeof msg.requestId === "string"
|
|
2119
|
+
&& (msg.sourcePath === undefined || typeof msg.sourcePath === "string")
|
|
2120
|
+
&& (msg.resourceDir === undefined || typeof msg.resourceDir === "string")
|
|
2121
|
+
) {
|
|
2122
|
+
return {
|
|
2123
|
+
type: "load_git_diff_request",
|
|
2124
|
+
requestId: msg.requestId,
|
|
2125
|
+
sourcePath: typeof msg.sourcePath === "string" ? msg.sourcePath : undefined,
|
|
2126
|
+
resourceDir: typeof msg.resourceDir === "string" ? msg.resourceDir : undefined,
|
|
2127
|
+
};
|
|
2128
|
+
}
|
|
2129
|
+
|
|
1907
2130
|
if (msg.type === "cancel_request" && typeof msg.requestId === "string") {
|
|
1908
2131
|
return {
|
|
1909
2132
|
type: "cancel_request",
|
|
@@ -3258,6 +3481,8 @@ ${cssVarsBlock}
|
|
|
3258
3481
|
<button id="saveAsBtn" type="button" title="Save editor content to a new file path.">Save editor as…</button>
|
|
3259
3482
|
<button id="saveOverBtn" type="button" title="Overwrite current file with editor content." disabled>Save editor</button>
|
|
3260
3483
|
<label class="file-label" title="Load a local file into editor text.">Load file content<input id="fileInput" type="file" accept=".md,.markdown,.mdx,.js,.mjs,.cjs,.jsx,.ts,.mts,.cts,.tsx,.py,.pyw,.sh,.bash,.zsh,.json,.jsonc,.json5,.rs,.c,.h,.cpp,.cxx,.cc,.hpp,.hxx,.jl,.f90,.f95,.f03,.f,.for,.r,.R,.m,.tex,.latex,.diff,.patch,.java,.go,.rb,.swift,.html,.htm,.css,.xml,.yaml,.yml,.toml,.lua,.txt,.rst,.adoc" /></label>
|
|
3484
|
+
<button id="loadGitDiffBtn" type="button" title="Load the current git diff from the Studio context into the editor.">Load git diff</button>
|
|
3485
|
+
<button id="getEditorBtn" type="button" title="Load the current terminal editor draft into Studio.">Load from pi editor</button>
|
|
3261
3486
|
</div>
|
|
3262
3487
|
</header>
|
|
3263
3488
|
|
|
@@ -3286,7 +3511,6 @@ ${cssVarsBlock}
|
|
|
3286
3511
|
<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>
|
|
3287
3512
|
<button id="copyDraftBtn" type="button">Copy editor text</button>
|
|
3288
3513
|
<button id="sendEditorBtn" type="button">Send to pi editor</button>
|
|
3289
|
-
<button id="getEditorBtn" type="button" title="Load the current terminal editor draft into Studio.">Load from pi editor</button>
|
|
3290
3514
|
</div>
|
|
3291
3515
|
<div class="source-actions-row">
|
|
3292
3516
|
<button id="insertHeaderBtn" type="button" title="Insert annotated-reply protocol header (source metadata, [an: ...] syntax hint, precedence note, and end marker).">Insert annotated reply header</button>
|
|
@@ -3483,6 +3707,7 @@ ${cssVarsBlock}
|
|
|
3483
3707
|
const saveOverBtn = document.getElementById("saveOverBtn");
|
|
3484
3708
|
const sendEditorBtn = document.getElementById("sendEditorBtn");
|
|
3485
3709
|
const getEditorBtn = document.getElementById("getEditorBtn");
|
|
3710
|
+
const loadGitDiffBtn = document.getElementById("loadGitDiffBtn");
|
|
3486
3711
|
const sendRunBtn = document.getElementById("sendRunBtn");
|
|
3487
3712
|
const copyDraftBtn = document.getElementById("copyDraftBtn");
|
|
3488
3713
|
const saveAnnotatedBtn = document.getElementById("saveAnnotatedBtn");
|
|
@@ -3539,6 +3764,10 @@ ${cssVarsBlock}
|
|
|
3539
3764
|
let contextPercent = null;
|
|
3540
3765
|
let updateInstalledVersion = null;
|
|
3541
3766
|
let updateLatestVersion = null;
|
|
3767
|
+
let windowHasFocus = typeof document.hasFocus === "function" ? document.hasFocus() : true;
|
|
3768
|
+
let titleAttentionMessage = "";
|
|
3769
|
+
let titleAttentionRequestId = null;
|
|
3770
|
+
let titleAttentionRequestKind = null;
|
|
3542
3771
|
|
|
3543
3772
|
function parseFiniteNumber(value) {
|
|
3544
3773
|
if (value == null || value === "") return null;
|
|
@@ -3769,6 +3998,7 @@ ${cssVarsBlock}
|
|
|
3769
3998
|
if (kind === "compact") return "compacting context";
|
|
3770
3999
|
if (kind === "send_to_editor") return "sending to pi editor";
|
|
3771
4000
|
if (kind === "get_from_editor") return "loading from pi editor";
|
|
4001
|
+
if (kind === "load_git_diff") return "loading git diff";
|
|
3772
4002
|
if (kind === "save_as" || kind === "save_over") return "saving editor text";
|
|
3773
4003
|
return "submitting request";
|
|
3774
4004
|
}
|
|
@@ -3899,12 +4129,63 @@ ${cssVarsBlock}
|
|
|
3899
4129
|
return changed;
|
|
3900
4130
|
}
|
|
3901
4131
|
|
|
4132
|
+
function isTitleAttentionRequestKind(kind) {
|
|
4133
|
+
return kind === "annotation" || kind === "critique" || kind === "direct";
|
|
4134
|
+
}
|
|
4135
|
+
|
|
4136
|
+
function armTitleAttentionForRequest(requestId, kind) {
|
|
4137
|
+
if (typeof requestId !== "string" || !isTitleAttentionRequestKind(kind)) {
|
|
4138
|
+
titleAttentionRequestId = null;
|
|
4139
|
+
titleAttentionRequestKind = null;
|
|
4140
|
+
return;
|
|
4141
|
+
}
|
|
4142
|
+
titleAttentionRequestId = requestId;
|
|
4143
|
+
titleAttentionRequestKind = kind;
|
|
4144
|
+
}
|
|
4145
|
+
|
|
4146
|
+
function clearArmedTitleAttention(requestId) {
|
|
4147
|
+
if (typeof requestId === "string" && titleAttentionRequestId && requestId !== titleAttentionRequestId) {
|
|
4148
|
+
return;
|
|
4149
|
+
}
|
|
4150
|
+
titleAttentionRequestId = null;
|
|
4151
|
+
titleAttentionRequestKind = null;
|
|
4152
|
+
}
|
|
4153
|
+
|
|
4154
|
+
function clearTitleAttention() {
|
|
4155
|
+
if (!titleAttentionMessage) return;
|
|
4156
|
+
titleAttentionMessage = "";
|
|
4157
|
+
updateDocumentTitle();
|
|
4158
|
+
}
|
|
4159
|
+
|
|
4160
|
+
function shouldShowTitleAttention() {
|
|
4161
|
+
const focused = typeof document.hasFocus === "function" ? document.hasFocus() : windowHasFocus;
|
|
4162
|
+
return Boolean(document.hidden) || !focused;
|
|
4163
|
+
}
|
|
4164
|
+
|
|
4165
|
+
function getTitleAttentionMessage(kind) {
|
|
4166
|
+
if (kind === "critique") return "● Critique ready";
|
|
4167
|
+
if (kind === "direct") return "● Response ready";
|
|
4168
|
+
return "● Reply ready";
|
|
4169
|
+
}
|
|
4170
|
+
|
|
4171
|
+
function maybeShowTitleAttentionForCompletedRequest(requestId, kind) {
|
|
4172
|
+
const matchedRequest = typeof requestId === "string" && titleAttentionRequestId && requestId === titleAttentionRequestId;
|
|
4173
|
+
const completedKind = isTitleAttentionRequestKind(kind) ? kind : titleAttentionRequestKind;
|
|
4174
|
+
clearArmedTitleAttention(requestId);
|
|
4175
|
+
if (!matchedRequest || !completedKind || !shouldShowTitleAttention()) {
|
|
4176
|
+
return;
|
|
4177
|
+
}
|
|
4178
|
+
titleAttentionMessage = getTitleAttentionMessage(completedKind);
|
|
4179
|
+
updateDocumentTitle();
|
|
4180
|
+
}
|
|
4181
|
+
|
|
3902
4182
|
function updateDocumentTitle() {
|
|
3903
4183
|
const modelText = modelLabel && modelLabel.trim() ? modelLabel.trim() : "none";
|
|
3904
4184
|
const terminalText = terminalSessionLabel && terminalSessionLabel.trim() ? terminalSessionLabel.trim() : "unknown";
|
|
3905
4185
|
const titleParts = ["pi Studio"];
|
|
3906
4186
|
if (terminalText && terminalText !== "unknown") titleParts.push(terminalText);
|
|
3907
4187
|
if (modelText && modelText !== "none") titleParts.push(modelText);
|
|
4188
|
+
if (titleAttentionMessage) titleParts.unshift(titleAttentionMessage);
|
|
3908
4189
|
document.title = titleParts.join(" · ");
|
|
3909
4190
|
}
|
|
3910
4191
|
|
|
@@ -3998,6 +4279,24 @@ ${cssVarsBlock}
|
|
|
3998
4279
|
|
|
3999
4280
|
renderStatus();
|
|
4000
4281
|
|
|
4282
|
+
window.addEventListener("focus", () => {
|
|
4283
|
+
windowHasFocus = true;
|
|
4284
|
+
clearTitleAttention();
|
|
4285
|
+
});
|
|
4286
|
+
|
|
4287
|
+
window.addEventListener("blur", () => {
|
|
4288
|
+
windowHasFocus = false;
|
|
4289
|
+
});
|
|
4290
|
+
|
|
4291
|
+
document.addEventListener("visibilitychange", () => {
|
|
4292
|
+
if (!document.hidden) {
|
|
4293
|
+
windowHasFocus = typeof document.hasFocus === "function" ? document.hasFocus() : windowHasFocus;
|
|
4294
|
+
if (windowHasFocus) {
|
|
4295
|
+
clearTitleAttention();
|
|
4296
|
+
}
|
|
4297
|
+
}
|
|
4298
|
+
});
|
|
4299
|
+
|
|
4001
4300
|
function updateSourceBadge() {
|
|
4002
4301
|
const label = sourceState && sourceState.label ? sourceState.label : "blank";
|
|
4003
4302
|
sourceBadgeEl.textContent = "Editor origin: " + label;
|
|
@@ -5090,6 +5389,7 @@ ${cssVarsBlock}
|
|
|
5090
5389
|
saveOverBtn.disabled = uiBusy || !canSaveOver;
|
|
5091
5390
|
sendEditorBtn.disabled = uiBusy;
|
|
5092
5391
|
if (getEditorBtn) getEditorBtn.disabled = uiBusy;
|
|
5392
|
+
if (loadGitDiffBtn) loadGitDiffBtn.disabled = uiBusy;
|
|
5093
5393
|
syncRunAndCritiqueButtons();
|
|
5094
5394
|
copyDraftBtn.disabled = uiBusy;
|
|
5095
5395
|
if (highlightSelect) highlightSelect.disabled = uiBusy;
|
|
@@ -5810,8 +6110,10 @@ ${cssVarsBlock}
|
|
|
5810
6110
|
setStatus("No matching Studio request is running.", "warning");
|
|
5811
6111
|
return false;
|
|
5812
6112
|
}
|
|
5813
|
-
const
|
|
6113
|
+
const requestId = pendingRequestId;
|
|
6114
|
+
const sent = sendMessage({ type: "cancel_request", requestId });
|
|
5814
6115
|
if (!sent) return false;
|
|
6116
|
+
clearArmedTitleAttention(requestId);
|
|
5815
6117
|
setStatus("Stopping request…", "warning");
|
|
5816
6118
|
return true;
|
|
5817
6119
|
}
|
|
@@ -6119,6 +6421,7 @@ ${cssVarsBlock}
|
|
|
6119
6421
|
return;
|
|
6120
6422
|
}
|
|
6121
6423
|
|
|
6424
|
+
const completedRequestId = typeof message.requestId === "string" ? message.requestId : pendingRequestId;
|
|
6122
6425
|
const responseKind =
|
|
6123
6426
|
typeof message.kind === "string"
|
|
6124
6427
|
? message.kind
|
|
@@ -6151,6 +6454,7 @@ ${cssVarsBlock}
|
|
|
6151
6454
|
} else {
|
|
6152
6455
|
setStatus("Response ready.", "success");
|
|
6153
6456
|
}
|
|
6457
|
+
maybeShowTitleAttentionForCompletedRequest(completedRequestId, responseKind);
|
|
6154
6458
|
return;
|
|
6155
6459
|
}
|
|
6156
6460
|
|
|
@@ -6285,6 +6589,31 @@ ${cssVarsBlock}
|
|
|
6285
6589
|
return;
|
|
6286
6590
|
}
|
|
6287
6591
|
|
|
6592
|
+
if (message.type === "git_diff_snapshot") {
|
|
6593
|
+
if (typeof message.requestId === "string" && pendingRequestId === message.requestId) {
|
|
6594
|
+
pendingRequestId = null;
|
|
6595
|
+
pendingKind = null;
|
|
6596
|
+
}
|
|
6597
|
+
|
|
6598
|
+
const content = typeof message.content === "string" ? message.content : "";
|
|
6599
|
+
const label = typeof message.label === "string" && message.label.trim()
|
|
6600
|
+
? message.label.trim()
|
|
6601
|
+
: "git diff";
|
|
6602
|
+
setEditorText(content, { preserveScroll: false, preserveSelection: false });
|
|
6603
|
+
setSourceState({ source: "blank", label, path: null });
|
|
6604
|
+
setEditorLanguage("diff");
|
|
6605
|
+
setBusy(false);
|
|
6606
|
+
setWsState("Ready");
|
|
6607
|
+
refreshResponseUi();
|
|
6608
|
+
setStatus(
|
|
6609
|
+
typeof message.message === "string" && message.message.trim()
|
|
6610
|
+
? message.message
|
|
6611
|
+
: "Loaded current git diff.",
|
|
6612
|
+
"success",
|
|
6613
|
+
);
|
|
6614
|
+
return;
|
|
6615
|
+
}
|
|
6616
|
+
|
|
6288
6617
|
if (message.type === "studio_state") {
|
|
6289
6618
|
const busy = Boolean(message.busy);
|
|
6290
6619
|
agentBusyFromServer = Boolean(message.agentBusy);
|
|
@@ -6352,6 +6681,9 @@ ${cssVarsBlock}
|
|
|
6352
6681
|
pendingRequestId = null;
|
|
6353
6682
|
pendingKind = null;
|
|
6354
6683
|
}
|
|
6684
|
+
if (typeof message.requestId === "string") {
|
|
6685
|
+
clearArmedTitleAttention(message.requestId);
|
|
6686
|
+
}
|
|
6355
6687
|
stickyStudioKind = null;
|
|
6356
6688
|
setBusy(false);
|
|
6357
6689
|
setWsState("Ready");
|
|
@@ -6367,6 +6699,9 @@ ${cssVarsBlock}
|
|
|
6367
6699
|
pendingRequestId = null;
|
|
6368
6700
|
pendingKind = null;
|
|
6369
6701
|
}
|
|
6702
|
+
if (typeof message.requestId === "string") {
|
|
6703
|
+
clearArmedTitleAttention(message.requestId);
|
|
6704
|
+
}
|
|
6370
6705
|
stickyStudioKind = null;
|
|
6371
6706
|
setBusy(false);
|
|
6372
6707
|
setWsState("Ready");
|
|
@@ -6375,8 +6710,17 @@ ${cssVarsBlock}
|
|
|
6375
6710
|
}
|
|
6376
6711
|
|
|
6377
6712
|
if (message.type === "info") {
|
|
6713
|
+
if (typeof message.requestId === "string" && pendingRequestId === message.requestId) {
|
|
6714
|
+
pendingRequestId = null;
|
|
6715
|
+
pendingKind = null;
|
|
6716
|
+
setBusy(false);
|
|
6717
|
+
setWsState("Ready");
|
|
6718
|
+
}
|
|
6378
6719
|
if (typeof message.message === "string") {
|
|
6379
|
-
setStatus(
|
|
6720
|
+
setStatus(
|
|
6721
|
+
message.message,
|
|
6722
|
+
typeof message.level === "string" ? message.level : undefined,
|
|
6723
|
+
);
|
|
6380
6724
|
}
|
|
6381
6725
|
}
|
|
6382
6726
|
|
|
@@ -6525,10 +6869,12 @@ ${cssVarsBlock}
|
|
|
6525
6869
|
setStatus("Studio is busy.", "warning");
|
|
6526
6870
|
return null;
|
|
6527
6871
|
}
|
|
6872
|
+
clearTitleAttention();
|
|
6528
6873
|
const requestId = makeRequestId();
|
|
6529
6874
|
pendingRequestId = requestId;
|
|
6530
6875
|
pendingKind = kind;
|
|
6531
6876
|
stickyStudioKind = kind;
|
|
6877
|
+
armTitleAttentionForRequest(requestId, kind);
|
|
6532
6878
|
setBusy(true);
|
|
6533
6879
|
setWsState("Submitting");
|
|
6534
6880
|
setStatus(getStudioBusyStatus(kind), "warning");
|
|
@@ -7014,6 +7360,29 @@ ${cssVarsBlock}
|
|
|
7014
7360
|
});
|
|
7015
7361
|
}
|
|
7016
7362
|
|
|
7363
|
+
if (loadGitDiffBtn) {
|
|
7364
|
+
loadGitDiffBtn.addEventListener("click", () => {
|
|
7365
|
+
const requestId = beginUiAction("load_git_diff");
|
|
7366
|
+
if (!requestId) return;
|
|
7367
|
+
|
|
7368
|
+
const effectivePath = getEffectiveSavePath();
|
|
7369
|
+
const sent = sendMessage({
|
|
7370
|
+
type: "load_git_diff_request",
|
|
7371
|
+
requestId,
|
|
7372
|
+
sourcePath: effectivePath || sourceState.path || undefined,
|
|
7373
|
+
resourceDir: resourceDirInput && resourceDirInput.value.trim()
|
|
7374
|
+
? resourceDirInput.value.trim()
|
|
7375
|
+
: undefined,
|
|
7376
|
+
});
|
|
7377
|
+
|
|
7378
|
+
if (!sent) {
|
|
7379
|
+
pendingRequestId = null;
|
|
7380
|
+
pendingKind = null;
|
|
7381
|
+
setBusy(false);
|
|
7382
|
+
}
|
|
7383
|
+
});
|
|
7384
|
+
}
|
|
7385
|
+
|
|
7017
7386
|
sendRunBtn.addEventListener("click", () => {
|
|
7018
7387
|
if (getAbortablePendingKind() === "direct") {
|
|
7019
7388
|
requestCancelForPendingRequest("direct");
|
|
@@ -7624,6 +7993,43 @@ export default function (pi: ExtensionAPI) {
|
|
|
7624
7993
|
return;
|
|
7625
7994
|
}
|
|
7626
7995
|
|
|
7996
|
+
if (msg.type === "load_git_diff_request") {
|
|
7997
|
+
if (!isValidRequestId(msg.requestId)) {
|
|
7998
|
+
sendToClient(client, { type: "error", requestId: msg.requestId, message: "Invalid request ID." });
|
|
7999
|
+
return;
|
|
8000
|
+
}
|
|
8001
|
+
if (isStudioBusy()) {
|
|
8002
|
+
sendToClient(client, { type: "busy", requestId: msg.requestId, message: "Studio is busy." });
|
|
8003
|
+
return;
|
|
8004
|
+
}
|
|
8005
|
+
|
|
8006
|
+
const baseDir = resolveStudioGitDiffBaseDir(msg.sourcePath, msg.resourceDir, studioCwd);
|
|
8007
|
+
const diffResult = readStudioGitDiff(baseDir);
|
|
8008
|
+
if (!diffResult.ok) {
|
|
8009
|
+
sendToClient(client, {
|
|
8010
|
+
type: "info",
|
|
8011
|
+
requestId: msg.requestId,
|
|
8012
|
+
message: diffResult.message,
|
|
8013
|
+
level: diffResult.level,
|
|
8014
|
+
});
|
|
8015
|
+
return;
|
|
8016
|
+
}
|
|
8017
|
+
|
|
8018
|
+
initialStudioDocument = {
|
|
8019
|
+
text: diffResult.text,
|
|
8020
|
+
label: diffResult.label,
|
|
8021
|
+
source: "blank",
|
|
8022
|
+
};
|
|
8023
|
+
sendToClient(client, {
|
|
8024
|
+
type: "git_diff_snapshot",
|
|
8025
|
+
requestId: msg.requestId,
|
|
8026
|
+
content: diffResult.text,
|
|
8027
|
+
label: diffResult.label,
|
|
8028
|
+
message: "Loaded current git diff into Studio.",
|
|
8029
|
+
});
|
|
8030
|
+
return;
|
|
8031
|
+
}
|
|
8032
|
+
|
|
7627
8033
|
if (msg.type === "cancel_request") {
|
|
7628
8034
|
if (!isValidRequestId(msg.requestId)) {
|
|
7629
8035
|
sendToClient(client, { type: "error", requestId: msg.requestId, message: "Invalid request ID." });
|