pi-studio 0.5.2 → 0.5.3
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 +15 -0
- package/index.ts +160 -11
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -88,6 +88,21 @@ All notable changes to `pi-studio` are documented here.
|
|
|
88
88
|
|
|
89
89
|
## [Unreleased]
|
|
90
90
|
|
|
91
|
+
## [0.5.3] — 2026-03-06
|
|
92
|
+
|
|
93
|
+
### Added
|
|
94
|
+
- New terminal command: `/studio-current <path>` loads a file into currently open Studio tab(s) without opening a new browser session.
|
|
95
|
+
- `/studio --help` now includes `/studio-current` usage.
|
|
96
|
+
|
|
97
|
+
### Changed
|
|
98
|
+
- Footer compact action label is now **Compact**.
|
|
99
|
+
- Footer metadata now includes in-Studio npm update hint text when an update is available (`Update: installed → latest`).
|
|
100
|
+
- Update notification timing now runs after Studio open notifications, so the update message is not immediately overwritten.
|
|
101
|
+
- Slash-command autocomplete order now lists `/studio` before `/studio-current`.
|
|
102
|
+
|
|
103
|
+
### Fixed
|
|
104
|
+
- Removed low-value terminal toasts for Studio websocket connect/disconnect that could overwrite more important notifications.
|
|
105
|
+
|
|
91
106
|
## [0.5.2] — 2026-03-06
|
|
92
107
|
|
|
93
108
|
### Changed
|
package/index.ts
CHANGED
|
@@ -2931,7 +2931,7 @@ ${cssVarsBlock}
|
|
|
2931
2931
|
|
|
2932
2932
|
<footer>
|
|
2933
2933
|
<span id="statusLine"><span id="statusSpinner" aria-hidden="true"> </span><span id="status">Booting studio…</span></span>
|
|
2934
|
-
<span id="footerMeta" class="footer-meta"><span id="footerMetaText" class="footer-meta-text">Model: ${initialModel} · Terminal: ${initialTerminal} · Context: unknown</span><button id="compactBtn" class="footer-compact-btn" type="button" title="Trigger pi context compaction now.">Compact
|
|
2934
|
+
<span id="footerMeta" class="footer-meta"><span id="footerMetaText" class="footer-meta-text">Model: ${initialModel} · Terminal: ${initialTerminal} · Context: unknown</span><button id="compactBtn" class="footer-compact-btn" type="button" title="Trigger pi context compaction now.">Compact</button></span>
|
|
2935
2935
|
<span class="shortcut-hint">Focus pane: Cmd/Ctrl+Esc (or F10), Esc to exit · Run editor text: Cmd/Ctrl+Enter</span>
|
|
2936
2936
|
</footer>
|
|
2937
2937
|
|
|
@@ -3065,6 +3065,8 @@ ${cssVarsBlock}
|
|
|
3065
3065
|
let contextTokens = null;
|
|
3066
3066
|
let contextWindow = null;
|
|
3067
3067
|
let contextPercent = null;
|
|
3068
|
+
let updateInstalledVersion = null;
|
|
3069
|
+
let updateLatestVersion = null;
|
|
3068
3070
|
|
|
3069
3071
|
function parseFiniteNumber(value) {
|
|
3070
3072
|
if (value == null || value === "") return null;
|
|
@@ -3072,6 +3074,12 @@ ${cssVarsBlock}
|
|
|
3072
3074
|
return Number.isFinite(parsed) ? parsed : null;
|
|
3073
3075
|
}
|
|
3074
3076
|
|
|
3077
|
+
function parseNonEmptyString(value) {
|
|
3078
|
+
if (typeof value !== "string") return null;
|
|
3079
|
+
const trimmed = value.trim();
|
|
3080
|
+
return trimmed ? trimmed : null;
|
|
3081
|
+
}
|
|
3082
|
+
|
|
3075
3083
|
contextTokens = parseFiniteNumber(document.body && document.body.dataset ? document.body.dataset.contextTokens : null);
|
|
3076
3084
|
contextWindow = parseFiniteNumber(document.body && document.body.dataset ? document.body.dataset.contextWindow : null);
|
|
3077
3085
|
contextPercent = parseFiniteNumber(document.body && document.body.dataset ? document.body.dataset.contextPercent : null);
|
|
@@ -3199,6 +3207,12 @@ ${cssVarsBlock}
|
|
|
3199
3207
|
if (typeof message.contextTokens === "number") summary.contextTokens = message.contextTokens;
|
|
3200
3208
|
if (typeof message.contextWindow === "number") summary.contextWindow = message.contextWindow;
|
|
3201
3209
|
if (typeof message.contextPercent === "number") summary.contextPercent = message.contextPercent;
|
|
3210
|
+
if (typeof message.updateInstalledVersion === "string") summary.updateInstalledVersion = message.updateInstalledVersion;
|
|
3211
|
+
if (typeof message.updateLatestVersion === "string") summary.updateLatestVersion = message.updateLatestVersion;
|
|
3212
|
+
if (message.document && typeof message.document === "object" && typeof message.document.text === "string") {
|
|
3213
|
+
summary.documentLength = message.document.text.length;
|
|
3214
|
+
if (typeof message.document.label === "string") summary.documentLabel = message.document.label;
|
|
3215
|
+
}
|
|
3202
3216
|
if (typeof message.compactInProgress === "boolean") summary.compactInProgress = message.compactInProgress;
|
|
3203
3217
|
if (typeof message.stopReason === "string") summary.stopReason = message.stopReason;
|
|
3204
3218
|
if (typeof message.markdown === "string") summary.markdownLength = message.markdown.length;
|
|
@@ -3388,6 +3402,30 @@ ${cssVarsBlock}
|
|
|
3388
3402
|
return changed;
|
|
3389
3403
|
}
|
|
3390
3404
|
|
|
3405
|
+
function applyUpdateInfoFromMessage(message) {
|
|
3406
|
+
if (!message || typeof message !== "object") return false;
|
|
3407
|
+
|
|
3408
|
+
let changed = false;
|
|
3409
|
+
|
|
3410
|
+
if (Object.prototype.hasOwnProperty.call(message, "updateInstalledVersion")) {
|
|
3411
|
+
const nextInstalled = parseNonEmptyString(message.updateInstalledVersion);
|
|
3412
|
+
if (nextInstalled !== updateInstalledVersion) {
|
|
3413
|
+
updateInstalledVersion = nextInstalled;
|
|
3414
|
+
changed = true;
|
|
3415
|
+
}
|
|
3416
|
+
}
|
|
3417
|
+
|
|
3418
|
+
if (Object.prototype.hasOwnProperty.call(message, "updateLatestVersion")) {
|
|
3419
|
+
const nextLatest = parseNonEmptyString(message.updateLatestVersion);
|
|
3420
|
+
if (nextLatest !== updateLatestVersion) {
|
|
3421
|
+
updateLatestVersion = nextLatest;
|
|
3422
|
+
changed = true;
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
|
|
3426
|
+
return changed;
|
|
3427
|
+
}
|
|
3428
|
+
|
|
3391
3429
|
function updateDocumentTitle() {
|
|
3392
3430
|
const modelText = modelLabel && modelLabel.trim() ? modelLabel.trim() : "none";
|
|
3393
3431
|
const terminalText = terminalSessionLabel && terminalSessionLabel.trim() ? terminalSessionLabel.trim() : "unknown";
|
|
@@ -3401,7 +3439,13 @@ ${cssVarsBlock}
|
|
|
3401
3439
|
const modelText = modelLabel && modelLabel.trim() ? modelLabel.trim() : "none";
|
|
3402
3440
|
const terminalText = terminalSessionLabel && terminalSessionLabel.trim() ? terminalSessionLabel.trim() : "unknown";
|
|
3403
3441
|
const contextText = formatContextUsageText();
|
|
3404
|
-
|
|
3442
|
+
let updateText = "";
|
|
3443
|
+
if (updateLatestVersion) {
|
|
3444
|
+
updateText = updateInstalledVersion
|
|
3445
|
+
? "Update: " + updateInstalledVersion + " → " + updateLatestVersion
|
|
3446
|
+
: "Update: " + updateLatestVersion + " available";
|
|
3447
|
+
}
|
|
3448
|
+
const text = "Model: " + modelText + " · Terminal: " + terminalText + " · " + contextText + (updateText ? " · " + updateText : "");
|
|
3405
3449
|
if (footerMetaTextEl) {
|
|
3406
3450
|
footerMetaTextEl.textContent = text;
|
|
3407
3451
|
footerMetaTextEl.title = text;
|
|
@@ -5332,7 +5376,9 @@ ${cssVarsBlock}
|
|
|
5332
5376
|
|
|
5333
5377
|
debugTrace("server_message", summarizeServerMessage(message));
|
|
5334
5378
|
|
|
5335
|
-
|
|
5379
|
+
const contextChanged = applyContextUsageFromMessage(message);
|
|
5380
|
+
const updateInfoChanged = applyUpdateInfoFromMessage(message);
|
|
5381
|
+
if (contextChanged || updateInfoChanged) {
|
|
5336
5382
|
updateFooterMeta();
|
|
5337
5383
|
}
|
|
5338
5384
|
|
|
@@ -5621,6 +5667,35 @@ ${cssVarsBlock}
|
|
|
5621
5667
|
return;
|
|
5622
5668
|
}
|
|
5623
5669
|
|
|
5670
|
+
if (message.type === "studio_document") {
|
|
5671
|
+
const nextDoc = message.document;
|
|
5672
|
+
if (!nextDoc || typeof nextDoc !== "object" || typeof nextDoc.text !== "string") {
|
|
5673
|
+
return;
|
|
5674
|
+
}
|
|
5675
|
+
|
|
5676
|
+
const nextSource =
|
|
5677
|
+
nextDoc.source === "file" || nextDoc.source === "last-response"
|
|
5678
|
+
? nextDoc.source
|
|
5679
|
+
: "blank";
|
|
5680
|
+
const nextLabel = typeof nextDoc.label === "string" && nextDoc.label.trim()
|
|
5681
|
+
? nextDoc.label.trim()
|
|
5682
|
+
: (nextSource === "file" ? "file" : "studio document");
|
|
5683
|
+
const nextPath = typeof nextDoc.path === "string" && nextDoc.path.trim()
|
|
5684
|
+
? nextDoc.path
|
|
5685
|
+
: null;
|
|
5686
|
+
|
|
5687
|
+
setEditorText(nextDoc.text, { preserveScroll: false, preserveSelection: false });
|
|
5688
|
+
setSourceState({ source: nextSource, label: nextLabel, path: nextPath });
|
|
5689
|
+
refreshResponseUi();
|
|
5690
|
+
setStatus(
|
|
5691
|
+
typeof message.message === "string" && message.message.trim()
|
|
5692
|
+
? message.message
|
|
5693
|
+
: "Loaded document from terminal.",
|
|
5694
|
+
"success",
|
|
5695
|
+
);
|
|
5696
|
+
return;
|
|
5697
|
+
}
|
|
5698
|
+
|
|
5624
5699
|
if (message.type === "studio_state") {
|
|
5625
5700
|
const busy = Boolean(message.busy);
|
|
5626
5701
|
agentBusyFromServer = Boolean(message.agentBusy);
|
|
@@ -6487,6 +6562,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
6487
6562
|
let updateCheckStarted = false;
|
|
6488
6563
|
let updateCheckCompleted = false;
|
|
6489
6564
|
const packageMetadata = readLocalPackageMetadata();
|
|
6565
|
+
const installedPackageVersion = packageMetadata?.version ?? null;
|
|
6566
|
+
let updateAvailableLatestVersion: string | null = null;
|
|
6490
6567
|
|
|
6491
6568
|
const isStudioBusy = () => agentBusy || activeRequest !== null || compactInProgress;
|
|
6492
6569
|
|
|
@@ -6579,10 +6656,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
6579
6656
|
const latest = await fetchLatestNpmVersion(metadata.name, UPDATE_CHECK_TIMEOUT_MS);
|
|
6580
6657
|
if (!latest) return;
|
|
6581
6658
|
if (!isVersionBehind(metadata.version, latest)) return;
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
6659
|
+
|
|
6660
|
+
updateAvailableLatestVersion = latest;
|
|
6661
|
+
broadcastState();
|
|
6662
|
+
|
|
6663
|
+
const notification =
|
|
6664
|
+
`Update available for ${metadata.name}: ${metadata.version} → ${latest}. Run: pi install npm:${metadata.name}`;
|
|
6665
|
+
ctx.ui.notify(notification, "info");
|
|
6666
|
+
broadcast({ type: "info", message: notification, level: "info" });
|
|
6586
6667
|
} finally {
|
|
6587
6668
|
updateCheckCompleted = true;
|
|
6588
6669
|
}
|
|
@@ -6689,6 +6770,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
6689
6770
|
contextTokens: contextUsageSnapshot.tokens,
|
|
6690
6771
|
contextWindow: contextUsageSnapshot.contextWindow,
|
|
6691
6772
|
contextPercent: contextUsageSnapshot.percent,
|
|
6773
|
+
updateInstalledVersion: installedPackageVersion,
|
|
6774
|
+
updateLatestVersion: updateAvailableLatestVersion,
|
|
6692
6775
|
compactInProgress,
|
|
6693
6776
|
activeRequestId: activeRequest?.id ?? compactRequestId ?? null,
|
|
6694
6777
|
activeRequestKind: activeRequest?.kind ?? (compactInProgress ? "compact" : null),
|
|
@@ -6793,6 +6876,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
6793
6876
|
contextTokens: contextUsageSnapshot.tokens,
|
|
6794
6877
|
contextWindow: contextUsageSnapshot.contextWindow,
|
|
6795
6878
|
contextPercent: contextUsageSnapshot.percent,
|
|
6879
|
+
updateInstalledVersion: installedPackageVersion,
|
|
6880
|
+
updateLatestVersion: updateAvailableLatestVersion,
|
|
6796
6881
|
compactInProgress,
|
|
6797
6882
|
activeRequestId: activeRequest?.id ?? compactRequestId ?? null,
|
|
6798
6883
|
activeRequestKind: activeRequest?.kind ?? (compactInProgress ? "compact" : null),
|
|
@@ -7424,7 +7509,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
7424
7509
|
|
|
7425
7510
|
wsServer.on("connection", (ws) => {
|
|
7426
7511
|
clients.add(ws);
|
|
7427
|
-
|
|
7512
|
+
emitDebugEvent("studio_ws_connected", { clients: clients.size });
|
|
7428
7513
|
broadcastState();
|
|
7429
7514
|
|
|
7430
7515
|
ws.on("message", (data) => {
|
|
@@ -7438,7 +7523,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
7438
7523
|
|
|
7439
7524
|
ws.on("close", () => {
|
|
7440
7525
|
clients.delete(ws);
|
|
7441
|
-
|
|
7526
|
+
emitDebugEvent("studio_ws_disconnected", { clients: clients.size });
|
|
7442
7527
|
});
|
|
7443
7528
|
|
|
7444
7529
|
ws.on("error", () => {
|
|
@@ -7771,7 +7856,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
7771
7856
|
+ " /studio --blank Open with blank editor\n"
|
|
7772
7857
|
+ " /studio --last Open with last model response\n"
|
|
7773
7858
|
+ " /studio --status Show studio status\n"
|
|
7774
|
-
+ " /studio --stop Stop studio server"
|
|
7859
|
+
+ " /studio --stop Stop studio server\n"
|
|
7860
|
+
+ " /studio-current <path> Load a file into currently open Studio tab(s)",
|
|
7775
7861
|
"info",
|
|
7776
7862
|
);
|
|
7777
7863
|
return;
|
|
@@ -7784,7 +7870,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
7784
7870
|
syncStudioResponseHistory(ctx.sessionManager.getBranch());
|
|
7785
7871
|
broadcastState();
|
|
7786
7872
|
broadcastResponseHistory();
|
|
7787
|
-
void maybeNotifyUpdateAvailable(ctx);
|
|
7788
7873
|
// Seed theme vars so first ping doesn't trigger a false update
|
|
7789
7874
|
try {
|
|
7790
7875
|
const currentStyle = getStudioThemeStyle(ctx.ui.theme);
|
|
@@ -7884,7 +7969,71 @@ export default function (pi: ExtensionAPI) {
|
|
|
7884
7969
|
ctx.ui.notify(`Studio URL: ${url}`, "info");
|
|
7885
7970
|
} catch (error) {
|
|
7886
7971
|
ctx.ui.notify(`Failed to open browser: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
7972
|
+
} finally {
|
|
7973
|
+
void maybeNotifyUpdateAvailable(ctx);
|
|
7974
|
+
}
|
|
7975
|
+
},
|
|
7976
|
+
});
|
|
7977
|
+
|
|
7978
|
+
pi.registerCommand("studio-current", {
|
|
7979
|
+
description: "Load a file into current open Studio tab(s) without opening a new browser session",
|
|
7980
|
+
handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
7981
|
+
const trimmed = args.trim();
|
|
7982
|
+
if (!trimmed || trimmed === "help" || trimmed === "--help" || trimmed === "-h") {
|
|
7983
|
+
ctx.ui.notify(
|
|
7984
|
+
"Usage: /studio-current <path>\n"
|
|
7985
|
+
+ " Load a file into currently open Studio tab(s) without opening a new browser window.",
|
|
7986
|
+
"info",
|
|
7987
|
+
);
|
|
7988
|
+
return;
|
|
7989
|
+
}
|
|
7990
|
+
|
|
7991
|
+
const pathArg = parsePathArgument(trimmed);
|
|
7992
|
+
if (!pathArg) {
|
|
7993
|
+
ctx.ui.notify("Invalid file path argument.", "error");
|
|
7994
|
+
return;
|
|
7995
|
+
}
|
|
7996
|
+
|
|
7997
|
+
const file = readStudioFile(pathArg, ctx.cwd);
|
|
7998
|
+
if (!file.ok) {
|
|
7999
|
+
ctx.ui.notify(file.message, "error");
|
|
8000
|
+
return;
|
|
8001
|
+
}
|
|
8002
|
+
|
|
8003
|
+
if (!serverState || serverState.clients.size === 0) {
|
|
8004
|
+
ctx.ui.notify("No open Studio tab is connected. Run /studio first.", "warning");
|
|
8005
|
+
return;
|
|
8006
|
+
}
|
|
8007
|
+
|
|
8008
|
+
await ctx.waitForIdle();
|
|
8009
|
+
lastCommandCtx = ctx;
|
|
8010
|
+
refreshRuntimeMetadata({ cwd: ctx.cwd, model: ctx.model });
|
|
8011
|
+
refreshContextUsage(ctx);
|
|
8012
|
+
syncStudioResponseHistory(ctx.sessionManager.getBranch());
|
|
8013
|
+
|
|
8014
|
+
const nextDoc: InitialStudioDocument = {
|
|
8015
|
+
text: file.text,
|
|
8016
|
+
label: file.label,
|
|
8017
|
+
source: "file",
|
|
8018
|
+
path: file.resolvedPath,
|
|
8019
|
+
};
|
|
8020
|
+
initialStudioDocument = nextDoc;
|
|
8021
|
+
|
|
8022
|
+
broadcastState();
|
|
8023
|
+
broadcastResponseHistory();
|
|
8024
|
+
broadcast({
|
|
8025
|
+
type: "studio_document",
|
|
8026
|
+
document: nextDoc,
|
|
8027
|
+
message: `Loaded ${file.label} from terminal command.`,
|
|
8028
|
+
});
|
|
8029
|
+
|
|
8030
|
+
if (file.text.length > 200_000) {
|
|
8031
|
+
ctx.ui.notify(
|
|
8032
|
+
"Loaded a large file into Studio. Critique requests currently reject documents over 200k characters.",
|
|
8033
|
+
"warning",
|
|
8034
|
+
);
|
|
7887
8035
|
}
|
|
8036
|
+
ctx.ui.notify(`Loaded file into open Studio tab(s): ${file.label}`, "info");
|
|
7888
8037
|
},
|
|
7889
8038
|
});
|
|
7890
8039
|
}
|