@piagent/platform 1.2.0 → 1.2.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.
- package/CHANGELOG.md +133 -1
- package/README.md +1 -1
- package/docs/command-reference-vietnamese.md +8 -3
- package/docs/distribution-standard.md +7 -7
- package/docs/mcp-and-tools.md +49 -3
- package/docs/operator-manual-vietnamese.md +20 -8
- package/docs/publishing-for-teams.md +1 -0
- package/docs/release-install-policy.md +6 -6
- package/docs/security-threat-model.md +4 -3
- package/docs/team-onboarding.md +3 -3
- package/docs/usage-observability.md +29 -0
- package/docs-site/architecture.html +1 -1
- package/docs-site/commands.html +1 -1
- package/docs-site/content/mcp-approval.html +54 -2
- package/docs-site/content/quickstart.html +1 -1
- package/docs-site/index.html +1 -1
- package/docs-site/mcp-approval.html +55 -3
- package/docs-site/mcp-auth.html +1 -1
- package/docs-site/mcp-servers.html +1 -1
- package/docs-site/mcp.html +1 -1
- package/docs-site/permissions.html +1 -1
- package/docs-site/profiles.html +1 -1
- package/docs-site/quickstart.html +2 -2
- package/docs-site/runtime.html +1 -1
- package/docs-site/scope.html +1 -1
- package/docs-site/security.html +1 -1
- package/docs-site/team.html +1 -1
- package/docs-site/workflows.html +1 -1
- package/package.json +1 -1
- package/packages/piagent-core/capabilities/capability-core.js +41 -4
- package/packages/piagent-core/capabilities/capability-sources.js +73 -12
- package/packages/piagent-core/extensions/piagent-guard.ts +139 -25
- package/packages/piagent-core/extensions/policy-core.js +1586 -98
- package/packages/piagent-core/mcp/mcp-approval-store.js +56 -15
- package/packages/piagent-core/mcp/mcp-auth-readiness.js +31 -3
- package/packages/piagent-core/mcp/mcp-command-actions.js +139 -30
- package/packages/piagent-core/mcp/mcp-config-layers.js +339 -6
- package/packages/piagent-core/mcp/mcp-import-kinds.js +240 -0
- package/packages/piagent-core/mcp/mcp-server-entry.js +51 -0
- package/packages/piagent-core/mcp/mcp-session-view.js +55 -4
- package/packages/piagent-core/mcp/mcp-tool-naming.js +90 -0
- package/packages/piagent-core/package.json +1 -1
- package/scripts/configure-model-scope.sh +19 -3
- package/scripts/configure-subagents.sh +17 -4
- package/scripts/init-project.sh +15 -8
- package/scripts/mcp-manage.mjs +111 -22
- package/scripts/pi-model-catalog.sh +11 -1
- package/scripts/pi-session-stats.sh +27 -1
- package/scripts/pi-usage-history.mjs +567 -0
- package/scripts/quality-benchmark.sh +37 -9
- package/scripts/setup.sh +21 -13
- package/scripts/uninstall-global.sh +10 -4
- package/scripts/verify-local.sh +19 -0
- package/scripts/verify-release-identity.mjs +29 -0
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<span>Pi Agent Platform</span>
|
|
28
28
|
</a>
|
|
29
29
|
<div class="top-actions">
|
|
30
|
-
<span class="pill"><span class="pill-dot"></span>v1.2.
|
|
30
|
+
<span class="pill"><span class="pill-dot"></span>v1.2.1 docs</span>
|
|
31
31
|
<a class="button social" href="https://github.com/Vt-mmm/piagent" target="_blank" rel="noopener noreferrer" aria-label="Mở GitHub repository">
|
|
32
32
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
33
33
|
<path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
package/docs-site/commands.html
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<span>Pi Agent Platform</span>
|
|
28
28
|
</a>
|
|
29
29
|
<div class="top-actions">
|
|
30
|
-
<span class="pill"><span class="pill-dot"></span>v1.2.
|
|
30
|
+
<span class="pill"><span class="pill-dot"></span>v1.2.1 docs</span>
|
|
31
31
|
<a class="button social" href="https://github.com/Vt-mmm/piagent" target="_blank" rel="noopener noreferrer" aria-label="Mở GitHub repository">
|
|
32
32
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
33
33
|
<path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
</div>
|
|
25
25
|
<div class="card">
|
|
26
26
|
<h3 id="enforce-tool-call">Enforce ở tool call</h3>
|
|
27
|
-
<p>Guard
|
|
27
|
+
<p>Guard đọc cả proxy (<span class="inline-code">mcp</span> với <span class="inline-code">input.server</span>) lẫn direct tool. Gate chỉ đọc proxy sẽ bị đi vòng bằng cách bật <span class="inline-code">directTools</span>.</p>
|
|
28
28
|
</div>
|
|
29
29
|
</div>
|
|
30
30
|
</section>
|
|
@@ -40,6 +40,54 @@
|
|
|
40
40
|
<p>Server ở <span class="inline-code">global</span> và <span class="inline-code">pi-global</span> không qua gate. Hai layer đó nằm ngoài mọi repo — không repo nào đặt được server vào đó — và bắt người dùng duyệt config của chính mình trên từng máy là vô nghĩa.</p>
|
|
41
41
|
</section>
|
|
42
42
|
|
|
43
|
+
<section class="section">
|
|
44
|
+
<h2 id="imports">Server đến qua <span class="inline-code">imports</span></h2>
|
|
45
|
+
|
|
46
|
+
<p>Config MCP nhận thêm key <span class="inline-code">imports</span>, kéo định nghĩa server từ config của công cụ khác. Server vào session theo đường này không nằm trong bốn scope nào, nên nó là đường vòng thứ hai quanh gate — và không cần bật <span class="inline-code">directTools</span>.</p>
|
|
47
|
+
|
|
48
|
+
<p>Trong sáu kind, chỉ <span class="inline-code">vscode</span> trỏ vào đường dẫn <strong>trong project</strong> (<span class="inline-code">.vscode/mcp.json</span>). Clone repo là mang theo file đó, nên server của nó phải duyệt như mọi server repo khai báo thẳng.</p>
|
|
49
|
+
|
|
50
|
+
<div class="table-wrap">
|
|
51
|
+
<table>
|
|
52
|
+
<thead>
|
|
53
|
+
<tr><th>Tình huống</th><th>Xử lý</th></tr>
|
|
54
|
+
</thead>
|
|
55
|
+
<tbody>
|
|
56
|
+
<tr>
|
|
57
|
+
<td>Layer bất kỳ import kind repo-relative</td>
|
|
58
|
+
<td>Server phải duyệt, kể cả khi <span class="inline-code">global</span> khai báo — nội dung đọc lại vẫn nằm trong repo.</td>
|
|
59
|
+
</tr>
|
|
60
|
+
<tr>
|
|
61
|
+
<td>Repo scope import kind bất kỳ</td>
|
|
62
|
+
<td>Server phải duyệt. Repo đang chọn hộ máy này chạy server nào.</td>
|
|
63
|
+
</tr>
|
|
64
|
+
<tr>
|
|
65
|
+
<td>Repo scope import kind không đọc được nội dung (<span class="inline-code">codex</span>, TOML)</td>
|
|
66
|
+
<td>Chặn mọi tool call, khi file đó có thật trên máy. Từ chối ở dòng khai báo: liệt kê được server của năm kind kia không nói gì về kind thứ sáu. Khai một kind máy này không cài thì là config cần dọn, không phải session cần dừng.</td>
|
|
67
|
+
</tr>
|
|
68
|
+
<tr>
|
|
69
|
+
<td><span class="inline-code">remove</span> / <span class="inline-code">enable</span> / <span class="inline-code">disable</span> trên server đến qua <span class="inline-code">imports</span></td>
|
|
70
|
+
<td>Từ chối, kèm tên file thật cần sửa. File được định vị là config của công cụ kia; ghi ngược bằng writer của platform sẽ viết lại file Cursor/VS Code của người dùng theo format chính công cụ đó không dùng.</td>
|
|
71
|
+
</tr>
|
|
72
|
+
<tr>
|
|
73
|
+
<td><span class="inline-code">directTools</span> + <span class="inline-code">toolPrefix: "none"</span> trong config repo mang theo</td>
|
|
74
|
+
<td>Chặn mọi tool call, không riêng lời gọi MCP. Không prefix thì tên tool không còn dấu vết server, không truy được để đối chiếu quyết định — mà chặn riêng proxy <span class="inline-code">mcp</span> thì đúng những cái tên trần cần chặn lại đi thẳng qua.</td>
|
|
75
|
+
</tr>
|
|
76
|
+
</tbody>
|
|
77
|
+
</table>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<p><span class="inline-code">piagent-mcp doctor</span> nêu tên file có <span class="inline-code">imports</span> và các kind của nó, nêu layer không parse được, và nêu cả import target <em>có thật nhưng không đọc được</em> — cả ba trạng thái trước đây đều đọc ra rỗng, không phân biệt được với “không khai server nào”.</p>
|
|
81
|
+
|
|
82
|
+
<p>Với hai trạng thái chặn-toàn-bộ ở trên, <span class="inline-code">doctor</span> và <span class="inline-code">list</span> in ra lý do và exit 1 — kể cả <span class="inline-code">list --json</span>, vốn trả <span class="inline-code">servers: []</span> với exit 0 đúng lúc không gì chạy được. Exit code trả lời “MCP ở đây có chạy được không”, không phải “có in ra dòng nào không”. Cả ba surface — guard, <span class="inline-code">/piagent-mcp</span>, CLI — đọc một state chung, vì hai bên lệch nhau chính là cách một session bị chặn biến thành một session hỏng không rõ lý do.</p>
|
|
83
|
+
|
|
84
|
+
<p>Gate được cache theo project và tính lại khi signature trên các file phía sau nó đổi. Danh sách file đó lấy từ chính đường đi mà các reader dùng, kể cả file đang không tồn tại — <span class="inline-code">imports: ["codex"]</span> thành chặn đúng lúc file codex xuất hiện, và <span class="inline-code">directTools</span> thêm vào config global cá nhân là thứ guard đã load phải thấy ngay. Viết tay danh sách đó là cách nó lệch.</p>
|
|
85
|
+
|
|
86
|
+
<p>Adapter merge <span class="inline-code">settings</span> của cả bốn layer thành một block cho cả session. Nên <span class="inline-code">directTools</span> đặt ở file này và <span class="inline-code">toolPrefix: "none"</span> đặt ở file kia vẫn ra đúng tổ hợp làm gate mất chỗ bám, trong khi đọc riêng từng file thì không file nào có vấn đề. Settings được đọc sau merge, và báo cả hai file.</p>
|
|
87
|
+
|
|
88
|
+
<p>Cột <span class="inline-code">SCOPE</span> là layer khai báo <span class="inline-code">imports</span>, không phải nơi server được định nghĩa. Server import qua config <span class="inline-code">global</span> vẫn cần duyệt, vì file đọc lại nằm trong clone.</p>
|
|
89
|
+
</section>
|
|
90
|
+
|
|
43
91
|
<section class="section">
|
|
44
92
|
<h2 id="thiet-ke">Bốn quyết định thiết kế</h2>
|
|
45
93
|
|
|
@@ -61,9 +109,13 @@
|
|
|
61
109
|
<td>Pin theo digest của định nghĩa</td>
|
|
62
110
|
<td>Duyệt là duyệt <em>đúng</em> command và URL đó. Đổi command, đổi args, đổi URL thì server quay về <span class="inline-code">pending</span> chứ không kế thừa quyết định cũ.</td>
|
|
63
111
|
</tr>
|
|
112
|
+
<tr>
|
|
113
|
+
<td>Preview in ra bản đã merge, digest tính trên mảnh của repo</td>
|
|
114
|
+
<td>Adapter merge server trùng tên theo từng key qua các layer, nên repo khai mỗi <span class="inline-code">{"args": [...]}</span> vẫn lấy <span class="inline-code">command</span> từ layer dưới. Preview phải là thứ sẽ chạy thật. Digest thì giữ trên phần repo kiểm soát — băm cả bản merge sẽ đẩy server về <span class="inline-code">pending</span> mỗi lần người dùng sửa config global của chính mình.</td>
|
|
115
|
+
</tr>
|
|
64
116
|
<tr>
|
|
65
117
|
<td>Chặn ở <span class="inline-code">tool_call</span></td>
|
|
66
|
-
<td>Cả dạng proxy <span class="inline-code">mcp</span> lẫn
|
|
118
|
+
<td>Cả dạng proxy <span class="inline-code">mcp</span> lẫn direct tool, để không có đường vòng. Direct tool được truy theo tên server đang cấu hình, vì prefix của nó chính là tên server.</td>
|
|
67
119
|
</tr>
|
|
68
120
|
</tbody>
|
|
69
121
|
</table>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<article class="step">
|
|
5
5
|
<div>
|
|
6
6
|
<h3 id="cai-hai-lenh">Cài — hai lệnh</h3>
|
|
7
|
-
<p>Cần Node.js 22.19.0 trở lên. Không cần cài Pi Coding Agent trước: <span class="inline-code">piagent-setup</span> tự cài đúng bản <span class="inline-code">0.82.0</span> mà release <span class="inline-code">v1.2.
|
|
7
|
+
<p>Cần Node.js 22.19.0 trở lên. Không cần cài Pi Coding Agent trước: <span class="inline-code">piagent-setup</span> tự cài đúng bản <span class="inline-code">0.82.0</span> mà release <span class="inline-code">v1.2.1</span> đang pin. Chạy nó ngay trong thư mục project — nó cài Pi package, khởi tạo <span class="inline-code">.pi/</span>, cài MCP baseline cùng subagents, rồi chạy doctor. Thêm <span class="inline-code">--no-mcp</span>, <span class="inline-code">--no-subagents</span>, <span class="inline-code">--no-herdr</span> hoặc <span class="inline-code">--global-only</span> để bỏ bớt.</p>
|
|
8
8
|
<div class="table-wrap compact-table">
|
|
9
9
|
<table>
|
|
10
10
|
<thead>
|
package/docs-site/index.html
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<span>Pi Agent Platform</span>
|
|
28
28
|
</a>
|
|
29
29
|
<div class="top-actions">
|
|
30
|
-
<span class="pill"><span class="pill-dot"></span>v1.2.
|
|
30
|
+
<span class="pill"><span class="pill-dot"></span>v1.2.1 docs</span>
|
|
31
31
|
<a class="button social" href="https://github.com/Vt-mmm/piagent" target="_blank" rel="noopener noreferrer" aria-label="Mở GitHub repository">
|
|
32
32
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
33
33
|
<path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<span>Pi Agent Platform</span>
|
|
28
28
|
</a>
|
|
29
29
|
<div class="top-actions">
|
|
30
|
-
<span class="pill"><span class="pill-dot"></span>v1.2.
|
|
30
|
+
<span class="pill"><span class="pill-dot"></span>v1.2.1 docs</span>
|
|
31
31
|
<a class="button social" href="https://github.com/Vt-mmm/piagent" target="_blank" rel="noopener noreferrer" aria-label="Mở GitHub repository">
|
|
32
32
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
33
33
|
<path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
</div>
|
|
119
119
|
<div class="card">
|
|
120
120
|
<h3 id="enforce-tool-call">Enforce ở tool call</h3>
|
|
121
|
-
<p>Guard
|
|
121
|
+
<p>Guard đọc cả proxy (<span class="inline-code">mcp</span> với <span class="inline-code">input.server</span>) lẫn direct tool. Gate chỉ đọc proxy sẽ bị đi vòng bằng cách bật <span class="inline-code">directTools</span>.</p>
|
|
122
122
|
</div>
|
|
123
123
|
</div>
|
|
124
124
|
</section>
|
|
@@ -134,6 +134,54 @@
|
|
|
134
134
|
<p>Server ở <span class="inline-code">global</span> và <span class="inline-code">pi-global</span> không qua gate. Hai layer đó nằm ngoài mọi repo — không repo nào đặt được server vào đó — và bắt người dùng duyệt config của chính mình trên từng máy là vô nghĩa.</p>
|
|
135
135
|
</section>
|
|
136
136
|
|
|
137
|
+
<section class="section">
|
|
138
|
+
<h2 id="imports">Server đến qua <span class="inline-code">imports</span></h2>
|
|
139
|
+
|
|
140
|
+
<p>Config MCP nhận thêm key <span class="inline-code">imports</span>, kéo định nghĩa server từ config của công cụ khác. Server vào session theo đường này không nằm trong bốn scope nào, nên nó là đường vòng thứ hai quanh gate — và không cần bật <span class="inline-code">directTools</span>.</p>
|
|
141
|
+
|
|
142
|
+
<p>Trong sáu kind, chỉ <span class="inline-code">vscode</span> trỏ vào đường dẫn <strong>trong project</strong> (<span class="inline-code">.vscode/mcp.json</span>). Clone repo là mang theo file đó, nên server của nó phải duyệt như mọi server repo khai báo thẳng.</p>
|
|
143
|
+
|
|
144
|
+
<div class="table-wrap">
|
|
145
|
+
<table>
|
|
146
|
+
<thead>
|
|
147
|
+
<tr><th>Tình huống</th><th>Xử lý</th></tr>
|
|
148
|
+
</thead>
|
|
149
|
+
<tbody>
|
|
150
|
+
<tr>
|
|
151
|
+
<td>Layer bất kỳ import kind repo-relative</td>
|
|
152
|
+
<td>Server phải duyệt, kể cả khi <span class="inline-code">global</span> khai báo — nội dung đọc lại vẫn nằm trong repo.</td>
|
|
153
|
+
</tr>
|
|
154
|
+
<tr>
|
|
155
|
+
<td>Repo scope import kind bất kỳ</td>
|
|
156
|
+
<td>Server phải duyệt. Repo đang chọn hộ máy này chạy server nào.</td>
|
|
157
|
+
</tr>
|
|
158
|
+
<tr>
|
|
159
|
+
<td>Repo scope import kind không đọc được nội dung (<span class="inline-code">codex</span>, TOML)</td>
|
|
160
|
+
<td>Chặn mọi tool call, khi file đó có thật trên máy. Từ chối ở dòng khai báo: liệt kê được server của năm kind kia không nói gì về kind thứ sáu. Khai một kind máy này không cài thì là config cần dọn, không phải session cần dừng.</td>
|
|
161
|
+
</tr>
|
|
162
|
+
<tr>
|
|
163
|
+
<td><span class="inline-code">remove</span> / <span class="inline-code">enable</span> / <span class="inline-code">disable</span> trên server đến qua <span class="inline-code">imports</span></td>
|
|
164
|
+
<td>Từ chối, kèm tên file thật cần sửa. File được định vị là config của công cụ kia; ghi ngược bằng writer của platform sẽ viết lại file Cursor/VS Code của người dùng theo format chính công cụ đó không dùng.</td>
|
|
165
|
+
</tr>
|
|
166
|
+
<tr>
|
|
167
|
+
<td><span class="inline-code">directTools</span> + <span class="inline-code">toolPrefix: "none"</span> trong config repo mang theo</td>
|
|
168
|
+
<td>Chặn mọi tool call, không riêng lời gọi MCP. Không prefix thì tên tool không còn dấu vết server, không truy được để đối chiếu quyết định — mà chặn riêng proxy <span class="inline-code">mcp</span> thì đúng những cái tên trần cần chặn lại đi thẳng qua.</td>
|
|
169
|
+
</tr>
|
|
170
|
+
</tbody>
|
|
171
|
+
</table>
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
<p><span class="inline-code">piagent-mcp doctor</span> nêu tên file có <span class="inline-code">imports</span> và các kind của nó, nêu layer không parse được, và nêu cả import target <em>có thật nhưng không đọc được</em> — cả ba trạng thái trước đây đều đọc ra rỗng, không phân biệt được với “không khai server nào”.</p>
|
|
175
|
+
|
|
176
|
+
<p>Với hai trạng thái chặn-toàn-bộ ở trên, <span class="inline-code">doctor</span> và <span class="inline-code">list</span> in ra lý do và exit 1 — kể cả <span class="inline-code">list --json</span>, vốn trả <span class="inline-code">servers: []</span> với exit 0 đúng lúc không gì chạy được. Exit code trả lời “MCP ở đây có chạy được không”, không phải “có in ra dòng nào không”. Cả ba surface — guard, <span class="inline-code">/piagent-mcp</span>, CLI — đọc một state chung, vì hai bên lệch nhau chính là cách một session bị chặn biến thành một session hỏng không rõ lý do.</p>
|
|
177
|
+
|
|
178
|
+
<p>Gate được cache theo project và tính lại khi signature trên các file phía sau nó đổi. Danh sách file đó lấy từ chính đường đi mà các reader dùng, kể cả file đang không tồn tại — <span class="inline-code">imports: ["codex"]</span> thành chặn đúng lúc file codex xuất hiện, và <span class="inline-code">directTools</span> thêm vào config global cá nhân là thứ guard đã load phải thấy ngay. Viết tay danh sách đó là cách nó lệch.</p>
|
|
179
|
+
|
|
180
|
+
<p>Adapter merge <span class="inline-code">settings</span> của cả bốn layer thành một block cho cả session. Nên <span class="inline-code">directTools</span> đặt ở file này và <span class="inline-code">toolPrefix: "none"</span> đặt ở file kia vẫn ra đúng tổ hợp làm gate mất chỗ bám, trong khi đọc riêng từng file thì không file nào có vấn đề. Settings được đọc sau merge, và báo cả hai file.</p>
|
|
181
|
+
|
|
182
|
+
<p>Cột <span class="inline-code">SCOPE</span> là layer khai báo <span class="inline-code">imports</span>, không phải nơi server được định nghĩa. Server import qua config <span class="inline-code">global</span> vẫn cần duyệt, vì file đọc lại nằm trong clone.</p>
|
|
183
|
+
</section>
|
|
184
|
+
|
|
137
185
|
<section class="section">
|
|
138
186
|
<h2 id="thiet-ke">Bốn quyết định thiết kế</h2>
|
|
139
187
|
|
|
@@ -155,9 +203,13 @@
|
|
|
155
203
|
<td>Pin theo digest của định nghĩa</td>
|
|
156
204
|
<td>Duyệt là duyệt <em>đúng</em> command và URL đó. Đổi command, đổi args, đổi URL thì server quay về <span class="inline-code">pending</span> chứ không kế thừa quyết định cũ.</td>
|
|
157
205
|
</tr>
|
|
206
|
+
<tr>
|
|
207
|
+
<td>Preview in ra bản đã merge, digest tính trên mảnh của repo</td>
|
|
208
|
+
<td>Adapter merge server trùng tên theo từng key qua các layer, nên repo khai mỗi <span class="inline-code">{"args": [...]}</span> vẫn lấy <span class="inline-code">command</span> từ layer dưới. Preview phải là thứ sẽ chạy thật. Digest thì giữ trên phần repo kiểm soát — băm cả bản merge sẽ đẩy server về <span class="inline-code">pending</span> mỗi lần người dùng sửa config global của chính mình.</td>
|
|
209
|
+
</tr>
|
|
158
210
|
<tr>
|
|
159
211
|
<td>Chặn ở <span class="inline-code">tool_call</span></td>
|
|
160
|
-
<td>Cả dạng proxy <span class="inline-code">mcp</span> lẫn
|
|
212
|
+
<td>Cả dạng proxy <span class="inline-code">mcp</span> lẫn direct tool, để không có đường vòng. Direct tool được truy theo tên server đang cấu hình, vì prefix của nó chính là tên server.</td>
|
|
161
213
|
</tr>
|
|
162
214
|
</tbody>
|
|
163
215
|
</table>
|
package/docs-site/mcp-auth.html
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<span>Pi Agent Platform</span>
|
|
28
28
|
</a>
|
|
29
29
|
<div class="top-actions">
|
|
30
|
-
<span class="pill"><span class="pill-dot"></span>v1.2.
|
|
30
|
+
<span class="pill"><span class="pill-dot"></span>v1.2.1 docs</span>
|
|
31
31
|
<a class="button social" href="https://github.com/Vt-mmm/piagent" target="_blank" rel="noopener noreferrer" aria-label="Mở GitHub repository">
|
|
32
32
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
33
33
|
<path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<span>Pi Agent Platform</span>
|
|
28
28
|
</a>
|
|
29
29
|
<div class="top-actions">
|
|
30
|
-
<span class="pill"><span class="pill-dot"></span>v1.2.
|
|
30
|
+
<span class="pill"><span class="pill-dot"></span>v1.2.1 docs</span>
|
|
31
31
|
<a class="button social" href="https://github.com/Vt-mmm/piagent" target="_blank" rel="noopener noreferrer" aria-label="Mở GitHub repository">
|
|
32
32
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
33
33
|
<path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
package/docs-site/mcp.html
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<span>Pi Agent Platform</span>
|
|
28
28
|
</a>
|
|
29
29
|
<div class="top-actions">
|
|
30
|
-
<span class="pill"><span class="pill-dot"></span>v1.2.
|
|
30
|
+
<span class="pill"><span class="pill-dot"></span>v1.2.1 docs</span>
|
|
31
31
|
<a class="button social" href="https://github.com/Vt-mmm/piagent" target="_blank" rel="noopener noreferrer" aria-label="Mở GitHub repository">
|
|
32
32
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
33
33
|
<path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<span>Pi Agent Platform</span>
|
|
28
28
|
</a>
|
|
29
29
|
<div class="top-actions">
|
|
30
|
-
<span class="pill"><span class="pill-dot"></span>v1.2.
|
|
30
|
+
<span class="pill"><span class="pill-dot"></span>v1.2.1 docs</span>
|
|
31
31
|
<a class="button social" href="https://github.com/Vt-mmm/piagent" target="_blank" rel="noopener noreferrer" aria-label="Mở GitHub repository">
|
|
32
32
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
33
33
|
<path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
package/docs-site/profiles.html
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<span>Pi Agent Platform</span>
|
|
28
28
|
</a>
|
|
29
29
|
<div class="top-actions">
|
|
30
|
-
<span class="pill"><span class="pill-dot"></span>v1.2.
|
|
30
|
+
<span class="pill"><span class="pill-dot"></span>v1.2.1 docs</span>
|
|
31
31
|
<a class="button social" href="https://github.com/Vt-mmm/piagent" target="_blank" rel="noopener noreferrer" aria-label="Mở GitHub repository">
|
|
32
32
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
33
33
|
<path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<span>Pi Agent Platform</span>
|
|
28
28
|
</a>
|
|
29
29
|
<div class="top-actions">
|
|
30
|
-
<span class="pill"><span class="pill-dot"></span>v1.2.
|
|
30
|
+
<span class="pill"><span class="pill-dot"></span>v1.2.1 docs</span>
|
|
31
31
|
<a class="button social" href="https://github.com/Vt-mmm/piagent" target="_blank" rel="noopener noreferrer" aria-label="Mở GitHub repository">
|
|
32
32
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
33
33
|
<path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
<article class="step">
|
|
99
99
|
<div>
|
|
100
100
|
<h3 id="cai-hai-lenh">Cài — hai lệnh</h3>
|
|
101
|
-
<p>Cần Node.js 22.19.0 trở lên. Không cần cài Pi Coding Agent trước: <span class="inline-code">piagent-setup</span> tự cài đúng bản <span class="inline-code">0.82.0</span> mà release <span class="inline-code">v1.2.
|
|
101
|
+
<p>Cần Node.js 22.19.0 trở lên. Không cần cài Pi Coding Agent trước: <span class="inline-code">piagent-setup</span> tự cài đúng bản <span class="inline-code">0.82.0</span> mà release <span class="inline-code">v1.2.1</span> đang pin. Chạy nó ngay trong thư mục project — nó cài Pi package, khởi tạo <span class="inline-code">.pi/</span>, cài MCP baseline cùng subagents, rồi chạy doctor. Thêm <span class="inline-code">--no-mcp</span>, <span class="inline-code">--no-subagents</span>, <span class="inline-code">--no-herdr</span> hoặc <span class="inline-code">--global-only</span> để bỏ bớt.</p>
|
|
102
102
|
<div class="table-wrap compact-table">
|
|
103
103
|
<table>
|
|
104
104
|
<thead>
|
package/docs-site/runtime.html
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<span>Pi Agent Platform</span>
|
|
28
28
|
</a>
|
|
29
29
|
<div class="top-actions">
|
|
30
|
-
<span class="pill"><span class="pill-dot"></span>v1.2.
|
|
30
|
+
<span class="pill"><span class="pill-dot"></span>v1.2.1 docs</span>
|
|
31
31
|
<a class="button social" href="https://github.com/Vt-mmm/piagent" target="_blank" rel="noopener noreferrer" aria-label="Mở GitHub repository">
|
|
32
32
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
33
33
|
<path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
package/docs-site/scope.html
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<span>Pi Agent Platform</span>
|
|
28
28
|
</a>
|
|
29
29
|
<div class="top-actions">
|
|
30
|
-
<span class="pill"><span class="pill-dot"></span>v1.2.
|
|
30
|
+
<span class="pill"><span class="pill-dot"></span>v1.2.1 docs</span>
|
|
31
31
|
<a class="button social" href="https://github.com/Vt-mmm/piagent" target="_blank" rel="noopener noreferrer" aria-label="Mở GitHub repository">
|
|
32
32
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
33
33
|
<path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
package/docs-site/security.html
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<span>Pi Agent Platform</span>
|
|
28
28
|
</a>
|
|
29
29
|
<div class="top-actions">
|
|
30
|
-
<span class="pill"><span class="pill-dot"></span>v1.2.
|
|
30
|
+
<span class="pill"><span class="pill-dot"></span>v1.2.1 docs</span>
|
|
31
31
|
<a class="button social" href="https://github.com/Vt-mmm/piagent" target="_blank" rel="noopener noreferrer" aria-label="Mở GitHub repository">
|
|
32
32
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
33
33
|
<path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
package/docs-site/team.html
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<span>Pi Agent Platform</span>
|
|
28
28
|
</a>
|
|
29
29
|
<div class="top-actions">
|
|
30
|
-
<span class="pill"><span class="pill-dot"></span>v1.2.
|
|
30
|
+
<span class="pill"><span class="pill-dot"></span>v1.2.1 docs</span>
|
|
31
31
|
<a class="button social" href="https://github.com/Vt-mmm/piagent" target="_blank" rel="noopener noreferrer" aria-label="Mở GitHub repository">
|
|
32
32
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
33
33
|
<path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
package/docs-site/workflows.html
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<span>Pi Agent Platform</span>
|
|
28
28
|
</a>
|
|
29
29
|
<div class="top-actions">
|
|
30
|
-
<span class="pill"><span class="pill-dot"></span>v1.2.
|
|
30
|
+
<span class="pill"><span class="pill-dot"></span>v1.2.1 docs</span>
|
|
31
31
|
<a class="button social" href="https://github.com/Vt-mmm/piagent" target="_blank" rel="noopener noreferrer" aria-label="Mở GitHub repository">
|
|
32
32
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
33
33
|
<path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
package/package.json
CHANGED
|
@@ -238,8 +238,13 @@ export function validateCapabilityPack(document, options = {}) {
|
|
|
238
238
|
|
|
239
239
|
if (rejectUnknownKeys(spec.activation, new Set(["mode", "profiles", "triggers"]), `${source}.spec.activation`, errors)) {
|
|
240
240
|
if (!ACTIVATION_MODES.has(spec.activation.mode)) pushError(errors, `${source}.spec.activation.mode`, "must be explicit, profile, or trigger");
|
|
241
|
-
validateStringArray(spec.activation.profiles ?? [], `${source}.spec.activation.profiles`, errors, { maxItems: 64, pattern: NAME_PATTERN });
|
|
241
|
+
const profiles = validateStringArray(spec.activation.profiles ?? [], `${source}.spec.activation.profiles`, errors, { maxItems: 64, pattern: NAME_PATTERN });
|
|
242
242
|
const triggers = validateStringArray(spec.activation.triggers ?? [], `${source}.spec.activation.triggers`, errors, { maxItems: 64 });
|
|
243
|
+
// Both modes name the thing that switches them on, so both are checked for
|
|
244
|
+
// it. Only `trigger` was, which let a pack declare profile activation and
|
|
245
|
+
// list no profiles: resolution then read `.includes` off an absent array
|
|
246
|
+
// and died with a TypeError where it should have refused with a reason.
|
|
247
|
+
if (spec.activation.mode === "profile" && profiles.length === 0) pushError(errors, `${source}.spec.activation.profiles`, "must not be empty for profile activation");
|
|
243
248
|
if (spec.activation.mode === "trigger" && triggers.length === 0) pushError(errors, `${source}.spec.activation.triggers`, "must not be empty for trigger activation");
|
|
244
249
|
}
|
|
245
250
|
|
|
@@ -859,7 +864,10 @@ export function resolveCapabilityProfileDocument(root, profile, options = {}) {
|
|
|
859
864
|
const key = `${selector.name}@${selector.version}`;
|
|
860
865
|
const directPack = byKey.get(key);
|
|
861
866
|
if (!directPack) throw new CapabilityValidationError(`profile selects unknown capability pack ${key}`);
|
|
862
|
-
|
|
867
|
+
// Defaulted rather than assumed present. The validator now requires the list
|
|
868
|
+
// for this mode, but this line runs against whatever is on disk, and reading
|
|
869
|
+
// through to a TypeError would turn a refusal into a crash.
|
|
870
|
+
if (directPack.activation.mode === "profile" && !(directPack.activation.profiles ?? []).includes(profile.mode)) throw new CapabilityValidationError(`${key} is not enabled for profile mode ${profile.mode}`);
|
|
863
871
|
select(key);
|
|
864
872
|
}
|
|
865
873
|
|
|
@@ -995,22 +1003,51 @@ export function resolveCapabilityProfile(root, profilePath, options = {}) {
|
|
|
995
1003
|
const GRANTING_PERMISSIONS = ["capabilities", "filesystemRead", "filesystemWrite", "networkDomains", "externalActions"];
|
|
996
1004
|
const RESTRICTING_PERMISSIONS = ["protectedPaths", "readOnlyPaths", "shellProtectedPaths"];
|
|
997
1005
|
|
|
1006
|
+
// Everything above is about artifacts the platform ships. A pack vendored from
|
|
1007
|
+
// somewhere else is a different question, and gets a different answer.
|
|
1008
|
+
//
|
|
1009
|
+
// None of the reasons for leaving artifact content on the build side apply to
|
|
1010
|
+
// it. Its bytes change when that source decides they change, not when this
|
|
1011
|
+
// project updates. The manifest digest pins which artifacts a pack declares, not
|
|
1012
|
+
// what is in them, so a rewritten prompt or policy file keeps the same manifest.
|
|
1013
|
+
// And the argument that the platform can rewrite its own verifier is an argument
|
|
1014
|
+
// about the platform: an external source cannot, which is exactly why its
|
|
1015
|
+
// content is worth pinning. Prompts and policy files are what a hostile source
|
|
1016
|
+
// would edit, and it would rather not be asked again.
|
|
1017
|
+
//
|
|
1018
|
+
// `workspace` is the platform's own origin and is a reserved source name, so a
|
|
1019
|
+
// vendored source cannot claim it.
|
|
1020
|
+
function externalArtifacts(document) {
|
|
1021
|
+
const external = new Set(
|
|
1022
|
+
(document?.packs ?? [])
|
|
1023
|
+
.filter((pack) => pack?.origin && pack.origin !== "workspace")
|
|
1024
|
+
.map((pack) => `${pack.name}@${pack.version}`)
|
|
1025
|
+
);
|
|
1026
|
+
if (external.size === 0) return undefined;
|
|
1027
|
+
return (document?.artifacts ?? []).filter((artifact) => external.has(artifact?.pack));
|
|
1028
|
+
}
|
|
1029
|
+
|
|
998
1030
|
function consentedShape(document) {
|
|
999
1031
|
return {
|
|
1000
1032
|
schemaVersion: document?.schemaVersion,
|
|
1001
1033
|
apiVersion: document?.core?.apiVersion,
|
|
1002
1034
|
packageSource: document?.core?.packageSource,
|
|
1003
1035
|
profile: document?.profile,
|
|
1004
|
-
packs: document?.packs
|
|
1036
|
+
packs: document?.packs,
|
|
1037
|
+
externalArtifacts: externalArtifacts(document)
|
|
1005
1038
|
};
|
|
1006
1039
|
}
|
|
1007
1040
|
|
|
1041
|
+
const CONSENT_DRIFT_REASONS = {
|
|
1042
|
+
externalArtifacts: "an artifact shipped by a vendored capability source changed content"
|
|
1043
|
+
};
|
|
1044
|
+
|
|
1008
1045
|
function consentDrift(expected, actual) {
|
|
1009
1046
|
const before = consentedShape(actual);
|
|
1010
1047
|
const after = consentedShape(expected);
|
|
1011
1048
|
return Object.keys(after)
|
|
1012
1049
|
.filter((key) => stableJson(after[key]) !== stableJson(before[key]))
|
|
1013
|
-
.map((key) => `${key} no longer matches the locked value`);
|
|
1050
|
+
.map((key) => CONSENT_DRIFT_REASONS[key] ?? `${key} no longer matches the locked value`);
|
|
1014
1051
|
}
|
|
1015
1052
|
|
|
1016
1053
|
function permissionDrift(expected, actual) {
|
|
@@ -110,6 +110,39 @@ function assertContainedDirectory(projectRoot, target, label) {
|
|
|
110
110
|
return targetReal;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Verify a path this code is about to create, empty or write sits inside the
|
|
115
|
+
* project, without requiring it to exist yet.
|
|
116
|
+
*
|
|
117
|
+
* The read path can lstat its target and be done with it. A write path cannot:
|
|
118
|
+
* the destination is normally absent, and the failure that matters is an
|
|
119
|
+
* *ancestor* leading somewhere else. A repository shipping `.pi` as a symlink is
|
|
120
|
+
* the whole attack — `mkdir -p` follows it, the directory that appears below is
|
|
121
|
+
* a real directory, so a check on the immediate parent sees nothing wrong, and
|
|
122
|
+
* the recursive delete that comes next lands outside the project.
|
|
123
|
+
*
|
|
124
|
+
* @param {string} rootReal a project root already through realpath
|
|
125
|
+
* @param {string} target
|
|
126
|
+
* @param {string} label
|
|
127
|
+
*/
|
|
128
|
+
function assertContainedWritePath(rootReal, target, label) {
|
|
129
|
+
const relative = path.relative(rootReal, path.resolve(rootReal, target));
|
|
130
|
+
if (relative.startsWith("..") || path.isAbsolute(relative)) fail(`${label} resolves outside the project`);
|
|
131
|
+
let current = rootReal;
|
|
132
|
+
for (const segment of relative.split(path.sep)) {
|
|
133
|
+
if (!segment) continue;
|
|
134
|
+
current = path.join(current, segment);
|
|
135
|
+
let stat;
|
|
136
|
+
try {
|
|
137
|
+
stat = fs.lstatSync(current);
|
|
138
|
+
} catch {
|
|
139
|
+
// Absent from here down, so there is nothing further to follow.
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (stat.isSymbolicLink()) fail(`${label} must not resolve through a symbolic link (${segment})`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
113
146
|
function assertNoSymbolicLinks(root, label, budget = { remaining: MAX_VENDOR_ENTRIES }) {
|
|
114
147
|
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
|
115
148
|
if (budget.remaining-- <= 0) fail(`${label} contains more than ${MAX_VENDOR_ENTRIES} entries`);
|
|
@@ -212,27 +245,55 @@ export function vendorRemoteSource(projectRoot, entry) {
|
|
|
212
245
|
const [validated] = validateCapabilitySources([entry]);
|
|
213
246
|
if (!validated.source) fail(`capability source ${validated.name} is a local path and does not need vendoring`);
|
|
214
247
|
|
|
215
|
-
|
|
248
|
+
// Resolved the same way the read path resolves it, so the containment check
|
|
249
|
+
// below compares two paths of the same form.
|
|
250
|
+
const rootReal = fs.realpathSync(projectRoot);
|
|
251
|
+
const destination = vendorDirectoryFor(rootReal, validated.name);
|
|
252
|
+
const label = `capability source ${validated.name}`;
|
|
253
|
+
|
|
254
|
+
// Before anything is created and well before anything is removed: every call
|
|
255
|
+
// after this line writes or deletes, and the point of the check is to have
|
|
256
|
+
// happened first.
|
|
257
|
+
assertContainedWritePath(rootReal, destination, label);
|
|
216
258
|
const parent = path.dirname(destination);
|
|
217
259
|
fs.mkdirSync(parent, { recursive: true, mode: 0o755 });
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
260
|
+
// Re-checked after the directories exist. The first pass ran against a path
|
|
261
|
+
// that was mostly absent, so it proved nothing about the components this call
|
|
262
|
+
// just created or about one swapped underneath in between.
|
|
263
|
+
assertContainedWritePath(rootReal, destination, label);
|
|
264
|
+
|
|
265
|
+
// Fetched beside the destination and moved into place at the end, so a fetch
|
|
266
|
+
// that fails leaves the reviewed tree that was already there. Emptying the
|
|
267
|
+
// destination first meant a network error or a hostile archive destroyed a
|
|
268
|
+
// working vendor directory and left the project with neither version.
|
|
269
|
+
const staging = `${destination}.incoming.${process.pid}`;
|
|
270
|
+
assertContainedWritePath(rootReal, staging, label);
|
|
271
|
+
for (const sibling of fs.readdirSync(parent)) {
|
|
272
|
+
// Debris from a run that died between creating this and moving it.
|
|
273
|
+
if (sibling.startsWith(`${validated.name}.incoming.`)) fs.rmSync(path.join(parent, sibling), { recursive: true, force: true });
|
|
274
|
+
}
|
|
275
|
+
fs.mkdirSync(staging, { mode: 0o755 });
|
|
221
276
|
|
|
222
277
|
try {
|
|
223
|
-
if (validated.source.startsWith("npm:")) fetchNpmSource(validated.source.slice("npm:".length),
|
|
224
|
-
else fetchGitSource(validated.source,
|
|
278
|
+
if (validated.source.startsWith("npm:")) fetchNpmSource(validated.source.slice("npm:".length), staging);
|
|
279
|
+
else fetchGitSource(validated.source, staging);
|
|
225
280
|
|
|
226
281
|
// The fetched tree is untrusted until this passes. Checking here means a
|
|
227
|
-
// hostile archive is rejected before anything reads a manifest out of it
|
|
228
|
-
|
|
229
|
-
|
|
282
|
+
// hostile archive is rejected before anything reads a manifest out of it,
|
|
283
|
+
// and before it can replace the tree already in place.
|
|
284
|
+
assertNoSymbolicLinks(staging, `capability source ${validated.name}`);
|
|
285
|
+
if (!fs.existsSync(path.join(staging, "packs"))) {
|
|
230
286
|
fail(`capability source ${validated.name} provides no packs directory`);
|
|
231
287
|
}
|
|
232
|
-
|
|
233
|
-
//
|
|
234
|
-
// the failure, so remove it.
|
|
288
|
+
|
|
289
|
+
// Everything above succeeded, so the old tree can go.
|
|
235
290
|
fs.rmSync(destination, { recursive: true, force: true });
|
|
291
|
+
fs.renameSync(staging, destination);
|
|
292
|
+
} catch (error) {
|
|
293
|
+
// Only the half-written fetch is removed. The destination is either
|
|
294
|
+
// untouched, because the failure happened before the swap, or already
|
|
295
|
+
// replaced by a tree that passed every check.
|
|
296
|
+
fs.rmSync(staging, { recursive: true, force: true });
|
|
236
297
|
throw error;
|
|
237
298
|
}
|
|
238
299
|
|