pipane 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.
Files changed (99) hide show
  1. package/README.md +34 -1
  2. package/THIRD_PARTY_NOTICES.md +29 -0
  3. package/dist/client/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  4. package/dist/client/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  5. package/dist/client/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  6. package/dist/client/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  7. package/dist/client/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  8. package/dist/client/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  9. package/dist/client/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  10. package/dist/client/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  11. package/dist/client/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  12. package/dist/client/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  13. package/dist/client/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  14. package/dist/client/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  15. package/dist/client/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  16. package/dist/client/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  17. package/dist/client/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  18. package/dist/client/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  19. package/dist/client/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  20. package/dist/client/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  21. package/dist/client/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  22. package/dist/client/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  23. package/dist/client/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  24. package/dist/client/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  25. package/dist/client/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  26. package/dist/client/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  27. package/dist/client/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  28. package/dist/client/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  29. package/dist/client/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  30. package/dist/client/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  31. package/dist/client/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  32. package/dist/client/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  33. package/dist/client/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  34. package/dist/client/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  35. package/dist/client/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  36. package/dist/client/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  37. package/dist/client/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  38. package/dist/client/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  39. package/dist/client/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  40. package/dist/client/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  41. package/dist/client/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  42. package/dist/client/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  43. package/dist/client/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  44. package/dist/client/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  45. package/dist/client/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  46. package/dist/client/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  47. package/dist/client/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  48. package/dist/client/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  49. package/dist/client/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  50. package/dist/client/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  51. package/dist/client/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  52. package/dist/client/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  53. package/dist/client/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  54. package/dist/client/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  55. package/dist/client/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  56. package/dist/client/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  57. package/dist/client/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  58. package/dist/client/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  59. package/dist/client/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  60. package/dist/client/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  61. package/dist/client/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  62. package/dist/client/assets/index-Dl_wdLZH.css +1 -0
  63. package/dist/client/assets/index-hNqbnG06.js +2482 -0
  64. package/dist/client/index.html +2 -2
  65. package/dist/server/server/bundled-extensions.js +6 -0
  66. package/dist/server/server/extension-status.js +31 -0
  67. package/dist/server/server/local-settings.js +15 -0
  68. package/dist/server/server/pi-runtime.js +1 -1
  69. package/dist/server/server/process-pool.js +177 -120
  70. package/dist/server/server/rest-api.js +7 -74
  71. package/dist/server/server/server.js +37 -49
  72. package/dist/server/server/session-actor.js +202 -0
  73. package/dist/server/server/session-index.js +18 -2
  74. package/dist/server/server/session-jsonl.js +49 -6
  75. package/dist/server/server/session-registry.js +67 -0
  76. package/dist/server/server/worktree-name.js +39 -0
  77. package/dist/server/server/ws-handler.js +630 -342
  78. package/dist/server/shared/jsonl-sync.js +114 -3
  79. package/dist/server/shared/rpc-timeouts.js +4 -0
  80. package/dist/server/shared/thinking-levels.js +94 -0
  81. package/dist/server/shared/ws-protocol.js +1 -0
  82. package/extensions/canvas.ts +1 -1
  83. package/package.json +26 -14
  84. package/dist/client/assets/_node-stub_node_crypto-C_7epg3G.js +0 -1
  85. package/dist/client/assets/_node-stub_node_fs-B-VOzeXW.js +0 -1
  86. package/dist/client/assets/_node-stub_node_http-CROXaVGJ.js +0 -1
  87. package/dist/client/assets/_node-stub_node_os-TTKJha15.js +0 -1
  88. package/dist/client/assets/_node-stub_node_path-DJCJiKv7.js +0 -1
  89. package/dist/client/assets/_node-stub_stream-DDexIdn_.js +0 -1
  90. package/dist/client/assets/index-Bf_69wyB.js +0 -3
  91. package/dist/client/assets/index-CUE9pgq9.css +0 -1
  92. package/dist/client/assets/index-CekVLaL6.js +0 -1
  93. package/dist/client/assets/index-Cp2vV6HJ.js +0 -4433
  94. package/dist/client/assets/index-D14II7Hd.js +0 -45
  95. package/dist/client/assets/index-D16nGmo3.js +0 -131
  96. package/dist/server/server/attached-session.js +0 -209
  97. package/dist/server/server/load-trace-store.js +0 -48
  98. package/dist/server/server/session-lifecycle.js +0 -155
  99. package/patches/@mariozechner+pi-web-ui+0.55.3.patch +0 -3437
@@ -11,21 +11,91 @@
11
11
  * This module is isomorphic — works in both Node.js and browser.
12
12
  */
13
13
  // ── Hashing ────────────────────────────────────────────────────────────────
14
+ /**
15
+ * Pure JS SHA-256 for environments without SubtleCrypto (HTTP browser contexts).
16
+ */
17
+ function sha256js(str) {
18
+ const K = [
19
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
20
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
21
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
22
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
23
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
24
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
25
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
26
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
27
+ ];
28
+ const rr = (v, n) => (v >>> n) | (v << (32 - n));
29
+ // UTF-8 encode
30
+ const bytes = new TextEncoder().encode(str);
31
+ const bitLen = bytes.length * 8;
32
+ // Padding
33
+ const padded = new Uint8Array(Math.ceil((bytes.length + 9) / 64) * 64);
34
+ padded.set(bytes);
35
+ padded[bytes.length] = 0x80;
36
+ const dv = new DataView(padded.buffer);
37
+ dv.setUint32(padded.length - 4, bitLen, false);
38
+ if (bitLen > 0xffffffff)
39
+ dv.setUint32(padded.length - 8, Math.floor(bitLen / 0x100000000), false);
40
+ let [h0, h1, h2, h3, h4, h5, h6, h7] = [
41
+ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
42
+ ];
43
+ const w = new Int32Array(64);
44
+ for (let off = 0; off < padded.length; off += 64) {
45
+ for (let i = 0; i < 16; i++)
46
+ w[i] = dv.getInt32(off + i * 4, false);
47
+ for (let i = 16; i < 64; i++) {
48
+ const s0 = rr(w[i - 15], 7) ^ rr(w[i - 15], 18) ^ (w[i - 15] >>> 3);
49
+ const s1 = rr(w[i - 2], 17) ^ rr(w[i - 2], 19) ^ (w[i - 2] >>> 10);
50
+ w[i] = (w[i - 16] + s0 + w[i - 7] + s1) | 0;
51
+ }
52
+ let [a, b, c, d, e, f, g, h] = [h0, h1, h2, h3, h4, h5, h6, h7];
53
+ for (let i = 0; i < 64; i++) {
54
+ const S1 = rr(e, 6) ^ rr(e, 11) ^ rr(e, 25);
55
+ const ch = (e & f) ^ (~e & g);
56
+ const t1 = (h + S1 + ch + K[i] + w[i]) | 0;
57
+ const S0 = rr(a, 2) ^ rr(a, 13) ^ rr(a, 22);
58
+ const maj = (a & b) ^ (a & c) ^ (b & c);
59
+ const t2 = (S0 + maj) | 0;
60
+ h = g;
61
+ g = f;
62
+ f = e;
63
+ e = (d + t1) | 0;
64
+ d = c;
65
+ c = b;
66
+ b = a;
67
+ a = (t1 + t2) | 0;
68
+ }
69
+ h0 = (h0 + a) | 0;
70
+ h1 = (h1 + b) | 0;
71
+ h2 = (h2 + c) | 0;
72
+ h3 = (h3 + d) | 0;
73
+ h4 = (h4 + e) | 0;
74
+ h5 = (h5 + f) | 0;
75
+ h6 = (h6 + g) | 0;
76
+ h7 = (h7 + h) | 0;
77
+ }
78
+ return [h0, h1, h2, h3, h4, h5, h6, h7].map(v => (v >>> 0).toString(16).padStart(8, "0")).join("");
79
+ }
14
80
  /**
15
81
  * Compute a hex SHA-256 hash of a string.
16
82
  * Works in both Node.js (crypto) and browser (SubtleCrypto).
17
83
  */
18
84
  export async function computeHash(data) {
19
85
  if (typeof globalThis.crypto?.subtle?.digest === "function") {
20
- // Browser or Node 20+
86
+ // Browser or Node 20+ (requires secure context in browsers)
21
87
  const encoded = new TextEncoder().encode(data);
22
88
  const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", encoded);
23
89
  const hashArray = new Uint8Array(hashBuffer);
24
90
  return Array.from(hashArray).map(b => b.toString(16).padStart(2, "0")).join("");
25
91
  }
26
92
  // Node.js fallback
27
- const { createHash } = await import("node:crypto");
28
- return createHash("sha256").update(data, "utf8").digest("hex");
93
+ if (typeof globalThis.process !== "undefined") {
94
+ const { createHash } = await import("node:crypto");
95
+ return createHash("sha256").update(data, "utf8").digest("hex");
96
+ }
97
+ // Browser insecure context fallback (no SubtleCrypto) — pure JS SHA-256
98
+ return sha256js(data);
29
99
  }
30
100
  // Note: computeHashSync is NOT in this shared module because it depends on
31
101
  // node:crypto which is stubbed in the client Vite build. It lives in
@@ -139,3 +209,44 @@ export async function applySyncOp(currentStr, currentHash, op) {
139
209
  }
140
210
  return { data: newStr, hash: op.hash };
141
211
  }
212
+ /**
213
+ * Client-side: apply an ordered batch of hash-chained sync operations.
214
+ *
215
+ * Every advertised base hash is checked, but the resulting data is hashed only
216
+ * once after the final operation. Verifying the final SHA-256 still detects any
217
+ * corrupted intermediate patch while avoiding O(batch size) full-state hashes
218
+ * during bursty streaming.
219
+ */
220
+ export async function applySyncOps(currentStr, currentHash, ops) {
221
+ if (ops.length === 0)
222
+ return { data: currentStr, hash: currentHash };
223
+ let data = currentStr;
224
+ let hash = currentHash;
225
+ let finalOp;
226
+ for (const op of ops) {
227
+ finalOp = op;
228
+ if (op.op === "full") {
229
+ data = op.data;
230
+ hash = op.hash;
231
+ continue;
232
+ }
233
+ if (op.baseHash !== hash) {
234
+ console.warn("[jsonl-sync] Base hash mismatch, need full sync", {
235
+ expected: op.baseHash,
236
+ actual: hash,
237
+ });
238
+ return null;
239
+ }
240
+ data = applyPatches(data, op.patches);
241
+ hash = op.hash;
242
+ }
243
+ const actualHash = await computeHash(data);
244
+ if (actualHash !== hash) {
245
+ const message = finalOp?.op === "full"
246
+ ? "[jsonl-sync] Full sync hash mismatch"
247
+ : "[jsonl-sync] Post-patch hash mismatch";
248
+ console.error(message, { expected: hash, actual: actualHash });
249
+ return null;
250
+ }
251
+ return { data, hash };
252
+ }
@@ -0,0 +1,4 @@
1
+ /** Compaction performs an LLM summarization and can legitimately take minutes. */
2
+ export const COMPACT_RPC_TIMEOUT_MS = 10 * 60_000;
3
+ /** Allow for process acquisition/switching before the server starts compaction. */
4
+ export const COMPACT_CLIENT_TIMEOUT_MS = COMPACT_RPC_TIMEOUT_MS + 90_000;
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Version-tolerant thinking-level helpers.
3
+ *
4
+ * pipane can talk to a newer pi RPC process than the pi packages it compiles
5
+ * against, so this module intentionally uses structural types instead of
6
+ * importing ThinkingLevel or Model from a pinned dependency.
7
+ */
8
+ export const THINKING_LEVEL_ORDER = [
9
+ "off",
10
+ "minimal",
11
+ "low",
12
+ "medium",
13
+ "high",
14
+ "xhigh",
15
+ "max",
16
+ ];
17
+ /** Return the model ID from either a full RPC model or a compact session ref. */
18
+ export function getModelId(model) {
19
+ return model?.id ?? model?.modelId;
20
+ }
21
+ /** Compare full models and compact model refs by their stable wire identity. */
22
+ export function modelsMatch(a, b) {
23
+ const aId = getModelId(a);
24
+ const bId = getModelId(b);
25
+ return !!a && !!b && !!aId && !!bId && a.provider === b.provider && aId === bId;
26
+ }
27
+ /** Convert a full model or compact ref to the shape accepted by pi RPC. */
28
+ export function toCompactModelRef(model) {
29
+ const modelId = getModelId(model);
30
+ if (!model.provider || !modelId)
31
+ return undefined;
32
+ return { provider: model.provider, modelId };
33
+ }
34
+ /**
35
+ * Whether the model has adjustable reasoning controls.
36
+ *
37
+ * Modern pi models include `reasoning`; the fallbacks keep compatibility with
38
+ * older/custom model metadata where that field may be absent.
39
+ */
40
+ export function modelSupportsThinking(model) {
41
+ if (!model)
42
+ return false;
43
+ if (typeof model.reasoning === "boolean")
44
+ return model.reasoning;
45
+ if (model.thinkingLevelMap)
46
+ return true;
47
+ const provider = String(model.provider ?? "").toLowerCase();
48
+ const id = String(getModelId(model) ?? "").toLowerCase();
49
+ if (provider === "openai-codex")
50
+ return true;
51
+ if (provider === "openai" && id.startsWith("gpt-5"))
52
+ return true;
53
+ return false;
54
+ }
55
+ /**
56
+ * Mirror current pi capability semantics:
57
+ * - non-reasoning models support only `off`
58
+ * - standard levels through `high` are supported unless explicitly null
59
+ * - `xhigh` and `max` are opt-in and require a non-null map entry
60
+ */
61
+ export function getSupportedThinkingLevels(model) {
62
+ if (!modelSupportsThinking(model))
63
+ return ["off"];
64
+ const map = model?.thinkingLevelMap;
65
+ return THINKING_LEVEL_ORDER.filter((level) => {
66
+ const mapped = map?.[level];
67
+ if (mapped === null)
68
+ return false;
69
+ if (level === "xhigh" || level === "max")
70
+ return mapped !== undefined;
71
+ return true;
72
+ });
73
+ }
74
+ /** Clamp exactly as pi does: prefer the next supported level, then search down. */
75
+ export function clampThinkingLevel(model, requested) {
76
+ const available = getSupportedThinkingLevels(model);
77
+ if (available.includes(requested)) {
78
+ return requested;
79
+ }
80
+ const requestedIndex = THINKING_LEVEL_ORDER.indexOf(requested);
81
+ if (requestedIndex === -1)
82
+ return available[0] ?? "off";
83
+ for (let i = requestedIndex; i < THINKING_LEVEL_ORDER.length; i++) {
84
+ const candidate = THINKING_LEVEL_ORDER[i];
85
+ if (available.includes(candidate))
86
+ return candidate;
87
+ }
88
+ for (let i = requestedIndex - 1; i >= 0; i--) {
89
+ const candidate = THINKING_LEVEL_ORDER[i];
90
+ if (available.includes(candidate))
91
+ return candidate;
92
+ }
93
+ return available[0] ?? "off";
94
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -5,7 +5,7 @@
5
5
  * content in a side panel. Accepts either inline content or a file path.
6
6
  */
7
7
 
8
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
8
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
9
9
  import { Type } from "@sinclair/typebox";
10
10
  import { readFile } from "node:fs/promises";
11
11
  import { resolve } from "node:path";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pipane",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "A clean web interface for the pi coding agent — chat UI with real-time tool calls, streaming output, session management, and model picker",
5
5
  "private": false,
6
6
  "type": "module",
@@ -25,7 +25,7 @@
25
25
  "developer-tools"
26
26
  ],
27
27
  "engines": {
28
- "node": ">=20.0.0"
28
+ "node": ">=22.19.0"
29
29
  },
30
30
  "bin": {
31
31
  "pipane": "bin/pipane.js"
@@ -34,43 +34,55 @@
34
34
  "dist/",
35
35
  "bin/",
36
36
  "extensions/",
37
- "patches/",
38
37
  "LICENSE",
39
- "README.md"
38
+ "README.md",
39
+ "THIRD_PARTY_NOTICES.md"
40
40
  ],
41
41
  "scripts": {
42
- "postinstall": "patch-package",
43
42
  "dev": "./dev.sh",
43
+ "deploy:dev": "./deploy-dev.sh",
44
44
  "dev:server": "tsx watch src/server/server.ts",
45
45
  "dev:client": "vite",
46
46
  "build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && vite build && tsc -p tsconfig.server.json",
47
47
  "start": "NODE_ENV=production node dist/server/server/server.js",
48
48
  "prod": "./prod.sh",
49
49
  "check": "tsc --noEmit",
50
- "test": "vitest run",
50
+ "test": "npm run test:unit",
51
+ "test:unit": "vitest run",
51
52
  "test:watch": "vitest",
52
- "test:screenshots": "node node_modules/@playwright/test/cli.js test -c playwright.config.ts e2e/ui-screenshots.e2e.ts",
53
- "test:screenshots:update": "node node_modules/@playwright/test/cli.js test -c playwright.config.ts e2e/ui-screenshots.e2e.ts --update-snapshots",
53
+ "test:coverage": "vitest run --coverage",
54
+ "test:e2e": "npm run build && node node_modules/@playwright/test/cli.js test -c playwright.config.ts --timeout 60000",
55
+ "test:e2e:stress": "npm run build && node node_modules/@playwright/test/cli.js test -c playwright.config.ts e2e/real-stack.e2e.ts e2e/rerun-duplicate.e2e.ts --timeout 60000 --repeat-each 50 --workers 2",
56
+ "test:all": "npm run check && npm run test:coverage && npm run test:e2e",
57
+ "test:screenshots": "npm run build && node node_modules/@playwright/test/cli.js test -c playwright.config.ts e2e/ui-screenshots.e2e.ts",
58
+ "test:screenshots:update": "npm run build && node node_modules/@playwright/test/cli.js test -c playwright.config.ts e2e/ui-screenshots.e2e.ts --update-snapshots",
59
+ "test:walkthrough": "npm run build && RUN_WALKTHROUGH=1 node node_modules/@playwright/test/cli.js test -c playwright.walkthrough.config.ts --timeout 180000",
54
60
  "bench:sessions": "tsx scripts/bench-sessions.ts",
55
61
  "prepack": "npm run build"
56
62
  },
57
63
  "dependencies": {
64
+ "@earendil-works/pi-agent-core": "0.80.10",
65
+ "@earendil-works/pi-ai": "0.80.10",
66
+ "@earendil-works/pi-coding-agent": "0.80.10",
67
+ "@earendil-works/pi-tui": "0.80.10",
58
68
  "@mariozechner/mini-lit": "^0.2.0",
59
- "@mariozechner/pi-agent-core": "0.55.3",
60
- "@mariozechner/pi-ai": "0.55.3",
61
- "@mariozechner/pi-coding-agent": "0.55.3",
62
- "@mariozechner/pi-web-ui": "0.55.3",
69
+ "@sreetej510/pi-usage": "0.1.20",
63
70
  "@tailwindcss/vite": "^4.1.17",
71
+ "docx-preview": "^0.3.7",
64
72
  "express": "^5.1.0",
73
+ "highlight.js": "^11.11.1",
74
+ "jszip": "^3.10.1",
65
75
  "lit": "^3.3.1",
66
76
  "lucide": "^0.544.0",
67
- "patch-package": "^8.0.1",
68
- "ws": "^8.18.0"
77
+ "pdfjs-dist": "5.4.394",
78
+ "ws": "^8.18.0",
79
+ "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
69
80
  },
70
81
  "devDependencies": {
71
82
  "@playwright/test": "^1.58.2",
72
83
  "@types/express": "^5.0.0",
73
84
  "@types/ws": "^8.5.10",
85
+ "@vitest/coverage-v8": "3.2.7",
74
86
  "concurrently": "^9.0.0",
75
87
  "happy-dom": "^20.7.0",
76
88
  "tsx": "^4.19.0",
@@ -1 +0,0 @@
1
- function _(r,o){for(var n=0;n<o.length;n++){const e=o[n];if(typeof e!="string"&&!Array.isArray(e)){for(const t in e)if(t!=="default"&&!(t in r)){const u=Object.getOwnPropertyDescriptor(e,t);u&&Object.defineProperty(r,t,u.get?u:{enumerable:!0,get:()=>e[t]})}}}return Object.freeze(Object.defineProperty(r,Symbol.toStringTag,{value:"Module"}))}const c=new Proxy({},{get(r,o){return o==="__esModule"?!0:new Proxy(function(){},{get:()=>()=>{},apply:()=>({})})}}),f=_({__proto__:null,default:c},[c]);export{f as _};
@@ -1 +0,0 @@
1
- function _(r,o){for(var n=0;n<o.length;n++){const e=o[n];if(typeof e!="string"&&!Array.isArray(e)){for(const t in e)if(t!=="default"&&!(t in r)){const f=Object.getOwnPropertyDescriptor(e,t);f&&Object.defineProperty(r,t,f.get?f:{enumerable:!0,get:()=>e[t]})}}}return Object.freeze(Object.defineProperty(r,Symbol.toStringTag,{value:"Module"}))}const u=new Proxy({},{get(r,o){return o==="__esModule"?!0:new Proxy(function(){},{get:()=>()=>{},apply:()=>({})})}}),s=_({__proto__:null,default:u},[u]);export{s as _};
@@ -1 +0,0 @@
1
- function f(r,o){for(var n=0;n<o.length;n++){const e=o[n];if(typeof e!="string"&&!Array.isArray(e)){for(const t in e)if(t!=="default"&&!(t in r)){const u=Object.getOwnPropertyDescriptor(e,t);u&&Object.defineProperty(r,t,u.get?u:{enumerable:!0,get:()=>e[t]})}}}return Object.freeze(Object.defineProperty(r,Symbol.toStringTag,{value:"Module"}))}const _=new Proxy({},{get(r,o){return o==="__esModule"?!0:new Proxy(function(){},{get:()=>()=>{},apply:()=>({})})}}),c=f({__proto__:null,default:_},[_]);export{c as _};
@@ -1 +0,0 @@
1
- function f(r,o){for(var n=0;n<o.length;n++){const e=o[n];if(typeof e!="string"&&!Array.isArray(e)){for(const t in e)if(t!=="default"&&!(t in r)){const u=Object.getOwnPropertyDescriptor(e,t);u&&Object.defineProperty(r,t,u.get?u:{enumerable:!0,get:()=>e[t]})}}}return Object.freeze(Object.defineProperty(r,Symbol.toStringTag,{value:"Module"}))}const _=new Proxy({},{get(r,o){return o==="__esModule"?!0:new Proxy(function(){},{get:()=>()=>{},apply:()=>({})})}}),s=f({__proto__:null,default:_},[_]);export{s as _};
@@ -1 +0,0 @@
1
- function a(r,o){for(var n=0;n<o.length;n++){const e=o[n];if(typeof e!="string"&&!Array.isArray(e)){for(const t in e)if(t!=="default"&&!(t in r)){const u=Object.getOwnPropertyDescriptor(e,t);u&&Object.defineProperty(r,t,u.get?u:{enumerable:!0,get:()=>e[t]})}}}return Object.freeze(Object.defineProperty(r,Symbol.toStringTag,{value:"Module"}))}const _=new Proxy({},{get(r,o){return o==="__esModule"?!0:new Proxy(function(){},{get:()=>()=>{},apply:()=>({})})}}),f=a({__proto__:null,default:_},[_]);export{f as _};
@@ -1 +0,0 @@
1
- function u(t,e){for(var o=0;o<e.length;o++){const r=e[o];if(typeof r!="string"&&!Array.isArray(r)){for(const n in r)if(n!=="default"&&!(n in t)){const _=Object.getOwnPropertyDescriptor(r,n);_&&Object.defineProperty(t,n,_.get?_:{enumerable:!0,get:()=>r[n]})}}}return Object.freeze(Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}))}const s=new Proxy({},{get(t,e){return e==="__esModule"?!0:new Proxy(function(){},{get:()=>()=>{},apply:()=>({})})}}),c=u({__proto__:null,default:s},[s]),a=new Proxy({},{get(t,e){return e==="__esModule"?!0:new Proxy(function(){},{get:()=>()=>{},apply:()=>({})})}}),f=u({__proto__:null,default:a},[a]),l=new Proxy({},{get(t,e){return e==="__esModule"?!0:new Proxy(function(){},{get:()=>()=>{},apply:()=>({})})}}),p=u({__proto__:null,default:l},[l]);export{l as _,a,s as b,c,f as d,p as e};
@@ -1,3 +0,0 @@
1
- import{b as N,H as B}from"./index-Cp2vV6HJ.js";import{_ as M,a as x,b as P}from"./_node-stub_stream-DDexIdn_.js";const F=["ECONNRESET","EPIPE","ETIMEDOUT"],I=o=>{const e={};for(const t of Object.keys(o)){const n=o[t];e[t]=Array.isArray(n)?n.join(","):n}return e},m={setTimeout:(o,e)=>setTimeout(o,e),clearTimeout:o=>clearTimeout(o)},H=1e3,K=(o,e,t=0)=>{if(!t)return-1;const n=r=>{const i=m.setTimeout(()=>{o.destroy(),e(Object.assign(new Error(`@smithy/node-http-handler - the request socket did not establish a connection with the server within the configured timeout of ${t} ms.`),{name:"TimeoutError"}))},t-r),s=f=>{f?.connecting?f.on("connect",()=>{m.clearTimeout(i)}):m.clearTimeout(i)};o.socket?s(o.socket):o.on("socket",s)};return t<2e3?(n(0),0):m.setTimeout(n.bind(null,H),H)},V=(o,e,t=0,n,r)=>t?m.setTimeout(()=>{let i=`@smithy/node-http-handler - [${n?"ERROR":"WARN"}] a request has exceeded the configured ${t} ms requestTimeout.`;if(n){const s=Object.assign(new Error(i),{name:"TimeoutError",code:"ETIMEDOUT"});o.destroy(s),e(s)}else i+=" Init client requestHandler with throwOnRequestTimeout=true to turn this into an error.",r?.warn?.(i)},t):-1,z=3e3,q=(o,{keepAlive:e,keepAliveMsecs:t},n=z)=>{if(e!==!0)return-1;const r=()=>{o.socket?o.socket.setKeepAlive(e,t||0):o.on("socket",i=>{i.setKeepAlive(e,t||0)})};return n===0?(r(),0):m.setTimeout(r,n)},L=3e3,Q=(o,e,t=0)=>{const n=r=>{const i=t-r,s=()=>{o.destroy(),e(Object.assign(new Error(`@smithy/node-http-handler - the request socket timed out after ${t} ms of inactivity (configured by client requestHandler).`),{name:"TimeoutError"}))};o.socket?(o.socket.setTimeout(i,s),o.on("close",()=>o.socket?.removeListener("timeout",s))):o.setTimeout(i,s)};return 0<t&&t<6e3?(n(0),0):m.setTimeout(n.bind(null,t===0?0:L),L)},U=6e3;async function O(o,e,t=U,n=!1){const r=e.headers??{},i=r.Expect||r.expect;let s=-1,f=!0;!n&&i==="100-continue"&&(f=await Promise.race([new Promise(c=>{s=Number(m.setTimeout(()=>c(!0),Math.max(U,t)))}),new Promise(c=>{o.on("continue",()=>{m.clearTimeout(s),c(!0)}),o.on("response",()=>{m.clearTimeout(s),c(!1)}),o.on("error",()=>{m.clearTimeout(s),c(!1)})})])),f&&J(o,e.body)}function J(o,e){if(e instanceof M.Readable){e.pipe(o);return}if(e){const t=Buffer.isBuffer(e);if(t||typeof e=="string"){t&&e.byteLength===0?o.end():o.end(e);return}const r=e;if(typeof r=="object"&&r.buffer&&typeof r.byteOffset=="number"&&typeof r.byteLength=="number"){o.end(Buffer.from(r.buffer,r.byteOffset,r.byteLength));return}o.end(Buffer.from(e));return}o.end()}const oe=0;class _{config;configProvider;socketWarningTimestamp=0;externalAgent=!1;metadata={handlerProtocol:"http/1.1"};static create(e){return typeof e?.handle=="function"?e:new _(e)}static checkSocketUsage(e,t,n=console){const{sockets:r,requests:i,maxSockets:s}=e;if(typeof s!="number"||s===1/0||Date.now()-15e3<t)return t;if(r&&i)for(const c in r){const g=r[c]?.length??0,d=i[c]?.length??0;if(g>=s&&d>=2*s)return n?.warn?.(`@smithy/node-http-handler:WARN - socket usage at capacity=${g} and ${d} additional requests are enqueued.
2
- See https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/node-configuring-maxsockets.html
3
- or increase socketAcquisitionWarningTimeout=(millis) in the NodeHttpHandler config.`),Date.now()}return t}constructor(e){this.configProvider=new Promise((t,n)=>{typeof e=="function"?e().then(r=>{t(this.resolveDefaultConfig(r))}).catch(n):t(this.resolveDefaultConfig(e))})}resolveDefaultConfig(e){const{requestTimeout:t,connectionTimeout:n,socketTimeout:r,socketAcquisitionWarningTimeout:i,httpAgent:s,httpsAgent:f,throwOnRequestTimeout:c,logger:g}=e||{},d=!0,E=50;return{connectionTimeout:n,requestTimeout:t,socketTimeout:r,socketAcquisitionWarningTimeout:i,throwOnRequestTimeout:c,httpAgent:s instanceof P.Agent||typeof s?.destroy=="function"?(this.externalAgent=!0,s):new P.Agent({keepAlive:d,maxSockets:E,...s}),httpsAgent:f instanceof x.Agent||typeof f?.destroy=="function"?(this.externalAgent=!0,f):new x.Agent({keepAlive:d,maxSockets:E,...f}),logger:g}}destroy(){this.config?.httpAgent?.destroy(),this.config?.httpsAgent?.destroy()}async handle(e,{abortSignal:t,requestTimeout:n}={}){return this.config||(this.config=await this.configProvider),new Promise((r,i)=>{const s=this.config;let f;const c=[],g=async a=>{await f,c.forEach(m.clearTimeout),r(a)},d=async a=>{await f,c.forEach(m.clearTimeout),i(a)};if(t?.aborted){const a=new Error("Request aborted");a.name="AbortError",d(a);return}const E=e.protocol==="https:",b=e.headers??{},R=(b.Expect??b.expect)==="100-continue";let C=E?s.httpsAgent:s.httpAgent;R&&!this.externalAgent&&(C=new(E?x.Agent:P.Agent)({keepAlive:!1,maxSockets:1/0})),c.push(m.setTimeout(()=>{this.socketWarningTimestamp=_.checkSocketUsage(C,this.socketWarningTimestamp,s.logger)},s.socketAcquisitionWarningTimeout??(s.requestTimeout??2e3)+(s.connectionTimeout??1e3)));const S=N(e.query||{});let k;if(e.username!=null||e.password!=null){const a=e.username??"",u=e.password??"";k=`${a}:${u}`}let A=e.path;S&&(A+=`?${S}`),e.fragment&&(A+=`#${e.fragment}`);let T=e.hostname??"";T[0]==="["&&T.endsWith("]")?T=e.hostname.slice(1,-1):T=e.hostname;const v={headers:e.headers,host:T,method:e.method,path:A,port:e.port,agent:C,auth:k},l=(E?x.request:P.request)(v,a=>{const u=new B({statusCode:a.statusCode||-1,reason:a.statusMessage,headers:I(a.headers),body:a});g({response:u})});if(l.on("error",a=>{F.includes(a.code)?d(Object.assign(a,{name:"TimeoutError"})):d(a)}),t){const a=()=>{l.destroy();const u=new Error("Request aborted");u.name="AbortError",d(u)};if(typeof t.addEventListener=="function"){const u=t;u.addEventListener("abort",a,{once:!0}),l.once("close",()=>u.removeEventListener("abort",a))}else t.onabort=a}const p=n??s.requestTimeout;c.push(K(l,d,s.connectionTimeout)),c.push(V(l,d,p,s.throwOnRequestTimeout,s.logger??console)),c.push(Q(l,d,s.socketTimeout));const w=v.agent;typeof w=="object"&&"keepAlive"in w&&c.push(q(l,{keepAlive:w.keepAlive,keepAliveMsecs:w.keepAliveMsecs})),f=O(l,e,p,this.externalAgent).catch(a=>(c.forEach(m.clearTimeout),i(a)))})}updateHttpClientConfig(e,t){this.config=void 0,this.configProvider=this.configProvider.then(n=>({...n,[e]:t}))}httpHandlerConfigs(){return this.config??{}}}const $=new Proxy({},{get(o,e){return e==="__esModule"?!0:new Proxy(function(){},{get:()=>()=>{},apply:()=>({})})}});class G{sessions=[];constructor(e){this.sessions=e??[]}poll(){if(this.sessions.length>0)return this.sessions.shift()}offerLast(e){this.sessions.push(e)}contains(e){return this.sessions.includes(e)}remove(e){this.sessions=this.sessions.filter(t=>t!==e)}[Symbol.iterator](){return this.sessions[Symbol.iterator]()}destroy(e){for(const t of this.sessions)t===e&&(t.destroyed||t.destroy())}}class X{constructor(e){if(this.config=e,this.config.maxConcurrency&&this.config.maxConcurrency<=0)throw new RangeError("maxConcurrency must be greater than zero.")}config;sessionCache=new Map;lease(e,t){const n=this.getUrlString(e),r=this.sessionCache.get(n);if(r){const c=r.poll();if(c&&!this.config.disableConcurrency)return c}const i=$.connect(n);this.config.maxConcurrency&&i.settings({maxConcurrentStreams:this.config.maxConcurrency},c=>{if(c)throw new Error("Fail to set maxConcurrentStreams to "+this.config.maxConcurrency+"when creating new session for "+e.destination.toString())}),i.unref();const s=()=>{i.destroy(),this.deleteSession(n,i)};i.on("goaway",s),i.on("error",s),i.on("frameError",s),i.on("close",()=>this.deleteSession(n,i)),t.requestTimeout&&i.setTimeout(t.requestTimeout,s);const f=this.sessionCache.get(n)||new G;return f.offerLast(i),this.sessionCache.set(n,f),i}deleteSession(e,t){const n=this.sessionCache.get(e);n&&n.contains(t)&&(n.remove(t),this.sessionCache.set(e,n))}release(e,t){const n=this.getUrlString(e);this.sessionCache.get(n)?.offerLast(t)}destroy(){for(const[e,t]of this.sessionCache){for(const n of t)n.destroyed||n.destroy(),t.remove(n);this.sessionCache.delete(e)}}setMaxConcurrentStreams(e){if(e&&e<=0)throw new RangeError("maxConcurrentStreams must be greater than zero.");this.config.maxConcurrency=e}setDisableConcurrentStreams(e){this.config.disableConcurrency=e}getUrlString(e){return e.destination.toString()}}class j{config;configProvider;metadata={handlerProtocol:"h2"};connectionManager=new X({});static create(e){return typeof e?.handle=="function"?e:new j(e)}constructor(e){this.configProvider=new Promise((t,n)=>{typeof e=="function"?e().then(r=>{t(r||{})}).catch(n):t(e||{})})}destroy(){this.connectionManager.destroy()}async handle(e,{abortSignal:t,requestTimeout:n}={}){this.config||(this.config=await this.configProvider,this.connectionManager.setDisableConcurrentStreams(this.config.disableConcurrentStreams||!1),this.config.maxConcurrentStreams&&this.connectionManager.setMaxConcurrentStreams(this.config.maxConcurrentStreams));const{requestTimeout:r,disableConcurrentStreams:i}=this.config,s=n??r;return new Promise((f,c)=>{let g=!1,d;const E=async h=>{await d,f(h)},b=async h=>{await d,c(h)};if(t?.aborted){g=!0;const h=new Error("Request aborted");h.name="AbortError",b(h);return}const{hostname:R,method:C,port:S,protocol:k,query:A}=e;let T="";if(e.username!=null||e.password!=null){const h=e.username??"",y=e.password??"";T=`${h}:${y}@`}const v=`${k}//${T}${R}${S?`:${S}`:""}`,D={destination:new URL(v)},l=this.connectionManager.lease(D,{requestTimeout:this.config?.sessionTimeout,disableConcurrentStreams:i||!1}),p=h=>{i&&this.destroySession(l),g=!0,b(h)},w=N(A||{});let a=e.path;w&&(a+=`?${w}`),e.fragment&&(a+=`#${e.fragment}`);const u=l.request({...e.headers,[$.constants.HTTP2_HEADER_PATH]:a,[$.constants.HTTP2_HEADER_METHOD]:C});if(l.ref(),u.on("response",h=>{const y=new B({statusCode:h[":status"]||-1,headers:I(h),body:u});g=!0,E({response:y}),i&&(l.close(),this.connectionManager.deleteSession(v,l))}),s&&u.setTimeout(s,()=>{u.close();const h=new Error(`Stream timed out because of no activity for ${s} ms`);h.name="TimeoutError",p(h)}),t){const h=()=>{u.close();const y=new Error("Request aborted");y.name="AbortError",p(y)};if(typeof t.addEventListener=="function"){const y=t;y.addEventListener("abort",h,{once:!0}),u.once("close",()=>y.removeEventListener("abort",h))}else t.onabort=h}u.on("frameError",(h,y,W)=>{p(new Error(`Frame type id ${h} in stream id ${W} has failed with code ${y}.`))}),u.on("error",p),u.on("aborted",()=>{p(new Error(`HTTP/2 stream is abnormally aborted in mid-communication with result code ${u.rstCode}.`))}),u.on("close",()=>{l.unref(),i&&l.destroy(),g||p(new Error("Unexpected error: http2 request did not get a response"))}),d=O(u,e,s)})}updateHttpClientConfig(e,t){this.config=void 0,this.configProvider=this.configProvider.then(n=>({...n,[e]:t}))}httpHandlerConfigs(){return this.config??{}}destroySession(e){e.destroyed||e.destroy()}}class Y extends M.Writable{bufferedBytes=[];_write(e,t,n){this.bufferedBytes.push(e),n()}}const se=o=>Z(o)?ee(o):new Promise((e,t)=>{const n=new Y;o.pipe(n),o.on("error",r=>{n.end(),t(r)}),n.on("error",t),n.on("finish",function(){const r=new Uint8Array(Buffer.concat(this.bufferedBytes));e(r)})}),Z=o=>typeof ReadableStream=="function"&&o instanceof ReadableStream;async function ee(o){const e=[],t=o.getReader();let n=!1,r=0;for(;!n;){const{done:f,value:c}=await t.read();c&&(e.push(c),r+=c.length),n=f}const i=new Uint8Array(r);let s=0;for(const f of e)i.set(f,s),s+=f.length;return i}export{oe as DEFAULT_REQUEST_TIMEOUT,j as NodeHttp2Handler,_ as NodeHttpHandler,se as streamCollector};