@piagent/platform 1.41.0 → 1.42.1

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 (125) hide show
  1. package/CHANGELOG.md +1020 -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 +2 -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 +1612 -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 +4485 -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 +1616 -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/managed/dist/workspace/manifest.json +1 -1
  73. package/packages/piagent-core/managed/dist/workspace/worker.mjs +1 -1
  74. package/packages/piagent-core/package.json +1 -1
  75. package/packages/piagent-core/security/workspace-file-reader.ts +3 -1
  76. package/packages/piagent-webui/dist/client/assets/ActivityPanel-DRAiHliI.js +1 -0
  77. package/packages/piagent-webui/dist/client/assets/App-JMuP_9-r.js +5 -0
  78. package/packages/piagent-webui/dist/client/assets/GatewayApp-awcOLVTi.js +93 -0
  79. package/packages/piagent-webui/dist/client/assets/InspectionApp-DtDc9NxN.js +1 -0
  80. package/packages/piagent-webui/dist/client/assets/LineReview-DZ9ez8kO.js +3 -0
  81. package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-DeLo-xiz.js → MarkdownMessage-D7bbIWJe.js} +2 -2
  82. package/packages/piagent-webui/dist/client/assets/SessionAgentWorkspace-DLwK12hX.js +1 -0
  83. package/packages/piagent-webui/dist/client/assets/SessionInspectorDrawer-CsPNI6aU.js +2 -0
  84. package/packages/piagent-webui/dist/client/assets/SettingsNavigation-CM0s3QUC.js +3 -0
  85. package/packages/piagent-webui/dist/client/assets/SettingsPage-Cr6M2FVg.js +1 -0
  86. package/packages/piagent-webui/dist/client/assets/TerminalPanel-A78JVp4e.js +2 -0
  87. package/packages/piagent-webui/dist/client/assets/{TerminalView-r9rG24IO.js → TerminalView-CR4TO5f1.js} +3 -3
  88. package/packages/piagent-webui/dist/client/assets/WorkspaceCodeEditor-DPOp-ZoD.js +2 -0
  89. package/packages/piagent-webui/dist/client/assets/WorkspaceExplorer-H8Ogln3d.js +5 -0
  90. package/packages/piagent-webui/dist/client/assets/WorkspaceQuickOpen-vQ5DqfJl.js +1 -0
  91. package/packages/piagent-webui/dist/client/assets/api-BUjrtT1B.js +1 -0
  92. package/packages/piagent-webui/dist/client/assets/composer-drafts-BtMaVw8W.js +1 -0
  93. package/packages/piagent-webui/dist/client/assets/index-Dqb2hNan.css +1 -0
  94. package/packages/piagent-webui/dist/client/assets/index-qi6mjvTy.js +37 -0
  95. package/packages/piagent-webui/dist/client/assets/latest-request-DXcYQW86.js +1 -0
  96. package/packages/piagent-webui/dist/client/assets/tab-close-DHDqCxTL.js +1 -0
  97. package/packages/piagent-webui/dist/client/assets/terminal-transport-Dk5pYNX5.js +1 -0
  98. package/packages/piagent-webui/dist/client/assets/transcript-view-model-BNGQCkzY.js +3 -0
  99. package/packages/piagent-webui/dist/client/assets/ui-preferences-Dy4qeODT.js +1 -0
  100. package/packages/piagent-webui/dist/client/assets/vendor-CA5pzJiI.js +136 -0
  101. package/packages/piagent-webui/dist/client/assets/view-model-CWNiS4JN.js +1 -0
  102. package/packages/piagent-webui/dist/client/assets/workspace-navigation-C8GtShbg.js +1 -0
  103. package/packages/piagent-webui/dist/client/index.html +4 -9
  104. package/packages/piagent-webui/gateway/signed-session-journal.ts +16 -6
  105. package/packages/piagent-webui/server/loopback-server.ts +19 -5
  106. package/packages/piagent-webui/server/static-bundle.ts +10 -2
  107. package/scripts/build-docs-site.mjs +205 -348
  108. package/scripts/check-public-wording.mjs +2 -1
  109. package/scripts/docs-site-icons.mjs +84 -0
  110. package/scripts/docs-site-nav.mjs +302 -0
  111. package/scripts/docs-site-releases.mjs +227 -0
  112. package/scripts/verify-local.sh +17 -1
  113. package/packages/piagent-webui/dist/client/assets/Alert-DGWhP2MM.js +0 -1
  114. package/packages/piagent-webui/dist/client/assets/Button-QItfZUNj.js +0 -1
  115. package/packages/piagent-webui/dist/client/assets/DialogTitle-CyaFPaiW.js +0 -3
  116. package/packages/piagent-webui/dist/client/assets/TerminalPanel-BG-DP_qm.js +0 -2
  117. package/packages/piagent-webui/dist/client/assets/Typography-B9om7Evq.js +0 -1
  118. package/packages/piagent-webui/dist/client/assets/WorkspaceCodeEditor-BnqaR3pK.js +0 -2
  119. package/packages/piagent-webui/dist/client/assets/WorkspaceExplorer-BIxwiGtl.js +0 -5
  120. package/packages/piagent-webui/dist/client/assets/WorkspaceQuickOpen-CK3dILhQ.js +0 -1
  121. package/packages/piagent-webui/dist/client/assets/index-DBUTrEXi.css +0 -1
  122. package/packages/piagent-webui/dist/client/assets/index-rlyRccut.js +0 -102
  123. package/packages/piagent-webui/dist/client/assets/tab-close-BoJRyBO_.js +0 -1
  124. package/packages/piagent-webui/dist/client/assets/ui-preferences-BO3AV7QE.js +0 -10
  125. package/packages/piagent-webui/dist/client/assets/useId-DEefKUAT.js +0 -124
@@ -3,6 +3,10 @@ import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
 
6
+ import { icon, iconSprite, iconsUsedIn } from "./docs-site-icons.mjs";
7
+ import { NAV, NAV_BY_LOCALE } from "./docs-site-nav.mjs";
8
+ import { readReleases, renderChangelogMarkdown, renderHeroPill, renderLatestReleases, renderReleaseNotes } from "./docs-site-releases.mjs";
9
+
6
10
  // Assembles the docs site: one static page per topic, from a shared shell and a
7
11
  // content fragment each.
8
12
  //
@@ -12,15 +16,20 @@ import { fileURLToPath } from "node:url";
12
16
  // here instead and the output is committed. `--check` fails when the committed
13
17
  // output no longer matches its source, the same arrangement the capability
14
18
  // catalog already uses.
19
+ //
20
+ // Release notes have one source, docs-site/releases.json. It produces the
21
+ // Changelog page in both languages, the "latest" cards on the landing page and
22
+ // CHANGELOG.md, so the three can never disagree.
15
23
 
16
24
  const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
17
25
  const siteRoot = path.join(repoRoot, "docs-site");
18
26
  const contentRoot = path.join(siteRoot, "content");
19
27
 
20
28
  const SITE = {
21
- title: "Pi Agent Platform Docs",
29
+ title: "Pi Agent Platform",
22
30
  origin: "https://piagent.io.vn",
23
31
  repo: "https://github.com/Vt-mmm/piagent",
32
+ npm: "https://www.npmjs.com/package/@piagent/platform",
24
33
  facebook: "https://www.facebook.com/vinhtam0544/"
25
34
  };
26
35
 
@@ -44,262 +53,8 @@ const ANALYTICS_SNIPPET = ` <script>
44
53
  <script defer src="/_vercel/insights/script.js"></script>
45
54
  `;
46
55
 
47
- // Order here is the order in the sidebar, and it is also the order of the
48
- // previous/next links at the foot of each page.
49
- const NAV = [
50
- {
51
- group: "Get started",
52
- pages: [
53
- {
54
- slug: "index",
55
- href: "/",
56
- nav: "Overview",
57
- title: "Pi Agent Platform",
58
- lead: "Coding agent cho cả team: một dashboard cho Terminal và trình duyệt, model cá nhân hoặc của công ty, guard kiểm tra mọi tool call."
59
- },
60
- {
61
- slug: "ecosystem",
62
- nav: "Hệ sinh thái",
63
- title: "Piagent, Agent Watch và Agent Studio",
64
- lead: "Một dashboard, hai cách dùng model: tài khoản của bạn, hoặc model của công ty qua Agent Watch và Agent Studio, với Harness tự kiểm tra từng lượt."
65
- },
66
- {
67
- slug: "quickstart",
68
- nav: "Quickstart",
69
- title: "Step by step cho thành viên mới",
70
- lead: "Hai lệnh để cài, mở repo, chọn profile và quyền, rồi giao việc bằng lời thường; guard kiểm tra từng tool call."
71
- },
72
- {
73
- slug: "windows",
74
- nav: "Cài trên Windows",
75
- title: "Piagent trên Windows bằng một lệnh",
76
- lead: "Một lệnh PowerShell cài WSL2, Ubuntu, Piagent và Agent Watch, nối key công ty và thêm Piagent vào Start menu; chạy lại để cập nhật."
77
- },
78
- {
79
- slug: "whats-new",
80
- nav: "Có gì mới",
81
- title: "Bản 1.9.0: nói việc bằng lời, Harness của team kiểm tra",
82
- lead: "Bỏ task contract và lệnh workflow. Chế độ công ty tự yêu cầu check và review; nút Dừng, lệnh có mạng theo lần duyệt và thẻ duyệt dễ đọc hơn."
83
- },
84
- {
85
- slug: "scope",
86
- nav: "Phạm vi platform",
87
- title: "Platform này nhận phần nào về mình",
88
- lead: "Agent coding CLI nào cũng đã có cơ chế governance riêng. Platform này không thay thế lớp đó — nó đóng gói một cách vận hành cụ thể trên Pi để mọi repo dùng lại."
89
- }
90
- ]
91
- },
92
- {
93
- group: "Build",
94
- pages: [
95
- {
96
- slug: "profiles",
97
- nav: "Project profile",
98
- title: "Chọn đúng đường ray cho repo",
99
- lead: "Profile ghi vào .pi/piagent-profile.json và lock tương ứng. Nó quyết định protected path, verify command và capability mà project cấp."
100
- },
101
- {
102
- slug: "permissions",
103
- nav: "Permission modes",
104
- title: "Một command để đổi quyền theo task",
105
- lead: "Permission mode là session-local override. Đổi mode không sửa project profile."
106
- },
107
- {
108
- slug: "commands",
109
- nav: "Command catalog",
110
- title: "Command ngắn, rõ, ít token burn",
111
- lead: "Slash command gói sẵn policy và flow; một inspector menu duy nhất cho diff, command, safety và context. Thành viên không cần nhớ nhiều command rời."
112
- },
113
- {
114
- slug: "workflows",
115
- nav: "Workflows",
116
- title: "Workflow thay đổi thế nào sau update",
117
- lead: "Từ bản 1.9.0 không còn launcher workflow: nói việc cần làm bằng lời thường. Các command xem thêm chỉ để quan sát và không bao giờ mở lượt model."
118
- }
119
- ]
120
- },
121
- {
122
- group: "MCP",
123
- pages: [
124
- {
125
- slug: "mcp",
126
- nav: "MCP overview",
127
- title: "MCP trong Pi Agent Platform",
128
- lead: "Pi core không hard-code MCP. Platform cài pi-mcp-adapter để dùng MCP theo kiểu token-efficient, và thêm một lớp quản lý server lên trên bốn layer config."
129
- },
130
- {
131
- slug: "mcp-servers",
132
- nav: "Quản lý server",
133
- title: "Thêm, xem và gỡ MCP server",
134
- lead: "piagent-mcp có bề mặt lệnh đầy đủ chứ không chỉ seed preset: add, remove, get, list, enable, disable — trên cả bốn scope."
135
- },
136
- {
137
- slug: "mcp-auth",
138
- nav: "Auth và readiness",
139
- title: "Đăng nhập và trạng thái sẵn sàng",
140
- lead: "Platform không giữ token và không tự chạy OAuth. Nó từ chối ghi secret vào config, và cho biết server nào thật sự gọi được."
141
- },
142
- {
143
- slug: "mcp-approval",
144
- nav: "Approval gate",
145
- title: "Server đến từ repo phải được duyệt",
146
- lead: "Clone một repo lạ rồi mở session không được phép cho tác giả repo đó một process trên máy mình. Quyết định lưu ngoài repo và pin theo digest."
147
- }
148
- ]
149
- },
150
- {
151
- group: "Operate",
152
- pages: [
153
- {
154
- slug: "security",
155
- nav: "Security model",
156
- title: "Policy enforcement layer, không phải OS sandbox",
157
- lead: "Platform ưu tiên fail-closed trong bề mặt tool được kiểm soát: khi lock sai, profile sai hoặc quyền không đủ thì chặn, không đoán."
158
- },
159
- {
160
- slug: "architecture",
161
- nav: "Architecture",
162
- title: "Kiến trúc repo",
163
- lead: "Repo này không nhét business logic vào core. Core chỉ giữ policy, prompts, guard, skills và scripts dùng lại được."
164
- },
165
- {
166
- slug: "context-engine",
167
- nav: "Context Engine",
168
- title: "Ít context hơn, giữ nguyên chất lượng model",
169
- lead: "Index local, retrieval có xếp hạng, tool loading theo task, semantic compaction và telemetry minh bạch cho Agent Watch."
170
- },
171
- {
172
- slug: "benchmark",
173
- nav: "Benchmark",
174
- title: "Bằng chứng chất lượng và hiệu suất",
175
- lead: "Production benchmark so sánh Piagent với surface codex-cli trên cùng model, cùng thinking level, 18 nhóm bài và 108 session được chấm tự động."
176
- },
177
- {
178
- slug: "runtime",
179
- nav: "Runtime tools",
180
- title: "Terminal commands cho operator",
181
- lead: "Nhóm command này chạy ngoài slash command, dùng cho setup, doctor, benchmark, capability và MCP."
182
- },
183
- {
184
- slug: "team",
185
- nav: "Team usage",
186
- title: "Team nên dùng như một chuẩn vận hành",
187
- lead: "Mỗi repo chỉ cần link package, chọn profile đúng và giữ tài liệu project-specific ở repo của dự án đó."
188
- }
189
- ]
190
- }
191
- ];
192
-
193
- const EN_PAGE_COPY = {
194
- index: {
195
- nav: "Overview",
196
- title: "Pi Agent Platform",
197
- lead: "A coding agent for the whole team: one dashboard for Terminal and the browser, personal or company models, and a guard that checks every tool call."
198
- },
199
- ecosystem: {
200
- nav: "Ecosystem",
201
- title: "Piagent, Agent Watch and Agent Studio",
202
- lead: "One dashboard, two ways to use models: your own account, or the company's models through Agent Watch and Agent Studio, with a Harness that checks every turn."
203
- },
204
- quickstart: {
205
- nav: "Quickstart",
206
- title: "Step by step for new team members",
207
- lead: "Install with two commands, open a repository, choose its profile and permissions, then ask in plain language; the guard checks every tool call."
208
- },
209
- windows: {
210
- nav: "Windows",
211
- title: "Piagent on Windows with one command",
212
- 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."
213
- },
214
- "whats-new": {
215
- nav: "What’s new",
216
- title: "1.9.0: ask in plain language, the team's Harness checks",
217
- lead: "The task contract and workflow commands are gone. Company mode asks for checks and a review on its own; Stop, network commands on approval and clearer approval cards."
218
- },
219
- scope: {
220
- nav: "Platform scope",
221
- title: "What the platform owns",
222
- lead: "General coding agents already provide governance primitives. This platform packages one repeatable operating model for Pi across every repository."
223
- },
224
- profiles: {
225
- nav: "Project profiles",
226
- title: "Choose the right operating profile",
227
- lead: "A project profile defines protected paths, verification commands, and the capabilities granted to a repository."
228
- },
229
- permissions: {
230
- nav: "Permission modes",
231
- title: "Change task permissions with one command",
232
- lead: "Permission modes are session-local overrides. Changing a mode does not modify the project profile."
233
- },
234
- commands: {
235
- nav: "Command catalog",
236
- title: "Short commands with predictable behavior",
237
- lead: "Slash commands package policy and workflow directly, with one inspector menu for diffs, commands, safety, and context instead of many one-off commands."
238
- },
239
- workflows: {
240
- nav: "Workflows",
241
- title: "How workflows change after the update",
242
- lead: "Since 1.9.0 there are no workflow launchers: say what you need in plain language. The optional inspection commands only observe and never start a model turn."
243
- },
244
- mcp: {
245
- nav: "MCP overview",
246
- title: "MCP in Pi Agent Platform",
247
- lead: "The platform installs pi-mcp-adapter for token-efficient MCP access and manages servers across four configuration layers."
248
- },
249
- "mcp-servers": {
250
- nav: "Server management",
251
- title: "Add, inspect, and remove MCP servers",
252
- lead: "piagent-mcp provides deterministic add, remove, get, list, enable, and disable operations across all four scopes."
253
- },
254
- "mcp-auth": {
255
- nav: "Authentication",
256
- title: "Authentication and readiness",
257
- lead: "The platform does not store tokens or run OAuth. It rejects secrets in configuration and reports which servers are actually ready."
258
- },
259
- "mcp-approval": {
260
- nav: "Approval gate",
261
- title: "Repository servers require approval",
262
- lead: "Cloning an untrusted repository must not grant its author a process on the operator's machine. Decisions live outside the repository and are pinned by digest."
263
- },
264
- security: {
265
- nav: "Security model",
266
- title: "Policy enforcement, not an OS sandbox",
267
- lead: "Controlled tool surfaces fail closed when profiles, integrity locks or permissions are invalid."
268
- },
269
- architecture: {
270
- nav: "Architecture",
271
- title: "Repository architecture",
272
- lead: "Core contains reusable policy and runtime infrastructure, while project-specific business logic stays in the project that owns it."
273
- },
274
- "context-engine": {
275
- nav: "Context Engine",
276
- title: "Less context without reducing model quality",
277
- lead: "Local indexing, ranked retrieval, task-driven tool loading, semantic compaction, and transparent telemetry for Agent Watch."
278
- },
279
- benchmark: {
280
- nav: "Benchmark",
281
- title: "Quality and efficiency evidence",
282
- lead: "The production benchmark compares Piagent with codex-cli using the same model, thinking level, 18 task families, and 108 automatically graded sessions."
283
- },
284
- runtime: {
285
- nav: "Runtime tools",
286
- title: "Terminal commands for operators",
287
- lead: "Use these commands outside Pi for setup, updates, doctor checks, benchmarks, capabilities, and MCP management."
288
- },
289
- team: {
290
- nav: "Team usage",
291
- title: "A repeatable team operating standard",
292
- lead: "Each repository links the package, selects a fitting profile, and keeps project-specific documentation in the repository that owns it."
293
- }
294
- };
295
-
296
- const NAV_BY_LOCALE = {
297
- vi: NAV,
298
- en: NAV.map((section) => ({
299
- group: section.group,
300
- pages: section.pages.map((page) => ({ ...page, ...EN_PAGE_COPY[page.slug] }))
301
- }))
302
- };
56
+ // Set before first paint so a reader who chose light never sees a dark flash.
57
+ const THEME_SNIPPET = `<script>try{var t=localStorage.getItem("piagent-docs-theme");if(t==="light"||t==="dark")document.documentElement.dataset.theme=t}catch(e){}</script>`;
303
58
 
304
59
  const LOCALES = {
305
60
  vi: {
@@ -312,19 +67,28 @@ const LOCALES = {
312
67
  breadcrumb: "Docs",
313
68
  language: "Ngôn ngữ",
314
69
  githubAria: "Mở GitHub repository",
315
- menu: "Lộ trình",
316
- commands: "Commands",
70
+ menu: "Mở menu tài liệu",
71
+ docs: "Tài liệu",
72
+ changelog: "Changelog",
73
+ benchmark: "Benchmark",
317
74
  searchAria: "Tìm trong tài liệu",
318
- searchPlaceholder: "Tìm trang",
75
+ searchPlaceholder: "Tìm trong tài liệu…",
76
+ searchEmpty: "Không có trang nào khớp.",
77
+ searchHint: ["↑↓ chọn", "↵ mở", "esc đóng"],
78
+ themeAria: "Đổi giao diện sáng/tối",
319
79
  navAria: "Tài liệu",
320
80
  pagerAria: "Trang kế tiếp",
321
81
  previous: "Trước",
322
82
  next: "Tiếp",
323
83
  tocAria: "Mục lục trang",
324
84
  tocTitle: "Trên trang này",
85
+ editPage: "Sửa trang này",
325
86
  externalLinks: "Liên kết ngoài",
87
+ skip: "Tới nội dung",
326
88
  copySuccess: "Đã lấy",
327
- copyFallback: "Chọn lệnh"
89
+ copyFallback: "Chọn lệnh",
90
+ copyLabel: "Sao chép",
91
+ footer: "Pi Agent Platform · MIT · tài liệu tĩnh, không cookie"
328
92
  }
329
93
  },
330
94
  en: {
@@ -337,19 +101,28 @@ const LOCALES = {
337
101
  breadcrumb: "Docs",
338
102
  language: "Language",
339
103
  githubAria: "Open GitHub repository",
340
- menu: "Menu",
341
- commands: "Commands",
104
+ menu: "Open docs menu",
105
+ docs: "Docs",
106
+ changelog: "Changelog",
107
+ benchmark: "Benchmark",
342
108
  searchAria: "Search documentation",
343
- searchPlaceholder: "Search pages",
109
+ searchPlaceholder: "Search the docs…",
110
+ searchEmpty: "No page matches.",
111
+ searchHint: ["↑↓ select", "↵ open", "esc close"],
112
+ themeAria: "Switch light/dark theme",
344
113
  navAria: "Documentation",
345
114
  pagerAria: "Adjacent pages",
346
115
  previous: "Previous",
347
116
  next: "Next",
348
117
  tocAria: "Page contents",
349
118
  tocTitle: "On this page",
119
+ editPage: "Edit this page",
350
120
  externalLinks: "External links",
121
+ skip: "Skip to content",
351
122
  copySuccess: "Copied",
352
- copyFallback: "Select command"
123
+ copyFallback: "Select command",
124
+ copyLabel: "Copy",
125
+ footer: "Pi Agent Platform · MIT · static docs, no cookies"
353
126
  }
354
127
  }
355
128
  };
@@ -396,6 +169,9 @@ function stripTags(value) {
396
169
  return text;
397
170
  }
398
171
 
172
+ const decodeEntities = (value) => value
173
+ .replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, "\"").replace(/&#39;/g, "'").replace(/&amp;/g, "&");
174
+
399
175
  /** @param {string} currentSlug @param {typeof LOCALES.vi} locale @returns {string} */
400
176
  function renderNav(currentSlug, locale) {
401
177
  return locale.nav.map((section) => {
@@ -404,7 +180,7 @@ function renderNav(currentSlug, locale) {
404
180
  const current = page.slug === currentSlug;
405
181
  const classes = current ? "nav-link current" : "nav-link";
406
182
  const aria = current ? ' aria-current="page"' : "";
407
- return ` <a class="${classes}" href="${hrefFor(page, locale)}"${aria}>${escapeHtml(page.nav)}</a>`;
183
+ return ` <a class="${classes}" href="${hrefFor(page, locale)}"${aria}>${icon(page.icon)}<span>${escapeHtml(page.nav)}</span></a>`;
408
184
  })
409
185
  .join("\n");
410
186
  return ` <div class="nav-group">\n <p class="nav-title">${escapeHtml(section.group)}</p>\n${links}\n </div>`;
@@ -413,27 +189,28 @@ function renderNav(currentSlug, locale) {
413
189
 
414
190
  /** @param {ReturnType<typeof pagesFor>[number]} page @param {typeof LOCALES.vi} locale @returns {string} */
415
191
  function renderBreadcrumb(page, locale) {
416
- if (page.slug === "index") return "";
417
192
  return [
418
193
  ' <nav class="breadcrumb" aria-label="Breadcrumb">',
419
194
  ` <a href="${hrefFor({ slug: "index", href: "/" }, locale)}">${locale.ui.breadcrumb}</a>`,
420
195
  ' <span aria-hidden="true">/</span>',
421
196
  ` <span>${escapeHtml(page.group)}</span>`,
422
- ' <span aria-hidden="true">/</span>',
423
- ` <span>${escapeHtml(page.nav)}</span>`,
424
197
  " </nav>",
425
198
  ""
426
199
  ].join("\n");
427
200
  }
428
201
 
429
- /** @param {ReturnType<typeof pagesFor>[number]} page @returns {string} */
430
- function renderPageHead(page) {
431
- // The landing page carries its own hero, so it is not given a generated head.
432
- if (page.slug === "index") return "";
202
+ /** @param {ReturnType<typeof pagesFor>[number]} page @param {typeof LOCALES.vi} locale @returns {string} */
203
+ function renderPageHead(page, locale) {
204
+ const source = page.slug === "whats-new"
205
+ ? "docs-site/releases.json"
206
+ : `docs-site/content/${locale.prefix ? "en/" : ""}${page.slug}.html`;
433
207
  return [
434
208
  ' <div class="page-head">',
435
209
  ` <h1>${escapeHtml(page.title)}</h1>`,
436
210
  ` <p>${page.lead}</p>`,
211
+ ' <div class="page-meta">',
212
+ ` <a href="${SITE.repo}/blob/main/${source}" target="_blank" rel="noopener noreferrer">${icon("edit")}${locale.ui.editPage}</a>`,
213
+ " </div>",
437
214
  " </div>",
438
215
  ""
439
216
  ].join("\n");
@@ -448,7 +225,7 @@ function renderPager(index, pages, locale) {
448
225
  if (previous) {
449
226
  parts.push(
450
227
  ` <a class="pager-prev" href="${hrefFor(previous, locale)}">`,
451
- ` <span class="pager-label">${locale.ui.previous}</span>`,
228
+ ` <span class="pager-label">← ${locale.ui.previous}</span>`,
452
229
  ` <span class="pager-title">${escapeHtml(previous.nav)}</span>`,
453
230
  " </a>"
454
231
  );
@@ -456,7 +233,7 @@ function renderPager(index, pages, locale) {
456
233
  if (next) {
457
234
  parts.push(
458
235
  ` <a class="pager-next" href="${hrefFor(next, locale)}">`,
459
- ` <span class="pager-label">${locale.ui.next}</span>`,
236
+ ` <span class="pager-label">${locale.ui.next} →</span>`,
460
237
  ` <span class="pager-title">${escapeHtml(next.nav)}</span>`,
461
238
  " </a>"
462
239
  );
@@ -465,6 +242,65 @@ function renderPager(index, pages, locale) {
465
242
  return parts.join("\n");
466
243
  }
467
244
 
245
+ function renderTopbar(page, locale, version, home) {
246
+ const languageLinks = Object.values(LOCALES).map((candidate) => {
247
+ const current = candidate.code === locale.code;
248
+ const classes = current ? "language-option current" : "language-option";
249
+ const aria = current ? ' aria-current="true"' : "";
250
+ return `<a class="${classes}" href="${hrefFor(page, candidate)}" hreflang="${candidate.code}" lang="${candidate.code}"${aria}>${candidate.label}</a>`;
251
+ }).join("");
252
+ const link = (slug, label) => {
253
+ const current = page.slug === slug ? ' aria-current="page"' : "";
254
+ return `<a href="${hrefFor({ slug }, locale)}"${current}>${label}</a>`;
255
+ };
256
+ const menu = home ? "" : `\n <button class="icon-button mobile-menu" type="button" data-menu aria-label="${locale.ui.menu}">${icon("menu")}</button>`;
257
+ return ` <header class="topbar">${menu}
258
+ <a class="brand" href="${hrefFor({ slug: "index", href: "/" }, locale)}" aria-label="Pi Agent Platform">
259
+ <span class="brand-mark"><img src="/assets/piagent-logo.svg" alt="" width="28" height="28" /></span>
260
+ <span class="brand-name"><b>Piagent</b> <span>Platform</span></span>
261
+ </a>
262
+ <a class="version-badge" href="${hrefFor({ slug: "whats-new" }, locale)}" data-docs-version="v${version}" title="v${version} docs · ${locale.label}"><span class="dot"></span>v${version}</a>
263
+ <nav class="top-nav" aria-label="${locale.ui.navAria}">
264
+ ${link("quickstart", locale.ui.docs)}
265
+ ${link("whats-new", locale.ui.changelog)}
266
+ ${link("benchmark", locale.ui.benchmark)}
267
+ </nav>
268
+ <span class="top-spacer"></span>
269
+ <button class="search-trigger" type="button" data-search aria-label="${locale.ui.searchAria}">${icon("search")}<span>${locale.ui.searchPlaceholder}</span><kbd>⌘K</kbd></button>
270
+ <div class="top-actions">
271
+ <nav class="language-switch" aria-label="${locale.ui.language}">${languageLinks}</nav>
272
+ <button class="icon-button theme-toggle" type="button" data-theme-toggle aria-label="${locale.ui.themeAria}">${icon("moon", "icon icon-moon")}${icon("sun", "icon icon-sun")}</button>
273
+ <a class="icon-button" href="${SITE.repo}" target="_blank" rel="noopener noreferrer" aria-label="${locale.ui.githubAria}">${icon("github")}</a>
274
+ </div>
275
+ </header>
276
+ `;
277
+ }
278
+
279
+ function renderPalette(locale) {
280
+ const hints = locale.ui.searchHint.map((hint) => `<span>${hint}</span>`).join("");
281
+ return ` <div class="palette" data-palette data-index="/assets/search-${locale.code}.json" data-empty="${locale.ui.searchEmpty}" role="dialog" aria-modal="true" aria-label="${locale.ui.searchAria}">
282
+ <div class="palette-box">
283
+ <label class="palette-input">${icon("search")}<input type="search" placeholder="${locale.ui.searchPlaceholder}" aria-label="${locale.ui.searchAria}" autocomplete="off" spellcheck="false" /></label>
284
+ <div class="palette-results" data-results role="listbox"></div>
285
+ <div class="palette-foot">${hints}</div>
286
+ </div>
287
+ </div>
288
+ `;
289
+ }
290
+
291
+ function renderFooter(page, locale) {
292
+ const other = locale.code === "vi" ? LOCALES.en : LOCALES.vi;
293
+ return `<footer class="footer">
294
+ <p>${locale.ui.footer}</p>
295
+ <div class="footer-links" aria-label="${locale.ui.externalLinks}">
296
+ <a href="${SITE.repo}" target="_blank" rel="noopener noreferrer">GitHub</a>
297
+ <a href="${SITE.npm}" target="_blank" rel="noopener noreferrer">npm</a>
298
+ <a href="${hrefFor(page, other)}" hreflang="${other.code}">${other.code === "en" ? "English" : "Tiếng Việt"}</a>
299
+ <a href="${SITE.facebook}" target="_blank" rel="noopener noreferrer">Facebook</a>
300
+ </div>
301
+ </footer>`;
302
+ }
303
+
468
304
  /**
469
305
  * @param {ReturnType<typeof pagesFor>[number]} page
470
306
  * @param {number} index
@@ -475,101 +311,91 @@ function renderPager(index, pages, locale) {
475
311
  * @returns {string}
476
312
  */
477
313
  function renderPage(page, index, body, version, locale, pages) {
314
+ const home = page.slug === "index";
478
315
  const canonical = `${SITE.origin}${hrefFor(page, locale)}`;
479
316
  const alternateVi = `${SITE.origin}${hrefFor(page, LOCALES.vi)}`;
480
317
  const alternateEn = `${SITE.origin}${hrefFor(page, LOCALES.en)}`;
481
- const documentTitle = page.slug === "index" ? SITE.title : `${page.title} · ${SITE.title}`;
318
+ const documentTitle = home ? `${SITE.title} — ${stripTags(page.lead).split(":")[0]}` : `${page.title} · ${SITE.title}`;
482
319
  const description = stripTags(page.lead);
483
- const languageLinks = Object.values(LOCALES).map((candidate) => {
484
- const current = candidate.code === locale.code;
485
- const classes = current ? "language-option current" : "language-option";
486
- const aria = current ? ' aria-current="true"' : "";
487
- return `<a class="${classes}" href="${hrefFor(page, candidate)}" hreflang="${candidate.code}" lang="${candidate.code}"${aria}>${candidate.label}</a>`;
488
- }).join("\n ");
320
+
321
+ const main = home
322
+ ? ` <main id="content" class="home-main" data-article>
323
+ ${body}
324
+ </main>
325
+ <footer class="site-footer">
326
+ <div class="home-wrap">
327
+ <p>${locale.ui.footer}</p>
328
+ <nav aria-label="${locale.ui.externalLinks}">
329
+ <a href="${hrefFor({ slug: "quickstart" }, locale)}">${locale.ui.docs}</a>
330
+ <a href="${hrefFor({ slug: "whats-new" }, locale)}">${locale.ui.changelog}</a>
331
+ <a href="${SITE.repo}" target="_blank" rel="noopener noreferrer">GitHub</a>
332
+ <a href="${SITE.npm}" target="_blank" rel="noopener noreferrer">npm</a>
333
+ <a href="${hrefFor(page, locale.code === "vi" ? LOCALES.en : LOCALES.vi)}" hreflang="${locale.code === "vi" ? "en" : "vi"}">${locale.code === "vi" ? "English" : "Tiếng Việt"}</a>
334
+ </nav>
335
+ </div>
336
+ </footer>
337
+ `
338
+ : ` <div class="layout with-toc">
339
+ <aside class="sidebar" data-sidebar>
340
+ <nav aria-label="${locale.ui.navAria}">
341
+ ${renderNav(page.slug, locale)}
342
+ </nav>
343
+ </aside>
344
+
345
+ <main id="content">
346
+ <div class="content-shell" data-article>
347
+ ${renderBreadcrumb(page, locale)}${renderPageHead(page, locale)}${body}
348
+ ${renderPager(index, pages, locale)}
349
+ ${renderFooter(page, locale)}
350
+ </div>
351
+ </main>
352
+
353
+ <aside class="toc" aria-label="${locale.ui.tocAria}">
354
+ <p class="toc-title">${locale.ui.tocTitle}</p>
355
+ <nav data-toc></nav>
356
+ </aside>
357
+ </div>
358
+ `;
359
+
360
+ const shell = `${renderTopbar(page, locale, version, home)}
361
+ ${main}${renderPalette(locale)}`;
362
+ const navIcons = pagesFor(locale).map((candidate) => candidate.icon);
363
+ const sprite = iconSprite([...new Set([...iconsUsedIn(shell), ...navIcons, "hash"])].sort());
489
364
 
490
365
  return `<!doctype html>
491
- <html lang="${locale.code}" data-copy-success="${locale.ui.copySuccess}" data-copy-fallback="${locale.ui.copyFallback}">
366
+ <html lang="${locale.code}" data-copy-success="${locale.ui.copySuccess}" data-copy-fallback="${locale.ui.copyFallback}" data-copy-label="${locale.ui.copyLabel}">
492
367
  <head>
493
368
  <meta charset="utf-8" />
494
369
  <meta name="viewport" content="width=device-width, initial-scale=1" />
495
370
  <title>${escapeHtml(documentTitle)}</title>
496
371
  <meta name="description" content="${escapeHtml(description)}" />
497
- <meta name="theme-color" content="#050505" />
372
+ <meta name="theme-color" content="#09090b" media="(prefers-color-scheme: dark)" />
373
+ <meta name="theme-color" content="#fbfbf9" media="(prefers-color-scheme: light)" />
498
374
  <link rel="canonical" href="${canonical}" />
499
375
  <link rel="alternate" hreflang="vi" href="${alternateVi}" />
500
376
  <link rel="alternate" hreflang="en" href="${alternateEn}" />
501
377
  <link rel="alternate" hreflang="x-default" href="${alternateVi}" />
502
378
  <link rel="icon" href="/favicon.svg" type="image/svg+xml" />
379
+ <meta property="og:type" content="website" />
503
380
  <meta property="og:title" content="${escapeHtml(documentTitle)}" />
504
381
  <meta property="og:description" content="${escapeHtml(description)}" />
505
382
  <meta property="og:url" content="${canonical}" />
383
+ <meta property="og:image" content="${SITE.origin}/assets/og.png" />
384
+ <meta name="twitter:card" content="summary_large_image" />
385
+ ${THEME_SNIPPET}
506
386
  <link rel="preconnect" href="https://fonts.googleapis.com" />
507
387
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
508
388
  <link
509
- href="https://fonts.googleapis.com/css2?family=Be+Vietnam+Pro:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap"
389
+ href="https://fonts.googleapis.com/css2?family=Inter:wght@400;450;500;550;600;650;700;750&family=JetBrains+Mono:wght@400;500;600;700&display=swap"
510
390
  rel="stylesheet"
511
391
  />
512
392
  <link rel="stylesheet" href="/assets/docs.css" />
513
393
  <script src="/assets/docs.js" defer></script>
514
394
  </head>
515
- <body>
516
- <header class="topbar">
517
- <a class="brand" href="${hrefFor({ slug: "index", href: "/" }, locale)}" aria-label="Pi Agent Platform">
518
- <span class="brand-mark"><img src="/assets/piagent-logo.svg" alt="" /></span>
519
- <span>Pi Agent Platform</span>
520
- </a>
521
- <div class="top-actions">
522
- <span class="pill"><span class="pill-dot"></span>v${version} docs · ${locale.label}</span>
523
- <nav class="language-switch" aria-label="${locale.ui.language}">
524
- ${languageLinks}
525
- </nav>
526
- <a class="button social" href="${SITE.repo}" target="_blank" rel="noopener noreferrer" aria-label="${locale.ui.githubAria}">
527
- <svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
528
- <path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
529
- </svg>
530
- GitHub
531
- </a>
532
- <button class="button mobile-menu" type="button" data-menu>${locale.ui.menu}</button>
533
- <a class="button top-command-link" href="${hrefFor({ slug: "commands" }, locale)}">${locale.ui.commands}</a>
534
- </div>
535
- </header>
536
-
537
- <div class="layout with-toc">
538
- <aside class="sidebar" data-sidebar>
539
- <div class="sidebar-search" aria-label="${locale.ui.searchAria}">
540
- <svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
541
- <path d="M10.8 18.1a7.3 7.3 0 1 1 0-14.6 7.3 7.3 0 0 1 0 14.6Zm5.3-1.7 4.4 4.4" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
542
- </svg>
543
- <input type="search" placeholder="${locale.ui.searchPlaceholder}" data-filter />
544
- </div>
545
-
546
- <nav aria-label="${locale.ui.navAria}">
547
- ${renderNav(page.slug, locale)}
548
- </nav>
549
- </aside>
550
-
551
- <main>
552
- <div class="content-shell" data-article>
553
- ${renderBreadcrumb(page, locale)}${renderPageHead(page)}${body}
554
- ${renderPager(index, pages, locale)}
555
- <footer class="footer">
556
- <p>Pi Agent Platform docs · static HTML · governed agent workflow</p>
557
- <div class="footer-links" aria-label="${locale.ui.externalLinks}">
558
- <a href="${SITE.origin}" target="_blank" rel="noopener noreferrer">piagent.io.vn</a>
559
- <a href="${SITE.repo}" target="_blank" rel="noopener noreferrer">GitHub repository</a>
560
- <a href="${hrefFor(page, locale.code === "vi" ? LOCALES.en : LOCALES.vi)}" hreflang="${locale.code === "vi" ? "en" : "vi"}">${locale.code === "vi" ? "English" : "Tiếng Việt"}</a>
561
- <a href="${SITE.facebook}" target="_blank" rel="noopener noreferrer">Facebook profile</a>
562
- </div>
563
- </footer>
564
- </div>
565
- </main>
566
-
567
- <aside class="toc" aria-label="${locale.ui.tocAria}">
568
- <p class="toc-title">${locale.ui.tocTitle}</p>
569
- <nav data-toc></nav>
570
- </aside>
571
- </div>
572
- ${ANALYTICS_SNIPPET} </body>
395
+ <body${home ? ' class="home"' : ""}>
396
+ ${sprite}
397
+ <a class="skip-link" href="#content">${locale.ui.skip}</a>
398
+ ${shell}${ANALYTICS_SNIPPET} </body>
573
399
  </html>
574
400
  `;
575
401
  }
@@ -602,23 +428,54 @@ function assertPreBlocksIntact(slug, body, html) {
602
428
  }
603
429
  }
604
430
 
431
+ /** Page and heading entries the search palette loads on first use. */
432
+ function searchEntry(page, body, locale) {
433
+ const headings = [...body.matchAll(/<h([23])\b[^>]*\bid="([^"]+)"[^>]*>([\s\S]*?)<\/h\1>/g)]
434
+ .map(([, , id, text]) => ({ id, text: decodeEntities(stripTags(text)).replace(/\s+/g, " ").trim() }))
435
+ .filter((heading) => heading.text);
436
+ return {
437
+ href: hrefFor(page, locale),
438
+ icon: page.icon,
439
+ group: page.group,
440
+ nav: page.nav,
441
+ title: page.title,
442
+ lead: decodeEntities(stripTags(page.lead)),
443
+ headings
444
+ };
445
+ }
446
+
605
447
  function build() {
606
- const version = JSON.parse(fs.readFileSync(path.join(repoRoot, "package.json"), "utf8")).version;
448
+ const rootPackage = JSON.parse(fs.readFileSync(path.join(repoRoot, "package.json"), "utf8"));
449
+ const version = rootPackage.version;
450
+ const piHost = rootPackage.peerDependencies["@earendil-works/pi-coding-agent"];
451
+ const releases = readReleases(path.join(siteRoot, "releases.json"));
607
452
  /** @type {{file: string, html: string}[]} */
608
453
  const outputs = [];
609
454
  for (const locale of Object.values(LOCALES)) {
610
455
  const pages = pagesFor(locale);
611
- for (const [index, page] of pages.entries()) {
456
+ const index = [];
457
+ for (const [position, page] of pages.entries()) {
612
458
  const fragment = path.join(locale.contentRoot, `${page.slug}.html`);
613
459
  if (!fs.existsSync(fragment)) throw new Error(`missing content fragment: ${path.relative(repoRoot, fragment)}`);
614
- const body = fs.readFileSync(fragment, "utf8").replace(/\s+$/, "");
460
+ let body = fs.readFileSync(fragment, "utf8").replace(/\s+$/, "");
615
461
  if (body.includes("\t")) throw new Error(`${locale.code}/${page.slug}: content fragment contains a tab character`);
616
- const html = renderPage(page, index, body, version, locale, pages);
462
+ body = body
463
+ .replace("<!-- releases -->", () => renderReleaseNotes(releases, locale, { escapeHtml, icon }))
464
+ .replace("<!-- latest-releases -->", () => renderLatestReleases(releases, locale, { escapeHtml, icon, hrefFor }))
465
+ .replace("<!-- hero-pill -->", () => renderHeroPill(releases, locale, { escapeHtml, icon, hrefFor }))
466
+ .replaceAll("{{version}}", version)
467
+ .replaceAll("{{piHost}}", piHost)
468
+ .replaceAll("{{releaseCount}}", String(releases.filter((release) => release.status === "stable").length))
469
+ .replace(/\{\{icon:([a-z-]+)\}\}/g, (_, name) => icon(name));
470
+ const html = renderPage(page, position, body, version, locale, pages);
617
471
  assertPreBlocksIntact(`${locale.code}/${page.slug}`, body, html);
618
472
  const outputDir = locale.prefix ? path.join(siteRoot, locale.code) : siteRoot;
619
473
  outputs.push({ file: path.join(outputDir, `${page.slug}.html`), html });
474
+ index.push(searchEntry(page, body, locale));
620
475
  }
476
+ outputs.push({ file: path.join(siteRoot, "assets", `search-${locale.code}.json`), html: `${JSON.stringify(index)}\n` });
621
477
  }
478
+ outputs.push({ file: path.join(repoRoot, "CHANGELOG.md"), html: renderChangelogMarkdown(releases) });
622
479
  return outputs;
623
480
  }
624
481
 
@@ -639,7 +496,7 @@ function main(argv) {
639
496
  });
640
497
  if (stale.length > 0) {
641
498
  process.stderr.write(
642
- `FAIL: docs-site output is stale for ${stale.map((item) => path.relative(siteRoot, item.file)).join(", ")}. ` +
499
+ `FAIL: docs-site output is stale for ${stale.map((item) => path.relative(repoRoot, item.file)).join(", ")}. ` +
643
500
  "Run: node scripts/build-docs-site.mjs\n"
644
501
  );
645
502
  return 1;