Mike0021 commited on
Commit
e6f21d9
·
verified ·
1 Parent(s): 8d35e43

Add product costs and inventory demo with a concise profit suggestion

Browse files
app/README.md CHANGED
@@ -20,6 +20,7 @@ For development, use `npm run dev`. To use the already converted weights on this
20
  - Streaming conversation with genuine `@earendil-works/pi-agent-core` tool execution and automatic follow-up turns.
21
  - `bash`, `read`, `write`, and exact-match `edit` tools connected to the same just-bash filesystem.
22
  - File editor with Prism syntax colors for HTML/XML/SVG, CSS, JavaScript/TypeScript (including JSX/TSX), Python, Bash, JSON, Markdown, YAML, TOML, SQL, CSV, C/C++, C#, Java, Go, Rust, Dockerfiles, and diffs. Highlighting runs in a separate, lazily started worker and is debounced while typing. Native textarea editing, selection, undo, and scrolling are preserved. Unknown formats, files above 200,000 characters or 5,000 lines, excessive token output, and grammar work exceeding 1.5 seconds fall back to plain text. Text-file import, workspace export, and an interactive command entry are also included.
 
23
  - Successful `read` tool calls open and highlight the resolved file in the workspace, including relative paths and symlinks. Following waits for newly created files, preserves unsaved edits, and keeps the conversation’s focus and page scroll position. Common file types use a local subset of 18 Seti icons from VS Code’s built-in theme, totaling about 12 KB of SVGs.
24
  - The shell highlights Bash as you type, accepts multiline commands (Shift+Enter), recalls session history (↑/↓), and separates command/output blocks with colored exit states and errors. Clear or Ctrl+L clears the output. Its output area can be resized vertically.
25
  - Model name, loading progress, and loaded status live in a rounded, inset top bar aligned with the workspace width. New chat sits at the bottom left of the terminal, opposite the send controls. Panels use background shading and rounded corners instead of separator borders.
@@ -76,6 +77,8 @@ npm run test:runtime-meter
76
  npm run test:follow-read
77
  # File highlighting, native editing, saving, scrolling and large-file fallback:
78
  npm run test:editor
 
 
79
  # Syntax-colored command input, actual shell execution, history and layouts:
80
  npm run test:shell
81
  # Actual token usage, streaming, cancellation, reload and input focus:
 
20
  - Streaming conversation with genuine `@earendil-works/pi-agent-core` tool execution and automatic follow-up turns.
21
  - `bash`, `read`, `write`, and exact-match `edit` tools connected to the same just-bash filesystem.
22
  - File editor with Prism syntax colors for HTML/XML/SVG, CSS, JavaScript/TypeScript (including JSX/TSX), Python, Bash, JSON, Markdown, YAML, TOML, SQL, CSV, C/C++, C#, Java, Go, Rust, Dockerfiles, and diffs. Highlighting runs in a separate, lazily started worker and is debounced while typing. Native textarea editing, selection, undo, and scrolling are preserved. Unknown formats, files above 200,000 characters or 5,000 lines, excessive token output, and grammar work exceeding 1.5 seconds fall back to plain text. Text-file import, workspace export, and an interactive command entry are also included.
23
+ - The starter workspace pairs `sales.csv` with `products.csv` (matching product names, unit costs, and current stock), giving Pi data to discover and combine for profit or inventory questions. The cat suggests “What's our total sales profit?” without prescribing files or tools. Older saved sales workspaces receive the new file once; existing product files and later deletions are preserved.
24
  - Successful `read` tool calls open and highlight the resolved file in the workspace, including relative paths and symlinks. Following waits for newly created files, preserves unsaved edits, and keeps the conversation’s focus and page scroll position. Common file types use a local subset of 18 Seti icons from VS Code’s built-in theme, totaling about 12 KB of SVGs.
25
  - The shell highlights Bash as you type, accepts multiline commands (Shift+Enter), recalls session history (↑/↓), and separates command/output blocks with colored exit states and errors. Clear or Ctrl+L clears the output. Its output area can be resized vertically.
26
  - Model name, loading progress, and loaded status live in a rounded, inset top bar aligned with the workspace width. New chat sits at the bottom left of the terminal, opposite the send controls. Panels use background shading and rounded corners instead of separator borders.
 
77
  npm run test:follow-read
78
  # File highlighting, native editing, saving, scrolling and large-file fallback:
79
  npm run test:editor
80
+ # Companion product data and preserving existing workspaces during its addition:
81
+ npm run test:starter-products
82
  # Syntax-colored command input, actual shell execution, history and layouts:
83
  npm run test:shell
84
  # Actual token usage, streaming, cancellation, reload and input focus:
app/package.json CHANGED
@@ -18,6 +18,7 @@
18
  "test:mascot": "node tests/mascot.mjs",
19
  "test:follow-read": "node tests/follow-read.mjs",
20
  "test:editor": "node tests/editor.mjs",
 
21
  "test:shell": "node tests/shell.mjs",
22
  "test:context": "node tests/context.mjs"
23
  },
 
18
  "test:mascot": "node tests/mascot.mjs",
19
  "test:follow-read": "node tests/follow-read.mjs",
20
  "test:editor": "node tests/editor.mjs",
21
+ "test:starter-products": "node tests/starter-products.mjs",
22
  "test:shell": "node tests/shell.mjs",
23
  "test:context": "node tests/context.mjs"
24
  },
app/src/agent.worker.mjs CHANGED
@@ -1,6 +1,6 @@
1
  import { Agent } from '@earendil-works/pi-agent-core';
2
  import { createInference, localModel } from './inference.mjs';
3
- import { createWorkspace } from './workspace.mjs';
4
  import { openStorage } from './storage.mjs';
5
  import { isModelCached } from './download.mjs';
6
 
@@ -18,7 +18,7 @@ You can create and edit text files, including HTML, CSS, JavaScript, and shell s
18
  Interpret short follow-ups using the conversation and inspect the relevant files when needed.
19
  Inspect files before editing them. After changing files, verify the result with a tool. Finish with a short factual answer.`;
20
 
21
- async function snapshot() { return { version: 1, files: await workspace.snapshot(), entries: await workspace.serialize(), messages: agent.state.messages }; }
22
  async function persist() {
23
  const state = await snapshot();
24
  try { await storage?.save(state); } catch (e) { emit({ type: 'persistence_error', error: 'Changes are in memory, but browser storage failed: ' + e.message }); }
@@ -31,6 +31,15 @@ const initialized = (async () => {
31
  catch (e) { emit({ type: 'persistence_error', error: 'Workspace saving is unavailable: ' + e.message }); }
32
  workspace = createWorkspace(saved?.files, saved?.entries);
33
  await workspace.ready;
 
 
 
 
 
 
 
 
 
34
  agent = new Agent({ initialState: { model: localModel, thinkingLevel: 'medium', systemPrompt, tools: workspace.tools, messages: saved?.messages ?? [] },
35
  streamFn: inference.streamFn, toolExecution: 'sequential',
36
  afterToolCall: ({ result, isError }) => ({ isError: isError || (result.details?.exitCode !== undefined && result.details.exitCode !== 0) }),
@@ -44,7 +53,12 @@ const initialized = (async () => {
44
  emit({ type: 'agent_event', event });
45
  if (event.type === 'tool_execution_end' || event.type === 'agent_end') await persist();
46
  });
47
- emit({ type: 'initialized', ...(await snapshot()) });
 
 
 
 
 
48
  })();
49
 
50
  onmessage = async ({ data }) => {
 
1
  import { Agent } from '@earendil-works/pi-agent-core';
2
  import { createInference, localModel } from './inference.mjs';
3
+ import { createWorkspace, initialFiles } from './workspace.mjs';
4
  import { openStorage } from './storage.mjs';
5
  import { isModelCached } from './download.mjs';
6
 
 
18
  Interpret short follow-ups using the conversation and inspect the relevant files when needed.
19
  Inspect files before editing them. After changing files, verify the result with a tool. Finish with a short factual answer.`;
20
 
21
+ async function snapshot() { return { version: 1, demoVersion: 1, files: await workspace.snapshot(), entries: await workspace.serialize(), messages: agent.state.messages }; }
22
  async function persist() {
23
  const state = await snapshot();
24
  try { await storage?.save(state); } catch (e) { emit({ type: 'persistence_error', error: 'Changes are in memory, but browser storage failed: ' + e.message }); }
 
31
  catch (e) { emit({ type: 'persistence_error', error: 'Workspace saving is unavailable: ' + e.message }); }
32
  workspace = createWorkspace(saved?.files, saved?.entries);
33
  await workspace.ready;
34
+ const upgradeDemo = saved && (saved.demoVersion ?? 0) < 1;
35
+ if (upgradeDemo) {
36
+ // Add this demo once to older sales workspaces. Existing files (including
37
+ // symlinks) win, and a later user deletion must survive subsequent reloads.
38
+ const paths = workspace.bash.fs.getAllPaths();
39
+ if (paths.includes('/workspace/sales.csv') && !paths.includes('/workspace/products.csv')) {
40
+ await workspace.write('/workspace/products.csv', initialFiles['/workspace/products.csv']);
41
+ }
42
+ }
43
  agent = new Agent({ initialState: { model: localModel, thinkingLevel: 'medium', systemPrompt, tools: workspace.tools, messages: saved?.messages ?? [] },
44
  streamFn: inference.streamFn, toolExecution: 'sequential',
45
  afterToolCall: ({ result, isError }) => ({ isError: isError || (result.details?.exitCode !== undefined && result.details.exitCode !== 0) }),
 
53
  emit({ type: 'agent_event', event });
54
  if (event.type === 'tool_execution_end' || event.type === 'agent_end') await persist();
55
  });
56
+ const state = await snapshot();
57
+ if (upgradeDemo) {
58
+ try { await storage?.save(state); }
59
+ catch (e) { emit({ type: 'persistence_error', error: 'Changes are in memory, but browser storage failed: ' + e.message }); }
60
+ }
61
+ emit({ type: 'initialized', ...state });
62
  })();
63
 
64
  onmessage = async ({ data }) => {
app/src/pi/empty-mascot.mjs CHANGED
@@ -65,7 +65,7 @@ export function createEmptyMascot(element, onSelect) {
65
  return {
66
  hide,
67
  get statusInset() { return mascot.hidden ? 0 : figure.offsetWidth + 20; },
68
- update({ screen, rows, viewportRows, contentRows, draft, hasSales }) {
69
  if (!screen || !rows) { hide(); return; }
70
  const area = screen.getBoundingClientRect(), root = element.getBoundingClientRect();
71
  const rowHeight = area.height / rows;
@@ -74,8 +74,12 @@ export function createEmptyMascot(element, onSelect) {
74
  if (!cat.hasAttribute('src')) cat.src = new URL('../../public/empty-cat-v1.png', import.meta.url).href;
75
  const withSuggestion = !draft.trim() && room >= 180;
76
  suggestion.hidden = !withSuggestion;
77
- suggestion.textContent = hasSales ? 'Ask me to calculate our total sales.' : 'Ask me to explore your files.';
78
- prompt = hasSales ? "What's our total sales revenue?" : 'Explore the workspace files and summarize what is here.';
 
 
 
 
79
  suggestion.setAttribute('aria-label', `Use suggested prompt: ${prompt}`);
80
  const size = Math.min(104, Math.floor(withSuggestion ? room - 76 : room));
81
  figure.style.width = figure.style.height = `${size}px`;
 
65
  return {
66
  hide,
67
  get statusInset() { return mascot.hidden ? 0 : figure.offsetWidth + 20; },
68
+ update({ screen, rows, viewportRows, contentRows, draft, hasSales, hasProducts }) {
69
  if (!screen || !rows) { hide(); return; }
70
  const area = screen.getBoundingClientRect(), root = element.getBoundingClientRect();
71
  const rowHeight = area.height / rows;
 
74
  if (!cat.hasAttribute('src')) cat.src = new URL('../../public/empty-cat-v1.png', import.meta.url).href;
75
  const withSuggestion = !draft.trim() && room >= 180;
76
  suggestion.hidden = !withSuggestion;
77
+ suggestion.textContent = hasSales
78
+ ? (hasProducts ? 'Ask me to calculate our total profit.' : 'Ask me to calculate our total sales.')
79
+ : 'Ask me to explore your files.';
80
+ prompt = hasSales
81
+ ? (hasProducts ? "What's our total sales profit?" : "What's our total sales revenue?")
82
+ : 'Explore the workspace files and summarize what is here.';
83
  suggestion.setAttribute('aria-label', `Use suggested prompt: ${prompt}`);
84
  const size = Math.min(104, Math.floor(withSuggestion ? room - 76 : room));
85
  figure.style.width = figure.style.height = `${size}px`;
app/src/pi/terminal-ui.mjs CHANGED
@@ -92,7 +92,7 @@ export function createTerminalUI(element, actions) {
92
  if (!hydrated || messages.some(message => message.role === 'user') || state.busy || inputPending) mascot.hide();
93
  else mascot.update({ screen, rows: xterm.rows,
94
  viewportRows: scroll.viewportHeight, contentRows: transcript.render(xterm.cols).length,
95
- draft: editor.getText(), hasSales: files.includes('sales.csv') });
96
  // Keep the status text to the left of the cat's lower body.
97
  const cellWidth = screen?.getBoundingClientRect().width / xterm.cols;
98
  const reserved = cellWidth > 0 ? Math.ceil(mascot.statusInset / cellWidth) : 0;
 
92
  if (!hydrated || messages.some(message => message.role === 'user') || state.busy || inputPending) mascot.hide();
93
  else mascot.update({ screen, rows: xterm.rows,
94
  viewportRows: scroll.viewportHeight, contentRows: transcript.render(xterm.cols).length,
95
+ draft: editor.getText(), hasSales: files.includes('sales.csv'), hasProducts: files.includes('products.csv') });
96
  // Keep the status text to the left of the cat's lower body.
97
  const cellWidth = screen?.getBoundingClientRect().width / xterm.cols;
98
  const reserved = cellWidth > 0 ? Math.ceil(mascot.statusInset / cellWidth) : 0;
app/src/workspace.mjs CHANGED
@@ -2,8 +2,9 @@ import { Bash } from 'just-bash/browser';
2
  import { Type } from '@earendil-works/pi-ai';
3
 
4
  export const initialFiles = {
5
- '/workspace/README.md': '# Browser workspace\n\nPi can read, write, edit, and run shell commands here.\nEverything stays in this browser. Try analyzing sales.csv.\n',
6
  '/workspace/sales.csv': 'product,units,price\nNotebook,12,5\nPencil,30,2\nFolder,8,3\n',
 
7
  };
8
  const limitText = text => text.length > 6000 ? text.slice(0, 6000) + '\n[Output truncated at 6,000 characters; use head, grep, or a narrower query.]' : text;
9
  const result = (text, details = {}) => ({ content: [{ type: 'text', text: limitText(text) }], details });
 
2
  import { Type } from '@earendil-works/pi-ai';
3
 
4
  export const initialFiles = {
5
+ '/workspace/README.md': '# Browser workspace\n\nPi can read, write, edit, and run shell commands here.\nEverything stays in this browser. Explore our sales, product costs, and stock levels.\n',
6
  '/workspace/sales.csv': 'product,units,price\nNotebook,12,5\nPencil,30,2\nFolder,8,3\n',
7
+ '/workspace/products.csv': 'product,unit_cost,stock\nNotebook,2.50,24\nPencil,0.50,120\nFolder,1.20,18\nEraser,0.35,40\nStapler,2.75,9\nRuler,0.80,30\n',
8
  };
9
  const limitText = text => text.length > 6000 ? text.slice(0, 6000) + '\n[Output truncated at 6,000 characters; use head, grep, or a narrower query.]' : text;
10
  const result = (text, details = {}) => ({ content: [{ type: 'text', text: limitText(text) }], details });
app/tests/mascot.mjs CHANGED
@@ -36,7 +36,7 @@ try {
36
  result.checks.push('Click and keyboard activation replay one hop without changing the prompt; reduced motion remains still');
37
  await page.locator('.mascot-suggestion').click();
38
  const prompt = await page.evaluate(() => window.browserPi.terminal.text);
39
- assert.equal(prompt, "What's our total sales revenue?");
40
  assert(await page.locator('.xterm-helper-textarea').evaluate(el => el === document.activeElement));
41
  assert.equal((await page.evaluate(() => window.browserPi.rpc('snapshot'))).messages.length, 0);
42
  await page.waitForFunction(() => document.querySelector('.mascot-suggestion').hidden);
@@ -76,6 +76,8 @@ try {
76
  result.checks.push('Seven viewports keep the cat on the input divider without covering the editor; short viewports prioritize the editor');
77
 
78
  await page.setViewportSize({ width: 1440, height: 1000 }); await visible();
 
 
79
  await page.evaluate(() => window.browserPi.rpc('shell', { command: 'rm /workspace/sales.csv' }));
80
  await page.waitForFunction(() => document.querySelector('.mascot-suggestion').textContent.includes('explore'));
81
  result.checks.push('Starter prompt follows actual workspace files');
 
36
  result.checks.push('Click and keyboard activation replay one hop without changing the prompt; reduced motion remains still');
37
  await page.locator('.mascot-suggestion').click();
38
  const prompt = await page.evaluate(() => window.browserPi.terminal.text);
39
+ assert.equal(prompt, "What's our total sales profit?");
40
  assert(await page.locator('.xterm-helper-textarea').evaluate(el => el === document.activeElement));
41
  assert.equal((await page.evaluate(() => window.browserPi.rpc('snapshot'))).messages.length, 0);
42
  await page.waitForFunction(() => document.querySelector('.mascot-suggestion').hidden);
 
76
  result.checks.push('Seven viewports keep the cat on the input divider without covering the editor; short viewports prioritize the editor');
77
 
78
  await page.setViewportSize({ width: 1440, height: 1000 }); await visible();
79
+ await page.evaluate(() => window.browserPi.rpc('shell', { command: 'rm /workspace/products.csv' }));
80
+ await page.waitForFunction(() => document.querySelector('.mascot-suggestion').textContent.includes('total sales'));
81
  await page.evaluate(() => window.browserPi.rpc('shell', { command: 'rm /workspace/sales.csv' }));
82
  await page.waitForFunction(() => document.querySelector('.mascot-suggestion').textContent.includes('explore'));
83
  result.checks.push('Starter prompt follows actual workspace files');
app/tests/protocol.test.mjs CHANGED
@@ -102,6 +102,8 @@ test('real just-bash executes pipelines and scripts; filesystem survives reload
102
  const workspace = createWorkspace();
103
  let result = await workspace.bash.exec("awk -F, 'NR>1 {sum += $2*$3} END {print sum}' sales.csv > total.txt; cat total.txt");
104
  assert.equal(result.stdout.trim(), '144'); assert.equal(result.exitCode, 0);
 
 
105
  await workspace.tools.find(t => t.name === 'write').execute('1', { path: 'hello.sh', content: 'printf "Hello from the browser\\n"' });
106
  result = await workspace.bash.exec('bash hello.sh'); assert.equal(result.stdout.trim(), 'Hello from the browser');
107
  await workspace.tools.find(t => t.name === 'edit').execute('2', { path: 'hello.sh', oldText: 'Hello', newText: '$& literal' });
 
102
  const workspace = createWorkspace();
103
  let result = await workspace.bash.exec("awk -F, 'NR>1 {sum += $2*$3} END {print sum}' sales.csv > total.txt; cat total.txt");
104
  assert.equal(result.stdout.trim(), '144'); assert.equal(result.exitCode, 0);
105
+ result = await workspace.bash.exec("awk -F, 'NR==FNR {if (FNR>1) cost[$1]=$2; next} FNR>1 {if (!($1 in cost)) {exit 1}; profit += $2*($3-cost[$1])} END {printf \"%.2f\\n\", profit}' products.csv sales.csv");
106
+ assert.equal(result.stdout.trim(), '89.40'); assert.equal(result.exitCode, 0);
107
  await workspace.tools.find(t => t.name === 'write').execute('1', { path: 'hello.sh', content: 'printf "Hello from the browser\\n"' });
108
  result = await workspace.bash.exec('bash hello.sh'); assert.equal(result.stdout.trim(), 'Hello from the browser');
109
  await workspace.tools.find(t => t.name === 'edit').execute('2', { path: 'hello.sh', oldText: 'Hello', newText: '$& literal' });
app/tests/starter-products.mjs ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { chromium } from 'playwright';
2
+ import assert from 'node:assert/strict';
3
+ import fs from 'node:fs/promises';
4
+
5
+ const url = process.argv[2] ?? 'http://127.0.0.1:4173/';
6
+ const label = process.argv[3] ?? 'agent-starter-products';
7
+ const browser = await chromium.launch({ executablePath: '/usr/bin/chromium', headless: true });
8
+ const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } });
9
+ const result = { url, checks: [], errors: [] };
10
+ page.on('pageerror', error => result.errors.push(error.message));
11
+ const ready = async () => { await page.waitForFunction(() => window.browserPi); await page.evaluate(() => window.browserPi.ready); };
12
+ const rpc = (action, args) => page.evaluate(({ action, args }) => window.browserPi.rpc(action, args), { action, args });
13
+ async function restoreLegacy(state) {
14
+ delete state.demoVersion;
15
+ await page.evaluate(async state => {
16
+ const db = await new Promise((resolve, reject) => {
17
+ const request = indexedDB.open('minicpm5-pi-workspace', 1);
18
+ request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error);
19
+ });
20
+ await new Promise((resolve, reject) => {
21
+ const tx = db.transaction('state', 'readwrite'); tx.objectStore('state').put(state, 'current');
22
+ tx.oncomplete = resolve; tx.onerror = () => reject(tx.error);
23
+ });
24
+ db.close();
25
+ }, state);
26
+ await page.reload(); await ready();
27
+ }
28
+ try {
29
+ await page.goto(url); await ready();
30
+ const fresh = await rpc('snapshot');
31
+ const products = fresh.files['/workspace/products.csv'];
32
+ assert.match(products, /^product,unit_cost,stock\n/);
33
+ assert.equal(products.trim().split('\n').length, 7);
34
+ await page.locator('.mascot-suggestion').waitFor({ state: 'visible' });
35
+ assert.equal(await page.locator('.mascot-suggestion').textContent(), 'Ask me to calculate our total profit.');
36
+ await page.getByRole('button', { name: 'products.csv', exact: true }).click();
37
+ assert.equal(await page.locator('#editor').inputValue(), products);
38
+ await page.screenshot({ path: `../validation/${label}.png`, fullPage: true });
39
+ result.checks.push('Fresh workspace has six products with unit costs and stock, and the same-length profit suggestion');
40
+
41
+ await rpc('shell', { command: "rm products.csv; printf 'Eraser,5,1.5\nStapler,12,4.99\nRuler,25,2.5\n' >> sales.csv; printf 'My notes\n' > notes.txt; mkdir empty; ln -s notes.txt link.txt" });
42
+ const legacy = await rpc('snapshot');
43
+ legacy.messages = [{ role: 'user', content: [{ type: 'text', text: 'Keep this conversation' }], timestamp: 1 }];
44
+ await restoreLegacy(legacy);
45
+ const upgraded = await rpc('snapshot');
46
+ assert.equal(upgraded.demoVersion, 1);
47
+ assert.deepEqual(upgraded.files, { ...legacy.files, '/workspace/products.csv': products });
48
+ assert.deepEqual(upgraded.messages, legacy.messages);
49
+ for (const entry of legacy.entries) assert.deepEqual(upgraded.entries.find(([path]) => path === entry[0]), entry);
50
+ const profit = await rpc('shell', { command: "awk -F, 'NR==FNR {if(FNR>1) cost[$1]=$2; next} FNR>1 {if(!($1 in cost)) exit 1; profit += $2*($3-cost[$1])} END {printf \"%.2f\\n\", profit}' products.csv sales.csv" });
51
+ assert.equal(profit.exitCode, 0); assert.equal(profit.stdout.trim(), '164.53');
52
+ result.checks.push('Older six-product sales data gains the companion file; existing files, entries, and chat remain intact; the join computes 164.53');
53
+
54
+ await rpc('shell', { command: 'rm products.csv' });
55
+ await page.reload(); await ready();
56
+ assert.equal((await rpc('snapshot')).files['/workspace/products.csv'], undefined);
57
+ await rpc('write', { path: '/workspace/products.csv', content: 'My own product data\n' });
58
+ await restoreLegacy(await rpc('snapshot'));
59
+ assert.equal((await rpc('snapshot')).files['/workspace/products.csv'], 'My own product data\n');
60
+ await restoreLegacy({ version: 1, files: { '/workspace/custom.txt': 'Custom workspace' }, messages: [] });
61
+ assert.deepEqual((await rpc('snapshot')).files, { '/workspace/custom.txt': 'Custom workspace' });
62
+ result.checks.push('The addition happens once: deleted files stay deleted, custom products are retained, and unrelated workspaces get no demo files');
63
+ assert.deepEqual(result.errors, []); result.passed = true;
64
+ console.log(JSON.stringify(result, null, 2));
65
+ } catch (error) { result.failure = error.stack; throw error; }
66
+ finally { await fs.writeFile(`../validation/${label}.json`, JSON.stringify(result, null, 2)); await browser.close(); }
assets/{agent.worker-CG915rYx.js → agent.worker-L7ew_XyL.js} RENAMED
The diff for this file is too large to render. See raw diff
 
assets/{index-DifYXNCD.js → index-8TcpvU1k.js} RENAMED
@@ -130,8 +130,8 @@ Please report this to https://github.com/markedjs/marked.`,e){let e=`<p>An error
130
  `).replace(/[\r\u0085\u2028\u2029]/g,`
131
  `)}function h(e){if(e||={},e.locator===void 0&&(e.locator=!0),this.assign=e.assign||t.assign,this.domHandler=e.domHandler||g,this.onError=e.onError||e.errorHandler,e.errorHandler&&typeof e.errorHandler!=`function`)throw TypeError(`errorHandler object is no longer supported, switch to onError!`);e.errorHandler&&e.errorHandler(`warning`,"The `errorHandler` option has been deprecated, use `onError` instead!",this),this.normalizeLineEndings=e.normalizeLineEndings||m,this.locator=!!e.locator,this.xmlns=this.assign(Object.create(null),e.xmlns)}h.prototype.parseFromString=function(e,n){if(!l(n))throw TypeError(`DOMParser.parseFromString: the provided mimeType "`+n+`" is not valid.`);var r=this.assign(Object.create(null),this.xmlns),a=i.XML_ENTITIES,o=r[``]||null;s(n)?(a=i.HTML_ENTITIES,o=d.HTML):n===u.XML_SVG_IMAGE&&(o=d.SVG),r[``]=o,r.xml=r.xml||d.XML;var c=new this.domHandler({mimeType:n,defaultNamespace:o,onError:this.onError}),f=this.locator?{}:void 0;this.locator&&c.setDocumentLocator(f);var m=new p;return m.errorHandler=c,m.domBuilder=c,!t.isHTMLMimeType(n)&&typeof e!=`string`&&m.errorHandler.fatalError(`source is not a string`),m.parse(this.normalizeLineEndings(String(e)),r,a),c.doc.documentElement||m.errorHandler.fatalError(`missing root element`),c.doc};function g(e){var t=e||{};this.mimeType=t.mimeType||u.XML_APPLICATION,this.defaultNamespace=t.defaultNamespace||null,this.cdata=!1,this.currentElement=void 0,this.doc=void 0,this.locator=void 0,this.onError=t.onError}function _(e,t){t.lineNumber=e.lineNumber,t.columnNumber=e.columnNumber}g.prototype={startDocument:function(){var e=new o;this.doc=c(this.mimeType)?e.createHTMLDocument(!1):e.createDocument(this.defaultNamespace,``)},startElement:function(e,t,n,r){var i=this.doc,a=i.createElementNS(e,n||t),o=r.length;b(this,a),this.currentElement=a,this.locator&&_(this.locator,a);for(var s=0;s<o;s++){var e=r.getURI(s),c=r.getValue(s),n=r.getQName(s),l=i.createAttributeNS(e,n);this.locator&&_(r.getLocator(s),l),l.value=l.nodeValue=c,a.setAttributeNode(l)}},endElement:function(e,t,n){this.currentElement=this.currentElement.parentNode},startPrefixMapping:function(e,t){},endPrefixMapping:function(e){},processingInstruction:function(e,t){var n=this.doc.createProcessingInstruction(e,t);this.locator&&_(this.locator,n),b(this,n)},ignorableWhitespace:function(e,t,n){},characters:function(e,t,n){if(e=y.apply(this,arguments),e){if(this.cdata)var r=this.doc.createCDATASection(e);else var r=this.doc.createTextNode(e);this.currentElement?this.currentElement.appendChild(r):/^\s*$/.test(e)&&this.doc.appendChild(r),this.locator&&_(this.locator,r)}},skippedEntity:function(e){},endDocument:function(){this.doc.normalize()},setDocumentLocator:function(e){e&&(e.lineNumber=0),this.locator=e},comment:function(e,t,n){e=y.apply(this,arguments);var r=this.doc.createComment(e);this.locator&&_(this.locator,r),b(this,r)},startCDATA:function(){this.cdata=!0},endCDATA:function(){this.cdata=!1},startDTD:function(e,t,n,r){var i=this.doc.implementation;if(i&&i.createDocumentType){var a=i.createDocumentType(e,t,n,r);this.locator&&_(this.locator,a),b(this,a),this.doc.doctype=a}},reportError:function(e,t){if(typeof this.onError==`function`)try{this.onError(e,t,this)}catch(n){throw new f(`Reporting `+e+` "`+t+`" caused `+n,this.locator)}else console.error(`[xmldom `+e+`] `+t,v(this.locator))},warning:function(e){this.reportError(`warning`,e)},error:function(e){this.reportError(`error`,e)},fatalError:function(e,t){throw this.reportError(`fatalError`,e),new f(e,this.locator,t)}};function v(e){if(e)return`
132
  @#[line:`+e.lineNumber+`,col:`+e.columnNumber+`]`}function y(e,t,n){return typeof e==`string`?e.substr(t,n):e.length>=t+n||t?new java.lang.String(e,t,n)+``:e}`endDTD,startEntity,endEntity,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,resolveEntity,getExternalSubset,notationDecl,unparsedEntityDecl`.replace(/\w+/g,function(e){g.prototype[e]=function(){return null}});function b(e,t){e.currentElement?e.currentElement.appendChild(t):e.doc.appendChild(t)}function x(e){if(e===`error`)throw`onErrorStopParsing`}function S(){throw`onWarningStopParsing`}e.__DOMHandler=g,e.DOMParser=h,e.normalizeLineEndings=m,e.onErrorStopParsing=x,e.onWarningStopParsing=S}));o((e=>{var t=Cg();e.assign=t.assign,e.hasDefaultHTMLNamespace=t.hasDefaultHTMLNamespace,e.isHTMLMimeType=t.isHTMLMimeType,e.isValidMimeType=t.isValidMimeType,e.MIME_TYPE=t.MIME_TYPE,e.NAMESPACE=t.NAMESPACE;var n=wg();e.DOMException=n.DOMException,e.DOMExceptionName=n.DOMExceptionName,e.ExceptionCode=n.ExceptionCode,e.ParseError=n.ParseError;var r=Eg();e.Attr=r.Attr,e.CDATASection=r.CDATASection,e.CharacterData=r.CharacterData,e.Comment=r.Comment,e.Document=r.Document,e.DocumentFragment=r.DocumentFragment,e.DocumentType=r.DocumentType,e.DOMImplementation=r.DOMImplementation,e.Element=r.Element,e.Entity=r.Entity,e.EntityReference=r.EntityReference,e.LiveNodeList=r.LiveNodeList,e.NamedNodeMap=r.NamedNodeMap,e.Node=r.Node,e.NodeList=r.NodeList,e.Notation=r.Notation,e.ProcessingInstruction=r.ProcessingInstruction,e.Text=r.Text,e.XMLSerializer=r.XMLSerializer;var i=kg();e.DOMParser=i.DOMParser,e.normalizeLineEndings=i.normalizeLineEndings,e.onErrorStopParsing=i.onErrorStopParsing,e.onWarningStopParsing=i.onWarningStopParsing}))();var Ag=e=>typeof e==`string`?e:(e??[]).filter(e=>e.type===`text`).map(e=>e.text).join(`
133
- `),jg=8192;function Mg(e){let t=e.findLast(e=>e.role===`assistant`&&e.usage?.input>0);return t?{inputTokens:t.usage.input,outputTokens:t.usage.output??0}:e.length?null:{inputTokens:0,outputTokens:0}}function Ng(e){if(!e)return`—/${jg.toLocaleString(`en-US`)} context`;let t=e.inputTokens+e.outputTokens,n=Math.max(0,jg-t);return`${t.toLocaleString(`en-US`)}/${jg.toLocaleString(`en-US`)} context · ${n.toLocaleString(`en-US`)} free`}function Pg(e,t){let n=document.createElement(`canvas`).getContext(`2d`),r=document.createElement(`span`);r.style.cssText=`display:inline-block;width:0;height:0;padding:0;border:0;vertical-align:baseline`;let i=document.createElement(`div`);i.className=`empty-mascot`,i.hidden=!0,i.style.visibility=`hidden`;let a=document.createElement(`button`);a.type=`button`,a.className=`mascot-suggestion`;let o=document.createElement(`button`);o.type=`button`,o.className=`mascot-cat`,o.setAttribute(`aria-label`,`Make the cat hop`);let s=document.createElement(`img`);s.width=s.height=104,s.alt=``,s.draggable=!1;let c=document.createElement(`span`);c.className=`mascot-eyelids`,c.setAttribute(`aria-hidden`,`true`),o.append(s,c),i.append(a,o),e.append(i);let l=``,u=!1,d=!1,f=0,p=0,m=new Set,h=matchMedia(`(prefers-reduced-motion: reduce)`);function g(e,t,n){for(let e of m)e.id===n.id&&(e.cancel(),m.delete(e));let r=e.animate(t,n);m.add(r),r.onfinish=()=>m.delete(r)}function _(){clearTimeout(f),f=0,!(i.hidden||document.hidden||h.matches||!u)&&(d=!0,g(o,[{transform:`translateY(0)`,easing:`ease-out`},{transform:`translateY(-4px)`,offset:.4,easing:`ease-in`},{transform:`translateY(0)`}],{id:`mascot-bump`,duration:420}))}o.onclick=_;function v(){clearTimeout(f),clearTimeout(p),f=p=0;for(let e of m)e.cancel();m.clear()}function y(){if(i.hidden||document.hidden||h.matches||!u){v();return}!d&&!f&&(f=setTimeout(_,1800)),p||=setTimeout(()=>{p=0,g(c,[{opacity:0,easing:`steps(1, end)`},{opacity:1,offset:.15},{opacity:1,offset:.8,easing:`steps(1, end)`},{opacity:0}],{id:`mascot-blink`,duration:180}),y()},5e3+Math.random()*4e3)}function b(){i.hidden=!0,v()}async function x(){try{await s.decode()}catch{return}i.isConnected&&(u=!0,i.style.visibility=``,y())}return s.addEventListener(`load`,x),document.addEventListener(`visibilitychange`,y),h.addEventListener(`change`,y),a.onclick=()=>t(l),{hide:b,get statusInset(){return i.hidden?0:o.offsetWidth+20},update({screen:t,rows:c,viewportRows:u,contentRows:d,draft:f,hasSales:p}){if(!t||!c){b();return}let m=t.getBoundingClientRect(),h=e.getBoundingClientRect(),g=m.height/c,_=(u-d)*g-16;if(_<48){b();return}s.hasAttribute(`src`)||(s.src=new URL(``+new URL(`../empty-cat-v1.png`,import.meta.url).href,``+import.meta.url).href);let v=!f.trim()&&_>=180;a.hidden=!v,a.textContent=p?`Ask me to calculate our total sales.`:`Ask me to explore your files.`,l=p?`What's our total sales revenue?`:`Explore the workspace files and summarize what is here.`,a.setAttribute(`aria-label`,`Use suggested prompt: ${l}`);let x=Math.min(104,Math.floor(v?_-76:_));o.style.width=o.style.height=`${x}px`,i.hidden=!1;let S=m.top+(u+1.5)*g,C=t.querySelector(`.xterm-rows`)?.children[u+1]?.firstElementChild,w=devicePixelRatio||1;if(C&&n){let e=getComputedStyle(C);C.parentElement.append(r);let t=r.getBoundingClientRect().top;r.remove(),n.font=`${e.fontWeight} ${parseFloat(e.fontSize)*w}px ${e.fontFamily}`;let i=n.measureText(`─`);S=t-(i.actualBoundingBoxAscent-i.actualBoundingBoxDescent)/2/w}S=Math.floor(S*w+1e-4)/w;let T=Math.round(x*1156/1250*w)/w;i.style.top=`${S-h.top+x-T-i.getBoundingClientRect().height}px`,y()},dispose(){b(),s.removeEventListener(`load`,x),document.removeEventListener(`visibilitychange`,y),h.removeEventListener(`change`,y),i.remove()}}}var Fg=e=>rd(String(e??``)).replace(/\r\n?/g,`
134
- `).replace(/[\x00-\x08\x0b-\x1f\x7f-\x9f]/g,``),Ig=[{value:`/help`,label:`/help`,description:`Browser terminal commands`},{value:`/load`,label:`/load`,description:`Load MiniCPM on this device`},{value:`/new`,label:`/new`,description:`Start a new conversation; keep files`},{value:`/clear`,label:`/clear`,description:`Same as /new`}];function Lg(e,t){let n=new vs({fontFamily:`ui-monospace, SFMono-Regular, Menlo, Consolas, monospace`,fontSize:13,lineHeight:1.3,theme:pg,cursorBlink:!1,scrollback:0,screenReaderMode:!0,allowProposedApi:!0});n.loadAddon(new bl);let r=document.createElement(`div`);r.className=`pi-terminal-mount`,e.append(r);let i=new xs;n.loadAddon(i),n.open(r);let a=new Sg(n),o=new Qf(a,!0,void 0,{copyOnSelect:!1,copySelection:async e=>{try{return await navigator.clipboard.writeText(e),!0}catch{return!1}},openUrl:e=>{/^https?:\/\//i.test(e)&&window.open(e,`_blank`,`noopener,noreferrer`)},scrollToEndIndicator:()=>Q.fg(`accent`,` ↓ Latest `),searchMatchStyle:e=>Q.bg(`selectedBg`,Q.fg(`text`,e)),searchCurrentMatchStyle:e=>Q.bg(`accent`,Q.fg(`userMessageBg`,Q.bold(e)))}),s=new ru({...tu,"app.interrupt":{defaultKeys:[`escape`,`ctrl+c`]},"app.exit":{defaultKeys:`ctrl+d`},"app.tools.expand":{defaultKeys:`ctrl+o`},"tui.editor.historyPrevious":{defaultKeys:`ctrl+p`},"tui.editor.historyNext":{defaultKeys:`ctrl+n`}});au(s);let c=new lg(o,hg(),s,{paddingX:0}),l=new Pd,u=new af(l,{follow:`end`,primary:!0,scrollbar:`auto`,scrollbarTrackStyle:e=>Q.fg(`borderMuted`,e),scrollbarThumbStyle:e=>Q.fg(`muted`,e)}),d=new xp(``,0,0),f=0,p=d.render.bind(d);d.render=e=>{let t=Math.min(f,Math.max(0,e-1));return p(e-t).map(e=>e+` `.repeat(t))},o.setLayoutRoot(new Jh([{component:u,basis:0,grow:1,minSize:0},{component:new Jh([d,c,{invalidate(){},render(e){let t=`/workspace · MiniCPM5-2B`,n=Ng(m.contextUsage),r=`${t} · ${n}`;return(r.length<=e-2?[r]:[t,n]).flatMap(t=>new xp(Q.fg(`dim`,t),1,0).render(e))}}]),basis:`auto`,shrink:0}])),o.setFocus(c);let m={loaded:!1,busy:!1,activity:``,contextUsage:Mg([])},h=[],g=[],_=!1,v=!1,y=!1,b=Pg(e,e=>{m.busy||v||c.getText().trim()||(c.setText(e),n.focus(),o.requestRender())});function x(){let e=r.querySelector(`.xterm-screen`);!y||g.some(e=>e.role===`user`)||m.busy||v?b.hide():b.update({screen:e,rows:n.rows,viewportRows:u.viewportHeight,contentRows:l.render(n.cols).length,draft:c.getText(),hasSales:h.includes(`sales.csv`)});let t=e?.getBoundingClientRect().width/n.cols,i=t>0?Math.ceil(b.statusInset/t):0;i!==f&&(f=i,o.requestRender())}let S=n.onRender(x),C=new Map,w=[],T=new Set,E=new Pd;E.addChild(new bp(Q.fg(`accent`,Q.bold(`π Pi`))+Q.fg(`muted`,` browser workspace`),1,1)),E.addChild(new bp(`Ask Pi to work with the files on the right.
135
 
136
  /load load MiniCPM
137
  /help show commands
@@ -156,4 +156,4 @@ Node CLI extensions, npm, Python and native binaries are unavailable.`);else if(
156
  `)),r.style.height=`auto`,r.style.height=Math.min(104,Math.max(32,r.scrollHeight))+`px`,i.scrollTop=r.scrollTop,i.scrollLeft=r.scrollLeft,v()}function b(e){r.value=e,y(),r.setSelectionRange(e.length,e.length)}function x(){m||(d.length=0,a.replaceChildren(l.cloneNode(!0)),v())}function S(e,t,n){if(!t)return;let r=document.createElement(`pre`);r.className=n,r.textContent=Hg(t),e.append(r)}o.onsubmit=async e=>{e.preventDefault();let n=r.value;if(m||h||!n.trim())return;m=!0,u.at(-1)!==n&&u.push(n),u.length>50&&u.shift(),f=u.length,p=``,b(``),r.focus(),d.length||a.replaceChildren();let i=document.createElement(`section`);i.className=`shell-entry`;let o=document.createElement(`div`);o.className=`shell-entry-heading`;let s=document.createElement(`span`);s.className=`shell-prompt`,s.textContent=`$`;let c=document.createElement(`pre`);c.className=`shell-code`,c.append(Ug(Hg(n)));let l=document.createElement(`span`);l.className=`shell-status`,l.textContent=`Running…`,o.append(s,c,l),i.append(o),a.append(i),d.push(i),_();try{let e=await t(n),r=g();S(i,e.stdout,`shell-stdout`),S(i,e.stderr,`shell-stderr`),l.classList.add(e.exitCode===0?`success`:`error`),l.textContent=`[exit ${e.exitCode}]`,r&&_()}catch(e){let t=g();S(i,e.message,`shell-stderr`),l.classList.add(`error`),l.textContent=`[error]`,r.value||b(n),t&&_()}finally{for(;d.length>1&&(d.length>40||a.textContent.length>48e3);)d.shift().remove();m=!1,v()}},r.addEventListener(`input`,()=>{f=u.length,p=r.value,y()}),r.addEventListener(`scroll`,()=>{i.scrollTop=r.scrollTop,i.scrollLeft=r.scrollLeft}),r.addEventListener(`keydown`,e=>{if(e.isComposing||e.keyCode===229)return;if(e.key===`Enter`&&!e.shiftKey){e.preventDefault(),o.requestSubmit();return}if(e.ctrlKey&&e.key.toLowerCase()===`l`){e.preventDefault(),x();return}if(e.ctrlKey||e.altKey||e.metaKey||e.shiftKey||r.selectionStart!==r.selectionEnd)return;let t=!r.value.slice(0,r.selectionStart).includes(`
157
  `),n=!r.value.slice(r.selectionEnd).includes(`
158
  `);e.key===`ArrowUp`&&t&&f>0?(e.preventDefault(),f===u.length&&(p=r.value),b(u[--f])):e.key===`ArrowDown`&&n&&f<u.length&&(e.preventDefault(),b(++f===u.length?p:u[f]))}),c.onclick=()=>{x(),r.focus()};let C=0;return new ResizeObserver(([e])=>{e.contentRect.width!==C&&(C=e.contentRect.width,y())}).observe(r.parentElement),y(),{setBusy(e){h=e,v()}}}function Gg(e){let t=e.querySelector(`.runtime-value`),n=e.querySelector(`.runtime-sparkline`),r=e.querySelector(`.runtime-bars`),i=e.querySelector(`[role="status"]`),a=e.querySelector(`.runtime-engine`),o=e.querySelector(`.runtime-content`);new ResizeObserver(([t])=>{e.style.width=`calc(${Math.ceil(t.contentRect.width)}px + 2 * var(--runtime-padding))`}).observe(o);let s=!1,c,l=[],u;function d(o,s,d){clearTimeout(u),o!==c&&(i.textContent=d??`${s} in this browser.`),c=o,a.textContent=c===`tool`?`JavaScript`:`WebGPU`,e.dataset.state=c,t.textContent=s,n.setAttribute(`hidden`,``),e.title=d??`${s} in this browser.`,c!==`generating`&&(l=[],r.replaceChildren())}function f(){d(`idle`,s?`Ready`:`Idle`,s?`Model ready. Inference and tools run in this browser.`:`Model loads when you send a message. Inference and tools run in this browser.`)}function p(e){if(!Number.isFinite(e)||e<0){d(`generating`,`Generating…`);return}d(`generating`,`${e<10?e.toFixed(1):Math.round(e)} tok/s`,`Live token generation speed, including thinking tokens. Prompt-processing time is excluded.`),l.push(e),l.length>12&&l.shift();let t=Math.max(40,...l),i=[...Array(12-l.length).fill(null),...l];r.replaceChildren(...i.map((e,n)=>{if(e===null||e===0){let t=document.createElementNS(`http://www.w3.org/2000/svg`,`circle`);return t.setAttribute(`cx`,String(n*4+1)),t.setAttribute(`cy`,`17`),t.setAttribute(`r`,`1`),t.setAttribute(`opacity`,e===null?`.35`:`.6`),t}let r=document.createElementNS(`http://www.w3.org/2000/svg`,`rect`),i=Math.max(2,Math.min(16,e/t*16));return r.setAttribute(`x`,String(n*4)),r.setAttribute(`y`,(18-i).toFixed(2)),r.setAttribute(`width`,`2`),r.setAttribute(`height`,i.toFixed(2)),r.setAttribute(`rx`,`1`),r})),n.removeAttribute(`hidden`),u=setTimeout(()=>{d(`generating`,`Generating…`),l=[],r.replaceChildren()},2e3)}return f(),{update(e){e.type===`loaded`?(s=!0,f()):e.type===`idle`||e.type===`initialized`?f():e.type===`fatal`?(s=!1,d(`error`,`Unavailable`)):e.type===`busy`?e.action===`load`?d(`loading`,`Loading…`,e.cachedOnly?`Loading model from this browser…`:void 0):e.action===`prompt`?d(`prefill`,`Reading prompt…`):e.action===`shell`?d(`tool`,`Running bash…`):e.action===`read`?d(`tool`,`Reading file…`):e.action===`write`?d(`tool`,`Writing file…`):d(`working`,`Working…`):e.type===`load_progress`?d(`loading`,e.phase===`download`&&e.loaded<e.total?`Downloading ${Math.round(e.loaded/e.total*100)}%`:e.phase===`warmup`?`Warming up…`:`Preparing…`):e.type===`inference_activity`?e.phase===`prefill`?d(`prefill`,`Reading prompt…`):e.phase===`decode`?p(e.rate):(c===`generating`||c===`prefill`)&&d(`working`,`Working…`):e.type===`agent_event`&&e.event.type===`tool_execution_start`&&d(`tool`,{bash:`Running bash…`,read:`Reading file…`,write:`Writing file…`,edit:`Editing file…`}[e.event.toolName]??`Running tool…`)}}}var Kg={default:`#b0b0bd`,markdown:`#519aba`,csv:`#8dc149`,json:`#cbcb41`,javascript:`#cbcb41`,typescript:`#519aba`,react:`#519aba`,html:`#e37933`,css:`#519aba`,python:`#519aba`,shell:`#8dc149`,config:`#b0b0bd`,image:`#a074c4`,svg:`#a074c4`,git:`#e37933`,pdf:`#cc3e44`,zip:`#b0b0bd`,db:`#cbcb41`},qg={md:`markdown`,markdown:`markdown`,mdx:`markdown`,csv:`csv`,tsv:`csv`,json:`json`,jsonc:`json`,jsonl:`json`,js:`javascript`,mjs:`javascript`,cjs:`javascript`,ts:`typescript`,mts:`typescript`,cts:`typescript`,jsx:`react`,tsx:`react`,html:`html`,htm:`html`,xml:`html`,css:`css`,scss:`css`,sass:`css`,less:`css`,py:`python`,pyi:`python`,sh:`shell`,bash:`shell`,zsh:`shell`,fish:`shell`,yaml:`config`,yml:`config`,toml:`config`,ini:`config`,conf:`config`,env:`config`,png:`image`,jpg:`image`,jpeg:`image`,gif:`image`,webp:`image`,avif:`image`,ico:`image`,svg:`svg`,pdf:`pdf`,zip:`zip`,gz:`zip`,tar:`zip`,sql:`db`,sqlite:`db`,db:`db`};function Jg(e){let t=e.split(`/`).at(-1).toLowerCase();return[`.gitignore`,`.gitattributes`,`.gitmodules`].includes(t)?`git`:t===`.env`||t.startsWith(`.env.`)||t===`.editorconfig`?`config`:qg[t.split(`.`).at(-1)]??`default`}function Yg(e){let t=Jg(e),n=document.createElement(`span`);return n.className=`file-icon`,n.dataset.kind=t,n.setAttribute(`aria-hidden`,`true`),n.style.setProperty(`--file-color`,Kg[t]),n.style.setProperty(`--file-icon`,`url("${new URL(`file-icons/${t}.svg`,document.baseURI).href}")`),n}var Xg={html:`markup`,htm:`markup`,xml:`markup`,svg:`markup`,vue:`markup`,css:`css`,js:`javascript`,mjs:`javascript`,cjs:`javascript`,jsx:`jsx`,ts:`typescript`,mts:`typescript`,cts:`typescript`,tsx:`tsx`,json:`json`,ipynb:`json`,md:`markdown`,markdown:`markdown`,py:`python`,pyw:`python`,sh:`bash`,bash:`bash`,zsh:`bash`,yml:`yaml`,yaml:`yaml`,toml:`toml`,sql:`sql`,csv:`csv`,c:`c`,h:`c`,cpp:`cpp`,cc:`cpp`,cxx:`cpp`,hpp:`cpp`,cs:`csharp`,java:`java`,go:`go`,rs:`rust`,diff:`diff`,patch:`diff`};function Zg(e=``){let t=e.split(`/`).at(-1).toLowerCase();return/^dockerfile(?:\.|$)/.test(t)?`docker`:/^\.(?:bashrc|bash_profile|zshrc|profile)$/.test(t)?`bash`:Xg[t.split(`.`).at(-1)]??null}function Qg(e){if(e.length>2e5)return!1;let t=1;for(let n=0;n<e.length;n++)if(e.charCodeAt(n)===10&&++t>5e3)return!1;return!0}function $g(e){let t=e.querySelector(`#editor`),n=e.querySelector(`#editor-highlight`),r=``,i=0,a,o,s,c=!1,l=!1,u;function d(){n.style.width=t.clientWidth+`px`,n.style.height=t.clientHeight+`px`,n.scrollTop=t.scrollTop,n.scrollLeft=t.scrollLeft}function f(){e.classList.remove(`is-highlighted`)}function p(){clearTimeout(o),s?.terminate(),s=void 0,c=!1}function m(){if(c||!u)return;let t=u;u=void 0;try{s||(s=new Worker(new URL(new URL(`editor-highlight.worker-oPpFVFqT.js`,import.meta.url).href,``+import.meta.url),{type:`module`}),s.onmessage=({data:t})=>{clearTimeout(o),c=!1,t.id===i&&t.html!==null&&(n.innerHTML=t.html+`
159
- `,d(),e.classList.add(`is-highlighted`)),m()},s.onerror=()=>{p(),u=void 0}),c=!0,s.postMessage(t),o=setTimeout(()=>{p(),m()},1500)}catch{p()}}function h(n=0){i++,clearTimeout(a),u=void 0,f();let o=Zg(r),s=t.value;if(e.dataset.language=o??`plain`,l||!s||!o||!Qg(s))return;let c=i;a=setTimeout(()=>{u={id:c,text:s,language:o},m()},n)}t.addEventListener(`input`,()=>h(80)),t.addEventListener(`compositionstart`,()=>{l=!0,h()}),t.addEventListener(`compositionend`,()=>{l=!1,h(80)}),t.addEventListener(`scroll`,d,{passive:!0});let g=new ResizeObserver(d);return g.observe(t),{setFile(e,n){if(r===e&&t.value===n)return;let i=r!==e;r=e,t.value!==n&&(t.value=n),i&&(t.scrollTop=0,t.scrollLeft=0),h()},dispose(){clearTimeout(a),p(),g.disconnect()}}}var $=e=>document.querySelector(e),e_=new Worker(new URL(new URL(`agent.worker-CG915rYx.js`,import.meta.url).href,``+import.meta.url),{type:`module`}),t_=new Map,n_=1,r_=!1,i_=!1,a_,o_=!1,s_={},c_=[],l_,u_,d_=new Promise(e=>{l_=e}),f_={events:[],generations:[],runtimeSamples:[],device:null,contextUsage:Mg([])},p_=Gg($(`#runtime-meter`)),m_=$g($(`.editor-input`)),h_=Lg($(`#pi-terminal`),{load:()=>d_.then(O_),prompt:e=>d_.then(()=>k_(e)),newChat:()=>d_.then(A_),stop:()=>v_(`stop`),shell:e=>{if(o_)throw Error(`Save your file edits before running commands.`);return v_(`shell`,{command:e})}}),g_=Rg($(`#model-dialog`),{load:O_,stop:()=>v_(`stop`),onClose:()=>h_.focus()}),__=Wg($(`.workspace > .terminal`),{run:async e=>{if(await d_,o_)throw Error(`Save your file edits before running commands.`);if(r_)throw Error(`Wait for Pi to finish or stop the current task first.`);return v_(`shell`,{command:e})}});function v_(e,t){return new Promise((n,r)=>{let i=n_++;t_.set(i,{resolve:n,reject:r}),e_.postMessage({id:i,action:e,args:t})})}function y_(e){$(`#notice`).textContent=e,$(`#notice`).hidden=!e}function b_(e){return window.self===window.top||!/denied|storage directory access/i.test(e)?e:($(`#open-tab`).href=location.href,$(`#open-tab`).hidden=!1,`Your browser blocked storage in this embedded app. Open the workspace in a new tab to load the model and save files.`)}function x_(){$(`#load`).disabled=r_||i_,$(`#load`).classList.toggle(`is-loaded`,i_),i_?$(`#load`).textContent=`Loaded`:r_||($(`#load`).textContent=`Load model`),$(`#load`).title=$(`#load-status`).textContent+` `+$(`#load-detail`).textContent,$(`#send`).disabled=r_,$(`#newline`).disabled=r_,h_.setState({busy:r_,loaded:i_,activity:$(`#activity`).textContent}),$(`#stop`).hidden=!r_;for(let e of[`#new-chat`,`#add-file`,`#upload`])$(e).disabled=r_;__.setBusy(r_),$(`#save`).disabled=r_||!a_||!o_,$(`#editor`).disabled=r_||!a_}function S_(){h_.setFiles(s_),$(`#files`).replaceChildren();for(let e of Object.keys(s_).sort()){let t=document.createElement(`button`),n=document.createElement(`span`);n.className=`file-name`,n.textContent=e.replace(/^\/workspace\//,``),t.append(Yg(e),n),t.title=e,t.classList.toggle(`selected`,e===a_),t.onclick=()=>C_(e),$(`#files`).append(t)}a_&&!o_&&m_.setFile(a_,s_[a_]??``)}function C_(e){if(u_=void 0,o_){y_(`Save the open file before selecting another file.`);return}a_=e,$(`#filename`).textContent=e,$(`#filetype`).replaceChildren(Yg(e)),S_(),x_()}function w_(){if(o_){u_=void 0;return}if(!u_||!Object.hasOwn(s_,u_))return;let e=u_,t=e!==a_;C_(e),t&&($(`#editor`).scrollTop=0,$(`#editor`).scrollLeft=0);let n=[...$(`#files`).children].find(t=>t.title===e);if(n){let e=n.getBoundingClientRect(),t=$(`#files`).getBoundingClientRect();e.top<t.top?$(`#files`).scrollTop+=e.top-t.top:e.bottom>t.bottom&&($(`#files`).scrollTop+=e.bottom-t.bottom)}}function T_(){h_.setMessages(c_)}function E_(e){f_.contextUsage=e,h_.setState({contextUsage:e})}e_.onmessage=({data:e})=>{if(p_.update(e),e.type===`inference_activity`&&(f_.runtimeSamples.push(e),f_.runtimeSamples.length>160&&f_.runtimeSamples.shift()),e.type===`reply`){let t=t_.get(e.id);t_.delete(e.id),e.error?t?.reject(Object.assign(Error(e.error),{name:e.errorName||`Error`,code:e.errorCode})):t?.resolve(e.value)}else if(e.type===`initialized`)s_=e.files,c_=e.messages,S_(),T_(),E_(Mg(c_)),s_[`/workspace/README.md`]&&C_(`/workspace/README.md`),document.activeElement===document.body&&h_.focus(),l_();else if(e.type===`busy`)e.action===`load`&&($(`#load`).textContent=`Loading…`),r_=!0,$(`#activity`).textContent=e.action===`prompt`?`Pi is working…`:e.action===`load`?e.cachedOnly?`Loading model from this browser…`:`Loading model…`:`Working…`,x_();else if(e.type===`idle`)r_=!1,$(`#activity`).textContent=i_?`Ready · running on your device`:`Files and chat are saved on this device.`,x_();else if(e.type===`workspace`)s_=e.files,S_(),w_();else if(e.type===`device`)f_.device=e.device;else if(e.type===`load_progress`)g_.update(e),e.phase===`download`?($(`#progress`).hidden=!1,$(`#progress`).value=e.loaded/e.total*100,$(`#load-status`).textContent=e.loaded===e.total?`Download verified. Preparing model…`:`Downloading and verifying model…`,$(`#load-detail`).textContent=`${(e.loaded/1e9).toFixed(2)} / ${(e.total/1e9).toFixed(2)} GB · ${Math.round(e.loaded/e.total*100)}%${e.cached?` · ${(e.cached/1e9).toFixed(2)} GB cached`:``}`,$(`#load`).textContent=`Loading ${Math.round(e.loaded/e.total*100)}%`):($(`#load-status`).textContent=e.phase===`compile`?`Preparing WebGPU session…`:`Warming up GPU shaders…`,$(`#progress`).removeAttribute(`value`),$(`#load`).textContent=e.phase===`compile`?`Preparing…`:`Almost ready…`),$(`#load`).title=$(`#load-status`).textContent+` `+$(`#load-detail`).textContent;else if(e.type===`loaded`)i_=!0,f_.device=e.device,$(`#progress`).hidden=!0,$(`#load`).textContent=`Loaded`,$(`#load-status`).textContent=`Ready. Model inference and tools run entirely in this browser.`,$(`#load-detail`).textContent=e.cachedBytes?`Verified model restored from browser storage.`:`SHA-256 verified · model cached for next time.`,x_();else if(e.type===`agent_event`){let t=e.event;if(f_.events.push(t.type),f_.events.length>500&&f_.events.shift(),t.message){let e=c_.findIndex(e=>e.timestamp===t.message.timestamp&&e.role===t.message.role&&e.toolCallId===t.message.toolCallId);e<0?c_.push(t.message):c_[e]=t.message,T_()}t.type===`tool_execution_start`&&($(`#activity`).textContent=`Running ${t.toolName}…`),t.type===`tool_execution_end`&&t.toolName===`read`&&!t.isError&&typeof t.result?.details?.path==`string`&&(u_=t.result.details.path,w_()),t.type===`turn_start`&&($(`#activity`).textContent=`Pi is thinking…`),h_.setState({activity:$(`#activity`).textContent})}else e.type===`context_usage`?E_({inputTokens:e.inputTokens,outputTokens:e.outputTokens}):e.type===`generation`?(f_.generations.push(e),f_.generations.length>100&&f_.generations.shift()):e.type===`context_trim`?y_(`Older turns were left out of the model context. Your full chat remains saved.`):e.type===`notice`?y_(e.text):e.type===`persistence_error`?y_(b_(e.error)):e.type===`fatal`&&(i_=!1,y_(e.error),x_())},e_.onerror=e=>{p_.update({type:`fatal`});let t=Error(e.message||`The worker stopped. Reload to restore your saved workspace.`);y_(t.message);for(let e of t_.values())e.reject(t);t_.clear(),r_=!1,i_=!1,x_()};var D_=e=>async t=>{t?.preventDefault();try{await d_,await e(t)}catch(e){y_(e.message)}};async function O_({cachedOnly:e=!1}={}){y_(``);let t=new URLSearchParams(location.search);try{return await v_(`load`,{runtimeURL:new URL(`./runtime/`,location.href).href,local:t.get(`source`)===`local`,origin:location.origin,limit128:t.has(`limit128`),cachedOnly:e})}catch(e){throw e.message=b_(e.message),$(`#load-status`).textContent=e.message,$(`#progress`).hidden=!0,e}}async function k_(e){if(!e.trim())return;if(o_)throw Error(`Save your file edits before asking Pi to work on them.`);if(!i_){let t;if(await v_(`cache_status`))try{await O_({cachedOnly:!0})}catch(e){if(e.name===`AbortError`)throw e;e.code!==`MODEL_CACHE_MISS`&&(t=e.message)}if(!i_&&!await g_.request(e,{errorMessage:t}))throw new DOMException(`Message kept for later.`,`AbortError`)}y_(``);let t=await v_(`prompt`,{text:e});return c_=t.messages,T_(),t}$(`#load`).onclick=D_(O_),$(`#send`).onclick=()=>{h_.submit(),h_.focus()},$(`#newline`).onclick=()=>h_.newline(),$(`#stop`).onclick=D_(()=>v_(`stop`)),$(`.terminal-panel`).addEventListener(`click`,e=>{!e.defaultPrevented&&e.button===0&&e.target instanceof Element&&(e.target.closest(`#pi-terminal, button, a, input, textarea, select, label, summary, [role="button"], [contenteditable]`)||window.getSelection()?.isCollapsed!==!1&&h_.focus())});async function A_(){u_=void 0,c_=(await v_(`new_chat`)).messages,E_(Mg(c_)),h_.setMessages(c_,{reset:!0}),y_(``)}$(`#new-chat`).onclick=D_(A_),$(`#editor`).oninput=()=>{o_=!0,x_()},$(`#save`).onclick=D_(async()=>{await v_(`write`,{path:a_,content:$(`#editor`).value}),o_=!1,S_(),x_(),y_(``)}),$(`#add-file`).onclick=D_(async()=>{if(o_)throw Error(`Save the open file first.`);let e=window.prompt(`File path, relative to /workspace:`);if(!e?.trim())return;let t=e.startsWith(`/`)?e:`/workspace/`+e;if(Object.hasOwn(s_,t))throw Error(`That file already exists.`);await v_(`write`,{path:t,content:``}),C_(t)}),$(`#upload`).onchange=D_(async()=>{if(o_)throw Error(`Save the open file first.`);for(let e of $(`#upload`).files){if(e.size>1024**2)throw Error(`Import text files of 1 MiB or less.`);let t=await e.text();if(t.includes(`\0`))throw Error(`Import supports text files only.`);let n=`/workspace/`+e.name;if(Object.hasOwn(s_,n))throw Error(e.name+` already exists. Rename it before importing.`);await v_(`write`,{path:n,content:t}),C_(n)}$(`#upload`).value=``}),$(`#export`).onclick=D_(async()=>{let e=await v_(`snapshot`),t=URL.createObjectURL(new Blob([JSON.stringify(e,null,2)],{type:`application/json`})),n=document.createElement(`a`);n.href=t,n.download=`pi-workspace.json`,n.click(),setTimeout(()=>URL.revokeObjectURL(t),1e3)}),window.browserPi={ready:d_,load:O_,prompt:k_,rpc:v_,diagnostics:f_,terminal:h_,runtimeMeter:p_,get loaded(){return i_},get busy(){return r_}},x_();
 
130
  `).replace(/[\r\u0085\u2028\u2029]/g,`
131
  `)}function h(e){if(e||={},e.locator===void 0&&(e.locator=!0),this.assign=e.assign||t.assign,this.domHandler=e.domHandler||g,this.onError=e.onError||e.errorHandler,e.errorHandler&&typeof e.errorHandler!=`function`)throw TypeError(`errorHandler object is no longer supported, switch to onError!`);e.errorHandler&&e.errorHandler(`warning`,"The `errorHandler` option has been deprecated, use `onError` instead!",this),this.normalizeLineEndings=e.normalizeLineEndings||m,this.locator=!!e.locator,this.xmlns=this.assign(Object.create(null),e.xmlns)}h.prototype.parseFromString=function(e,n){if(!l(n))throw TypeError(`DOMParser.parseFromString: the provided mimeType "`+n+`" is not valid.`);var r=this.assign(Object.create(null),this.xmlns),a=i.XML_ENTITIES,o=r[``]||null;s(n)?(a=i.HTML_ENTITIES,o=d.HTML):n===u.XML_SVG_IMAGE&&(o=d.SVG),r[``]=o,r.xml=r.xml||d.XML;var c=new this.domHandler({mimeType:n,defaultNamespace:o,onError:this.onError}),f=this.locator?{}:void 0;this.locator&&c.setDocumentLocator(f);var m=new p;return m.errorHandler=c,m.domBuilder=c,!t.isHTMLMimeType(n)&&typeof e!=`string`&&m.errorHandler.fatalError(`source is not a string`),m.parse(this.normalizeLineEndings(String(e)),r,a),c.doc.documentElement||m.errorHandler.fatalError(`missing root element`),c.doc};function g(e){var t=e||{};this.mimeType=t.mimeType||u.XML_APPLICATION,this.defaultNamespace=t.defaultNamespace||null,this.cdata=!1,this.currentElement=void 0,this.doc=void 0,this.locator=void 0,this.onError=t.onError}function _(e,t){t.lineNumber=e.lineNumber,t.columnNumber=e.columnNumber}g.prototype={startDocument:function(){var e=new o;this.doc=c(this.mimeType)?e.createHTMLDocument(!1):e.createDocument(this.defaultNamespace,``)},startElement:function(e,t,n,r){var i=this.doc,a=i.createElementNS(e,n||t),o=r.length;b(this,a),this.currentElement=a,this.locator&&_(this.locator,a);for(var s=0;s<o;s++){var e=r.getURI(s),c=r.getValue(s),n=r.getQName(s),l=i.createAttributeNS(e,n);this.locator&&_(r.getLocator(s),l),l.value=l.nodeValue=c,a.setAttributeNode(l)}},endElement:function(e,t,n){this.currentElement=this.currentElement.parentNode},startPrefixMapping:function(e,t){},endPrefixMapping:function(e){},processingInstruction:function(e,t){var n=this.doc.createProcessingInstruction(e,t);this.locator&&_(this.locator,n),b(this,n)},ignorableWhitespace:function(e,t,n){},characters:function(e,t,n){if(e=y.apply(this,arguments),e){if(this.cdata)var r=this.doc.createCDATASection(e);else var r=this.doc.createTextNode(e);this.currentElement?this.currentElement.appendChild(r):/^\s*$/.test(e)&&this.doc.appendChild(r),this.locator&&_(this.locator,r)}},skippedEntity:function(e){},endDocument:function(){this.doc.normalize()},setDocumentLocator:function(e){e&&(e.lineNumber=0),this.locator=e},comment:function(e,t,n){e=y.apply(this,arguments);var r=this.doc.createComment(e);this.locator&&_(this.locator,r),b(this,r)},startCDATA:function(){this.cdata=!0},endCDATA:function(){this.cdata=!1},startDTD:function(e,t,n,r){var i=this.doc.implementation;if(i&&i.createDocumentType){var a=i.createDocumentType(e,t,n,r);this.locator&&_(this.locator,a),b(this,a),this.doc.doctype=a}},reportError:function(e,t){if(typeof this.onError==`function`)try{this.onError(e,t,this)}catch(n){throw new f(`Reporting `+e+` "`+t+`" caused `+n,this.locator)}else console.error(`[xmldom `+e+`] `+t,v(this.locator))},warning:function(e){this.reportError(`warning`,e)},error:function(e){this.reportError(`error`,e)},fatalError:function(e,t){throw this.reportError(`fatalError`,e),new f(e,this.locator,t)}};function v(e){if(e)return`
132
  @#[line:`+e.lineNumber+`,col:`+e.columnNumber+`]`}function y(e,t,n){return typeof e==`string`?e.substr(t,n):e.length>=t+n||t?new java.lang.String(e,t,n)+``:e}`endDTD,startEntity,endEntity,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,resolveEntity,getExternalSubset,notationDecl,unparsedEntityDecl`.replace(/\w+/g,function(e){g.prototype[e]=function(){return null}});function b(e,t){e.currentElement?e.currentElement.appendChild(t):e.doc.appendChild(t)}function x(e){if(e===`error`)throw`onErrorStopParsing`}function S(){throw`onWarningStopParsing`}e.__DOMHandler=g,e.DOMParser=h,e.normalizeLineEndings=m,e.onErrorStopParsing=x,e.onWarningStopParsing=S}));o((e=>{var t=Cg();e.assign=t.assign,e.hasDefaultHTMLNamespace=t.hasDefaultHTMLNamespace,e.isHTMLMimeType=t.isHTMLMimeType,e.isValidMimeType=t.isValidMimeType,e.MIME_TYPE=t.MIME_TYPE,e.NAMESPACE=t.NAMESPACE;var n=wg();e.DOMException=n.DOMException,e.DOMExceptionName=n.DOMExceptionName,e.ExceptionCode=n.ExceptionCode,e.ParseError=n.ParseError;var r=Eg();e.Attr=r.Attr,e.CDATASection=r.CDATASection,e.CharacterData=r.CharacterData,e.Comment=r.Comment,e.Document=r.Document,e.DocumentFragment=r.DocumentFragment,e.DocumentType=r.DocumentType,e.DOMImplementation=r.DOMImplementation,e.Element=r.Element,e.Entity=r.Entity,e.EntityReference=r.EntityReference,e.LiveNodeList=r.LiveNodeList,e.NamedNodeMap=r.NamedNodeMap,e.Node=r.Node,e.NodeList=r.NodeList,e.Notation=r.Notation,e.ProcessingInstruction=r.ProcessingInstruction,e.Text=r.Text,e.XMLSerializer=r.XMLSerializer;var i=kg();e.DOMParser=i.DOMParser,e.normalizeLineEndings=i.normalizeLineEndings,e.onErrorStopParsing=i.onErrorStopParsing,e.onWarningStopParsing=i.onWarningStopParsing}))();var Ag=e=>typeof e==`string`?e:(e??[]).filter(e=>e.type===`text`).map(e=>e.text).join(`
133
+ `),jg=8192;function Mg(e){let t=e.findLast(e=>e.role===`assistant`&&e.usage?.input>0);return t?{inputTokens:t.usage.input,outputTokens:t.usage.output??0}:e.length?null:{inputTokens:0,outputTokens:0}}function Ng(e){if(!e)return`—/${jg.toLocaleString(`en-US`)} context`;let t=e.inputTokens+e.outputTokens,n=Math.max(0,jg-t);return`${t.toLocaleString(`en-US`)}/${jg.toLocaleString(`en-US`)} context · ${n.toLocaleString(`en-US`)} free`}function Pg(e,t){let n=document.createElement(`canvas`).getContext(`2d`),r=document.createElement(`span`);r.style.cssText=`display:inline-block;width:0;height:0;padding:0;border:0;vertical-align:baseline`;let i=document.createElement(`div`);i.className=`empty-mascot`,i.hidden=!0,i.style.visibility=`hidden`;let a=document.createElement(`button`);a.type=`button`,a.className=`mascot-suggestion`;let o=document.createElement(`button`);o.type=`button`,o.className=`mascot-cat`,o.setAttribute(`aria-label`,`Make the cat hop`);let s=document.createElement(`img`);s.width=s.height=104,s.alt=``,s.draggable=!1;let c=document.createElement(`span`);c.className=`mascot-eyelids`,c.setAttribute(`aria-hidden`,`true`),o.append(s,c),i.append(a,o),e.append(i);let l=``,u=!1,d=!1,f=0,p=0,m=new Set,h=matchMedia(`(prefers-reduced-motion: reduce)`);function g(e,t,n){for(let e of m)e.id===n.id&&(e.cancel(),m.delete(e));let r=e.animate(t,n);m.add(r),r.onfinish=()=>m.delete(r)}function _(){clearTimeout(f),f=0,!(i.hidden||document.hidden||h.matches||!u)&&(d=!0,g(o,[{transform:`translateY(0)`,easing:`ease-out`},{transform:`translateY(-4px)`,offset:.4,easing:`ease-in`},{transform:`translateY(0)`}],{id:`mascot-bump`,duration:420}))}o.onclick=_;function v(){clearTimeout(f),clearTimeout(p),f=p=0;for(let e of m)e.cancel();m.clear()}function y(){if(i.hidden||document.hidden||h.matches||!u){v();return}!d&&!f&&(f=setTimeout(_,1800)),p||=setTimeout(()=>{p=0,g(c,[{opacity:0,easing:`steps(1, end)`},{opacity:1,offset:.15},{opacity:1,offset:.8,easing:`steps(1, end)`},{opacity:0}],{id:`mascot-blink`,duration:180}),y()},5e3+Math.random()*4e3)}function b(){i.hidden=!0,v()}async function x(){try{await s.decode()}catch{return}i.isConnected&&(u=!0,i.style.visibility=``,y())}return s.addEventListener(`load`,x),document.addEventListener(`visibilitychange`,y),h.addEventListener(`change`,y),a.onclick=()=>t(l),{hide:b,get statusInset(){return i.hidden?0:o.offsetWidth+20},update({screen:t,rows:c,viewportRows:u,contentRows:d,draft:f,hasSales:p,hasProducts:m}){if(!t||!c){b();return}let h=t.getBoundingClientRect(),g=e.getBoundingClientRect(),_=h.height/c,v=(u-d)*_-16;if(v<48){b();return}s.hasAttribute(`src`)||(s.src=new URL(``+new URL(`../empty-cat-v1.png`,import.meta.url).href,``+import.meta.url).href);let x=!f.trim()&&v>=180;a.hidden=!x,a.textContent=p?m?`Ask me to calculate our total profit.`:`Ask me to calculate our total sales.`:`Ask me to explore your files.`,l=p?m?`What's our total sales profit?`:`What's our total sales revenue?`:`Explore the workspace files and summarize what is here.`,a.setAttribute(`aria-label`,`Use suggested prompt: ${l}`);let S=Math.min(104,Math.floor(x?v-76:v));o.style.width=o.style.height=`${S}px`,i.hidden=!1;let C=h.top+(u+1.5)*_,w=t.querySelector(`.xterm-rows`)?.children[u+1]?.firstElementChild,T=devicePixelRatio||1;if(w&&n){let e=getComputedStyle(w);w.parentElement.append(r);let t=r.getBoundingClientRect().top;r.remove(),n.font=`${e.fontWeight} ${parseFloat(e.fontSize)*T}px ${e.fontFamily}`;let i=n.measureText(`─`);C=t-(i.actualBoundingBoxAscent-i.actualBoundingBoxDescent)/2/T}C=Math.floor(C*T+1e-4)/T;let E=Math.round(S*1156/1250*T)/T;i.style.top=`${C-g.top+S-E-i.getBoundingClientRect().height}px`,y()},dispose(){b(),s.removeEventListener(`load`,x),document.removeEventListener(`visibilitychange`,y),h.removeEventListener(`change`,y),i.remove()}}}var Fg=e=>rd(String(e??``)).replace(/\r\n?/g,`
134
+ `).replace(/[\x00-\x08\x0b-\x1f\x7f-\x9f]/g,``),Ig=[{value:`/help`,label:`/help`,description:`Browser terminal commands`},{value:`/load`,label:`/load`,description:`Load MiniCPM on this device`},{value:`/new`,label:`/new`,description:`Start a new conversation; keep files`},{value:`/clear`,label:`/clear`,description:`Same as /new`}];function Lg(e,t){let n=new vs({fontFamily:`ui-monospace, SFMono-Regular, Menlo, Consolas, monospace`,fontSize:13,lineHeight:1.3,theme:pg,cursorBlink:!1,scrollback:0,screenReaderMode:!0,allowProposedApi:!0});n.loadAddon(new bl);let r=document.createElement(`div`);r.className=`pi-terminal-mount`,e.append(r);let i=new xs;n.loadAddon(i),n.open(r);let a=new Sg(n),o=new Qf(a,!0,void 0,{copyOnSelect:!1,copySelection:async e=>{try{return await navigator.clipboard.writeText(e),!0}catch{return!1}},openUrl:e=>{/^https?:\/\//i.test(e)&&window.open(e,`_blank`,`noopener,noreferrer`)},scrollToEndIndicator:()=>Q.fg(`accent`,` ↓ Latest `),searchMatchStyle:e=>Q.bg(`selectedBg`,Q.fg(`text`,e)),searchCurrentMatchStyle:e=>Q.bg(`accent`,Q.fg(`userMessageBg`,Q.bold(e)))}),s=new ru({...tu,"app.interrupt":{defaultKeys:[`escape`,`ctrl+c`]},"app.exit":{defaultKeys:`ctrl+d`},"app.tools.expand":{defaultKeys:`ctrl+o`},"tui.editor.historyPrevious":{defaultKeys:`ctrl+p`},"tui.editor.historyNext":{defaultKeys:`ctrl+n`}});au(s);let c=new lg(o,hg(),s,{paddingX:0}),l=new Pd,u=new af(l,{follow:`end`,primary:!0,scrollbar:`auto`,scrollbarTrackStyle:e=>Q.fg(`borderMuted`,e),scrollbarThumbStyle:e=>Q.fg(`muted`,e)}),d=new xp(``,0,0),f=0,p=d.render.bind(d);d.render=e=>{let t=Math.min(f,Math.max(0,e-1));return p(e-t).map(e=>e+` `.repeat(t))},o.setLayoutRoot(new Jh([{component:u,basis:0,grow:1,minSize:0},{component:new Jh([d,c,{invalidate(){},render(e){let t=`/workspace · MiniCPM5-2B`,n=Ng(m.contextUsage),r=`${t} · ${n}`;return(r.length<=e-2?[r]:[t,n]).flatMap(t=>new xp(Q.fg(`dim`,t),1,0).render(e))}}]),basis:`auto`,shrink:0}])),o.setFocus(c);let m={loaded:!1,busy:!1,activity:``,contextUsage:Mg([])},h=[],g=[],_=!1,v=!1,y=!1,b=Pg(e,e=>{m.busy||v||c.getText().trim()||(c.setText(e),n.focus(),o.requestRender())});function x(){let e=r.querySelector(`.xterm-screen`);!y||g.some(e=>e.role===`user`)||m.busy||v?b.hide():b.update({screen:e,rows:n.rows,viewportRows:u.viewportHeight,contentRows:l.render(n.cols).length,draft:c.getText(),hasSales:h.includes(`sales.csv`),hasProducts:h.includes(`products.csv`)});let t=e?.getBoundingClientRect().width/n.cols,i=t>0?Math.ceil(b.statusInset/t):0;i!==f&&(f=i,o.requestRender())}let S=n.onRender(x),C=new Map,w=[],T=new Set,E=new Pd;E.addChild(new bp(Q.fg(`accent`,Q.bold(`π Pi`))+Q.fg(`muted`,` browser workspace`),1,1)),E.addChild(new bp(`Ask Pi to work with the files on the right.
135
 
136
  /load load MiniCPM
137
  /help show commands
 
156
  `)),r.style.height=`auto`,r.style.height=Math.min(104,Math.max(32,r.scrollHeight))+`px`,i.scrollTop=r.scrollTop,i.scrollLeft=r.scrollLeft,v()}function b(e){r.value=e,y(),r.setSelectionRange(e.length,e.length)}function x(){m||(d.length=0,a.replaceChildren(l.cloneNode(!0)),v())}function S(e,t,n){if(!t)return;let r=document.createElement(`pre`);r.className=n,r.textContent=Hg(t),e.append(r)}o.onsubmit=async e=>{e.preventDefault();let n=r.value;if(m||h||!n.trim())return;m=!0,u.at(-1)!==n&&u.push(n),u.length>50&&u.shift(),f=u.length,p=``,b(``),r.focus(),d.length||a.replaceChildren();let i=document.createElement(`section`);i.className=`shell-entry`;let o=document.createElement(`div`);o.className=`shell-entry-heading`;let s=document.createElement(`span`);s.className=`shell-prompt`,s.textContent=`$`;let c=document.createElement(`pre`);c.className=`shell-code`,c.append(Ug(Hg(n)));let l=document.createElement(`span`);l.className=`shell-status`,l.textContent=`Running…`,o.append(s,c,l),i.append(o),a.append(i),d.push(i),_();try{let e=await t(n),r=g();S(i,e.stdout,`shell-stdout`),S(i,e.stderr,`shell-stderr`),l.classList.add(e.exitCode===0?`success`:`error`),l.textContent=`[exit ${e.exitCode}]`,r&&_()}catch(e){let t=g();S(i,e.message,`shell-stderr`),l.classList.add(`error`),l.textContent=`[error]`,r.value||b(n),t&&_()}finally{for(;d.length>1&&(d.length>40||a.textContent.length>48e3);)d.shift().remove();m=!1,v()}},r.addEventListener(`input`,()=>{f=u.length,p=r.value,y()}),r.addEventListener(`scroll`,()=>{i.scrollTop=r.scrollTop,i.scrollLeft=r.scrollLeft}),r.addEventListener(`keydown`,e=>{if(e.isComposing||e.keyCode===229)return;if(e.key===`Enter`&&!e.shiftKey){e.preventDefault(),o.requestSubmit();return}if(e.ctrlKey&&e.key.toLowerCase()===`l`){e.preventDefault(),x();return}if(e.ctrlKey||e.altKey||e.metaKey||e.shiftKey||r.selectionStart!==r.selectionEnd)return;let t=!r.value.slice(0,r.selectionStart).includes(`
157
  `),n=!r.value.slice(r.selectionEnd).includes(`
158
  `);e.key===`ArrowUp`&&t&&f>0?(e.preventDefault(),f===u.length&&(p=r.value),b(u[--f])):e.key===`ArrowDown`&&n&&f<u.length&&(e.preventDefault(),b(++f===u.length?p:u[f]))}),c.onclick=()=>{x(),r.focus()};let C=0;return new ResizeObserver(([e])=>{e.contentRect.width!==C&&(C=e.contentRect.width,y())}).observe(r.parentElement),y(),{setBusy(e){h=e,v()}}}function Gg(e){let t=e.querySelector(`.runtime-value`),n=e.querySelector(`.runtime-sparkline`),r=e.querySelector(`.runtime-bars`),i=e.querySelector(`[role="status"]`),a=e.querySelector(`.runtime-engine`),o=e.querySelector(`.runtime-content`);new ResizeObserver(([t])=>{e.style.width=`calc(${Math.ceil(t.contentRect.width)}px + 2 * var(--runtime-padding))`}).observe(o);let s=!1,c,l=[],u;function d(o,s,d){clearTimeout(u),o!==c&&(i.textContent=d??`${s} in this browser.`),c=o,a.textContent=c===`tool`?`JavaScript`:`WebGPU`,e.dataset.state=c,t.textContent=s,n.setAttribute(`hidden`,``),e.title=d??`${s} in this browser.`,c!==`generating`&&(l=[],r.replaceChildren())}function f(){d(`idle`,s?`Ready`:`Idle`,s?`Model ready. Inference and tools run in this browser.`:`Model loads when you send a message. Inference and tools run in this browser.`)}function p(e){if(!Number.isFinite(e)||e<0){d(`generating`,`Generating…`);return}d(`generating`,`${e<10?e.toFixed(1):Math.round(e)} tok/s`,`Live token generation speed, including thinking tokens. Prompt-processing time is excluded.`),l.push(e),l.length>12&&l.shift();let t=Math.max(40,...l),i=[...Array(12-l.length).fill(null),...l];r.replaceChildren(...i.map((e,n)=>{if(e===null||e===0){let t=document.createElementNS(`http://www.w3.org/2000/svg`,`circle`);return t.setAttribute(`cx`,String(n*4+1)),t.setAttribute(`cy`,`17`),t.setAttribute(`r`,`1`),t.setAttribute(`opacity`,e===null?`.35`:`.6`),t}let r=document.createElementNS(`http://www.w3.org/2000/svg`,`rect`),i=Math.max(2,Math.min(16,e/t*16));return r.setAttribute(`x`,String(n*4)),r.setAttribute(`y`,(18-i).toFixed(2)),r.setAttribute(`width`,`2`),r.setAttribute(`height`,i.toFixed(2)),r.setAttribute(`rx`,`1`),r})),n.removeAttribute(`hidden`),u=setTimeout(()=>{d(`generating`,`Generating…`),l=[],r.replaceChildren()},2e3)}return f(),{update(e){e.type===`loaded`?(s=!0,f()):e.type===`idle`||e.type===`initialized`?f():e.type===`fatal`?(s=!1,d(`error`,`Unavailable`)):e.type===`busy`?e.action===`load`?d(`loading`,`Loading…`,e.cachedOnly?`Loading model from this browser…`:void 0):e.action===`prompt`?d(`prefill`,`Reading prompt…`):e.action===`shell`?d(`tool`,`Running bash…`):e.action===`read`?d(`tool`,`Reading file…`):e.action===`write`?d(`tool`,`Writing file…`):d(`working`,`Working…`):e.type===`load_progress`?d(`loading`,e.phase===`download`&&e.loaded<e.total?`Downloading ${Math.round(e.loaded/e.total*100)}%`:e.phase===`warmup`?`Warming up…`:`Preparing…`):e.type===`inference_activity`?e.phase===`prefill`?d(`prefill`,`Reading prompt…`):e.phase===`decode`?p(e.rate):(c===`generating`||c===`prefill`)&&d(`working`,`Working…`):e.type===`agent_event`&&e.event.type===`tool_execution_start`&&d(`tool`,{bash:`Running bash…`,read:`Reading file…`,write:`Writing file…`,edit:`Editing file…`}[e.event.toolName]??`Running tool…`)}}}var Kg={default:`#b0b0bd`,markdown:`#519aba`,csv:`#8dc149`,json:`#cbcb41`,javascript:`#cbcb41`,typescript:`#519aba`,react:`#519aba`,html:`#e37933`,css:`#519aba`,python:`#519aba`,shell:`#8dc149`,config:`#b0b0bd`,image:`#a074c4`,svg:`#a074c4`,git:`#e37933`,pdf:`#cc3e44`,zip:`#b0b0bd`,db:`#cbcb41`},qg={md:`markdown`,markdown:`markdown`,mdx:`markdown`,csv:`csv`,tsv:`csv`,json:`json`,jsonc:`json`,jsonl:`json`,js:`javascript`,mjs:`javascript`,cjs:`javascript`,ts:`typescript`,mts:`typescript`,cts:`typescript`,jsx:`react`,tsx:`react`,html:`html`,htm:`html`,xml:`html`,css:`css`,scss:`css`,sass:`css`,less:`css`,py:`python`,pyi:`python`,sh:`shell`,bash:`shell`,zsh:`shell`,fish:`shell`,yaml:`config`,yml:`config`,toml:`config`,ini:`config`,conf:`config`,env:`config`,png:`image`,jpg:`image`,jpeg:`image`,gif:`image`,webp:`image`,avif:`image`,ico:`image`,svg:`svg`,pdf:`pdf`,zip:`zip`,gz:`zip`,tar:`zip`,sql:`db`,sqlite:`db`,db:`db`};function Jg(e){let t=e.split(`/`).at(-1).toLowerCase();return[`.gitignore`,`.gitattributes`,`.gitmodules`].includes(t)?`git`:t===`.env`||t.startsWith(`.env.`)||t===`.editorconfig`?`config`:qg[t.split(`.`).at(-1)]??`default`}function Yg(e){let t=Jg(e),n=document.createElement(`span`);return n.className=`file-icon`,n.dataset.kind=t,n.setAttribute(`aria-hidden`,`true`),n.style.setProperty(`--file-color`,Kg[t]),n.style.setProperty(`--file-icon`,`url("${new URL(`file-icons/${t}.svg`,document.baseURI).href}")`),n}var Xg={html:`markup`,htm:`markup`,xml:`markup`,svg:`markup`,vue:`markup`,css:`css`,js:`javascript`,mjs:`javascript`,cjs:`javascript`,jsx:`jsx`,ts:`typescript`,mts:`typescript`,cts:`typescript`,tsx:`tsx`,json:`json`,ipynb:`json`,md:`markdown`,markdown:`markdown`,py:`python`,pyw:`python`,sh:`bash`,bash:`bash`,zsh:`bash`,yml:`yaml`,yaml:`yaml`,toml:`toml`,sql:`sql`,csv:`csv`,c:`c`,h:`c`,cpp:`cpp`,cc:`cpp`,cxx:`cpp`,hpp:`cpp`,cs:`csharp`,java:`java`,go:`go`,rs:`rust`,diff:`diff`,patch:`diff`};function Zg(e=``){let t=e.split(`/`).at(-1).toLowerCase();return/^dockerfile(?:\.|$)/.test(t)?`docker`:/^\.(?:bashrc|bash_profile|zshrc|profile)$/.test(t)?`bash`:Xg[t.split(`.`).at(-1)]??null}function Qg(e){if(e.length>2e5)return!1;let t=1;for(let n=0;n<e.length;n++)if(e.charCodeAt(n)===10&&++t>5e3)return!1;return!0}function $g(e){let t=e.querySelector(`#editor`),n=e.querySelector(`#editor-highlight`),r=``,i=0,a,o,s,c=!1,l=!1,u;function d(){n.style.width=t.clientWidth+`px`,n.style.height=t.clientHeight+`px`,n.scrollTop=t.scrollTop,n.scrollLeft=t.scrollLeft}function f(){e.classList.remove(`is-highlighted`)}function p(){clearTimeout(o),s?.terminate(),s=void 0,c=!1}function m(){if(c||!u)return;let t=u;u=void 0;try{s||(s=new Worker(new URL(new URL(`editor-highlight.worker-oPpFVFqT.js`,import.meta.url).href,``+import.meta.url),{type:`module`}),s.onmessage=({data:t})=>{clearTimeout(o),c=!1,t.id===i&&t.html!==null&&(n.innerHTML=t.html+`
159
+ `,d(),e.classList.add(`is-highlighted`)),m()},s.onerror=()=>{p(),u=void 0}),c=!0,s.postMessage(t),o=setTimeout(()=>{p(),m()},1500)}catch{p()}}function h(n=0){i++,clearTimeout(a),u=void 0,f();let o=Zg(r),s=t.value;if(e.dataset.language=o??`plain`,l||!s||!o||!Qg(s))return;let c=i;a=setTimeout(()=>{u={id:c,text:s,language:o},m()},n)}t.addEventListener(`input`,()=>h(80)),t.addEventListener(`compositionstart`,()=>{l=!0,h()}),t.addEventListener(`compositionend`,()=>{l=!1,h(80)}),t.addEventListener(`scroll`,d,{passive:!0});let g=new ResizeObserver(d);return g.observe(t),{setFile(e,n){if(r===e&&t.value===n)return;let i=r!==e;r=e,t.value!==n&&(t.value=n),i&&(t.scrollTop=0,t.scrollLeft=0),h()},dispose(){clearTimeout(a),p(),g.disconnect()}}}var $=e=>document.querySelector(e),e_=new Worker(new URL(new URL(`agent.worker-L7ew_XyL.js`,import.meta.url).href,``+import.meta.url),{type:`module`}),t_=new Map,n_=1,r_=!1,i_=!1,a_,o_=!1,s_={},c_=[],l_,u_,d_=new Promise(e=>{l_=e}),f_={events:[],generations:[],runtimeSamples:[],device:null,contextUsage:Mg([])},p_=Gg($(`#runtime-meter`)),m_=$g($(`.editor-input`)),h_=Lg($(`#pi-terminal`),{load:()=>d_.then(O_),prompt:e=>d_.then(()=>k_(e)),newChat:()=>d_.then(A_),stop:()=>v_(`stop`),shell:e=>{if(o_)throw Error(`Save your file edits before running commands.`);return v_(`shell`,{command:e})}}),g_=Rg($(`#model-dialog`),{load:O_,stop:()=>v_(`stop`),onClose:()=>h_.focus()}),__=Wg($(`.workspace > .terminal`),{run:async e=>{if(await d_,o_)throw Error(`Save your file edits before running commands.`);if(r_)throw Error(`Wait for Pi to finish or stop the current task first.`);return v_(`shell`,{command:e})}});function v_(e,t){return new Promise((n,r)=>{let i=n_++;t_.set(i,{resolve:n,reject:r}),e_.postMessage({id:i,action:e,args:t})})}function y_(e){$(`#notice`).textContent=e,$(`#notice`).hidden=!e}function b_(e){return window.self===window.top||!/denied|storage directory access/i.test(e)?e:($(`#open-tab`).href=location.href,$(`#open-tab`).hidden=!1,`Your browser blocked storage in this embedded app. Open the workspace in a new tab to load the model and save files.`)}function x_(){$(`#load`).disabled=r_||i_,$(`#load`).classList.toggle(`is-loaded`,i_),i_?$(`#load`).textContent=`Loaded`:r_||($(`#load`).textContent=`Load model`),$(`#load`).title=$(`#load-status`).textContent+` `+$(`#load-detail`).textContent,$(`#send`).disabled=r_,$(`#newline`).disabled=r_,h_.setState({busy:r_,loaded:i_,activity:$(`#activity`).textContent}),$(`#stop`).hidden=!r_;for(let e of[`#new-chat`,`#add-file`,`#upload`])$(e).disabled=r_;__.setBusy(r_),$(`#save`).disabled=r_||!a_||!o_,$(`#editor`).disabled=r_||!a_}function S_(){h_.setFiles(s_),$(`#files`).replaceChildren();for(let e of Object.keys(s_).sort()){let t=document.createElement(`button`),n=document.createElement(`span`);n.className=`file-name`,n.textContent=e.replace(/^\/workspace\//,``),t.append(Yg(e),n),t.title=e,t.classList.toggle(`selected`,e===a_),t.onclick=()=>C_(e),$(`#files`).append(t)}a_&&!o_&&m_.setFile(a_,s_[a_]??``)}function C_(e){if(u_=void 0,o_){y_(`Save the open file before selecting another file.`);return}a_=e,$(`#filename`).textContent=e,$(`#filetype`).replaceChildren(Yg(e)),S_(),x_()}function w_(){if(o_){u_=void 0;return}if(!u_||!Object.hasOwn(s_,u_))return;let e=u_,t=e!==a_;C_(e),t&&($(`#editor`).scrollTop=0,$(`#editor`).scrollLeft=0);let n=[...$(`#files`).children].find(t=>t.title===e);if(n){let e=n.getBoundingClientRect(),t=$(`#files`).getBoundingClientRect();e.top<t.top?$(`#files`).scrollTop+=e.top-t.top:e.bottom>t.bottom&&($(`#files`).scrollTop+=e.bottom-t.bottom)}}function T_(){h_.setMessages(c_)}function E_(e){f_.contextUsage=e,h_.setState({contextUsage:e})}e_.onmessage=({data:e})=>{if(p_.update(e),e.type===`inference_activity`&&(f_.runtimeSamples.push(e),f_.runtimeSamples.length>160&&f_.runtimeSamples.shift()),e.type===`reply`){let t=t_.get(e.id);t_.delete(e.id),e.error?t?.reject(Object.assign(Error(e.error),{name:e.errorName||`Error`,code:e.errorCode})):t?.resolve(e.value)}else if(e.type===`initialized`)s_=e.files,c_=e.messages,S_(),T_(),E_(Mg(c_)),s_[`/workspace/README.md`]&&C_(`/workspace/README.md`),document.activeElement===document.body&&h_.focus(),l_();else if(e.type===`busy`)e.action===`load`&&($(`#load`).textContent=`Loading…`),r_=!0,$(`#activity`).textContent=e.action===`prompt`?`Pi is working…`:e.action===`load`?e.cachedOnly?`Loading model from this browser…`:`Loading model…`:`Working…`,x_();else if(e.type===`idle`)r_=!1,$(`#activity`).textContent=i_?`Ready · running on your device`:`Files and chat are saved on this device.`,x_();else if(e.type===`workspace`)s_=e.files,S_(),w_();else if(e.type===`device`)f_.device=e.device;else if(e.type===`load_progress`)g_.update(e),e.phase===`download`?($(`#progress`).hidden=!1,$(`#progress`).value=e.loaded/e.total*100,$(`#load-status`).textContent=e.loaded===e.total?`Download verified. Preparing model…`:`Downloading and verifying model…`,$(`#load-detail`).textContent=`${(e.loaded/1e9).toFixed(2)} / ${(e.total/1e9).toFixed(2)} GB · ${Math.round(e.loaded/e.total*100)}%${e.cached?` · ${(e.cached/1e9).toFixed(2)} GB cached`:``}`,$(`#load`).textContent=`Loading ${Math.round(e.loaded/e.total*100)}%`):($(`#load-status`).textContent=e.phase===`compile`?`Preparing WebGPU session…`:`Warming up GPU shaders…`,$(`#progress`).removeAttribute(`value`),$(`#load`).textContent=e.phase===`compile`?`Preparing…`:`Almost ready…`),$(`#load`).title=$(`#load-status`).textContent+` `+$(`#load-detail`).textContent;else if(e.type===`loaded`)i_=!0,f_.device=e.device,$(`#progress`).hidden=!0,$(`#load`).textContent=`Loaded`,$(`#load-status`).textContent=`Ready. Model inference and tools run entirely in this browser.`,$(`#load-detail`).textContent=e.cachedBytes?`Verified model restored from browser storage.`:`SHA-256 verified · model cached for next time.`,x_();else if(e.type===`agent_event`){let t=e.event;if(f_.events.push(t.type),f_.events.length>500&&f_.events.shift(),t.message){let e=c_.findIndex(e=>e.timestamp===t.message.timestamp&&e.role===t.message.role&&e.toolCallId===t.message.toolCallId);e<0?c_.push(t.message):c_[e]=t.message,T_()}t.type===`tool_execution_start`&&($(`#activity`).textContent=`Running ${t.toolName}…`),t.type===`tool_execution_end`&&t.toolName===`read`&&!t.isError&&typeof t.result?.details?.path==`string`&&(u_=t.result.details.path,w_()),t.type===`turn_start`&&($(`#activity`).textContent=`Pi is thinking…`),h_.setState({activity:$(`#activity`).textContent})}else e.type===`context_usage`?E_({inputTokens:e.inputTokens,outputTokens:e.outputTokens}):e.type===`generation`?(f_.generations.push(e),f_.generations.length>100&&f_.generations.shift()):e.type===`context_trim`?y_(`Older turns were left out of the model context. Your full chat remains saved.`):e.type===`notice`?y_(e.text):e.type===`persistence_error`?y_(b_(e.error)):e.type===`fatal`&&(i_=!1,y_(e.error),x_())},e_.onerror=e=>{p_.update({type:`fatal`});let t=Error(e.message||`The worker stopped. Reload to restore your saved workspace.`);y_(t.message);for(let e of t_.values())e.reject(t);t_.clear(),r_=!1,i_=!1,x_()};var D_=e=>async t=>{t?.preventDefault();try{await d_,await e(t)}catch(e){y_(e.message)}};async function O_({cachedOnly:e=!1}={}){y_(``);let t=new URLSearchParams(location.search);try{return await v_(`load`,{runtimeURL:new URL(`./runtime/`,location.href).href,local:t.get(`source`)===`local`,origin:location.origin,limit128:t.has(`limit128`),cachedOnly:e})}catch(e){throw e.message=b_(e.message),$(`#load-status`).textContent=e.message,$(`#progress`).hidden=!0,e}}async function k_(e){if(!e.trim())return;if(o_)throw Error(`Save your file edits before asking Pi to work on them.`);if(!i_){let t;if(await v_(`cache_status`))try{await O_({cachedOnly:!0})}catch(e){if(e.name===`AbortError`)throw e;e.code!==`MODEL_CACHE_MISS`&&(t=e.message)}if(!i_&&!await g_.request(e,{errorMessage:t}))throw new DOMException(`Message kept for later.`,`AbortError`)}y_(``);let t=await v_(`prompt`,{text:e});return c_=t.messages,T_(),t}$(`#load`).onclick=D_(O_),$(`#send`).onclick=()=>{h_.submit(),h_.focus()},$(`#newline`).onclick=()=>h_.newline(),$(`#stop`).onclick=D_(()=>v_(`stop`)),$(`.terminal-panel`).addEventListener(`click`,e=>{!e.defaultPrevented&&e.button===0&&e.target instanceof Element&&(e.target.closest(`#pi-terminal, button, a, input, textarea, select, label, summary, [role="button"], [contenteditable]`)||window.getSelection()?.isCollapsed!==!1&&h_.focus())});async function A_(){u_=void 0,c_=(await v_(`new_chat`)).messages,E_(Mg(c_)),h_.setMessages(c_,{reset:!0}),y_(``)}$(`#new-chat`).onclick=D_(A_),$(`#editor`).oninput=()=>{o_=!0,x_()},$(`#save`).onclick=D_(async()=>{await v_(`write`,{path:a_,content:$(`#editor`).value}),o_=!1,S_(),x_(),y_(``)}),$(`#add-file`).onclick=D_(async()=>{if(o_)throw Error(`Save the open file first.`);let e=window.prompt(`File path, relative to /workspace:`);if(!e?.trim())return;let t=e.startsWith(`/`)?e:`/workspace/`+e;if(Object.hasOwn(s_,t))throw Error(`That file already exists.`);await v_(`write`,{path:t,content:``}),C_(t)}),$(`#upload`).onchange=D_(async()=>{if(o_)throw Error(`Save the open file first.`);for(let e of $(`#upload`).files){if(e.size>1024**2)throw Error(`Import text files of 1 MiB or less.`);let t=await e.text();if(t.includes(`\0`))throw Error(`Import supports text files only.`);let n=`/workspace/`+e.name;if(Object.hasOwn(s_,n))throw Error(e.name+` already exists. Rename it before importing.`);await v_(`write`,{path:n,content:t}),C_(n)}$(`#upload`).value=``}),$(`#export`).onclick=D_(async()=>{let e=await v_(`snapshot`),t=URL.createObjectURL(new Blob([JSON.stringify(e,null,2)],{type:`application/json`})),n=document.createElement(`a`);n.href=t,n.download=`pi-workspace.json`,n.click(),setTimeout(()=>URL.revokeObjectURL(t),1e3)}),window.browserPi={ready:d_,load:O_,prompt:k_,rpc:v_,diagnostics:f_,terminal:h_,runtimeMeter:p_,get loaded(){return i_},get busy(){return r_}},x_();
deployment-manifest.json CHANGED
@@ -21,8 +21,8 @@
21
  "sha256": "196d96e03432dd5a222e63d9757817a65589d27e6562f7f7c748c9eee40a7694"
22
  },
23
  "app/README.md": {
24
- "bytes": 15007,
25
- "sha256": "cc6a35cfd00bde85bbf8060e1c32d389e2763a8d31d4e99712dd6bb9a5d72100"
26
  },
27
  "app/index.html": {
28
  "bytes": 8758,
@@ -33,8 +33,8 @@
33
  "sha256": "b8a7cb766e4d87879e27ebd656b2e2a7b1bfb2ffd50c52598736fd878b35fe84"
34
  },
35
  "app/package.json": {
36
- "bytes": 1430,
37
- "sha256": "9c82eee9f4bdd46ac11aea007c831c83354116fe2c9046bd8c4ac2baddce3bff"
38
  },
39
  "app/public/empty-cat-v1.png": {
40
  "bytes": 668737,
@@ -165,8 +165,8 @@
165
  "sha256": "f92958fc5397fff319591bbf7ea751d96719e9f07a46544036a26a91de90e5d8"
166
  },
167
  "app/src/agent.worker.mjs": {
168
- "bytes": 5047,
169
- "sha256": "0c432d94bd8117ef85c0fb1c33193c28fe994bd3a50c76cd33a80725b9c7efba"
170
  },
171
  "app/src/context-usage.mjs": {
172
  "bytes": 870,
@@ -221,16 +221,16 @@
221
  "sha256": "62f09aa13c695980717c7bc7e25ad6aba66254faa994bf81f39d2a912a89ff83"
222
  },
223
  "app/src/pi/empty-mascot.mjs": {
224
- "bytes": 5934,
225
- "sha256": "8c09c473a385ad8469868f7c767d560afa8e2f0d07636d9be69310677d65d3cc"
226
  },
227
  "app/src/pi/entry.mjs": {
228
  "bytes": 1351,
229
  "sha256": "f7e6ae0ba428b78126c6873f9784dbf7d9220421cd6510ad893819492ed5ff9b"
230
  },
231
  "app/src/pi/terminal-ui.mjs": {
232
- "bytes": 17682,
233
- "sha256": "c460240513b369804391b7099451ab0457b021331e1beeda97abf3226a6fae0e"
234
  },
235
  "app/src/pi/theme.mjs": {
236
  "bytes": 2453,
@@ -273,8 +273,8 @@
273
  "sha256": "90e4f781f4f27f1e7d4ad38ea6c591bd80f1d366f9816717e5469f3ad838cb9f"
274
  },
275
  "app/src/workspace.mjs": {
276
- "bytes": 5305,
277
- "sha256": "3df0b61b1820e5ffef028fb7bdbdea34ef6f18acc57d2f05e8d160968c7e00c7"
278
  },
279
  "app/tests/browser.mjs": {
280
  "bytes": 9598,
@@ -309,8 +309,8 @@
309
  "sha256": "7092f2cdfc729f5d761d257a73038e4fbccbc2cf5f1e62fd3a10f84301e5a0b2"
310
  },
311
  "app/tests/mascot.mjs": {
312
- "bytes": 8290,
313
- "sha256": "182f035c273018ac66d48c086c8c9c5f9100dba9659f6c2ca9efcf49fdb7f7dd"
314
  },
315
  "app/tests/model-probe.mjs": {
316
  "bytes": 2726,
@@ -321,8 +321,8 @@
321
  "sha256": "8331c6664f04d2a5858af36a25d53b33d6283229b7a105696e7040ca1695a881"
322
  },
323
  "app/tests/protocol.test.mjs": {
324
- "bytes": 8436,
325
- "sha256": "d2a72dd47c9fc9dbb9a6b765342f291339f4ffa1a0d29711086bde3515ad9892"
326
  },
327
  "app/tests/render.mjs": {
328
  "bytes": 12139,
@@ -352,6 +352,10 @@
352
  "bytes": 2589,
353
  "sha256": "717b65ec8cb5109ac349bbf79286d258a542af7e31a791b2f8f0cf2e7f2430d7"
354
  },
 
 
 
 
355
  "app/tests/terminal.mjs": {
356
  "bytes": 7329,
357
  "sha256": "d19a8df4e4559f75a6c8f5397dde82b42e522da49e637fb5a7ed5496603b8013"
@@ -400,9 +404,9 @@
400
  "bytes": 1679,
401
  "sha256": "eab9d66533efd95e346d22f3f455c4c77a85c1386e82e14f3e72c4fdbb8d9c16"
402
  },
403
- "assets/agent.worker-CG915rYx.js": {
404
- "bytes": 2097535,
405
- "sha256": "d729aae5656b286940b5b26c53d0373ae7f231c0eb7ec722975374f6eca12cf0"
406
  },
407
  "assets/editor-highlight.worker-oPpFVFqT.js": {
408
  "bytes": 61457,
@@ -412,9 +416,9 @@
412
  "bytes": 24522,
413
  "sha256": "7b79ed5d5fdd77b61ef795853f6898910ed934b437cad82082ed07f67d252b26"
414
  },
415
- "assets/index-DifYXNCD.js": {
416
- "bytes": 757746,
417
- "sha256": "f124f34d9add50d322839dc4f91c4be7b744e2c30c137327b5eb21784c7d98ea"
418
  },
419
  "assets/ort-wasm-simd-threaded.asyncify-DMmc6YqF.wasm": {
420
  "bytes": 23567050,
@@ -502,7 +506,7 @@
502
  },
503
  "index.html": {
504
  "bytes": 8855,
505
- "sha256": "1bec750c8a1bc3399ab07652caec4b3d0d0cfc911567b617e0c31fda1955d4aa"
506
  },
507
  "licenses/dependencies.txt": {
508
  "bytes": 1386543,
@@ -705,8 +709,8 @@
705
  "sha256": "b9bb39d5bd7bceb260d5eafa633f5af2e47a35d6132c8453d908ea6a4ad8a0f9"
706
  },
707
  "scripts/prepare_space.py": {
708
- "bytes": 6020,
709
- "sha256": "f46e92c02b938990ddd4f825ed6d6efd6f096e8f761fbdd5d7b90ccfa836642d"
710
  },
711
  "scripts/reference.py": {
712
  "bytes": 2652,
@@ -836,6 +840,10 @@
836
  "bytes": 1766,
837
  "sha256": "aca0d8cacb7c5d4a6a9205db5a60c54a619817b83c5f549572f892f26c5a0dfe"
838
  },
 
 
 
 
839
  "validation/agent-rounded-header-render.json": {
840
  "bytes": 7718,
841
  "sha256": "1ca5acd2d5f62f340416d575d3750dd865b6bd86ee08e5eaa35c84d99fad6693"
@@ -900,6 +908,10 @@
900
  "bytes": 2244,
901
  "sha256": "64f5f41f482beb1886a922d4f5872a8f82ad61b701ff45c9e21fb7980acd4ff7"
902
  },
 
 
 
 
903
  "validation/agent-terminal-dark-conversation.json": {
904
  "bytes": 73336,
905
  "sha256": "981c8ac9c0f48f696ed6b96c86e147da5d628e3d0ffa5493b40af13e5f7168fe"
 
21
  "sha256": "196d96e03432dd5a222e63d9757817a65589d27e6562f7f7c748c9eee40a7694"
22
  },
23
  "app/README.md": {
24
+ "bytes": 15515,
25
+ "sha256": "75e1e03b258556edc889b48480358f5ec8f2950224a92ea5be64688f1821a1d8"
26
  },
27
  "app/index.html": {
28
  "bytes": 8758,
 
33
  "sha256": "b8a7cb766e4d87879e27ebd656b2e2a7b1bfb2ffd50c52598736fd878b35fe84"
34
  },
35
  "app/package.json": {
36
+ "bytes": 1494,
37
+ "sha256": "a1742e6c628d57602e9223d46c8288ac36fcf78e8b45d114bd29a57125c0bc52"
38
  },
39
  "app/public/empty-cat-v1.png": {
40
  "bytes": 668737,
 
165
  "sha256": "f92958fc5397fff319591bbf7ea751d96719e9f07a46544036a26a91de90e5d8"
166
  },
167
  "app/src/agent.worker.mjs": {
168
+ "bytes": 5788,
169
+ "sha256": "532e0ef751d545ec42891db30addfd5f7af769e3cd1b9f0f27dd8b7e9874b7cc"
170
  },
171
  "app/src/context-usage.mjs": {
172
  "bytes": 870,
 
221
  "sha256": "62f09aa13c695980717c7bc7e25ad6aba66254faa994bf81f39d2a912a89ff83"
222
  },
223
  "app/src/pi/empty-mascot.mjs": {
224
+ "bytes": 6088,
225
+ "sha256": "4bebf8a86ba8710edfebce450684f4f55f161f8215009122ee1327cd808c471d"
226
  },
227
  "app/src/pi/entry.mjs": {
228
  "bytes": 1351,
229
  "sha256": "f7e6ae0ba428b78126c6873f9784dbf7d9220421cd6510ad893819492ed5ff9b"
230
  },
231
  "app/src/pi/terminal-ui.mjs": {
232
+ "bytes": 17727,
233
+ "sha256": "2d8ce6a8a3f04271c8bcac2ef7104457b3ad54425ef7c3d7f5f86ab7f5506e38"
234
  },
235
  "app/src/pi/theme.mjs": {
236
  "bytes": 2453,
 
273
  "sha256": "90e4f781f4f27f1e7d4ad38ea6c591bd80f1d366f9816717e5469f3ad838cb9f"
274
  },
275
  "app/src/workspace.mjs": {
276
+ "bytes": 5488,
277
+ "sha256": "c9be9b7a25ea04b40cdb30432c0cfcf4025d4e8454dd5a11a9c81404952f19e3"
278
  },
279
  "app/tests/browser.mjs": {
280
  "bytes": 9598,
 
309
  "sha256": "7092f2cdfc729f5d761d257a73038e4fbccbc2cf5f1e62fd3a10f84301e5a0b2"
310
  },
311
  "app/tests/mascot.mjs": {
312
+ "bytes": 8510,
313
+ "sha256": "1f84f41125c21c102b29cf7118fb5d92c15dfc3912e58ffe2e0f5dfd49871de6"
314
  },
315
  "app/tests/model-probe.mjs": {
316
  "bytes": 2726,
 
321
  "sha256": "8331c6664f04d2a5858af36a25d53b33d6283229b7a105696e7040ca1695a881"
322
  },
323
  "app/tests/protocol.test.mjs": {
324
+ "bytes": 8727,
325
+ "sha256": "67df25dfc319437f9613fa0399a264a6c10c091ed3711bec3721080d117ee39f"
326
  },
327
  "app/tests/render.mjs": {
328
  "bytes": 12139,
 
352
  "bytes": 2589,
353
  "sha256": "717b65ec8cb5109ac349bbf79286d258a542af7e31a791b2f8f0cf2e7f2430d7"
354
  },
355
+ "app/tests/starter-products.mjs": {
356
+ "bytes": 4460,
357
+ "sha256": "58455efc7e7e09eb4f309b59928d443db1318ebfd5e9a581189e166eda4bbf8d"
358
+ },
359
  "app/tests/terminal.mjs": {
360
  "bytes": 7329,
361
  "sha256": "d19a8df4e4559f75a6c8f5397dde82b42e522da49e637fb5a7ed5496603b8013"
 
404
  "bytes": 1679,
405
  "sha256": "eab9d66533efd95e346d22f3f455c4c77a85c1386e82e14f3e72c4fdbb8d9c16"
406
  },
407
+ "assets/agent.worker-L7ew_XyL.js": {
408
+ "bytes": 2098081,
409
+ "sha256": "e08312772a7af531dc7c54ab362eada50b016a4500b2e4b4f02f006778de56bf"
410
  },
411
  "assets/editor-highlight.worker-oPpFVFqT.js": {
412
  "bytes": 61457,
 
416
  "bytes": 24522,
417
  "sha256": "7b79ed5d5fdd77b61ef795853f6898910ed934b437cad82082ed07f67d252b26"
418
  },
419
+ "assets/index-8TcpvU1k.js": {
420
+ "bytes": 757876,
421
+ "sha256": "32ce41a1d226c6e2c82e1fecebf9ed84e4c4ce14f12540db1372c0f8ad825b8a"
422
  },
423
  "assets/ort-wasm-simd-threaded.asyncify-DMmc6YqF.wasm": {
424
  "bytes": 23567050,
 
506
  },
507
  "index.html": {
508
  "bytes": 8855,
509
+ "sha256": "a1138ffe838db29939fa05f77bc18fa1b01aa1ca33fb8c9eb33161103e2a88cd"
510
  },
511
  "licenses/dependencies.txt": {
512
  "bytes": 1386543,
 
709
  "sha256": "b9bb39d5bd7bceb260d5eafa633f5af2e47a35d6132c8453d908ea6a4ad8a0f9"
710
  },
711
  "scripts/prepare_space.py": {
712
+ "bytes": 6085,
713
+ "sha256": "833e771a7e836b8da16d9dd5e550c59415f85032e7fe1e1afc5ae4f605734695"
714
  },
715
  "scripts/reference.py": {
716
  "bytes": 2652,
 
840
  "bytes": 1766,
841
  "sha256": "aca0d8cacb7c5d4a6a9205db5a60c54a619817b83c5f549572f892f26c5a0dfe"
842
  },
843
+ "validation/agent-products-mascot.json": {
844
+ "bytes": 3395,
845
+ "sha256": "4e5844c3aba351b0d13711e42325b9ecbe07dd4287af675095e852846f8f7111"
846
+ },
847
  "validation/agent-rounded-header-render.json": {
848
  "bytes": 7718,
849
  "sha256": "1ca5acd2d5f62f340416d575d3750dd865b6bd86ee08e5eaa35c84d99fad6693"
 
908
  "bytes": 2244,
909
  "sha256": "64f5f41f482beb1886a922d4f5872a8f82ad61b701ff45c9e21fb7980acd4ff7"
910
  },
911
+ "validation/agent-starter-products.json": {
912
+ "bytes": 465,
913
+ "sha256": "1f2d1839c7e98fb3e9ec26799248761619c952a62be52c41630e50608caf33ce"
914
+ },
915
  "validation/agent-terminal-dark-conversation.json": {
916
  "bytes": 73336,
917
  "sha256": "981c8ac9c0f48f696ed6b96c86e147da5d628e3d0ffa5493b40af13e5f7168fe"
index.html CHANGED
@@ -20,7 +20,7 @@
20
  <meta name="twitter:image" content="https://huggingface.co/spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi/resolve/main/social-thumbnail-v1.png">
21
  <meta name="twitter:image:alt" content="Pi inside a browser window with the Chrome logo, in monochrome Macintosh style.">
22
  <title>Pi · Coding agent in your browser</title>
23
- <script type="module" crossorigin src="./assets/index-DifYXNCD.js"></script>
24
  <link rel="stylesheet" crossorigin href="./assets/index-7LHZzhK2.css">
25
  </head>
26
  <body>
 
20
  <meta name="twitter:image" content="https://huggingface.co/spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi/resolve/main/social-thumbnail-v1.png">
21
  <meta name="twitter:image:alt" content="Pi inside a browser window with the Chrome logo, in monochrome Macintosh style.">
22
  <title>Pi · Coding agent in your browser</title>
23
+ <script type="module" crossorigin src="./assets/index-8TcpvU1k.js"></script>
24
  <link rel="stylesheet" crossorigin href="./assets/index-7LHZzhK2.css">
25
  </head>
26
  <body>
scripts/prepare_space.py CHANGED
@@ -66,6 +66,7 @@ for name in [
66
  'agent-mascot-motion.json', 'agent-mascot-motion-layout.json',
67
  'agent-editor-focus.json',
68
  'agent-editor.json',
 
69
  'agent-follow-read.json', 'agent-workspace-runtime.json',
70
  ]:
71
  source = root / 'validation' / name
 
66
  'agent-mascot-motion.json', 'agent-mascot-motion-layout.json',
67
  'agent-editor-focus.json',
68
  'agent-editor.json',
69
+ 'agent-starter-products.json', 'agent-products-mascot.json',
70
  'agent-follow-read.json', 'agent-workspace-runtime.json',
71
  ]:
72
  source = root / 'validation' / name
validation/agent-products-mascot.json ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "url": "http://127.0.0.1:4173/",
3
+ "date": "2026-09-14T12:16:50.132Z",
4
+ "checks": [
5
+ "Bubble and cat stay hidden during a delayed image download and appear together when ready",
6
+ "Click and keyboard activation replay one hop without changing the prompt; reduced motion remains still",
7
+ "Suggestion fills and focuses Pi without sending; first-message modal and Cancel preserve the draft and zero-user state",
8
+ "Seven viewports keep the cat on the input divider without covering the editor; short viewports prioritize the editor",
9
+ "Starter prompt follows actual workspace files",
10
+ "Restored user history hides the mascot without fetching its image; New chat brings it back",
11
+ "Multiline drafts and keyboard-sized viewports stay unobstructed"
12
+ ],
13
+ "layouts": [
14
+ {
15
+ "width": 1440,
16
+ "height": 1000,
17
+ "visible": true,
18
+ "top": 689.21875,
19
+ "bottom": 858,
20
+ "left": 555.875,
21
+ "right": 751.875,
22
+ "contentBottom": 233,
23
+ "statusTop": 822,
24
+ "pawBottom": 850.1792,
25
+ "dividerCenter": 850.5,
26
+ "inputTop": 860,
27
+ "screenLeft": 23,
28
+ "screenRight": 759,
29
+ "overflow": false
30
+ },
31
+ {
32
+ "width": 1100,
33
+ "height": 800,
34
+ "visible": true,
35
+ "top": 499.21875,
36
+ "bottom": 668,
37
+ "left": 370.078125,
38
+ "right": 566.078125,
39
+ "contentBottom": 233,
40
+ "statusTop": 632,
41
+ "pawBottom": 660.1792,
42
+ "dividerCenter": 660.5,
43
+ "inputTop": 670,
44
+ "screenLeft": 23,
45
+ "screenRight": 571,
46
+ "overflow": false
47
+ },
48
+ {
49
+ "width": 851,
50
+ "height": 640,
51
+ "visible": true,
52
+ "top": 374,
53
+ "bottom": 478,
54
+ "left": 234.015625,
55
+ "right": 430.015625,
56
+ "contentBottom": 233,
57
+ "statusTop": 442,
58
+ "pawBottom": 470.1792,
59
+ "dividerCenter": 470.5,
60
+ "inputTop": 480,
61
+ "screenLeft": 23,
62
+ "screenRight": 438,
63
+ "overflow": false
64
+ },
65
+ {
66
+ "width": 850,
67
+ "height": 640,
68
+ "visible": true,
69
+ "top": 391,
70
+ "bottom": 495,
71
+ "left": 623,
72
+ "right": 819,
73
+ "contentBottom": 269,
74
+ "statusTop": 459,
75
+ "pawBottom": 487.1792,
76
+ "dividerCenter": 487.5,
77
+ "inputTop": 497,
78
+ "screenLeft": 19,
79
+ "screenRight": 825,
80
+ "overflow": false
81
+ },
82
+ {
83
+ "width": 390,
84
+ "height": 844,
85
+ "visible": true,
86
+ "top": 516.21875,
87
+ "bottom": 685,
88
+ "left": 163,
89
+ "right": 359,
90
+ "contentBottom": 288,
91
+ "statusTop": 649,
92
+ "pawBottom": 677.1792,
93
+ "dividerCenter": 677.5,
94
+ "inputTop": 687,
95
+ "screenLeft": 19,
96
+ "screenRight": 363,
97
+ "overflow": false
98
+ },
99
+ {
100
+ "width": 320,
101
+ "height": 568,
102
+ "visible": false,
103
+ "top": 0,
104
+ "bottom": 0,
105
+ "left": 0,
106
+ "right": 0,
107
+ "contentBottom": 288,
108
+ "statusTop": 364,
109
+ "pawBottom": 0,
110
+ "dividerCenter": 392.5,
111
+ "inputTop": 402,
112
+ "screenLeft": 19,
113
+ "screenRight": 301,
114
+ "overflow": false
115
+ },
116
+ {
117
+ "width": 390,
118
+ "height": 420,
119
+ "visible": false,
120
+ "top": 0,
121
+ "bottom": 0,
122
+ "left": 0,
123
+ "right": 0,
124
+ "contentBottom": 212,
125
+ "statusTop": 231,
126
+ "pawBottom": 0,
127
+ "dividerCenter": 259.5,
128
+ "inputTop": 269,
129
+ "screenLeft": 19,
130
+ "screenRight": 363,
131
+ "overflow": false
132
+ }
133
+ ],
134
+ "errors": [],
135
+ "passed": true
136
+ }
validation/agent-starter-products.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "url": "http://127.0.0.1:4173/",
3
+ "checks": [
4
+ "Fresh workspace has six products with unit costs and stock, and the same-length profit suggestion",
5
+ "Older six-product sales data gains the companion file; existing files, entries, and chat remain intact; the join computes 164.53",
6
+ "The addition happens once: deleted files stay deleted, custom products are retained, and unrelated workspaces get no demo files"
7
+ ],
8
+ "errors": [],
9
+ "passed": true
10
+ }