@piagent/platform 1.41.0 → 1.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/CHANGELOG.md +1006 -2038
  2. package/CONTRIBUTING.md +2 -0
  3. package/README.md +128 -325
  4. package/README.vi.md +149 -143
  5. package/docs/command-reference-vietnamese.md +2 -2
  6. package/docs/distribution-standard.md +6 -6
  7. package/docs/languages.json +2 -2
  8. package/docs/operator-manual-vietnamese.md +7 -7
  9. package/docs/publishing-for-teams.md +1 -0
  10. package/docs/release-install-policy.md +3 -3
  11. package/docs/team-onboarding.md +2 -2
  12. package/docs-site/architecture.html +71 -63
  13. package/docs-site/assets/docs.css +2736 -1030
  14. package/docs-site/assets/docs.js +451 -43
  15. package/docs-site/assets/og.png +0 -0
  16. package/docs-site/assets/piagent-logo.svg +9 -31
  17. package/docs-site/assets/search-en.json +1 -0
  18. package/docs-site/assets/search-vi.json +1 -0
  19. package/docs-site/benchmark.html +71 -63
  20. package/docs-site/commands.html +72 -64
  21. package/docs-site/content/en/index.html +220 -131
  22. package/docs-site/content/en/quickstart.html +74 -90
  23. package/docs-site/content/en/update.html +80 -0
  24. package/docs-site/content/en/whats-new.html +1 -684
  25. package/docs-site/content/index.html +220 -131
  26. package/docs-site/content/quickstart.html +73 -108
  27. package/docs-site/content/update.html +80 -0
  28. package/docs-site/content/whats-new.html +1 -684
  29. package/docs-site/context-engine.html +73 -65
  30. package/docs-site/ecosystem.html +73 -65
  31. package/docs-site/en/architecture.html +67 -59
  32. package/docs-site/en/benchmark.html +67 -59
  33. package/docs-site/en/commands.html +68 -60
  34. package/docs-site/en/context-engine.html +68 -60
  35. package/docs-site/en/ecosystem.html +68 -60
  36. package/docs-site/en/index.html +281 -220
  37. package/docs-site/en/mcp-approval.html +67 -59
  38. package/docs-site/en/mcp-auth.html +67 -59
  39. package/docs-site/en/mcp-servers.html +68 -60
  40. package/docs-site/en/mcp.html +64 -56
  41. package/docs-site/en/permissions.html +68 -60
  42. package/docs-site/en/profiles.html +68 -60
  43. package/docs-site/en/quickstart.html +142 -150
  44. package/docs-site/en/runtime.html +67 -59
  45. package/docs-site/en/scope.html +68 -60
  46. package/docs-site/en/security.html +64 -56
  47. package/docs-site/en/team.html +67 -59
  48. package/docs-site/en/update.html +228 -0
  49. package/docs-site/en/whats-new.html +1601 -725
  50. package/docs-site/en/windows.html +71 -63
  51. package/docs-site/en/workflows.html +68 -60
  52. package/docs-site/favicon.svg +9 -14
  53. package/docs-site/index.html +281 -220
  54. package/docs-site/mcp-approval.html +70 -62
  55. package/docs-site/mcp-auth.html +70 -62
  56. package/docs-site/mcp-servers.html +71 -63
  57. package/docs-site/mcp.html +70 -62
  58. package/docs-site/permissions.html +72 -64
  59. package/docs-site/profiles.html +72 -64
  60. package/docs-site/quickstart.html +145 -172
  61. package/docs-site/releases.json +4449 -0
  62. package/docs-site/runtime.html +73 -65
  63. package/docs-site/scope.html +73 -65
  64. package/docs-site/security.html +73 -65
  65. package/docs-site/team.html +71 -63
  66. package/docs-site/update.html +228 -0
  67. package/docs-site/whats-new.html +1605 -729
  68. package/docs-site/windows.html +75 -67
  69. package/docs-site/workflows.html +73 -65
  70. package/evals/private-holdout-v1/public-exposure.v2.json +1 -1
  71. package/package.json +1 -1
  72. package/packages/piagent-core/package.json +1 -1
  73. package/packages/piagent-webui/dist/client/assets/App-DBx4BU3F.js +5 -0
  74. package/packages/piagent-webui/dist/client/assets/LineReview-DzonwD-g.js +3 -0
  75. package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-DeLo-xiz.js → MarkdownMessage-DchODuO7.js} +2 -2
  76. package/packages/piagent-webui/dist/client/assets/SessionInspectorDrawer-BHHNlct3.js +1 -0
  77. package/packages/piagent-webui/dist/client/assets/SettingsPage-DX28MsZA.js +1 -0
  78. package/packages/piagent-webui/dist/client/assets/TerminalPanel-C--BiN0p.js +2 -0
  79. package/packages/piagent-webui/dist/client/assets/{TerminalView-r9rG24IO.js → TerminalView-BML7uS6R.js} +3 -3
  80. package/packages/piagent-webui/dist/client/assets/WorkspaceCodeEditor-B7ieqDnt.js +2 -0
  81. package/packages/piagent-webui/dist/client/assets/WorkspaceExplorer-BOafuPU-.js +5 -0
  82. package/packages/piagent-webui/dist/client/assets/WorkspaceQuickOpen-CGoGfUPk.js +1 -0
  83. package/packages/piagent-webui/dist/client/assets/api-BUjrtT1B.js +1 -0
  84. package/packages/piagent-webui/dist/client/assets/index-C7Azrwpq.js +132 -0
  85. package/packages/piagent-webui/dist/client/assets/index-Dqb2hNan.css +1 -0
  86. package/packages/piagent-webui/dist/client/assets/tab-close-Crj8vsUq.js +1 -0
  87. package/packages/piagent-webui/dist/client/assets/ui-preferences-B3UfDp-5.js +1 -0
  88. package/packages/piagent-webui/dist/client/assets/vendor-Ch5hIe0W.js +136 -0
  89. package/packages/piagent-webui/dist/client/index.html +5 -8
  90. package/scripts/build-docs-site.mjs +205 -348
  91. package/scripts/check-public-wording.mjs +2 -1
  92. package/scripts/docs-site-icons.mjs +84 -0
  93. package/scripts/docs-site-nav.mjs +302 -0
  94. package/scripts/docs-site-releases.mjs +227 -0
  95. package/packages/piagent-webui/dist/client/assets/Alert-DGWhP2MM.js +0 -1
  96. package/packages/piagent-webui/dist/client/assets/Button-QItfZUNj.js +0 -1
  97. package/packages/piagent-webui/dist/client/assets/DialogTitle-CyaFPaiW.js +0 -3
  98. package/packages/piagent-webui/dist/client/assets/TerminalPanel-BG-DP_qm.js +0 -2
  99. package/packages/piagent-webui/dist/client/assets/Typography-B9om7Evq.js +0 -1
  100. package/packages/piagent-webui/dist/client/assets/WorkspaceCodeEditor-BnqaR3pK.js +0 -2
  101. package/packages/piagent-webui/dist/client/assets/WorkspaceExplorer-BIxwiGtl.js +0 -5
  102. package/packages/piagent-webui/dist/client/assets/WorkspaceQuickOpen-CK3dILhQ.js +0 -1
  103. package/packages/piagent-webui/dist/client/assets/index-DBUTrEXi.css +0 -1
  104. package/packages/piagent-webui/dist/client/assets/index-rlyRccut.js +0 -102
  105. package/packages/piagent-webui/dist/client/assets/tab-close-BoJRyBO_.js +0 -1
  106. package/packages/piagent-webui/dist/client/assets/ui-preferences-BO3AV7QE.js +0 -10
  107. package/packages/piagent-webui/dist/client/assets/useId-DEefKUAT.js +0 -124
@@ -1,86 +1,494 @@
1
1
  // Behaviour shared by every docs page. Loaded with `defer`, so the DOM is parsed
2
- // before any of this runs and no page needs an inline script of its own.
2
+ // before any of this runs and no page needs an inline script of its own (the
3
+ // theme is the one exception: it is set in <head> so the page never flashes).
3
4
 
4
- const sidebar = document.querySelector("[data-sidebar]");
5
- const menuButton = document.querySelector("[data-menu]");
6
- const filterInput = document.querySelector("[data-filter]");
7
- const navLinks = Array.from(document.querySelectorAll(".nav-link"));
5
+ const root = document.documentElement;
6
+ const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
8
7
 
9
- menuButton?.addEventListener("click", () => {
10
- sidebar?.classList.toggle("open");
8
+ /** Storage can be missing or throw (private windows, blocked site data). */
9
+ const store = {
10
+ get(key) {
11
+ try {
12
+ return window.localStorage.getItem(key);
13
+ } catch {
14
+ return null;
15
+ }
16
+ },
17
+ set(key, value) {
18
+ try {
19
+ window.localStorage.setItem(key, value);
20
+ } catch {
21
+ /* the page works without it */
22
+ }
23
+ }
24
+ };
25
+
26
+ const ICON_COPY = '<svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V6a2 2 0 0 1 2-2h9"/></svg>';
27
+ const ICON_CHECK = '<svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12 5 5 9-10"/></svg>';
28
+
29
+ // ---------- Theme ----------
30
+
31
+ document.querySelector("[data-theme-toggle]")?.addEventListener("click", () => {
32
+ const current = root.dataset.theme
33
+ || (window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark");
34
+ const next = current === "light" ? "dark" : "light";
35
+ root.dataset.theme = next;
36
+ store.set("piagent-docs-theme", next);
11
37
  });
12
38
 
13
- navLinks.forEach((link) => {
39
+ // ---------- Sidebar (mobile) ----------
40
+
41
+ const sidebar = document.querySelector("[data-sidebar]");
42
+ document.querySelector("[data-menu]")?.addEventListener("click", () => {
43
+ sidebar?.classList.toggle("open");
44
+ });
45
+ document.querySelectorAll(".nav-link").forEach((link) => {
14
46
  link.addEventListener("click", () => sidebar?.classList.remove("open"));
15
47
  });
48
+ document.addEventListener("click", (event) => {
49
+ if (!sidebar?.classList.contains("open")) return;
50
+ if (sidebar.contains(event.target) || event.target.closest("[data-menu]")) return;
51
+ sidebar.classList.remove("open");
52
+ });
53
+
54
+ // ---------- Copy ----------
55
+
56
+ async function copyText(text) {
57
+ try {
58
+ await navigator.clipboard.writeText(text);
59
+ return true;
60
+ } catch {
61
+ return false;
62
+ }
63
+ }
64
+
65
+ // Buttons that carry their command in an attribute keep the visible text
66
+ // formatted; the label says what happened and goes back on its own.
67
+ document.querySelectorAll("[data-clip]").forEach((button) => {
68
+ button.addEventListener("click", async () => {
69
+ const ok = await copyText(button.getAttribute("data-clip") || "");
70
+ const original = button.innerHTML;
71
+ const iconOnly = !button.textContent.trim();
72
+ if (iconOnly) {
73
+ button.innerHTML = ok ? ICON_CHECK : ICON_COPY;
74
+ } else {
75
+ button.textContent = ok ? root.dataset.copySuccess || "Copied" : root.dataset.copyFallback || "Select command";
76
+ }
77
+ button.classList.toggle("done", ok);
78
+ setTimeout(() => {
79
+ button.innerHTML = original;
80
+ button.classList.remove("done");
81
+ }, 1400);
82
+ });
83
+ });
84
+
85
+ // Every other code block gets an icon button. Comments after `#` are not copied.
86
+ document.querySelectorAll("[data-article] pre").forEach((pre) => {
87
+ if (pre.closest(".command-card")) return;
88
+ const button = document.createElement("button");
89
+ button.type = "button";
90
+ button.className = "code-copy";
91
+ button.setAttribute("aria-label", root.dataset.copyLabel || "Copy");
92
+ button.innerHTML = ICON_COPY;
93
+ button.addEventListener("click", async () => {
94
+ const text = pre.innerText.split("\n").map((line) => line.replace(/\s+#\s.*$/, "")).join("\n").trim();
95
+ const ok = await copyText(text);
96
+ button.innerHTML = ok ? ICON_CHECK : ICON_COPY;
97
+ button.classList.toggle("done", ok);
98
+ setTimeout(() => {
99
+ button.innerHTML = ICON_COPY;
100
+ button.classList.remove("done");
101
+ }, 1400);
102
+ });
103
+ pre.append(button);
104
+ });
105
+
106
+ // ---------- Tabs ----------
107
+ // A tab group with `data-tabs="os"` remembers its choice, and every other group
108
+ // with the same name on the site follows it.
16
109
 
17
- // The filter hides whole groups once none of their links match, so a search that
18
- // finds nothing in a group does not leave its heading floating over a gap.
19
- filterInput?.addEventListener("input", (event) => {
20
- const value = event.target.value.trim().toLowerCase();
21
- navLinks.forEach((link) => {
22
- const text = link.textContent.toLowerCase();
23
- link.style.display = !value || text.includes(value) ? "flex" : "none";
110
+ function selectTab(group, value) {
111
+ const buttons = group.querySelectorAll(":scope > .tab-list [data-tab]");
112
+ if (![...buttons].some((button) => button.dataset.tab === value)) return;
113
+ buttons.forEach((button) => {
114
+ const selected = button.dataset.tab === value;
115
+ button.setAttribute("aria-selected", String(selected));
116
+ button.tabIndex = selected ? 0 : -1;
117
+ });
118
+ group.querySelectorAll(":scope > [data-panel]").forEach((panel) => {
119
+ panel.hidden = panel.dataset.panel !== value;
24
120
  });
25
- document.querySelectorAll(".nav-group").forEach((group) => {
26
- const visible = Array.from(group.querySelectorAll(".nav-link")).some((link) => link.style.display !== "none");
27
- group.style.display = visible ? "" : "none";
121
+ }
122
+
123
+ function detectOs() {
124
+ const platform = (navigator.userAgentData?.platform || navigator.platform || "").toLowerCase();
125
+ if (platform.includes("win")) return "windows";
126
+ if (platform.includes("mac")) return "macos";
127
+ return "linux";
128
+ }
129
+
130
+ document.querySelectorAll("[data-tabs]").forEach((group) => {
131
+ const name = group.dataset.tabs;
132
+ const saved = store.get(`piagent-docs-tab-${name}`) || (name === "os" ? detectOs() : null);
133
+ if (saved) selectTab(group, saved === "linux" && !group.querySelector('[data-tab="linux"]') ? "macos" : saved);
134
+ group.querySelectorAll(":scope > .tab-list [data-tab]").forEach((button) => {
135
+ button.addEventListener("click", () => {
136
+ store.set(`piagent-docs-tab-${name}`, button.dataset.tab);
137
+ document.querySelectorAll(`[data-tabs="${name}"]`).forEach((peer) => selectTab(peer, button.dataset.tab));
138
+ });
139
+ button.addEventListener("keydown", (event) => {
140
+ if (event.key !== "ArrowRight" && event.key !== "ArrowLeft") return;
141
+ const all = [...group.querySelectorAll(":scope > .tab-list [data-tab]")];
142
+ const next = all[(all.indexOf(button) + (event.key === "ArrowRight" ? 1 : all.length - 1)) % all.length];
143
+ next.focus();
144
+ next.click();
145
+ });
28
146
  });
29
147
  });
30
148
 
31
- // Table of contents, built from the headings the page actually has. Generating it
32
- // here rather than writing it into each page means a heading can never be renamed
33
- // in the prose and left stale in the sidebar.
149
+ // ---------- Table of contents and heading anchors ----------
150
+ // Built from the headings the page actually has, so a heading can never be
151
+ // renamed in the prose and left stale in the sidebar.
152
+
34
153
  const tocHost = document.querySelector("[data-toc]");
35
154
  const article = document.querySelector("[data-article]");
36
155
 
156
+ if (article) {
157
+ for (const heading of article.querySelectorAll("h2[id], h3[id]")) {
158
+ const anchor = document.createElement("a");
159
+ anchor.className = "heading-anchor";
160
+ anchor.href = `#${heading.id}`;
161
+ anchor.setAttribute("aria-hidden", "true");
162
+ anchor.tabIndex = -1;
163
+ anchor.textContent = "#";
164
+ heading.prepend(anchor);
165
+ }
166
+ }
167
+
37
168
  if (tocHost && article) {
38
- const headings = Array.from(article.querySelectorAll("h2[id], h3[id]"));
169
+ const headings = Array.from(article.querySelectorAll("h2[id], h3[id]")).filter((heading) => !heading.hasAttribute("data-toc-skip"));
39
170
  if (headings.length < 2) {
40
- tocHost.remove();
171
+ tocHost.closest(".toc")?.remove();
41
172
  document.querySelector(".layout")?.classList.remove("with-toc");
42
173
  } else {
43
174
  for (const heading of headings) {
44
175
  const link = document.createElement("a");
45
176
  link.href = `#${heading.id}`;
46
- link.textContent = heading.textContent.trim();
177
+ link.textContent = heading.textContent.replace(/^#/, "").trim();
47
178
  if (heading.tagName === "H3") link.classList.add("toc-h3");
48
179
  tocHost.append(link);
49
180
  }
50
-
51
181
  const tocLinks = Array.from(tocHost.querySelectorAll("a"));
52
182
  const observer = new IntersectionObserver(
53
183
  (entries) => {
54
184
  const visible = entries
55
185
  .filter((entry) => entry.isIntersecting)
56
- .sort((a, b) => b.intersectionRatio - a.intersectionRatio)[0];
186
+ .sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top)[0];
57
187
  if (!visible) return;
58
188
  for (const link of tocLinks) {
59
189
  link.classList.toggle("active", link.getAttribute("href") === `#${visible.target.id}`);
60
190
  }
61
191
  },
62
- { rootMargin: "-12% 0px -70% 0px", threshold: [0.1, 0.5, 1] }
192
+ { rootMargin: "-10% 0px -70% 0px", threshold: [0, 1] }
63
193
  );
64
194
  for (const heading of headings) observer.observe(heading);
65
195
  }
66
196
  }
67
197
 
68
- // Copy buttons carry the command in an attribute so the visible text can stay
69
- // formatted; the label says what happened and goes back on its own.
70
- document.querySelectorAll("[data-clip]").forEach((button) => {
71
- button.addEventListener("click", async () => {
72
- const text = button.getAttribute("data-clip") || "";
73
- const original = button.textContent;
74
- const copySuccess = document.documentElement.dataset.copySuccess || "Copied";
75
- const copyFallback = document.documentElement.dataset.copyFallback || "Select command";
76
- try {
77
- await navigator.clipboard.writeText(text);
78
- button.textContent = copySuccess;
79
- } catch {
80
- button.textContent = copyFallback;
198
+ // ---------- Search palette (⌘K) ----------
199
+
200
+ const palette = document.querySelector("[data-palette]");
201
+ const paletteInput = palette?.querySelector("input");
202
+ const paletteResults = palette?.querySelector("[data-results]");
203
+ let searchIndex = null;
204
+ let hits = [];
205
+ let active = 0;
206
+
207
+ function normalize(text) {
208
+ return text.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/đ/g, "d");
209
+ }
210
+
211
+ async function loadIndex() {
212
+ if (searchIndex) return searchIndex;
213
+ try {
214
+ const response = await fetch(palette.dataset.index);
215
+ const pages = await response.json();
216
+ searchIndex = pages.flatMap((page) => [
217
+ { title: page.title, sub: page.group, href: page.href, icon: page.icon, text: normalize(`${page.title} ${page.nav} ${page.lead}`), weight: 2 },
218
+ ...page.headings.map((heading) => ({
219
+ title: heading.text,
220
+ sub: page.nav,
221
+ href: `${page.href}#${heading.id}`,
222
+ icon: "hash",
223
+ text: normalize(heading.text),
224
+ weight: 1
225
+ }))
226
+ ]);
227
+ } catch {
228
+ searchIndex = [];
229
+ }
230
+ return searchIndex;
231
+ }
232
+
233
+ function iconSvg(name) {
234
+ return document.querySelector(`#icon-${name}`) ? `<svg class="icon" aria-hidden="true"><use href="#icon-${name}"/></svg>` : "";
235
+ }
236
+
237
+ function renderHits() {
238
+ if (!paletteResults) return;
239
+ if (hits.length === 0) {
240
+ paletteResults.innerHTML = `<p class="palette-empty">${palette.dataset.empty || "No results"}</p>`;
241
+ return;
242
+ }
243
+ paletteResults.innerHTML = "";
244
+ hits.forEach((hit, index) => {
245
+ const link = document.createElement("a");
246
+ link.href = hit.href;
247
+ link.setAttribute("role", "option");
248
+ link.setAttribute("aria-selected", String(index === active));
249
+ link.innerHTML = `${iconSvg(hit.icon)}<span><span class="hit-title"></span><span class="hit-sub"></span></span>`;
250
+ link.querySelector(".hit-title").textContent = hit.title;
251
+ link.querySelector(".hit-sub").textContent = hit.sub;
252
+ link.addEventListener("mousemove", () => {
253
+ if (active === index) return;
254
+ active = index;
255
+ paletteResults.querySelectorAll("a").forEach((item, i) => item.setAttribute("aria-selected", String(i === active)));
256
+ });
257
+ paletteResults.append(link);
258
+ });
259
+ }
260
+
261
+ async function runSearch() {
262
+ const index = await loadIndex();
263
+ const query = normalize(paletteInput.value.trim());
264
+ const words = query.split(/\s+/).filter(Boolean);
265
+ hits = (words.length === 0
266
+ ? index.filter((item) => item.weight === 2)
267
+ : index
268
+ .map((item) => {
269
+ if (!words.every((word) => item.text.includes(word))) return null;
270
+ const starts = normalize(item.title).startsWith(words[0]) ? 2 : 0;
271
+ return { ...item, score: item.weight + starts };
272
+ })
273
+ .filter(Boolean)
274
+ .sort((a, b) => b.score - a.score)
275
+ ).slice(0, 12);
276
+ active = 0;
277
+ renderHits();
278
+ }
279
+
280
+ function openPalette() {
281
+ if (!palette) return;
282
+ palette.classList.add("open");
283
+ paletteInput.value = "";
284
+ runSearch();
285
+ requestAnimationFrame(() => paletteInput.focus());
286
+ }
287
+
288
+ function closePalette() {
289
+ palette?.classList.remove("open");
290
+ }
291
+
292
+ document.querySelectorAll("[data-search]").forEach((button) => button.addEventListener("click", openPalette));
293
+ palette?.addEventListener("click", (event) => {
294
+ if (event.target === palette) closePalette();
295
+ });
296
+ paletteInput?.addEventListener("input", runSearch);
297
+ paletteInput?.addEventListener("keydown", (event) => {
298
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
299
+ event.preventDefault();
300
+ if (hits.length === 0) return;
301
+ active = (active + (event.key === "ArrowDown" ? 1 : hits.length - 1)) % hits.length;
302
+ const items = paletteResults.querySelectorAll("a");
303
+ items.forEach((item, i) => item.setAttribute("aria-selected", String(i === active)));
304
+ items[active]?.scrollIntoView({ block: "nearest" });
305
+ } else if (event.key === "Enter" && hits[active]) {
306
+ window.location.href = hits[active].href;
307
+ closePalette();
308
+ }
309
+ });
310
+ document.addEventListener("keydown", (event) => {
311
+ if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
312
+ event.preventDefault();
313
+ palette?.classList.contains("open") ? closePalette() : openPalette();
314
+ } else if (event.key === "Escape") {
315
+ closePalette();
316
+ sidebar?.classList.remove("open");
317
+ } else if (event.key === "/" && !/input|textarea/i.test(document.activeElement?.tagName || "")) {
318
+ event.preventDefault();
319
+ openPalette();
320
+ }
321
+ });
322
+
323
+ // ---------- Landing page effects ----------
324
+ // Content is in the HTML and readable without JavaScript. Effects only add
325
+ // motion on top, use transform and opacity, and stand down for reduced motion.
326
+
327
+ const revealTargets = document.querySelectorAll("[data-reveal]");
328
+ if (revealTargets.length > 0 && !reducedMotion && "IntersectionObserver" in window) {
329
+ root.classList.add("reveal-ready");
330
+ const revealObserver = new IntersectionObserver((entries) => {
331
+ for (const entry of entries) {
332
+ if (!entry.isIntersecting) continue;
333
+ entry.target.classList.add("in");
334
+ revealObserver.unobserve(entry.target);
81
335
  }
82
- setTimeout(() => {
83
- button.textContent = original;
84
- }, 1200);
336
+ }, { rootMargin: "0px 0px -8% 0px", threshold: 0.12 });
337
+ revealTargets.forEach((target) => revealObserver.observe(target));
338
+ } else {
339
+ revealTargets.forEach((target) => target.classList.add("in"));
340
+ }
341
+
342
+ // Count-up: the real number is already in the markup; this replays it once.
343
+ document.querySelectorAll("[data-count]").forEach((node) => {
344
+ const target = Number(node.dataset.count);
345
+ if (!Number.isFinite(target) || reducedMotion) return;
346
+ const decimals = Number(node.dataset.decimals || 0);
347
+ const format = (value) => value.toLocaleString(root.lang === "vi" ? "vi-VN" : "en-US", {
348
+ minimumFractionDigits: decimals,
349
+ maximumFractionDigits: decimals
350
+ });
351
+ const finalText = node.textContent;
352
+ const observer = new IntersectionObserver((entries) => {
353
+ if (!entries[0].isIntersecting) return;
354
+ observer.disconnect();
355
+ const start = performance.now();
356
+ const duration = 1200;
357
+ const tick = (now) => {
358
+ const progress = Math.min(1, (now - start) / duration);
359
+ const eased = 1 - Math.pow(2, -10 * progress);
360
+ node.textContent = progress < 1 ? format(target * eased) : finalText;
361
+ if (progress < 1) requestAnimationFrame(tick);
362
+ };
363
+ requestAnimationFrame(tick);
364
+ }, { threshold: 0.6 });
365
+ observer.observe(node);
366
+ });
367
+
368
+ // Spotlight on feature tiles: one listener for the grid, one frame per move.
369
+ document.querySelectorAll("[data-spotlight]").forEach((grid) => {
370
+ if (!window.matchMedia("(hover: hover)").matches) return;
371
+ let frame = 0;
372
+ grid.addEventListener("pointermove", (event) => {
373
+ const tile = event.target.closest(".tile");
374
+ if (!tile || frame) return;
375
+ frame = requestAnimationFrame(() => {
376
+ const rect = tile.getBoundingClientRect();
377
+ tile.style.setProperty("--mx", `${event.clientX - rect.left}px`);
378
+ tile.style.setProperty("--my", `${event.clientY - rect.top}px`);
379
+ frame = 0;
380
+ });
381
+ });
382
+ });
383
+
384
+ // Flow line draws itself when the section comes into view.
385
+ document.querySelectorAll("[data-flow]").forEach((flow) => {
386
+ if (reducedMotion) {
387
+ flow.classList.add("in");
388
+ return;
389
+ }
390
+ const observer = new IntersectionObserver((entries) => {
391
+ if (!entries[0].isIntersecting) return;
392
+ flow.classList.add("in");
393
+ observer.disconnect();
394
+ }, { threshold: 0.4 });
395
+ observer.observe(flow);
396
+ });
397
+
398
+ // Terminal demo: lines appear in order, the prompt types, then it loops while
399
+ // on screen. With reduced motion every line is shown at once.
400
+ document.querySelectorAll("[data-term]").forEach((term) => {
401
+ const lines = [...term.querySelectorAll(".term-line, .term-card, .term-result")];
402
+ const typed = term.querySelector("[data-type]");
403
+ const fullText = typed?.dataset.type || "";
404
+ if (reducedMotion) {
405
+ lines.forEach((line) => line.classList.add("show"));
406
+ if (typed) typed.textContent = fullText;
407
+ return;
408
+ }
409
+ let timers = [];
410
+ let running = false;
411
+ const clear = () => {
412
+ timers.forEach(clearTimeout);
413
+ timers = [];
414
+ };
415
+ const play = () => {
416
+ clear();
417
+ running = true;
418
+ lines.forEach((line) => line.classList.remove("show"));
419
+ if (typed) typed.textContent = "";
420
+ let at = 300;
421
+ if (typed) {
422
+ lines[0]?.classList.add("show");
423
+ for (let i = 1; i <= fullText.length; i += 1) {
424
+ timers.push(setTimeout(() => {
425
+ typed.textContent = fullText.slice(0, i);
426
+ }, at));
427
+ at += 28;
428
+ }
429
+ at += 350;
430
+ }
431
+ lines.forEach((line, index) => {
432
+ if (typed && index === 0) return;
433
+ const pause = Number(line.dataset.wait || 520);
434
+ at += pause;
435
+ timers.push(setTimeout(() => line.classList.add("show"), at));
436
+ });
437
+ timers.push(setTimeout(() => {
438
+ if (running) play();
439
+ }, at + 6000));
440
+ };
441
+ const observer = new IntersectionObserver((entries) => {
442
+ if (entries[0].isIntersecting && !running) play();
443
+ if (!entries[0].isIntersecting && running) {
444
+ running = false;
445
+ clear();
446
+ lines.forEach((line) => line.classList.add("show"));
447
+ if (typed) typed.textContent = fullText;
448
+ }
449
+ }, { threshold: 0.25 });
450
+ observer.observe(term);
451
+ });
452
+
453
+ // Screenshot gallery: tab buttons swap the image and caption in place.
454
+ document.querySelectorAll("[data-gallery]").forEach((gallery) => {
455
+ const image = gallery.querySelector("img");
456
+ const caption = gallery.querySelector("figcaption");
457
+ gallery.querySelectorAll("[data-shot]").forEach((button) => {
458
+ button.addEventListener("click", () => {
459
+ gallery.querySelectorAll("[data-shot]").forEach((peer) => peer.setAttribute("aria-selected", String(peer === button)));
460
+ image.style.opacity = "0";
461
+ const next = new Image();
462
+ next.src = button.dataset.shot;
463
+ next.onload = () => {
464
+ image.src = button.dataset.shot;
465
+ image.alt = button.dataset.alt || "";
466
+ caption.textContent = button.dataset.caption || "";
467
+ image.style.opacity = "1";
468
+ };
469
+ });
470
+ });
471
+ });
472
+
473
+ // Changelog filter: show only the items of one kind, and hide releases that
474
+ // have none of it.
475
+ const releaseFilter = document.querySelector(".release-filter");
476
+ releaseFilter?.querySelectorAll("[data-filter-kind]").forEach((button) => {
477
+ button.addEventListener("click", () => {
478
+ const kind = button.dataset.filterKind;
479
+ releaseFilter.querySelectorAll("[data-filter-kind]").forEach((peer) => peer.setAttribute("aria-pressed", String(peer === button)));
480
+ document.querySelectorAll("[data-releases] [data-release], .archive-row").forEach((release) => {
481
+ let shown = 0;
482
+ release.querySelectorAll("[data-kind]").forEach((item) => {
483
+ const visible = kind === "all" || item.dataset.kind === kind;
484
+ item.hidden = !visible;
485
+ if (visible) shown += 1;
486
+ });
487
+ release.hidden = shown === 0;
488
+ });
489
+ document.querySelectorAll(".release-archive").forEach((group) => {
490
+ group.hidden = !group.querySelector(".archive-row:not([hidden])");
491
+ if (kind !== "all" && !group.hidden) group.open = true;
492
+ });
85
493
  });
86
494
  });
Binary file
@@ -1,35 +1,13 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" role="img" aria-labelledby="title desc">
2
- <title id="title">Pi Agent Platform logo</title>
3
- <desc id="desc">Rounded dark monogram mark with Pi letters and guarded runtime lines.</desc>
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-labelledby="title">
2
+ <title id="title">Piagent</title>
4
3
  <defs>
5
- <linearGradient id="shell" x1="17" y1="8" x2="111" y2="120" gradientUnits="userSpaceOnUse">
6
- <stop offset="0" stop-color="#1c2116"/>
7
- <stop offset="0.5" stop-color="#0b0d0f"/>
8
- <stop offset="1" stop-color="#131917"/>
4
+ <linearGradient id="pa-bg" x1="0" y1="0" x2="64" y2="64" gradientUnits="userSpaceOnUse">
5
+ <stop offset="0" stop-color="#1b1e17"/>
6
+ <stop offset="1" stop-color="#09090b"/>
9
7
  </linearGradient>
10
- <linearGradient id="accent" x1="28" y1="24" x2="100" y2="108" gradientUnits="userSpaceOnUse">
11
- <stop offset="0" stop-color="#e6ff8c"/>
12
- <stop offset="1" stop-color="#8be9c8"/>
13
- </linearGradient>
14
- <radialGradient id="glow" cx="38" cy="30" r="86" gradientUnits="userSpaceOnUse">
15
- <stop offset="0" stop-color="#d8ff7a" stop-opacity="0.34"/>
16
- <stop offset="0.58" stop-color="#8be9c8" stop-opacity="0.08"/>
17
- <stop offset="1" stop-color="#8be9c8" stop-opacity="0"/>
18
- </radialGradient>
19
- <filter id="soft" x="-24" y="-24" width="176" height="176" color-interpolation-filters="sRGB">
20
- <feDropShadow dx="0" dy="14" stdDeviation="13" flood-color="#000000" flood-opacity="0.45"/>
21
- <feDropShadow dx="0" dy="0" stdDeviation="6" flood-color="#d8ff7a" flood-opacity="0.2"/>
22
- </filter>
23
8
  </defs>
24
- <rect width="128" height="128" rx="32" fill="#050505"/>
25
- <rect x="10" y="10" width="108" height="108" rx="28" fill="url(#shell)" filter="url(#soft)"/>
26
- <rect x="10.75" y="10.75" width="106.5" height="106.5" rx="27.25" fill="none" stroke="url(#accent)" stroke-opacity="0.72" stroke-width="1.5"/>
27
- <path d="M26 76c19-1 23-19 38-22 15-2 20 10 38 8" fill="none" stroke="url(#accent)" stroke-width="6" stroke-linecap="round" stroke-opacity="0.88"/>
28
- <path d="M26 90c22 1 30-14 43-15 15-1 20 10 33 7" fill="none" stroke="#f4f4ef" stroke-width="2.5" stroke-linecap="round" stroke-opacity="0.22"/>
29
- <circle cx="34" cy="34" r="4" fill="#d8ff7a"/>
30
- <circle cx="96" cy="35" r="3" fill="#8be9c8" opacity="0.85"/>
31
- <circle cx="94" cy="95" r="2.5" fill="#d8ff7a" opacity="0.72"/>
32
- <rect x="25" y="25" width="78" height="78" rx="20" fill="url(#glow)" opacity="0.8"/>
33
- <text x="64" y="78" text-anchor="middle" font-family="JetBrains Mono, IBM Plex Mono, ui-monospace, monospace" font-size="42" font-weight="700" letter-spacing="-3" fill="#f4f4ef">Pi</text>
34
- <path d="M37 93h54" stroke="#d8ff7a" stroke-width="3" stroke-linecap="round" opacity="0.8"/>
9
+ <rect width="64" height="64" rx="15" fill="url(#pa-bg)"/>
10
+ <rect x=".75" y=".75" width="62.5" height="62.5" rx="14.25" fill="none" stroke="#ffffff" stroke-opacity=".12" stroke-width="1.5"/>
11
+ <path d="M13 21.5h27M21 22v22M32.5 22v17.5c0 3 1.6 4.5 4.5 4.5" fill="none" stroke="#f2f2ed" stroke-width="5.5" stroke-linecap="round"/>
12
+ <rect x="44" y="19.5" width="6.5" height="25" rx="1.8" fill="#c6f432"/>
35
13
  </svg>
@@ -0,0 +1 @@
1
+ [{"href":"/en/","icon":"home","group":"Get started","nav":"Overview","title":"Pi Agent Platform","lead":"A coding agent for the whole team: a guard checks every tool call, the team's Harness runs checks and review, one dashboard for Terminal and the browser.","headings":[{"id":"different","text":"The agent works fast. The platform keeps it right."},{"id":"one-turn","text":"You say what you need. The rest is verified."},{"id":"screens","text":"Captured from a running dashboard"},{"id":"two-modes","text":"One dashboard, two ways to use models"},{"id":"install","text":"Running in two minutes"},{"id":"latest-updates","text":"Steady releases, every change written down"}]},{"href":"/en/ecosystem","icon":"network","group":"Get started","nav":"Ecosystem","title":"Piagent, Agent Watch and Agent Studio","lead":"What runs on your machine, what runs on the company server, and when to use your own model or the company's.","headings":[{"id":"overview","text":"One dashboard, two ways to use models"},{"id":"parts","text":"Each part does one job"},{"id":"piagent","text":"Piagent"},{"id":"guard","text":"Guard"},{"id":"agent-watch","text":"Agent Watch"},{"id":"agent-studio","text":"Agent Studio"},{"id":"one-turn","text":"From a message to \"A check passed · Review found no blocking issue\""},{"id":"choose","text":"When to use personal, when to use company"}]},{"href":"/en/quickstart","icon":"rocket","group":"Get started","nav":"Install","title":"Install and run your first turn","lead":"Two commands to install. Open a repository, pick its profile and permissions, then ask in plain language.","headings":[{"id":"install","text":"Install"},{"id":"open-project","text":"Open a project"},{"id":"read-project","text":"Let the agent read the project"},{"id":"profile-and-permission","text":"Pick a profile and permission"},{"id":"ask","text":"Ask in plain language"},{"id":"check","text":"Check the installation"},{"id":"supported","text":"Supported systems"},{"id":"next","text":"Next"}]},{"href":"/en/windows","icon":"windows","group":"Get started","nav":"Windows","title":"Install on Windows with one command","lead":"One PowerShell command installs WSL2, Ubuntu, Piagent and Agent Watch, binds the company key and adds Piagent to the Start menu. Run it again to update.","headings":[{"id":"one-command","text":"Install with one command"},{"id":"every-day","text":"Every day"},{"id":"when-setup-fails","text":"When setup fails"},{"id":"why-wsl2","text":"Why WSL2"}]},{"href":"/en/update","icon":"refresh","group":"Get started","nav":"Update & uninstall","title":"Update, roll back and uninstall","lead":"One command moves to a new release, and the dashboard and Terminal tell you when one is out. Rollback and uninstall both preview first.","headings":[{"id":"update","text":"Update"},{"id":"what-it-does","text":"What it does, in order"},{"id":"options","text":"Common options"},{"id":"pin-for-a-team","text":"Pin one release for a team"},{"id":"rollback","text":"Roll back"},{"id":"uninstall","text":"Uninstall"},{"id":"windows","text":"On Windows"}]},{"href":"/en/whats-new","icon":"sparkles","group":"Get started","nav":"Changelog","title":"Changelog","lead":"Every release: what changed and which Studio or Agent Watch it needs. Newest first.","headings":[{"id":"latest","text":"Latest"},{"id":"v1-42-0-title","text":"A redesigned dashboard and older messages that load without jumping"},{"id":"v1-41-0-title","text":"Front-end change reports with before/after screenshots"},{"id":"v1-40-0-title","text":"Local projects are reported to Studio too"},{"id":"v1-39-0-title","text":"Warnings when a team key is used outside its team"},{"id":"v1-38-2-title","text":"Folder tree, colour themes and an editor that stays in view"},{"id":"v1-38-1-title","text":"Published release of v1.38.0"},{"id":"v1-38-0-title","text":"Piagent becomes an editor, and Bypass asks nothing not published"},{"id":"v1-37-1-title","text":"Run closes and helper reports Studio can trust"},{"id":"v1-37-0-title","text":"A cut answer no longer stops a long task"},{"id":"v1-36-1-title","text":"A brief Agent Watch failure no longer stops a task"},{"id":"v1-36-0-title","text":"Team memory learns from what went wrong"},{"id":"v1-35-1-title","text":"A passing model provider failure no longer stops a long task"},{"id":"v1-35-0-title","text":"Long company tasks go on across Studio updates"},{"id":"v1-34-3-title","text":"Agent Watch notifies for company conversations"},{"id":"v1-34-2-title","text":"Helper outcomes reach Studio"},{"id":"v1-34-1-title","text":"Helper outcomes, line review and Agent Watch notifications"},{"id":"v1-33-0-title","text":"Company and personal sessions run on Pi 1.3.0"},{"id":"v1-32-1-title","text":"Bypass survives a reload, and a tab in use stays signed in"},{"id":"v1-32-0-title","text":"Failed or stopped company streams end at Studio too"},{"id":"v1-31-1-title","text":"memory_ask is there from the first conversation after an update"},{"id":"v1-31-0-title","text":"Team memory follows the work"},{"id":"v1-30-1-title","text":"Company Terminal hides model names; clear message in the home folder"},{"id":"v1-30-0-title","text":"Large contexts over slow connections; a clearer company Terminal"},{"id":"v1-29-0-title","text":"Company Terminal shows the Harness and keeps work inside the company"},{"id":"archive","text":"Earlier releases"}]},{"href":"/en/scope","icon":"target","group":"Get started","nav":"Platform scope","title":"What the platform owns","lead":"Every agent has governance primitives. This platform packages one repeatable operating model on Pi for every repository.","headings":[{"id":"not-a-new-agent","text":"This is not a new agent"},{"id":"complementary-layers","text":"The two layers are complementary"},{"id":"lower-layer","text":"The lower layer sees processes"},{"id":"upper-layer","text":"The upper layer sees intent"},{"id":"when-not-needed","text":"When you may not need it"}]},{"href":"/en/profiles","icon":"sliders","group":"Build","nav":"Project profiles","title":"Choose the right profile","lead":"A profile sets protected paths, verification commands and the capabilities a repository is granted.","headings":[]},{"href":"/en/permissions","icon":"key","group":"Build","nav":"Permission modes","title":"Change permissions per task","lead":"Permission modes apply to the current session only. Changing one does not modify the project profile.","headings":[]},{"href":"/en/commands","icon":"terminal","group":"Build","nav":"Command catalog","title":"Short commands, predictable behaviour","lead":"Slash commands package policy and flow, with one inspector for diffs, commands, safety and context.","headings":[{"id":"session-basics","text":"Session basics"},{"id":"project-profile","text":"Project profile"},{"id":"implementation-workflow","text":"Assigning work"},{"id":"git-handoff","text":"Git handoff"},{"id":"fresh-sessions","text":"New sessions"},{"id":"mcp","text":"MCP"},{"id":"subagents","text":"Subagents"},{"id":"product-status-commands","text":"Inspect runtime decisions"},{"id":"skills-and-packages","text":"Audit test quality, run experiment loops"}]},{"href":"/en/workflows","icon":"branch","group":"Build","nav":"Workflows","title":"Day-to-day work","lead":"No workflow launchers: say what you need in plain language. Inspection commands only observe and never start a model turn.","headings":[{"id":"conclusion","text":"No new command is mandatory in the daily flow"},{"id":"command-effects","text":"How far the inspection commands affect a workflow"},{"id":"example-flow","text":"A complete flow can look like this"}]},{"href":"/en/mcp","icon":"plug","group":"MCP","nav":"MCP overview","title":"MCP in Pi Agent Platform","lead":"The platform installs pi-mcp-adapter for token-efficient MCP access and manages servers across four configuration layers.","headings":[{"id":"start","text":"Where to start"},{"id":"three-similar-names","text":"Three similar names that are not connected"},{"id":"four-config-layers","text":"Four configuration layers"},{"id":"presets","text":"Pinned presets"},{"id":"token-efficiency","text":"Token efficiency"}]},{"href":"/en/mcp-servers","icon":"server","group":"MCP","nav":"Server management","title":"Add, inspect and remove MCP servers","lead":"piagent-mcp provides add, remove, get, list, enable and disable across all four scopes.","headings":[{"id":"two-surfaces","text":"Two surfaces for two contexts"},{"id":"commands","text":"Commands"},{"id":"in-session","text":"In a session"},{"id":"add-server","text":"Add a server"},{"id":"inspect","text":"Inspect"},{"id":"disable-remove","text":"Disable and remove"},{"id":"preset-command","text":"Preset"},{"id":"output","text":"Output names the modified file"},{"id":"updates","text":"Custom servers and updates"},{"id":"preserved","text":"Preserved"},{"id":"overwritten","text":"Replaced"}]},{"href":"/en/mcp-auth","icon":"lock","group":"MCP","nav":"Authentication","title":"Authentication and readiness","lead":"The platform stores no tokens and runs no OAuth. It rejects secrets in configuration and reports which servers are actually ready.","headings":[{"id":"no-token-storage","text":"The platform does not store tokens"},{"id":"reject-secrets","text":"Secrets are rejected before configuration is written"},{"id":"field-name","text":"Field-name detection"},{"id":"value-shape","text":"Value-shape detection"},{"id":"readiness","text":"Readiness states"}]},{"href":"/en/mcp-approval","icon":"shield-check","group":"MCP","nav":"Approval gate","title":"Repository servers require approval","lead":"Cloning an untrusted repository must not give its author a process on your machine. Decisions live outside the repository, pinned by digest.","headings":[{"id":"problem","text":"The problem"},{"id":"three-properties","text":"Three properties"},{"id":"outside-repository","text":"The decision lives outside the repository"},{"id":"digest-pinning","text":"Pinned by digest"},{"id":"tool-call-enforcement","text":"Enforced at tool call"},{"id":"limit","text":"Limit"},{"id":"imports","text":"Servers supplied through imports"},{"id":"design-decisions","text":"Design decisions"},{"id":"migration","text":"Migration"}]},{"href":"/en/security","icon":"shield","group":"Operate","nav":"Security model","title":"Policy enforcement, not an OS sandbox","lead":"Controlled tool surfaces fail closed when profiles, integrity locks or permissions are invalid.","headings":[{"id":"protected-paths","text":"Protected paths"},{"id":"shell-hardening","text":"Shell hardening"},{"id":"secret-redaction","text":"Secret redaction"},{"id":"capability-lock","text":"Capability lock"},{"id":"human-action-gate","text":"Human-action gate"},{"id":"verify-evidence","text":"Real checks"}]},{"href":"/en/architecture","icon":"layers","group":"Operate","nav":"Architecture","title":"Repository architecture","lead":"Core holds reusable policy and runtime infrastructure; business logic stays in the project that owns it.","headings":[{"id":"composition","text":"Composition"},{"id":"runtime-adapters","text":"Runtime adapters"},{"id":"core-services","text":"Core services"},{"id":"integrations","text":"Integrations"},{"id":"entrypoints","text":"Entrypoints"},{"id":"product-assets","text":"Product assets"},{"id":"adaptive-runtime","text":"Piagent now separates runtime decisions from Pi wiring"},{"id":"state-ownership","text":"Each state has one source of truth"}]},{"href":"/en/context-engine","icon":"cpu","group":"Operate","nav":"Context Engine","title":"Less context, same model quality","lead":"Local indexing, ranked retrieval, task-driven tool loading, semantic compaction and transparent telemetry.","headings":[{"id":"dynamic-tools","text":"Task-driven tools"},{"id":"local-index","text":"Local index"},{"id":"ranked-context","text":"Ranked context"},{"id":"semantic-compact","text":"Semantic compaction"},{"id":"test-impact","text":"Test impact"},{"id":"agent-watch","text":"Agent Watch"}]},{"href":"/en/benchmark","icon":"chart","group":"Operate","nav":"Benchmark","title":"Quality and efficiency evidence","lead":"Piagent against codex-cli on the same model and thinking level: 18 task families, 108 automatically graded sessions.","headings":[{"id":"benchmark-verdict","text":"Historical fresh-token score: 9.97/10"},{"id":"configuration","text":"Same model, same tasks, automatic grading"},{"id":"scores","text":"Token reduction does not trade away quality"},{"id":"tokens","text":"Measure the fixed workload, not only successful tasks"},{"id":"coverage","text":"Six domains with three families each"},{"id":"benchmark-bands","text":"Use the right suite for the decision"},{"id":"transparency","text":"Interpret results within their measured scope"},{"id":"historical-v1-2-12","text":"v1.2.12 remains historical evidence"}]},{"href":"/en/runtime","icon":"wrench","group":"Operate","nav":"Runtime tools","title":"Terminal commands for operators","lead":"Commands that run outside Pi: setup, updates, doctor, benchmarks, capabilities and MCP.","headings":[]},{"href":"/en/team","icon":"users","group":"Operate","nav":"Team usage","title":"One operating standard for the team","lead":"Each repository links the package, selects a fitting profile and keeps project documentation in its own repository.","headings":[{"id":"do","text":"Do"},{"id":"avoid","text":"Avoid"}]}]