pi-studio 0.5.15 → 0.5.17

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/index.ts +40 -15
  3. 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.17] — 2026-03-17
8
+
9
+ ### Fixed
10
+ - Studio preview and PDF rendering now accept Markdown lists without a preceding blank line, so common model output like `What I read:\n- item` renders as a real list instead of collapsing into a paragraph.
11
+
12
+ ## [0.5.16] — 2026-03-17
13
+
14
+ ### Fixed
15
+ - Response-history prompt loading now keeps the correct generating prompt for both Studio editor-sent requests and prompts entered directly in the terminal, instead of sometimes reusing stale editor text.
16
+
7
17
  ## [0.5.15] — 2026-03-16
8
18
 
9
19
  ### Added
package/index.ts CHANGED
@@ -29,6 +29,7 @@ interface StudioServerState {
29
29
  interface ActiveStudioRequest {
30
30
  id: string;
31
31
  kind: StudioRequestKind;
32
+ prompt: string | null;
32
33
  timer: NodeJS.Timeout;
33
34
  startedAt: number;
34
35
  }
@@ -1589,7 +1590,7 @@ async function preprocessStudioMermaidForPdf(markdown: string, workDir: string):
1589
1590
 
1590
1591
  async function renderStudioMarkdownWithPandoc(markdown: string, isLatex?: boolean, resourcePath?: string): Promise<string> {
1591
1592
  const pandocCommand = process.env.PANDOC_PATH?.trim() || "pandoc";
1592
- const inputFormat = isLatex ? "latex" : "markdown+tex_math_dollars+tex_math_single_backslash+tex_math_double_backslash+autolink_bare_uris-raw_html";
1593
+ const inputFormat = isLatex ? "latex" : "markdown+lists_without_preceding_blankline+tex_math_dollars+tex_math_single_backslash+tex_math_double_backslash+autolink_bare_uris-raw_html";
1593
1594
  const args = ["-f", inputFormat, "-t", "html5", "--mathml", "--wrap=none"];
1594
1595
  if (resourcePath) {
1595
1596
  args.push(`--resource-path=${resourcePath}`);
@@ -1818,7 +1819,7 @@ async function renderStudioPdfWithPandoc(
1818
1819
  };
1819
1820
 
1820
1821
  if (!isLatex && effectiveEditorLanguage === "diff") {
1821
- const inputFormat = "markdown+tex_math_dollars+autolink_bare_uris+superscript+subscript-raw_html";
1822
+ const inputFormat = "markdown+lists_without_preceding_blankline+tex_math_dollars+autolink_bare_uris+superscript+subscript-raw_html";
1822
1823
  const diffMarkdown = prepareStudioPdfMarkdown(markdown, false, effectiveEditorLanguage);
1823
1824
  try {
1824
1825
  return await runPandocPdfExport(inputFormat, diffMarkdown);
@@ -1834,7 +1835,7 @@ async function renderStudioPdfWithPandoc(
1834
1835
 
1835
1836
  const inputFormat = isLatex
1836
1837
  ? "latex"
1837
- : "markdown+tex_math_dollars+tex_math_single_backslash+tex_math_double_backslash+autolink_bare_uris+superscript+subscript-raw_html";
1838
+ : "markdown+lists_without_preceding_blankline+tex_math_dollars+tex_math_single_backslash+tex_math_double_backslash+autolink_bare_uris+superscript+subscript-raw_html";
1838
1839
  const normalizedMarkdown = prepareStudioPdfMarkdown(markdown, isLatex, effectiveEditorLanguage);
1839
1840
 
1840
1841
  const tempDir = join(tmpdir(), `pi-studio-pdf-${Date.now()}-${randomUUID()}`);
@@ -2199,6 +2200,12 @@ function extractLatestAssistantFromEntries(entries: SessionEntry[]): string | nu
2199
2200
  return null;
2200
2201
  }
2201
2202
 
2203
+ function normalizePromptText(text: string | null | undefined): string | null {
2204
+ if (typeof text !== "string") return null;
2205
+ const trimmed = text.trim();
2206
+ return trimmed.length > 0 ? trimmed : null;
2207
+ }
2208
+
2202
2209
  function extractUserText(message: unknown): string | null {
2203
2210
  const msg = message as {
2204
2211
  role?: string;
@@ -2207,8 +2214,7 @@ function extractUserText(message: unknown): string | null {
2207
2214
  if (!msg || msg.role !== "user") return null;
2208
2215
 
2209
2216
  if (typeof msg.content === "string") {
2210
- const text = msg.content.trim();
2211
- return text.length > 0 ? text : null;
2217
+ return normalizePromptText(msg.content);
2212
2218
  }
2213
2219
 
2214
2220
  if (!Array.isArray(msg.content)) return null;
@@ -2230,8 +2236,16 @@ function extractUserText(message: unknown): string | null {
2230
2236
  }
2231
2237
  }
2232
2238
 
2233
- const text = blocks.join("\n\n").trim();
2234
- return text.length > 0 ? text : null;
2239
+ return normalizePromptText(blocks.join("\n\n"));
2240
+ }
2241
+
2242
+ function findLatestUserPrompt(entries: SessionEntry[]): string | null {
2243
+ let latestPrompt: string | null = null;
2244
+ for (const entry of entries) {
2245
+ if (!entry || entry.type !== "message") continue;
2246
+ latestPrompt = extractUserText((entry as { message?: unknown }).message) ?? latestPrompt;
2247
+ }
2248
+ return latestPrompt;
2235
2249
  }
2236
2250
 
2237
2251
  function parseEntryTimestamp(timestamp: unknown): number {
@@ -2945,6 +2959,8 @@ export default function (pi: ExtensionAPI) {
2945
2959
  let currentModelLabel = "none";
2946
2960
  let terminalSessionLabel = buildTerminalSessionLabel(studioCwd);
2947
2961
  let studioResponseHistory: StudioResponseHistoryItem[] = [];
2962
+ let latestSessionUserPrompt: string | null = null;
2963
+ let pendingTurnPrompt: string | null = null;
2948
2964
  let contextUsageSnapshot: StudioContextUsageSnapshot = {
2949
2965
  tokens: null,
2950
2966
  contextWindow: null,
@@ -3261,6 +3277,7 @@ export default function (pi: ExtensionAPI) {
3261
3277
  };
3262
3278
 
3263
3279
  const syncStudioResponseHistory = (entries: SessionEntry[]) => {
3280
+ latestSessionUserPrompt = findLatestUserPrompt(entries);
3264
3281
  studioResponseHistory = buildResponseHistoryFromEntries(entries, RESPONSE_HISTORY_LIMIT);
3265
3282
  const latest = studioResponseHistory[studioResponseHistory.length - 1];
3266
3283
  if (!latest) {
@@ -3470,7 +3487,7 @@ export default function (pi: ExtensionAPI) {
3470
3487
  return { ok: true, kind };
3471
3488
  };
3472
3489
 
3473
- const beginRequest = (requestId: string, kind: StudioRequestKind): boolean => {
3490
+ const beginRequest = (requestId: string, kind: StudioRequestKind, prompt?: string | null): boolean => {
3474
3491
  suppressedStudioResponse = null;
3475
3492
  emitDebugEvent("begin_request_attempt", {
3476
3493
  requestId,
@@ -3501,6 +3518,7 @@ export default function (pi: ExtensionAPI) {
3501
3518
  activeRequest = {
3502
3519
  id: requestId,
3503
3520
  kind,
3521
+ prompt: normalizePromptText(prompt),
3504
3522
  startedAt: Date.now(),
3505
3523
  timer,
3506
3524
  };
@@ -3652,10 +3670,9 @@ export default function (pi: ExtensionAPI) {
3652
3670
  return;
3653
3671
  }
3654
3672
 
3655
- if (!beginRequest(msg.requestId, "critique")) return;
3656
-
3657
3673
  const lens = resolveLens(msg.lens, document);
3658
3674
  const prompt = buildCritiquePrompt(document, lens);
3675
+ if (!beginRequest(msg.requestId, "critique", prompt)) return;
3659
3676
 
3660
3677
  try {
3661
3678
  pi.sendUserMessage(prompt);
@@ -3682,7 +3699,7 @@ export default function (pi: ExtensionAPI) {
3682
3699
  return;
3683
3700
  }
3684
3701
 
3685
- if (!beginRequest(msg.requestId, "annotation")) return;
3702
+ if (!beginRequest(msg.requestId, "annotation", text)) return;
3686
3703
 
3687
3704
  try {
3688
3705
  pi.sendUserMessage(text);
@@ -3709,7 +3726,7 @@ export default function (pi: ExtensionAPI) {
3709
3726
  return;
3710
3727
  }
3711
3728
 
3712
- if (!beginRequest(msg.requestId, "direct")) return;
3729
+ if (!beginRequest(msg.requestId, "direct", msg.text)) return;
3713
3730
 
3714
3731
  try {
3715
3732
  pi.sendUserMessage(msg.text);
@@ -4419,6 +4436,7 @@ export default function (pi: ExtensionAPI) {
4419
4436
  };
4420
4437
 
4421
4438
  pi.on("session_start", async (_event, ctx) => {
4439
+ pendingTurnPrompt = null;
4422
4440
  hydrateLatestAssistant(ctx.sessionManager.getBranch());
4423
4441
  clearCompactionState();
4424
4442
  agentBusy = false;
@@ -4436,6 +4454,7 @@ export default function (pi: ExtensionAPI) {
4436
4454
  pi.on("session_switch", async (_event, ctx) => {
4437
4455
  clearActiveRequest({ notify: "Session switched. Studio request state cleared.", level: "warning" });
4438
4456
  clearCompactionState();
4457
+ pendingTurnPrompt = null;
4439
4458
  lastCommandCtx = null;
4440
4459
  hydrateLatestAssistant(ctx.sessionManager.getBranch());
4441
4460
  agentBusy = false;
@@ -4523,6 +4542,11 @@ export default function (pi: ExtensionAPI) {
4523
4542
  activeRequestKind: activeRequest?.kind ?? null,
4524
4543
  });
4525
4544
 
4545
+ if (role === "user") {
4546
+ pendingTurnPrompt = extractUserText(event.message);
4547
+ return;
4548
+ }
4549
+
4526
4550
  // Assistant is handing off to tool calls; request is still in progress.
4527
4551
  if (stopReason === "toolUse") {
4528
4552
  emitDebugEvent("message_end_tool_use", {
@@ -4536,6 +4560,7 @@ export default function (pi: ExtensionAPI) {
4536
4560
  if (!markdown) return;
4537
4561
 
4538
4562
  if (suppressedStudioResponse) {
4563
+ pendingTurnPrompt = null;
4539
4564
  emitDebugEvent("suppressed_cancelled_response", {
4540
4565
  requestId: suppressedStudioResponse.requestId,
4541
4566
  kind: suppressedStudioResponse.kind,
@@ -4549,9 +4574,7 @@ export default function (pi: ExtensionAPI) {
4549
4574
  refreshContextUsage(ctx);
4550
4575
  const latestHistoryItem = studioResponseHistory[studioResponseHistory.length - 1];
4551
4576
  if (!latestHistoryItem || latestHistoryItem.markdown !== markdown) {
4552
- const fallbackPrompt = studioResponseHistory.length > 0
4553
- ? studioResponseHistory[studioResponseHistory.length - 1]?.prompt ?? null
4554
- : null;
4577
+ const fallbackPrompt = activeRequest?.prompt ?? pendingTurnPrompt ?? latestSessionUserPrompt ?? null;
4555
4578
  const fallbackHistoryItem: StudioResponseHistoryItem = {
4556
4579
  id: randomUUID(),
4557
4580
  markdown,
@@ -4567,6 +4590,7 @@ export default function (pi: ExtensionAPI) {
4567
4590
  const latestItem = studioResponseHistory[studioResponseHistory.length - 1];
4568
4591
  const responseTimestamp = latestItem?.timestamp ?? Date.now();
4569
4592
  const responseThinking = latestItem?.thinking ?? thinking ?? null;
4593
+ pendingTurnPrompt = null;
4570
4594
 
4571
4595
  if (activeRequest) {
4572
4596
  const requestId = activeRequest.id;
@@ -4627,6 +4651,7 @@ export default function (pi: ExtensionAPI) {
4627
4651
 
4628
4652
  pi.on("agent_end", async () => {
4629
4653
  agentBusy = false;
4654
+ pendingTurnPrompt = null;
4630
4655
  refreshContextUsage();
4631
4656
  emitDebugEvent("agent_end", {
4632
4657
  activeRequestId: activeRequest?.id ?? null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-studio",
3
- "version": "0.5.15",
3
+ "version": "0.5.17",
4
4
  "description": "Browser GUI for structured critique workflows in pi",
5
5
  "type": "module",
6
6
  "license": "MIT",