@piagent/platform 1.1.9 → 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.
Files changed (81) hide show
  1. package/CHANGELOG.md +175 -1
  2. package/README.md +2 -2
  3. package/docs/command-reference-vietnamese.md +19 -5
  4. package/docs/distribution-standard.md +7 -7
  5. package/docs/mcp-and-tools.md +154 -2
  6. package/docs/model-options.md +10 -8
  7. package/docs/operator-manual-vietnamese.md +25 -13
  8. package/docs/publishing-for-teams.md +2 -0
  9. package/docs/quickstart-vietnamese.md +2 -2
  10. package/docs/release-install-policy.md +6 -6
  11. package/docs/security-threat-model.md +4 -3
  12. package/docs/team-onboarding.md +4 -4
  13. package/docs/usage-observability.md +29 -0
  14. package/docs/vercel-docs-site.md +34 -4
  15. package/docs-site/architecture.html +152 -0
  16. package/docs-site/assets/docs.css +1001 -0
  17. package/docs-site/assets/docs.js +84 -0
  18. package/docs-site/commands.html +203 -0
  19. package/docs-site/content/architecture.html +29 -0
  20. package/docs-site/content/commands.html +80 -0
  21. package/docs-site/content/index.html +80 -0
  22. package/docs-site/content/mcp-approval.html +133 -0
  23. package/docs-site/content/mcp-auth.html +76 -0
  24. package/docs-site/content/mcp-servers.html +111 -0
  25. package/docs-site/content/mcp.html +120 -0
  26. package/docs-site/content/permissions.html +54 -0
  27. package/docs-site/content/profiles.html +68 -0
  28. package/docs-site/content/quickstart.html +144 -0
  29. package/docs-site/content/runtime.html +45 -0
  30. package/docs-site/content/scope.html +71 -0
  31. package/docs-site/content/security.html +51 -0
  32. package/docs-site/content/team.html +23 -0
  33. package/docs-site/content/workflows.html +47 -0
  34. package/docs-site/index.html +131 -1534
  35. package/docs-site/mcp-approval.html +256 -0
  36. package/docs-site/mcp-auth.html +199 -0
  37. package/docs-site/mcp-servers.html +234 -0
  38. package/docs-site/mcp.html +243 -0
  39. package/docs-site/permissions.html +177 -0
  40. package/docs-site/profiles.html +191 -0
  41. package/docs-site/quickstart.html +267 -0
  42. package/docs-site/runtime.html +168 -0
  43. package/docs-site/scope.html +194 -0
  44. package/docs-site/security.html +174 -0
  45. package/docs-site/team.html +142 -0
  46. package/docs-site/workflows.html +170 -0
  47. package/package.json +4 -2
  48. package/packages/piagent-core/capabilities/capability-core.js +58 -5
  49. package/packages/piagent-core/capabilities/capability-sources.js +73 -12
  50. package/packages/piagent-core/extensions/document-intake.ts +5 -5
  51. package/packages/piagent-core/extensions/piagent-guard.ts +432 -2
  52. package/packages/piagent-core/extensions/policy-core.js +1586 -98
  53. package/packages/piagent-core/mcp/mcp-approval-store.js +216 -0
  54. package/packages/piagent-core/mcp/mcp-auth-readiness.js +225 -0
  55. package/packages/piagent-core/mcp/mcp-command-actions.js +400 -0
  56. package/packages/piagent-core/mcp/mcp-config-layers.js +466 -0
  57. package/packages/piagent-core/mcp/mcp-import-kinds.js +240 -0
  58. package/packages/piagent-core/mcp/mcp-server-catalog.js +150 -0
  59. package/packages/piagent-core/mcp/mcp-server-entry.js +293 -0
  60. package/packages/piagent-core/mcp/mcp-session-view.js +236 -0
  61. package/packages/piagent-core/mcp/mcp-tool-naming.js +90 -0
  62. package/packages/piagent-core/package.json +7 -3
  63. package/packages/piagent-core/prompts/model-options.md +8 -8
  64. package/scripts/build-docs-site.mjs +421 -0
  65. package/scripts/configure-model-scope.sh +21 -5
  66. package/scripts/configure-subagents.sh +18 -5
  67. package/scripts/init-project.sh +15 -8
  68. package/scripts/install-global.sh +5 -5
  69. package/scripts/mcp-manage.mjs +656 -0
  70. package/scripts/pi-model-catalog.sh +11 -1
  71. package/scripts/pi-session-stats.sh +27 -1
  72. package/scripts/pi-usage-history.mjs +567 -0
  73. package/scripts/piagent-cli.mjs +1 -1
  74. package/scripts/preview-docs-site.mjs +88 -0
  75. package/scripts/quality-benchmark.sh +37 -9
  76. package/scripts/setup.sh +23 -15
  77. package/scripts/team-doctor.sh +18 -2
  78. package/scripts/uninstall-global.sh +10 -4
  79. package/scripts/verify-local.sh +65 -19
  80. package/scripts/verify-release-identity.mjs +37 -2
  81. package/scripts/configure-mcp.sh +0 -349
@@ -0,0 +1,142 @@
1
+ <!doctype html>
2
+ <html lang="vi">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Team nên dùng như một chuẩn vận hành · Pi Agent Platform Docs</title>
7
+ <meta name="description" content="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 đó." />
8
+ <meta name="theme-color" content="#050505" />
9
+ <link rel="canonical" href="https://piagent.io.vn/team" />
10
+ <link rel="icon" href="/favicon.svg" type="image/svg+xml" />
11
+ <meta property="og:title" content="Team nên dùng như một chuẩn vận hành · Pi Agent Platform Docs" />
12
+ <meta property="og:description" content="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 đó." />
13
+ <meta property="og:url" content="https://piagent.io.vn/team" />
14
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
15
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
16
+ <link
17
+ 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"
18
+ rel="stylesheet"
19
+ />
20
+ <link rel="stylesheet" href="/assets/docs.css" />
21
+ <script src="/assets/docs.js" defer></script>
22
+ </head>
23
+ <body>
24
+ <header class="topbar">
25
+ <a class="brand" href="/" aria-label="Pi Agent Platform">
26
+ <span class="brand-mark"><img src="/assets/piagent-logo.svg" alt="" /></span>
27
+ <span>Pi Agent Platform</span>
28
+ </a>
29
+ <div class="top-actions">
30
+ <span class="pill"><span class="pill-dot"></span>v1.2.1 docs</span>
31
+ <a class="button social" href="https://github.com/Vt-mmm/piagent" target="_blank" rel="noopener noreferrer" aria-label="Mở GitHub repository">
32
+ <svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
33
+ <path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
34
+ </svg>
35
+ GitHub
36
+ </a>
37
+ <button class="button mobile-menu" type="button" data-menu>Lộ trình</button>
38
+ <a class="button" href="/commands">Commands</a>
39
+ </div>
40
+ </header>
41
+
42
+ <div class="layout with-toc">
43
+ <aside class="sidebar" data-sidebar>
44
+ <div class="sidebar-search" aria-label="Tìm trong tài liệu">
45
+ <svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
46
+ <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" />
47
+ </svg>
48
+ <input type="search" placeholder="Tìm trang" data-filter />
49
+ </div>
50
+
51
+ <nav aria-label="Tài liệu">
52
+ <div class="nav-group">
53
+ <p class="nav-title">Get started</p>
54
+ <a class="nav-link" href="/">Overview</a>
55
+ <a class="nav-link" href="/quickstart">Quickstart</a>
56
+ <a class="nav-link" href="/scope">Phạm vi platform</a>
57
+ </div>
58
+ <div class="nav-group">
59
+ <p class="nav-title">Build</p>
60
+ <a class="nav-link" href="/profiles">Project profile</a>
61
+ <a class="nav-link" href="/permissions">Permission modes</a>
62
+ <a class="nav-link" href="/commands">Command catalog</a>
63
+ <a class="nav-link" href="/workflows">Workflows</a>
64
+ </div>
65
+ <div class="nav-group">
66
+ <p class="nav-title">MCP</p>
67
+ <a class="nav-link" href="/mcp">MCP overview</a>
68
+ <a class="nav-link" href="/mcp-servers">Quản lý server</a>
69
+ <a class="nav-link" href="/mcp-auth">Auth và readiness</a>
70
+ <a class="nav-link" href="/mcp-approval">Approval gate</a>
71
+ </div>
72
+ <div class="nav-group">
73
+ <p class="nav-title">Operate</p>
74
+ <a class="nav-link" href="/security">Security model</a>
75
+ <a class="nav-link" href="/architecture">Architecture</a>
76
+ <a class="nav-link" href="/runtime">Runtime tools</a>
77
+ <a class="nav-link current" href="/team" aria-current="page">Team usage</a>
78
+ </div>
79
+ </nav>
80
+ </aside>
81
+
82
+ <main>
83
+ <div class="content-shell" data-article>
84
+ <nav class="breadcrumb" aria-label="Breadcrumb">
85
+ <a href="/">Docs</a>
86
+ <span aria-hidden="true">/</span>
87
+ <span>Operate</span>
88
+ <span aria-hidden="true">/</span>
89
+ <span>Team usage</span>
90
+ </nav>
91
+ <div class="page-head">
92
+ <h1>Team nên dùng như một chuẩn vận hành</h1>
93
+ <p>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 đó.</p>
94
+ </div>
95
+ <section class="section">
96
+
97
+ <div class="split">
98
+ <article class="card">
99
+ <h3 id="viec-nen-lam">Việc nên làm</h3>
100
+ <p>Chạy <span class="inline-code">/onboard-project</span>, áp <span class="inline-code">/profile auto</span>, dùng workflow command, verify thật và ghi blockers rõ ràng.</p>
101
+ </article>
102
+ <article class="card">
103
+ <h3 id="viec-nen-tranh">Việc nên tránh</h3>
104
+ <p>Đưa token, auth file, cache, session, private examples hoặc business logic riêng vào core package public.</p>
105
+ </article>
106
+ </div>
107
+
108
+ <div class="callout ok-callout">
109
+ <strong>Chuẩn handoff</strong>
110
+ <p>Final response nên nói ngắn: đã đổi gì, verify nào đã chạy, có dùng subagent không, và còn rủi ro hoặc next step nào cần người review.</p>
111
+ </div>
112
+
113
+ <div class="callout">
114
+ <strong>Release docs gate</strong>
115
+ <p>Version docs mới chỉ được promote lên Vercel sau khi tag-triggered CI pass và stable dry-run resolve đúng commit SHA. Phải promote đúng tagged commit, không chèn merge commit hay docs-only edit khác. Domain canonical là <span class="inline-code">piagent.io.vn</span>; apex phải là Vercel primary và <span class="inline-code">www</span> redirect về apex.</p>
116
+ </div>
117
+ </section>
118
+ <nav class="pager" aria-label="Trang kế tiếp">
119
+ <a class="pager-prev" href="/runtime">
120
+ <span class="pager-label">Trước</span>
121
+ <span class="pager-title">Runtime tools</span>
122
+ </a>
123
+ </nav>
124
+
125
+ <footer class="footer">
126
+ <p>Pi Agent Platform docs · static HTML · governed agent workflow</p>
127
+ <div class="footer-links" aria-label="External links">
128
+ <a href="https://piagent.io.vn" target="_blank" rel="noopener noreferrer">piagent.io.vn</a>
129
+ <a href="https://github.com/Vt-mmm/piagent" target="_blank" rel="noopener noreferrer">GitHub repository</a>
130
+ <a href="https://www.facebook.com/vinhtam0544/" target="_blank" rel="noopener noreferrer">Facebook profile</a>
131
+ </div>
132
+ </footer>
133
+ </div>
134
+ </main>
135
+
136
+ <aside class="toc" aria-label="Mục lục trang">
137
+ <p class="toc-title">Trên trang này</p>
138
+ <nav data-toc></nav>
139
+ </aside>
140
+ </div>
141
+ </body>
142
+ </html>
@@ -0,0 +1,170 @@
1
+ <!doctype html>
2
+ <html lang="vi">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Các luồng chính · Pi Agent Platform Docs</title>
7
+ <meta name="description" content="Chọn workflow theo intent thay vì mô tả lại policy trong prompt. Cách này giảm context lãng phí và giữ hành vi nhất quán." />
8
+ <meta name="theme-color" content="#050505" />
9
+ <link rel="canonical" href="https://piagent.io.vn/workflows" />
10
+ <link rel="icon" href="/favicon.svg" type="image/svg+xml" />
11
+ <meta property="og:title" content="Các luồng chính · Pi Agent Platform Docs" />
12
+ <meta property="og:description" content="Chọn workflow theo intent thay vì mô tả lại policy trong prompt. Cách này giảm context lãng phí và giữ hành vi nhất quán." />
13
+ <meta property="og:url" content="https://piagent.io.vn/workflows" />
14
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
15
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
16
+ <link
17
+ 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"
18
+ rel="stylesheet"
19
+ />
20
+ <link rel="stylesheet" href="/assets/docs.css" />
21
+ <script src="/assets/docs.js" defer></script>
22
+ </head>
23
+ <body>
24
+ <header class="topbar">
25
+ <a class="brand" href="/" aria-label="Pi Agent Platform">
26
+ <span class="brand-mark"><img src="/assets/piagent-logo.svg" alt="" /></span>
27
+ <span>Pi Agent Platform</span>
28
+ </a>
29
+ <div class="top-actions">
30
+ <span class="pill"><span class="pill-dot"></span>v1.2.1 docs</span>
31
+ <a class="button social" href="https://github.com/Vt-mmm/piagent" target="_blank" rel="noopener noreferrer" aria-label="Mở GitHub repository">
32
+ <svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
33
+ <path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
34
+ </svg>
35
+ GitHub
36
+ </a>
37
+ <button class="button mobile-menu" type="button" data-menu>Lộ trình</button>
38
+ <a class="button" href="/commands">Commands</a>
39
+ </div>
40
+ </header>
41
+
42
+ <div class="layout with-toc">
43
+ <aside class="sidebar" data-sidebar>
44
+ <div class="sidebar-search" aria-label="Tìm trong tài liệu">
45
+ <svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
46
+ <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" />
47
+ </svg>
48
+ <input type="search" placeholder="Tìm trang" data-filter />
49
+ </div>
50
+
51
+ <nav aria-label="Tài liệu">
52
+ <div class="nav-group">
53
+ <p class="nav-title">Get started</p>
54
+ <a class="nav-link" href="/">Overview</a>
55
+ <a class="nav-link" href="/quickstart">Quickstart</a>
56
+ <a class="nav-link" href="/scope">Phạm vi platform</a>
57
+ </div>
58
+ <div class="nav-group">
59
+ <p class="nav-title">Build</p>
60
+ <a class="nav-link" href="/profiles">Project profile</a>
61
+ <a class="nav-link" href="/permissions">Permission modes</a>
62
+ <a class="nav-link" href="/commands">Command catalog</a>
63
+ <a class="nav-link current" href="/workflows" aria-current="page">Workflows</a>
64
+ </div>
65
+ <div class="nav-group">
66
+ <p class="nav-title">MCP</p>
67
+ <a class="nav-link" href="/mcp">MCP overview</a>
68
+ <a class="nav-link" href="/mcp-servers">Quản lý server</a>
69
+ <a class="nav-link" href="/mcp-auth">Auth và readiness</a>
70
+ <a class="nav-link" href="/mcp-approval">Approval gate</a>
71
+ </div>
72
+ <div class="nav-group">
73
+ <p class="nav-title">Operate</p>
74
+ <a class="nav-link" href="/security">Security model</a>
75
+ <a class="nav-link" href="/architecture">Architecture</a>
76
+ <a class="nav-link" href="/runtime">Runtime tools</a>
77
+ <a class="nav-link" href="/team">Team usage</a>
78
+ </div>
79
+ </nav>
80
+ </aside>
81
+
82
+ <main>
83
+ <div class="content-shell" data-article>
84
+ <nav class="breadcrumb" aria-label="Breadcrumb">
85
+ <a href="/">Docs</a>
86
+ <span aria-hidden="true">/</span>
87
+ <span>Build</span>
88
+ <span aria-hidden="true">/</span>
89
+ <span>Workflows</span>
90
+ </nav>
91
+ <div class="page-head">
92
+ <h1>Các luồng chính</h1>
93
+ <p>Chọn workflow theo intent thay vì mô tả lại policy trong prompt. Cách này giảm context lãng phí và giữ hành vi nhất quán.</p>
94
+ </div>
95
+ <section class="section">
96
+ <div class="callout ok-callout">
97
+ <strong>Git trong Pi Agent</strong>
98
+ <p>Không có namespace <span class="inline-code">/git</span> riêng. Dùng <span class="inline-code">/commit</span> để commit local đã review, dùng <span class="inline-code">/pr</span> để chuẩn bị PR. Các hành động như <span class="inline-code">git push</span> hoặc stage rộng <span class="inline-code">git add -A</span> vẫn cần operator xác nhận.</p>
99
+ </div>
100
+
101
+ <div class="callout">
102
+ <strong>Solo-first orchestration</strong>
103
+ <p>Dùng <span class="inline-code">/piagent-orchestration</span> để xem mode, max subagents, review lenses, Field Guide và writer policy. Mặc định parent agent tự làm chính; subagents chỉ dùng khi scout, planning hoặc review có lane độc lập đáng token.</p>
104
+ </div>
105
+
106
+ <div class="command-grid">
107
+ <article class="command-card">
108
+ <header><span>Implementation bounded</span><button class="clip-btn" data-clip="/task Fix the failing checkout test and verify.">Lấy lệnh</button></header>
109
+ <pre><code>/task Fix the failing checkout test and verify.</code></pre>
110
+ </article>
111
+ <article class="command-card">
112
+ <header><span>Read-only scout</span><button class="clip-btn" data-clip="/scout Map the auth flow and risk areas.">Lấy lệnh</button></header>
113
+ <pre><code>/scout Map the auth flow and risk areas.</code></pre>
114
+ </article>
115
+ <article class="command-card">
116
+ <header><span>Plan trước khi code</span><button class="clip-btn" data-clip="/plan Add multi-tenant billing support.">Lấy lệnh</button></header>
117
+ <pre><code>/plan Add multi-tenant billing support.</code></pre>
118
+ </article>
119
+ <article class="command-card">
120
+ <header><span>Review diff</span><button class="clip-btn" data-clip="/review">Lấy lệnh</button></header>
121
+ <pre><code>/review</code></pre>
122
+ </article>
123
+ <article class="command-card">
124
+ <header><span>Commit local có guard</span><button class="clip-btn" data-clip="/commit &quot;feat: add guarded git workflow&quot;">Lấy lệnh</button></header>
125
+ <pre><code>/commit "feat: add guarded git workflow"</code></pre>
126
+ </article>
127
+ <article class="command-card">
128
+ <header><span>Chuẩn bị pull request</span><button class="clip-btn" data-clip="/pr &quot;Prepare PR for guarded git workflow&quot;">Lấy lệnh</button></header>
129
+ <pre><code>/pr "Prepare PR for guarded git workflow"</code></pre>
130
+ </article>
131
+ <article class="command-card">
132
+ <header><span>Backend spec sang frontend</span><button class="clip-btn" data-clip="/be-to-fe Implement frontend support for the new user status endpoint.">Lấy lệnh</button></header>
133
+ <pre><code>/profile be-readonly-fe
134
+ /be-to-fe Implement frontend support for the new user status endpoint.</code></pre>
135
+ </article>
136
+ <article class="command-card">
137
+ <header><span>Cải thiện platform</span><button class="clip-btn" data-clip="/platform-improve Harden command docs and run verification.">Lấy lệnh</button></header>
138
+ <pre><code>/platform-improve Harden command docs and run verification.</code></pre>
139
+ </article>
140
+ </div>
141
+ </section>
142
+ <nav class="pager" aria-label="Trang kế tiếp">
143
+ <a class="pager-prev" href="/commands">
144
+ <span class="pager-label">Trước</span>
145
+ <span class="pager-title">Command catalog</span>
146
+ </a>
147
+ <a class="pager-next" href="/mcp">
148
+ <span class="pager-label">Tiếp</span>
149
+ <span class="pager-title">MCP overview</span>
150
+ </a>
151
+ </nav>
152
+
153
+ <footer class="footer">
154
+ <p>Pi Agent Platform docs · static HTML · governed agent workflow</p>
155
+ <div class="footer-links" aria-label="External links">
156
+ <a href="https://piagent.io.vn" target="_blank" rel="noopener noreferrer">piagent.io.vn</a>
157
+ <a href="https://github.com/Vt-mmm/piagent" target="_blank" rel="noopener noreferrer">GitHub repository</a>
158
+ <a href="https://www.facebook.com/vinhtam0544/" target="_blank" rel="noopener noreferrer">Facebook profile</a>
159
+ </div>
160
+ </footer>
161
+ </div>
162
+ </main>
163
+
164
+ <aside class="toc" aria-label="Mục lục trang">
165
+ <p class="toc-title">Trên trang này</p>
166
+ <nav data-toc></nav>
167
+ </aside>
168
+ </div>
169
+ </body>
170
+ </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piagent/platform",
3
- "version": "1.1.9",
3
+ "version": "1.2.1",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -117,13 +117,15 @@
117
117
  "audit:runtime": "bash scripts/audit-runtime-host.sh",
118
118
  "release:identity": "node scripts/verify-release-identity.mjs",
119
119
  "vercel:preflight": "node scripts/verify-vercel-link.mjs",
120
+ "site:build": "node scripts/build-docs-site.mjs",
121
+ "site:preview": "node scripts/preview-docs-site.mjs",
120
122
  "site:check": "node scripts/check-published-site.mjs",
121
123
  "smoke": "bash scripts/runtime-policy-smoke.sh",
122
124
  "usage": "bash scripts/pi-session-stats.sh",
123
125
  "auto": "bash scripts/pi-auto.sh",
124
126
  "models": "bash scripts/pi-model-catalog.sh",
125
127
  "model-scope": "bash scripts/configure-model-scope.sh",
126
- "mcp": "bash scripts/configure-mcp.sh",
128
+ "mcp": "node scripts/mcp-manage.mjs",
127
129
  "subagents": "bash scripts/configure-subagents.sh",
128
130
  "capabilities:check": "node scripts/capability-catalog.mjs catalog --check",
129
131
  "capabilities:catalog": "node scripts/capability-catalog.mjs catalog --write",
@@ -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
 
@@ -808,7 +813,23 @@ function buildCoreIntegrity(root) {
808
813
  // for a different reason: it starts a process on session start and puts a
809
814
  // line in the operator's terminal, so tampering with it buys code execution
810
815
  // and a message the operator has been trained to trust.
811
- "packages/piagent-core/extensions/update-check.js"
816
+ "packages/piagent-core/extensions/update-check.js",
817
+ // The MCP files decide which servers a session may call. mcp-approval-store.js
818
+ // holds the decision itself, mcp-auth-readiness.js turns it into the verdict
819
+ // the guard acts on, and mcp-config-layers.js says which layers a repository
820
+ // can reach and therefore which servers the gate covers at all. Editing any of
821
+ // the three turns off the gate that stops a cloned repository from running its
822
+ // own MCP server here.
823
+ "packages/piagent-core/mcp/mcp-approval-store.js",
824
+ "packages/piagent-core/mcp/mcp-auth-readiness.js",
825
+ "packages/piagent-core/mcp/mcp-config-layers.js",
826
+ // mcp-session-view.js reports which servers are approved and which are not,
827
+ // and mcp-command-actions.js is what `/piagent-mcp approve` runs. An
828
+ // operator decides based on the definition these two put in front of them,
829
+ // so a file that can lie about what a server runs is a way to collect a
830
+ // decision that was never really made.
831
+ "packages/piagent-core/mcp/mcp-session-view.js",
832
+ "packages/piagent-core/mcp/mcp-command-actions.js"
812
833
  ];
813
834
  return files.map((relativePath) => {
814
835
  const resolved = resolveRegularFile(root, relativePath, MAX_ARTIFACT_BYTES);
@@ -843,7 +864,10 @@ export function resolveCapabilityProfileDocument(root, profile, options = {}) {
843
864
  const key = `${selector.name}@${selector.version}`;
844
865
  const directPack = byKey.get(key);
845
866
  if (!directPack) throw new CapabilityValidationError(`profile selects unknown capability pack ${key}`);
846
- if (directPack.activation.mode === "profile" && !directPack.activation.profiles.includes(profile.mode)) throw new CapabilityValidationError(`${key} is not enabled for profile mode ${profile.mode}`);
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}`);
847
871
  select(key);
848
872
  }
849
873
 
@@ -979,22 +1003,51 @@ export function resolveCapabilityProfile(root, profilePath, options = {}) {
979
1003
  const GRANTING_PERMISSIONS = ["capabilities", "filesystemRead", "filesystemWrite", "networkDomains", "externalActions"];
980
1004
  const RESTRICTING_PERMISSIONS = ["protectedPaths", "readOnlyPaths", "shellProtectedPaths"];
981
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
+
982
1030
  function consentedShape(document) {
983
1031
  return {
984
1032
  schemaVersion: document?.schemaVersion,
985
1033
  apiVersion: document?.core?.apiVersion,
986
1034
  packageSource: document?.core?.packageSource,
987
1035
  profile: document?.profile,
988
- packs: document?.packs
1036
+ packs: document?.packs,
1037
+ externalArtifacts: externalArtifacts(document)
989
1038
  };
990
1039
  }
991
1040
 
1041
+ const CONSENT_DRIFT_REASONS = {
1042
+ externalArtifacts: "an artifact shipped by a vendored capability source changed content"
1043
+ };
1044
+
992
1045
  function consentDrift(expected, actual) {
993
1046
  const before = consentedShape(actual);
994
1047
  const after = consentedShape(expected);
995
1048
  return Object.keys(after)
996
1049
  .filter((key) => stableJson(after[key]) !== stableJson(before[key]))
997
- .map((key) => `${key} no longer matches the locked value`);
1050
+ .map((key) => CONSENT_DRIFT_REASONS[key] ?? `${key} no longer matches the locked value`);
998
1051
  }
999
1052
 
1000
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
- const destination = vendorDirectoryFor(projectRoot, validated.name);
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
- if (fs.lstatSync(parent).isSymbolicLink()) fail(`${parent} must not be a symbolic link`);
219
- fs.rmSync(destination, { recursive: true, force: true });
220
- fs.mkdirSync(destination, { mode: 0o755 });
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), destination);
224
- else fetchGitSource(validated.source, destination);
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
- assertNoSymbolicLinks(destination, `capability source ${validated.name}`);
229
- if (!fs.existsSync(path.join(destination, "packs"))) {
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
- } catch (error) {
233
- // A half-written vendor directory would resolve on the next run and hide
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
 
@@ -13,11 +13,11 @@ import zlib from "node:zlib";
13
13
  // operator has to copy files into the repository just to be read, which is how
14
14
  // vendor PDFs end up committed.
15
15
  //
16
- // Both reference CLIs treat read as the permissive axis and write as the scoped
17
- // one: Claude Code grants extra directories through `additionalDirectories` and
18
- // `--add-dir` and then reads them without prompting, and Codex scopes
19
- // `writable_roots` while leaving reads broad. This follows that shape with one
20
- // extra restriction — inside a granted root only document extensions open, so
16
+ // The established shape for agent CLIs is to treat read as the permissive axis
17
+ // and write as the scoped one: extra directories are granted and then read
18
+ // without prompting, while the set of writable roots stays narrow. This follows
19
+ // that shape with one extra restriction — inside a granted root only document
20
+ // extensions open, so
21
21
  // granting ~/Downloads does not also hand over an installer or a key file that
22
22
  // happens to be sitting there.
23
23
  //