@firstpick/pi-package-webui 0.1.4 → 0.1.6

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/index.ts CHANGED
@@ -11,6 +11,8 @@ const webuiBin = path.join(packageRoot, "bin", "pi-webui.mjs");
11
11
  const DEFAULT_HOST = "127.0.0.1";
12
12
  const DEFAULT_PORT = 31415;
13
13
  const START_TIMEOUT_MS = 12_000;
14
+ const START_TIMEOUT_PER_RESTORED_TAB_MS = 4_000;
15
+ const START_TIMEOUT_MAX_MS = 60_000;
14
16
 
15
17
  type WebuiAddress = {
16
18
  host: string;
@@ -292,7 +294,18 @@ async function fetchRestorableTabs(url: string, existing: ExistingWebui, options
292
294
  const baseUrl = url.replace(/\/$/, "");
293
295
  const detailed = await fetchJsonWithTimeout(`${baseUrl}/api/webui-status?detailed=1&events=0`, {}, 7_000);
294
296
  const statusData = detailed?.ok && detailed.body?.ok === true ? detailed.body.data : undefined;
295
- return mergeRestorableTabsFromStatusSources([statusData?.restorableTabs, statusData?.tabs, existing.restorableTabs, existing.tabs], options);
297
+
298
+ // Restart should preserve the tabs that are currently open in the Web UI.
299
+ // Older servers may expose recently closed tabs through `restorableTabs`, so
300
+ // prefer explicit live tab lists whenever the running server provides them.
301
+ const openTabSources: unknown[] = [];
302
+ const detailedTabs = statusData?.tabs;
303
+ if (Array.isArray(detailedTabs)) openTabSources.push(detailedTabs);
304
+ if (Array.isArray(existing.tabs)) openTabSources.push(existing.tabs);
305
+ if (openTabSources.length > 0) return mergeRestorableTabsFromStatusSources(openTabSources, options);
306
+
307
+ // Legacy fallback for servers that predate `tabs` in status/health payloads.
308
+ return mergeRestorableTabsFromStatusSources([statusData?.restorableTabs, existing.restorableTabs], options);
296
309
  }
297
310
 
298
311
  function sleep(ms: number): Promise<void> {
@@ -449,7 +462,12 @@ function terminateFailedChild(child: WebuiChild): void {
449
462
  child.stderr.destroy();
450
463
  }
451
464
 
452
- function waitForWebuiUrl(child: WebuiChild): Promise<string> {
465
+ function startupTimeoutMs(restoreTabCount: number): number {
466
+ const extraTabs = Math.max(0, restoreTabCount - 1);
467
+ return Math.min(START_TIMEOUT_MAX_MS, START_TIMEOUT_MS + extraTabs * START_TIMEOUT_PER_RESTORED_TAB_MS);
468
+ }
469
+
470
+ function waitForWebuiUrl(child: WebuiChild, timeoutMs = START_TIMEOUT_MS): Promise<string> {
453
471
  return new Promise((resolve, reject) => {
454
472
  let settled = false;
455
473
  let output = "";
@@ -472,8 +490,8 @@ function waitForWebuiUrl(child: WebuiChild): Promise<string> {
472
490
  };
473
491
 
474
492
  const timeout = setTimeout(() => {
475
- finish(new Error(`Timed out waiting for Pi Web UI to start. Output:\n${output.trim() || "(no output)"}`));
476
- }, START_TIMEOUT_MS);
493
+ finish(new Error(`Timed out after ${Math.round(timeoutMs / 1000)}s waiting for Pi Web UI to start. Output:\n${output.trim() || "(no output)"}`));
494
+ }, timeoutMs);
477
495
 
478
496
  child.stdout.on("data", inspect);
479
497
  child.stderr.on("data", inspect);
@@ -501,7 +519,7 @@ async function startWebui(options: StartWebuiOptions, ctx: ExtensionCommandConte
501
519
  windowsHide: true,
502
520
  });
503
521
 
504
- return waitForWebuiUrl(child);
522
+ return waitForWebuiUrl(child, startupTimeoutMs(restoreTabs.length));
505
523
  }
506
524
 
507
525
  type WebuiStatusFetchResult = {
@@ -741,6 +759,34 @@ function statusUsage(): string {
741
759
  ].join("\n");
742
760
  }
743
761
 
762
+ type WebuiTreeNavigateArgs = {
763
+ entryId: string;
764
+ summarize?: boolean;
765
+ customInstructions?: string;
766
+ replaceInstructions?: boolean;
767
+ label?: string;
768
+ };
769
+
770
+ function parseWebuiTreeNavigateArgs(args: string): WebuiTreeNavigateArgs {
771
+ let parsed: unknown;
772
+ try {
773
+ parsed = JSON.parse(args || "{}");
774
+ } catch (error) {
775
+ throw new Error(`Invalid Web UI tree navigation payload: ${error instanceof Error ? error.message : String(error)}`);
776
+ }
777
+ if (!parsed || typeof parsed !== "object") throw new Error("Web UI tree navigation payload must be an object");
778
+ const payload = parsed as Record<string, unknown>;
779
+ const entryId = typeof payload.entryId === "string" ? payload.entryId.trim() : "";
780
+ if (!entryId) throw new Error("Web UI tree navigation requires entryId");
781
+ return {
782
+ entryId,
783
+ summarize: payload.summarize === true,
784
+ customInstructions: typeof payload.customInstructions === "string" ? payload.customInstructions : undefined,
785
+ replaceInstructions: payload.replaceInstructions === true,
786
+ label: typeof payload.label === "string" ? payload.label : undefined,
787
+ };
788
+ }
789
+
744
790
  export default function (pi: ExtensionAPI) {
745
791
  const startWebuiHandler = async (args: string, ctx: ExtensionCommandContext) => {
746
792
  let options: StartWebuiOptions;
@@ -780,11 +826,6 @@ export default function (pi: ExtensionAPI) {
780
826
  handler: startWebuiHandler,
781
827
  });
782
828
 
783
- pi.registerCommand("start-webui", {
784
- description: "Alias for /webui-start",
785
- handler: startWebuiHandler,
786
- });
787
-
788
829
  pi.registerCommand("webui-status", {
789
830
  description: "Show Pi Web UI URL, online state, network exposure, and optional detailed runtime info",
790
831
  handler: async (args, ctx) => {
@@ -807,4 +848,35 @@ export default function (pi: ExtensionAPI) {
807
848
  }
808
849
  },
809
850
  });
851
+
852
+ pi.registerCommand("webui-tree-navigate", {
853
+ description: "Internal Web UI helper for browser session-tree navigation",
854
+ handler: async (args, ctx) => {
855
+ let payload: WebuiTreeNavigateArgs;
856
+ try {
857
+ payload = parseWebuiTreeNavigateArgs(args);
858
+ } catch (error) {
859
+ ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
860
+ return;
861
+ }
862
+
863
+ try {
864
+ await ctx.waitForIdle();
865
+ const result = (await ctx.navigateTree(payload.entryId, {
866
+ summarize: payload.summarize,
867
+ customInstructions: payload.customInstructions,
868
+ replaceInstructions: payload.replaceInstructions,
869
+ label: payload.label,
870
+ })) as { cancelled: boolean; editorText?: string };
871
+ if (result.cancelled) {
872
+ ctx.ui.notify("Web UI tree navigation cancelled.", "warning");
873
+ return;
874
+ }
875
+ if (typeof result.editorText === "string") ctx.ui.setEditorText(result.editorText);
876
+ ctx.ui.notify("Web UI navigated the session tree.", "info");
877
+ } catch (error) {
878
+ ctx.ui.notify(`Web UI tree navigation failed:\n${error instanceof Error ? error.message : String(error)}`, "error");
879
+ }
880
+ },
881
+ });
810
882
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firstpick/pi-package-webui",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Pi Web UI companion package with a local browser UI CLI plus /webui-start and /webui-status commands.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -15,7 +15,29 @@
15
15
  ],
16
16
  "pi": {
17
17
  "extensions": [
18
- "./index.ts"
18
+ "./index.ts",
19
+ "../pi-extension-git-footer-status/index.ts",
20
+ "../pi-extension-release-aur/index.ts",
21
+ "../pi-extension-release-npm/index.ts",
22
+ "../pi-extension-stats/index.ts",
23
+ "../pi-extension-todo-progress/index.ts",
24
+ "node_modules/@firstpick/pi-extension-git-footer-status/index.ts",
25
+ "node_modules/@firstpick/pi-extension-release-aur/index.ts",
26
+ "node_modules/@firstpick/pi-extension-release-npm/index.ts",
27
+ "node_modules/@firstpick/pi-extension-stats/index.ts",
28
+ "node_modules/@firstpick/pi-extension-todo-progress/index.ts"
29
+ ],
30
+ "skills": [
31
+ "../pi-extension-release-aur/skills",
32
+ "node_modules/@firstpick/pi-extension-release-aur/skills"
33
+ ],
34
+ "prompts": [
35
+ "../pi-package-prompts-git-pr/prompts",
36
+ "node_modules/@firstpick/pi-prompts-git-pr/prompts"
37
+ ],
38
+ "themes": [
39
+ "../pi-package-themes-bundle/themes",
40
+ "node_modules/@firstpick/pi-themes-bundle/themes"
19
41
  ]
20
42
  },
21
43
  "bin": {
@@ -26,8 +48,16 @@
26
48
  "test": "node tests/mobile-static.test.mjs"
27
49
  },
28
50
  "dependencies": {
29
- "@earendil-works/pi-coding-agent": "^0.78.0",
30
- "@firstpick/pi-themes-bundle": "^0.1.0"
51
+ "@earendil-works/pi-coding-agent": "^0.78.0"
52
+ },
53
+ "optionalDependencies": {
54
+ "@firstpick/pi-extension-git-footer-status": "^0.2.1",
55
+ "@firstpick/pi-extension-release-aur": "^0.1.3",
56
+ "@firstpick/pi-extension-release-npm": "^0.3.3",
57
+ "@firstpick/pi-extension-stats": "^0.2.0",
58
+ "@firstpick/pi-extension-todo-progress": "^0.1.7",
59
+ "@firstpick/pi-prompts-git-pr": "^0.1.0",
60
+ "@firstpick/pi-themes-bundle": "^0.1.1"
31
61
  },
32
62
  "files": [
33
63
  "index.ts",