@piagent/platform 1.19.0 → 1.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +1 -1
  3. package/README.vi.md +1 -1
  4. package/docs/command-reference-vietnamese.md +2 -2
  5. package/docs/distribution-standard.md +6 -6
  6. package/docs/languages.json +2 -2
  7. package/docs/operator-manual-vietnamese.md +7 -7
  8. package/docs/publishing-for-teams.md +2 -0
  9. package/docs/release-install-policy.md +2 -2
  10. package/docs/team-onboarding.md +2 -2
  11. package/docs-site/architecture.html +1 -1
  12. package/docs-site/benchmark.html +1 -1
  13. package/docs-site/commands.html +1 -1
  14. package/docs-site/content/en/index.html +4 -4
  15. package/docs-site/content/en/whats-new.html +16 -0
  16. package/docs-site/content/index.html +4 -4
  17. package/docs-site/content/whats-new.html +16 -0
  18. package/docs-site/context-engine.html +1 -1
  19. package/docs-site/ecosystem.html +1 -1
  20. package/docs-site/en/architecture.html +1 -1
  21. package/docs-site/en/benchmark.html +1 -1
  22. package/docs-site/en/commands.html +1 -1
  23. package/docs-site/en/context-engine.html +1 -1
  24. package/docs-site/en/ecosystem.html +1 -1
  25. package/docs-site/en/index.html +5 -5
  26. package/docs-site/en/mcp-approval.html +1 -1
  27. package/docs-site/en/mcp-auth.html +1 -1
  28. package/docs-site/en/mcp-servers.html +1 -1
  29. package/docs-site/en/mcp.html +1 -1
  30. package/docs-site/en/permissions.html +1 -1
  31. package/docs-site/en/profiles.html +1 -1
  32. package/docs-site/en/quickstart.html +1 -1
  33. package/docs-site/en/runtime.html +1 -1
  34. package/docs-site/en/scope.html +1 -1
  35. package/docs-site/en/security.html +1 -1
  36. package/docs-site/en/team.html +1 -1
  37. package/docs-site/en/whats-new.html +17 -1
  38. package/docs-site/en/windows.html +1 -1
  39. package/docs-site/en/workflows.html +1 -1
  40. package/docs-site/index.html +5 -5
  41. package/docs-site/mcp-approval.html +1 -1
  42. package/docs-site/mcp-auth.html +1 -1
  43. package/docs-site/mcp-servers.html +1 -1
  44. package/docs-site/mcp.html +1 -1
  45. package/docs-site/permissions.html +1 -1
  46. package/docs-site/profiles.html +1 -1
  47. package/docs-site/quickstart.html +1 -1
  48. package/docs-site/runtime.html +1 -1
  49. package/docs-site/scope.html +1 -1
  50. package/docs-site/security.html +1 -1
  51. package/docs-site/team.html +1 -1
  52. package/docs-site/whats-new.html +17 -1
  53. package/docs-site/windows.html +1 -1
  54. package/docs-site/workflows.html +1 -1
  55. package/evals/private-holdout-v1/public-exposure.v2.json +2 -2
  56. package/package.json +1 -1
  57. package/packages/piagent-core/managed/call-purpose.mjs +20 -1
  58. package/packages/piagent-core/managed/command-tools.mjs +5 -8
  59. package/packages/piagent-core/managed/compaction.mjs +23 -0
  60. package/packages/piagent-core/managed/container-engine.mjs +0 -34
  61. package/packages/piagent-core/managed/doctor.mjs +5 -0
  62. package/packages/piagent-core/managed/helper-roles.mjs +1 -1
  63. package/packages/piagent-core/managed/helper-run.mjs +24 -6
  64. package/packages/piagent-core/managed/linux-sandbox.mjs +4 -1
  65. package/packages/piagent-core/managed/member-questions.d.mts +2 -2
  66. package/packages/piagent-core/managed/member-questions.mjs +5 -3
  67. package/packages/piagent-core/managed/package-proxy.mjs +181 -0
  68. package/packages/piagent-core/managed/patch-snapshot.mjs +31 -3
  69. package/packages/piagent-core/managed/review-snapshot-worker.mjs +18 -0
  70. package/packages/piagent-core/managed/sandbox-diagnostic.mjs +1 -1
  71. package/packages/piagent-core/managed/session.mjs +13 -8
  72. package/packages/piagent-core/managed/tool-boundary.mjs +52 -4
  73. package/packages/piagent-core/managed/tool-worker.mjs +15 -0
  74. package/packages/piagent-core/package.json +1 -1
  75. package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-nggiOt0N.js → MarkdownMessage-gIQf0oYL.js} +1 -1
  76. package/packages/piagent-webui/dist/client/assets/index-C86-7gi8.js +231 -0
  77. package/packages/piagent-webui/dist/client/assets/{index-Z91z6SlN.css → index-ZLemRWyJ.css} +1 -1
  78. package/packages/piagent-webui/dist/client/index.html +2 -2
  79. package/packages/piagent-webui/gateway/session-questions.ts +8 -3
  80. package/packages/piagent-webui/gateway/session-runtime-ownership.ts +2 -1
  81. package/packages/piagent-webui/gateway/session-runtime-state.ts +2 -0
  82. package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +9 -9
  83. package/packages/piagent-webui/dist/client/assets/index-CM9cs-gP.js +0 -230
@@ -30,7 +30,7 @@
30
30
  <span>Pi Agent Platform</span>
31
31
  </a>
32
32
  <div class="top-actions">
33
- <span class="pill"><span class="pill-dot"></span>v1.19.0 docs · EN</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.21.0 docs · EN</span>
34
34
  <nav class="language-switch" aria-label="Language">
35
35
  <a class="language-option" href="/scope" hreflang="vi" lang="vi">VI</a>
36
36
  <a class="language-option current" href="/en/scope" hreflang="en" lang="en" aria-current="true">EN</a>
@@ -30,7 +30,7 @@
30
30
  <span>Pi Agent Platform</span>
31
31
  </a>
32
32
  <div class="top-actions">
33
- <span class="pill"><span class="pill-dot"></span>v1.19.0 docs · EN</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.21.0 docs · EN</span>
34
34
  <nav class="language-switch" aria-label="Language">
35
35
  <a class="language-option" href="/security" hreflang="vi" lang="vi">VI</a>
36
36
  <a class="language-option current" href="/en/security" hreflang="en" lang="en" aria-current="true">EN</a>
@@ -30,7 +30,7 @@
30
30
  <span>Pi Agent Platform</span>
31
31
  </a>
32
32
  <div class="top-actions">
33
- <span class="pill"><span class="pill-dot"></span>v1.19.0 docs · EN</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.21.0 docs · EN</span>
34
34
  <nav class="language-switch" aria-label="Language">
35
35
  <a class="language-option" href="/team" hreflang="vi" lang="vi">VI</a>
36
36
  <a class="language-option current" href="/en/team" hreflang="en" lang="en" aria-current="true">EN</a>
@@ -30,7 +30,7 @@
30
30
  <span>Pi Agent Platform</span>
31
31
  </a>
32
32
  <div class="top-actions">
33
- <span class="pill"><span class="pill-dot"></span>v1.19.0 docs · EN</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.21.0 docs · EN</span>
34
34
  <nav class="language-switch" aria-label="Language">
35
35
  <a class="language-option" href="/whats-new" hreflang="vi" lang="vi">VI</a>
36
36
  <a class="language-option current" href="/en/whats-new" hreflang="en" lang="en" aria-current="true">EN</a>
@@ -104,6 +104,22 @@
104
104
  <h1>1.9.0: ask in plain language, the team's Harness checks</h1>
105
105
  <p>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.</p>
106
106
  </div>
107
+ <section class="section">
108
+ <p class="section-kicker">v1.21.0 · 2026-10-09</p>
109
+ <h2 id="v1-21-0">Decisions come one at a time, and you see at once which conversation finished or waits for you</h2>
110
+ <p class="section-lead">The agent's questions and approval requests no longer stack down the page: one card shows at a time, with steps above it (“Needs your decision · 1/3”) that say who asks (the main agent or which subagent), what kind (Ask / Approve) and let you jump to another. The main agent's come first, since your answer shapes its subagents' work; then subagents in work order (scout, research, verify, review); within one agent, what was asked first. Answering one brings up the next.</p>
111
+ <p class="section-lead">Several conversations at once: a notification at the top right says when another conversation finishes a turn, fails, stops, or starts waiting for your answer or approval; “Open” takes you there. “Done” fades after a few seconds; a waiting decision stays until you answer or open it. In the sidebar, a conversation waiting for you glows amber and reads “Needs your answer” or “Needs approval”, one that finished while you were elsewhere reads “Done · not seen” until you open it, and both move to the top of “Running · needs attention”. The tab title counts what waits, e.g. “(2) Piagent”.</p>
112
+ <p class="section-lead">Studio logs each compaction of a company conversation: why, the size before and after, and the outcome, in Logs → Lượt agent (needs Agent Studio 0.25.0).</p>
113
+ </section>
114
+
115
+ <section class="section">
116
+ <p class="section-kicker">v1.20.0 · 2026-10-09</p>
117
+ <h2 id="v1-20-0">Package installs in the company sandbox no longer ask</h2>
118
+ <p class="section-lead">Plain bash commands reach the usual registries: npm, PyPI, the Go proxy, crates.io, Maven Central, Gradle, NuGet, RubyGems, Packagist, pub.dev, CocoaPods and Hex, plus public GitHub downloads (such as the Gradle distribution <span class="inline-code">./gradlew</span> needs). So <span class="inline-code">npm install</span>, <span class="inline-code">pip install</span>, <span class="inline-code">go mod download</span>, <span class="inline-code">./mvnw test</span> and <span class="inline-code">./gradlew test</span> run without waiting for your approval. Piagent goes through a proxy that runs outside the sandbox: only those hosts, on ports 443 and 80, never an IP address, your machine or a private network. Any other host is refused, the error names it, and a command that needs it still goes through <span class="inline-code">run_with_network</span> for your approval. To add your company's registry, write its host name (or <span class="inline-code">*.domain</span>) in <span class="inline-code">~/.piagent/sandbox-domains</span>, one per line. Registry and git credentials still stay out of the sandbox, so nothing is pushed or published.</p>
119
+ <p class="section-lead">In Bypass, <span class="inline-code">run_with_docker</span> runs without asking, like <span class="inline-code">run_with_network</span>; only pushes, deploys, registry logins and deletes with an unknown target still ask.</p>
120
+ <p class="section-lead">Fixed: a subagent that works longer than 15 minutes (such as a verify running the whole test suite while the main agent waits) is no longer stopped with <span class="inline-code">managed-helper-cancelled</span>. Asked to review work committed in earlier turns, the review subagent no longer says “no patch”. It reads the commits since the commit, branch or <span class="inline-code">HEAD~N</span> the agent names; when the current turn changed nothing, it reads the branch’s commits since it left the main branch.</p>
121
+ </section>
122
+
107
123
  <section class="section">
108
124
  <p class="section-kicker">v1.19.0 · 2026-10-08</p>
109
125
  <h2 id="v1-19-0">Tasks with a checklist are carried to the end, and each turn says whether it finished</h2>
@@ -30,7 +30,7 @@
30
30
  <span>Pi Agent Platform</span>
31
31
  </a>
32
32
  <div class="top-actions">
33
- <span class="pill"><span class="pill-dot"></span>v1.19.0 docs · EN</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.21.0 docs · EN</span>
34
34
  <nav class="language-switch" aria-label="Language">
35
35
  <a class="language-option" href="/windows" hreflang="vi" lang="vi">VI</a>
36
36
  <a class="language-option current" href="/en/windows" hreflang="en" lang="en" aria-current="true">EN</a>
@@ -30,7 +30,7 @@
30
30
  <span>Pi Agent Platform</span>
31
31
  </a>
32
32
  <div class="top-actions">
33
- <span class="pill"><span class="pill-dot"></span>v1.19.0 docs · EN</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.21.0 docs · EN</span>
34
34
  <nav class="language-switch" aria-label="Language">
35
35
  <a class="language-option" href="/workflows" hreflang="vi" lang="vi">VI</a>
36
36
  <a class="language-option current" href="/en/workflows" hreflang="en" lang="en" aria-current="true">EN</a>
@@ -30,7 +30,7 @@
30
30
  <span>Pi Agent Platform</span>
31
31
  </a>
32
32
  <div class="top-actions">
33
- <span class="pill"><span class="pill-dot"></span>v1.19.0 docs · VI</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.21.0 docs · VI</span>
34
34
  <nav class="language-switch" aria-label="Ngôn ngữ">
35
35
  <a class="language-option current" href="/" hreflang="vi" lang="vi" aria-current="true">VI</a>
36
36
  <a class="language-option" href="/en/" hreflang="en" lang="en">EN</a>
@@ -94,7 +94,7 @@
94
94
  <main>
95
95
  <div class="content-shell" data-article>
96
96
  <section class="hero">
97
- <p class="eyebrow">Piagent 1.19.0</p>
97
+ <p class="eyebrow">Piagent 1.21.0</p>
98
98
  <h1>Coding agent cho cả team, kiểm tra từng bước.</h1>
99
99
  <p>
100
100
  Một dashboard cho Terminal và trình duyệt. Dùng model bằng tài khoản của bạn, hoặc model của công ty qua Agent Watch và Agent Studio. Guard kiểm tra từng tool call; ở chế độ công ty, Harness của team tự yêu cầu chạy check, cho subagent review và báo kết quả ngay dưới câu trả lời.
@@ -108,7 +108,7 @@
108
108
  <div class="hero-actions">
109
109
  <a class="button primary" href="/quickstart">Bắt đầu</a>
110
110
  <a class="button" href="/ecosystem">Hệ sinh thái</a>
111
- <a class="button" href="/whats-new">Có gì mới trong 1.19.0</a>
111
+ <a class="button" href="/whats-new">Có gì mới trong 1.21.0</a>
112
112
  <a class="button" href="/commands">Xem command</a>
113
113
  </div>
114
114
  </section>
@@ -184,9 +184,9 @@ piagent-setup</code></pre>
184
184
  <h2 id="bat-dau-tu-dau">Bắt đầu từ đâu</h2>
185
185
  <div class="card-grid">
186
186
  <a class="doc-card" href="/whats-new">
187
- <span class="card-kicker">Bản 1.19.0</span>
187
+ <span class="card-kicker">Bản 1.21.0</span>
188
188
  <h3>Có gì mới</h3>
189
- <p>Task công ty có checklist được làm đến hết, cuối mỗi lượt báo rõ xong, dừng giữa chừng hay lỗi, Docker trong sandbox công ty, folder chứa nhiều repo; ở 1.18.0: đổi nhánh Git trên dashboard, tự thử lại khi lỗi tạm thời, Claude Haiku 5.5; ở 1.17.0: dashboard hiện nhánh Git của project; ở 1.16.3: dashboard cập nhật xong vẫn tự kiểm tra bản mới; ở 1.16.2: Terminal nói giống dashboard: hỏi cập nhật, /bypass, lỗi và ghi chú Harness bằng tiếng Việt; ở 1.16.1: bản mới được báo bằng dialog và kiểm tra mỗi giờ, tab dashboard không còn mất đăng nhập; ở 1.16.0: Bypass cho cuộc trò chuyện công ty, đổi Harness không làm gián đoạn; ở 1.15.2: cuộc trò chuyện dài tự thu gọn trở lại; ở 1.15.1: Studio thấy mỗi call của model dùng để làm gì, nhiều cuộc trò chuyện trong một folder không nhận nhầm thay đổi của nhau; ở 1.15.0: subagent bị từ chối nói đúng lý do; ở 1.14: cập nhật giữ model bạn đã chọn; ở 1.13: cuộc trò chuyện giữ chỗ đang đọc, chữ của agent có định dạng, Piagent trên Windows qua WSL2: sandbox công ty bằng bubblewrap và Agent Watch cho Windows; ở 1.12: @ chọn file và folder trên máy, skill và command dùng bằng /, nhánh cuộc trò chuyện công ty gửi được tin nhắn.</p>
189
+ <p>Câu hỏi và yêu cầu duyệt hiện từng cái một (main agent trước), thông báo góc phải khi cuộc trò chuyện khác xong hay đang chờ bạn, Studio ghi lại mỗi lần compact; ở 1.20.0: cài package trong sandbox công ty không phải duyệt (npm, PyPI, Go, Maven, Gradle… qua proxy chỉ cho registry); ở 1.19.0: task công ty có checklist được làm đến hết, cuối mỗi lượt báo rõ xong, dừng giữa chừng hay lỗi, Docker trong sandbox công ty, folder chứa nhiều repo; ở 1.18.0: đổi nhánh Git trên dashboard, tự thử lại khi lỗi tạm thời, Claude Haiku 5.5; ở 1.17.0: dashboard hiện nhánh Git của project; ở 1.16.3: dashboard cập nhật xong vẫn tự kiểm tra bản mới; ở 1.16.2: Terminal nói giống dashboard: hỏi cập nhật, /bypass, lỗi và ghi chú Harness bằng tiếng Việt; ở 1.16.1: bản mới được báo bằng dialog và kiểm tra mỗi giờ, tab dashboard không còn mất đăng nhập; ở 1.16.0: Bypass cho cuộc trò chuyện công ty, đổi Harness không làm gián đoạn; ở 1.15.2: cuộc trò chuyện dài tự thu gọn trở lại; ở 1.15.1: Studio thấy mỗi call của model dùng để làm gì, nhiều cuộc trò chuyện trong một folder không nhận nhầm thay đổi của nhau; ở 1.15.0: subagent bị từ chối nói đúng lý do; ở 1.14: cập nhật giữ model bạn đã chọn; ở 1.13: cuộc trò chuyện giữ chỗ đang đọc, chữ của agent có định dạng, Piagent trên Windows qua WSL2: sandbox công ty bằng bubblewrap và Agent Watch cho Windows; ở 1.12: @ chọn file và folder trên máy, skill và command dùng bằng /, nhánh cuộc trò chuyện công ty gửi được tin nhắn.</p>
190
190
  </a>
191
191
  <a class="doc-card" href="/ecosystem">
192
192
  <span class="card-kicker">Get started</span>
@@ -30,7 +30,7 @@
30
30
  <span>Pi Agent Platform</span>
31
31
  </a>
32
32
  <div class="top-actions">
33
- <span class="pill"><span class="pill-dot"></span>v1.19.0 docs · VI</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.21.0 docs · VI</span>
34
34
  <nav class="language-switch" aria-label="Ngôn ngữ">
35
35
  <a class="language-option current" href="/mcp-approval" hreflang="vi" lang="vi" aria-current="true">VI</a>
36
36
  <a class="language-option" href="/en/mcp-approval" hreflang="en" lang="en">EN</a>
@@ -30,7 +30,7 @@
30
30
  <span>Pi Agent Platform</span>
31
31
  </a>
32
32
  <div class="top-actions">
33
- <span class="pill"><span class="pill-dot"></span>v1.19.0 docs · VI</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.21.0 docs · VI</span>
34
34
  <nav class="language-switch" aria-label="Ngôn ngữ">
35
35
  <a class="language-option current" href="/mcp-auth" hreflang="vi" lang="vi" aria-current="true">VI</a>
36
36
  <a class="language-option" href="/en/mcp-auth" hreflang="en" lang="en">EN</a>
@@ -30,7 +30,7 @@
30
30
  <span>Pi Agent Platform</span>
31
31
  </a>
32
32
  <div class="top-actions">
33
- <span class="pill"><span class="pill-dot"></span>v1.19.0 docs · VI</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.21.0 docs · VI</span>
34
34
  <nav class="language-switch" aria-label="Ngôn ngữ">
35
35
  <a class="language-option current" href="/mcp-servers" hreflang="vi" lang="vi" aria-current="true">VI</a>
36
36
  <a class="language-option" href="/en/mcp-servers" hreflang="en" lang="en">EN</a>
@@ -30,7 +30,7 @@
30
30
  <span>Pi Agent Platform</span>
31
31
  </a>
32
32
  <div class="top-actions">
33
- <span class="pill"><span class="pill-dot"></span>v1.19.0 docs · VI</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.21.0 docs · VI</span>
34
34
  <nav class="language-switch" aria-label="Ngôn ngữ">
35
35
  <a class="language-option current" href="/mcp" hreflang="vi" lang="vi" aria-current="true">VI</a>
36
36
  <a class="language-option" href="/en/mcp" hreflang="en" lang="en">EN</a>
@@ -30,7 +30,7 @@
30
30
  <span>Pi Agent Platform</span>
31
31
  </a>
32
32
  <div class="top-actions">
33
- <span class="pill"><span class="pill-dot"></span>v1.19.0 docs · VI</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.21.0 docs · VI</span>
34
34
  <nav class="language-switch" aria-label="Ngôn ngữ">
35
35
  <a class="language-option current" href="/permissions" hreflang="vi" lang="vi" aria-current="true">VI</a>
36
36
  <a class="language-option" href="/en/permissions" hreflang="en" lang="en">EN</a>
@@ -30,7 +30,7 @@
30
30
  <span>Pi Agent Platform</span>
31
31
  </a>
32
32
  <div class="top-actions">
33
- <span class="pill"><span class="pill-dot"></span>v1.19.0 docs · VI</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.21.0 docs · VI</span>
34
34
  <nav class="language-switch" aria-label="Ngôn ngữ">
35
35
  <a class="language-option current" href="/profiles" hreflang="vi" lang="vi" aria-current="true">VI</a>
36
36
  <a class="language-option" href="/en/profiles" hreflang="en" lang="en">EN</a>
@@ -30,7 +30,7 @@
30
30
  <span>Pi Agent Platform</span>
31
31
  </a>
32
32
  <div class="top-actions">
33
- <span class="pill"><span class="pill-dot"></span>v1.19.0 docs · VI</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.21.0 docs · VI</span>
34
34
  <nav class="language-switch" aria-label="Ngôn ngữ">
35
35
  <a class="language-option current" href="/quickstart" hreflang="vi" lang="vi" aria-current="true">VI</a>
36
36
  <a class="language-option" href="/en/quickstart" hreflang="en" lang="en">EN</a>
@@ -30,7 +30,7 @@
30
30
  <span>Pi Agent Platform</span>
31
31
  </a>
32
32
  <div class="top-actions">
33
- <span class="pill"><span class="pill-dot"></span>v1.19.0 docs · VI</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.21.0 docs · VI</span>
34
34
  <nav class="language-switch" aria-label="Ngôn ngữ">
35
35
  <a class="language-option current" href="/runtime" hreflang="vi" lang="vi" aria-current="true">VI</a>
36
36
  <a class="language-option" href="/en/runtime" hreflang="en" lang="en">EN</a>
@@ -30,7 +30,7 @@
30
30
  <span>Pi Agent Platform</span>
31
31
  </a>
32
32
  <div class="top-actions">
33
- <span class="pill"><span class="pill-dot"></span>v1.19.0 docs · VI</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.21.0 docs · VI</span>
34
34
  <nav class="language-switch" aria-label="Ngôn ngữ">
35
35
  <a class="language-option current" href="/scope" hreflang="vi" lang="vi" aria-current="true">VI</a>
36
36
  <a class="language-option" href="/en/scope" hreflang="en" lang="en">EN</a>
@@ -30,7 +30,7 @@
30
30
  <span>Pi Agent Platform</span>
31
31
  </a>
32
32
  <div class="top-actions">
33
- <span class="pill"><span class="pill-dot"></span>v1.19.0 docs · VI</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.21.0 docs · VI</span>
34
34
  <nav class="language-switch" aria-label="Ngôn ngữ">
35
35
  <a class="language-option current" href="/security" hreflang="vi" lang="vi" aria-current="true">VI</a>
36
36
  <a class="language-option" href="/en/security" hreflang="en" lang="en">EN</a>
@@ -30,7 +30,7 @@
30
30
  <span>Pi Agent Platform</span>
31
31
  </a>
32
32
  <div class="top-actions">
33
- <span class="pill"><span class="pill-dot"></span>v1.19.0 docs · VI</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.21.0 docs · VI</span>
34
34
  <nav class="language-switch" aria-label="Ngôn ngữ">
35
35
  <a class="language-option current" href="/team" hreflang="vi" lang="vi" aria-current="true">VI</a>
36
36
  <a class="language-option" href="/en/team" hreflang="en" lang="en">EN</a>
@@ -30,7 +30,7 @@
30
30
  <span>Pi Agent Platform</span>
31
31
  </a>
32
32
  <div class="top-actions">
33
- <span class="pill"><span class="pill-dot"></span>v1.19.0 docs · VI</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.21.0 docs · VI</span>
34
34
  <nav class="language-switch" aria-label="Ngôn ngữ">
35
35
  <a class="language-option current" href="/whats-new" hreflang="vi" lang="vi" aria-current="true">VI</a>
36
36
  <a class="language-option" href="/en/whats-new" hreflang="en" lang="en">EN</a>
@@ -104,6 +104,22 @@
104
104
  <h1>Bản 1.9.0: nói việc bằng lời, Harness của team kiểm tra</h1>
105
105
  <p>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.</p>
106
106
  </div>
107
+ <section class="section">
108
+ <p class="section-kicker">v1.21.0 · 2026-10-09</p>
109
+ <h2 id="v1-21-0">Việc cần bạn quyết hiện từng cái một, và biết ngay cuộc trò chuyện nào xong hay đang chờ bạn</h2>
110
+ <p class="section-lead">Câu hỏi và yêu cầu duyệt của agent không còn xếp chồng dài xuống trang: mỗi lần chỉ hiện một thẻ, phía trên có thanh “Việc cần bạn quyết · 1/3” ghi rõ ai hỏi (main agent hay subagent nào), loại việc (Hỏi / Duyệt) và cho bấm chuyển sang việc khác. Việc của main agent hiện trước vì câu trả lời của bạn định hướng việc của subagent; sau đó tới subagent theo thứ tự làm việc (scout, research, verify, review); trong cùng một agent, việc hỏi trước hiện trước. Trả lời xong thẻ này thì thẻ kế tiếp hiện lên.</p>
111
+ <p class="section-lead">Làm nhiều cuộc trò chuyện cùng lúc: góc phải trên màn hình hiện thông báo khi một cuộc trò chuyện khác xong lượt, bị lỗi, bị dừng, hoặc đang chờ bạn trả lời hay duyệt; bấm “Mở” để vào đúng cuộc trò chuyện đó. Thông báo “Xong” tự ẩn sau vài giây, việc đang chờ bạn thì giữ đến khi bạn trả lời hoặc mở. Ở cột trái, cuộc trò chuyện đang chờ bạn sáng viền cam và ghi “Cần bạn trả lời” hoặc “Cần duyệt”, cuộc trò chuyện xong khi bạn đang ở chỗ khác ghi “Xong · chưa xem” cho đến khi bạn mở, và cả hai được đưa lên đầu mục “Đang chạy · cần chú ý”. Tiêu đề tab đếm số việc đang chờ, ví dụ “(2) Piagent”.</p>
112
+ <p class="section-lead">Studio ghi lại mỗi lần cuộc trò chuyện công ty được thu gọn (compact): lý do, kích thước trước và sau, kết quả, hiện trong Logs → Lượt agent (cần Agent Studio 0.25.0).</p>
113
+ </section>
114
+
115
+ <section class="section">
116
+ <p class="section-kicker">v1.20.0 · 2026-10-09</p>
117
+ <h2 id="v1-20-0">Cài package trong sandbox công ty không phải duyệt nữa</h2>
118
+ <p class="section-lead">Lệnh bash thường tải được từ các registry quen thuộc: npm, PyPI, Go proxy, crates.io, Maven Central, Gradle, NuGet, RubyGems, Packagist, pub.dev, CocoaPods, Hex, và tải về public từ GitHub (ví dụ bản Gradle mà <span class="inline-code">./gradlew</span> cần). Nhờ vậy <span class="inline-code">npm install</span>, <span class="inline-code">pip install</span>, <span class="inline-code">go mod download</span>, <span class="inline-code">./mvnw test</span> hay <span class="inline-code">./gradlew test</span> chạy ngay, không phải chờ bạn duyệt. Piagent đi qua một proxy chạy ngoài sandbox: chỉ các host đó, cổng 443 và 80, không bao giờ tới IP trực tiếp, máy của bạn hay mạng nội bộ. Host khác bị chặn, lỗi ghi rõ host nào, và lệnh cần host đó vẫn phải qua <span class="inline-code">run_with_network</span> để bạn duyệt. Muốn thêm registry riêng của công ty, ghi tên host (hoặc <span class="inline-code">*.domain</span>) vào <span class="inline-code">~/.piagent/sandbox-domains</span>, mỗi dòng một host. Thông tin đăng nhập registry và git vẫn không vào sandbox, nên không push hay publish được.</p>
119
+ <p class="section-lead">Ở chế độ Bypass, <span class="inline-code">run_with_docker</span> chạy luôn không cần duyệt, giống <span class="inline-code">run_with_network</span>; chỉ push, deploy, đăng nhập registry hay lệnh xoá không rõ đích là vẫn hỏi.</p>
120
+ <p class="section-lead">Sửa lỗi: subagent chạy lâu hơn 15 phút (ví dụ verify chạy cả bộ test trong lúc main agent chờ) không còn bị dừng với <span class="inline-code">managed-helper-cancelled</span> nữa. Nhờ subagent review xem lại code đã commit ở các lượt trước thì nó không còn báo “không có patch”. Review đọc các commit kể từ commit, nhánh hoặc <span class="inline-code">HEAD~N</span> mà agent chỉ ra; nếu lượt hiện tại chưa sửa gì, nó đọc các commit của nhánh kể từ lúc tách khỏi nhánh chính.</p>
121
+ </section>
122
+
107
123
  <section class="section">
108
124
  <p class="section-kicker">v1.19.0 · 2026-10-08</p>
109
125
  <h2 id="v1-19-0">Task có checklist được làm đến hết, và mỗi lượt báo rõ đã xong hay chưa</h2>
@@ -30,7 +30,7 @@
30
30
  <span>Pi Agent Platform</span>
31
31
  </a>
32
32
  <div class="top-actions">
33
- <span class="pill"><span class="pill-dot"></span>v1.19.0 docs · VI</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.21.0 docs · VI</span>
34
34
  <nav class="language-switch" aria-label="Ngôn ngữ">
35
35
  <a class="language-option current" href="/windows" hreflang="vi" lang="vi" aria-current="true">VI</a>
36
36
  <a class="language-option" href="/en/windows" hreflang="en" lang="en">EN</a>
@@ -30,7 +30,7 @@
30
30
  <span>Pi Agent Platform</span>
31
31
  </a>
32
32
  <div class="top-actions">
33
- <span class="pill"><span class="pill-dot"></span>v1.19.0 docs · VI</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.21.0 docs · VI</span>
34
34
  <nav class="language-switch" aria-label="Ngôn ngữ">
35
35
  <a class="language-option current" href="/workflows" hreflang="vi" lang="vi" aria-current="true">VI</a>
36
36
  <a class="language-option" href="/en/workflows" hreflang="en" lang="en">EN</a>
@@ -256,8 +256,8 @@
256
256
  },
257
257
  {
258
258
  "path": "tests",
259
- "fileCount": 529,
260
- "sha256": "bf1347157d8de533df6eca703df76ab279f60abd03535ff8ab798f6bb8f2c2a8"
259
+ "fileCount": 532,
260
+ "sha256": "426273d76d2f8103f71fa35bc0ff18c77ca82f5b9bccf1e4ddd27fa9cd18e197"
261
261
  }
262
262
  ],
263
263
  "custodianComparisonRules": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piagent/platform",
3
- "version": "1.19.0",
3
+ "version": "1.21.0",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -36,7 +36,26 @@ export function purposeHeaders(owner, runtime, role, context) {
36
36
  else if (['harness', 'continue'].includes(owner.nextPurpose)) { purpose = owner.nextPurpose; owner.nextPurpose = null; }
37
37
  else purpose = 'answer';
38
38
  const title = purpose === 'brief' ? owner.jobTitles?.get(runtime) : '';
39
- return { 'X-Agent-Purpose': purpose, ...(after ? { 'X-Agent-After': after } : {}), ...(title ? { 'X-Agent-Task': encodeURIComponent(title) } : {}) };
39
+ return { 'X-Agent-Purpose': purpose, ...(after ? { 'X-Agent-After': after } : {}), ...(title ? { 'X-Agent-Task': encodeURIComponent(title) } : {}),
40
+ ...(role === 'main' ? compactionHeaders(owner, purpose) : {}) };
41
+ }
42
+
43
+ // The main agent's compaction (trackCompaction in compaction.mjs): each
44
+ // summary call names it, why it ran and which part it is ("<id>;threshold;2");
45
+ // the next other call says how it ended, with Pi's token counts before and
46
+ // after and how long it took ("<id>;threshold;done;244039;34237;81000"), once.
47
+ const count = n => Number.isFinite(n) && n >= 0 ? String(Math.round(n)) : '';
48
+ export function compactionHeaders(owner, purpose) {
49
+ const out = {}, now = owner.compaction, ended = owner.compacted;
50
+ if (purpose === 'summary' && now) {
51
+ now.part = Math.min(now.part + 1, 999);
52
+ out['X-Agent-Compaction'] = `${now.id};${now.reason};${now.part}`;
53
+ }
54
+ if (purpose !== 'summary' && ended) {
55
+ owner.compacted = null;
56
+ out['X-Agent-Compacted'] = [ended.id, ended.reason, ended.outcome, count(ended.before), count(ended.after), count(ended.ms)].join(';');
57
+ }
58
+ return out;
40
59
  }
41
60
 
42
61
  // Pi folds the system prompt into a leading system message before a call
@@ -1,19 +1,17 @@
1
- import { mustConfirm } from '../runtime/policy/bypass-policy.mjs';
2
- import { dockerConfirmation } from './container-engine.mjs';
3
1
  import { networkConfirmation } from './permission.mjs';
4
2
 
5
3
  // Commands that leave the plain sandbox, each after the member approves that
6
4
  // exact command: run_with_network (internet, a local server or browser) and,
7
5
  // when this machine has a Docker engine, run_with_docker (the same plus the
8
- // engine's socket). Bypass runs them without asking, except what must be
9
- // confirmed: for Docker also a container given host folders outside the
10
- // project, the engine's socket, or the host's processes or network.
6
+ // engine's socket). Bypass runs both without asking (owner, 2026-10-09),
7
+ // except what must be confirmed for any command (bypass-policy.mjs: pushes,
8
+ // deploys, registry logins, deletes known only at run time).
11
9
  const PARAMETERS = { type: 'object', properties: { command: { type: 'string', minLength: 1, maxLength: 4000 }, reason: { type: 'string', minLength: 1, maxLength: 300 }, timeout: { type: 'number', minimum: 1, maximum: 1800 } }, required: ['command', 'reason'], additionalProperties: false };
12
10
 
13
11
  const KINDS = {
14
12
  network: {
15
13
  name: 'run_with_network', label: 'Run with network', scope: 'network-command-once', provider: 'network', riskClass: 'medium',
16
- description: 'Run one shell command that needs the internet (package install, download, git pull of a public repository), or that starts a local server or a browser (end-to-end tests with Playwright\'s Chromium against a server on 127.0.0.1), after the user approves that exact command. Normal bash has no network and cannot listen on a port. Credentials (.npmrc, SSH keys, Keychain) stay unavailable, so private registries and git push are not possible here.',
14
+ description: 'Run one shell command that needs the internet beyond the package registries bash already reaches (a download site or registry the package proxy refuses), or that starts a local server or a browser (end-to-end tests with Playwright\'s Chromium against a server on 127.0.0.1), after the user approves that exact command. Normal bash has no network and cannot listen on a port. Credentials (.npmrc, SSH keys, Keychain) stay unavailable, so private registries and git push are not possible here.',
17
15
  allow: 'Run this exact command once, with internet access; a server it starts accepts connections while it runs.',
18
16
  title: 'Chạy lệnh có internet', allowText: 'chạy đúng lệnh này một lần, có internet; server nó mở nhận kết nối trong lúc chạy.',
19
17
  confirm: (self, command) => networkConfirmation(self, command),
@@ -23,8 +21,7 @@ const KINDS = {
23
21
  description: 'Run one shell command that needs the Docker engine of this machine: docker, docker compose (a database or service for tests), docker build, or tests that start containers themselves (Testcontainers), after the user approves that exact command. It has internet access too. Normal bash and run_with_network cannot reach Docker. Mount only folders inside the project. Registry credentials are unavailable: public images pull, private registries and docker push do not. Containers started in the background (-d, compose up -d) keep running after the command: stop them (docker compose down) when the work is done unless the member wants them kept.',
24
22
  allow: 'Run this exact command once, with internet access and the Docker engine; a container it starts can read folders Docker shares from this machine and may keep running.',
25
23
  title: 'Chạy lệnh dùng Docker', allowText: 'chạy đúng lệnh này một lần, có internet và Docker; container nó tạo có thể đọc thư mục Docker được chia sẻ trên máy và có thể tiếp tục chạy.',
26
- confirm: async (self, command) => self.permission !== 'trusted-full-access' ? 'ask'
27
- : await mustConfirm(command) ?? dockerConfirmation(command, { cwd: self.cwd, home: self.boundary.userHome }),
24
+ confirm: (self, command) => networkConfirmation(self, command),
28
25
  },
29
26
  };
30
27
 
@@ -1,4 +1,5 @@
1
1
  import path from 'node:path';
2
+ import { randomUUID } from 'node:crypto';
2
3
  import { pathToFileURL } from 'node:url';
3
4
  import { failureCode } from '../runtime/managed-failure.mjs';
4
5
  import { currentPlan, unfinished, planText } from './workflow.mjs';
@@ -107,3 +108,25 @@ export async function stageCompaction(session, api, sdk) {
107
108
  tokensBefore: preparation.tokensBefore, usage, details: { readFiles, modifiedFiles } });
108
109
  };
109
110
  }
111
+
112
+ // What a compaction did, for Studio's logs (Logs → "Lượt agent"): its summary
113
+ // calls name it (X-Agent-Compaction) and the main agent's next call says how
114
+ // it ended (X-Agent-Compacted, see compactionHeaders). Pi's own token counts
115
+ // only; no summary text leaves.
116
+ export function trackCompaction(owner, session, now = Date.now) {
117
+ const run = session._runDefaultCompaction?.bind(session);
118
+ if (!run) throw Error('managed-sdk-version-unqualified');
119
+ session._runDefaultCompaction = (preparation, ...rest) => {
120
+ if (owner.compaction) owner.compaction.before ??= preparation?.tokensBefore;
121
+ return run(preparation, ...rest);
122
+ };
123
+ session.subscribe(event => {
124
+ if (event?.type === 'compaction_start') owner.compaction = { id: randomUUID(), reason: event.reason, part: 0, at: now() };
125
+ else if (event?.type === 'compaction_end' && owner.compaction) {
126
+ const c = owner.compaction;
127
+ owner.compaction = null;
128
+ owner.compacted = { id: c.id, reason: c.reason, outcome: event.result ? 'done' : event.aborted ? 'aborted' : 'failed',
129
+ before: event.result?.tokensBefore ?? c.before, after: event.result?.estimatedTokensAfter, ms: now() - c.at };
130
+ }
131
+ });
132
+ }
@@ -66,37 +66,3 @@ export function engineEnvironment(engine, home) {
66
66
  ...(process.platform === 'darwin' ? { TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE: '/var/run/docker.sock' } : {}),
67
67
  };
68
68
  }
69
-
70
- // Why an approved-in-Bypass Docker command must still be asked about: host
71
- // folders outside the project mounted into a container, the engine's own
72
- // socket, or a container given the host's processes or network. The
73
- // project's compose files are read for the same (a volume naming ~ or an
74
- // absolute path outside the project). null when none applies.
75
- const COMPOSE_FILES = ['compose.yaml', 'compose.yml', 'docker-compose.yaml', 'docker-compose.yml', 'compose.override.yaml', 'compose.override.yml', 'docker-compose.override.yaml', 'docker-compose.override.yml'];
76
- function outside(source, cwd, home) {
77
- if (!source || /^[A-Za-z0-9][A-Za-z0-9_.-]*$/.test(source)) return false; // a named volume
78
- const expanded = source.replace(/^~(?=\/|$)/, home).replace(/^\$\{?HOME\}?(?=\/|$)/, home);
79
- if (/\$/.test(expanded)) return true;
80
- const resolved = path.resolve(cwd, expanded);
81
- return /docker\.sock$/.test(resolved) || !(resolved === cwd || resolved.startsWith(cwd + path.sep));
82
- }
83
- export function dockerConfirmation(command, { cwd, home }) {
84
- const text = String(command);
85
- if (/--privileged\b|--pid[= ]host\b|--network[= ]host\b|--net[= ]host\b|--userns[= ]host\b|--cap-add\b/.test(text)) return 'the container gets the host\'s processes, network or extra privileges';
86
- const mounts = [...text.matchAll(/(?:^|\s)(?:-v|--volume)(?:=|\s+)(['"]?)([^\s'"]+)\1/g)].map(match => match[2].split(':')[0]);
87
- for (const match of text.matchAll(/--mount(?:=|\s+)(['"]?)([^\s'"]+)\1/g)) {
88
- const source = /(?:^|,)(?:source|src)=([^,]+)/.exec(match[2])?.[1];
89
- if (/(?:^|,)type=bind\b/.test(match[2])) mounts.push(source ?? '');
90
- }
91
- if (mounts.some(source => outside(source, cwd, home))) return 'it mounts a folder outside the project into a container';
92
- if (/\bcompose\b/.test(text)) {
93
- for (const name of COMPOSE_FILES) {
94
- let content;
95
- try { content = fs.readFileSync(path.join(cwd, name), 'utf8'); } catch { continue; }
96
- if (/^\s*(privileged:\s*true|pid:\s*["']?host|network_mode:\s*["']?host)/m.test(content)) return `${name} gives a container the host's processes, network or privileges`;
97
- const sources = [...content.matchAll(/^\s*-\s*["']?([^\s"':]+):[^\s]/gm), ...content.matchAll(/^\s*source:\s*["']?([^\s"']+)/gm)].map(match => match[1]);
98
- if (sources.some(source => /^(\/|~|\.|\$)/.test(source) && outside(source, cwd, home))) return `${name} mounts a folder outside the project into a container`;
99
- }
100
- }
101
- return null;
102
- }
@@ -66,6 +66,11 @@ export async function runDoctor({ sdkRoot, origin, broker, network = true }) {
66
66
  let refused = false; try { await bash('curl -sS -m 5 https://example.com/ >/dev/null'); } catch { refused = true; }
67
67
  if (!refused) throw Error('a command reached the internet without approval'); return 'blocked';
68
68
  });
69
+ // Package registries through the runtime's proxy (package-proxy.mjs).
70
+ if (network) await check('package registries without approval (npm through the proxy)', false, async () => {
71
+ const status = await bash('curl -sS -m 15 -o /dev/null -w "%{http_code}" https://registry.npmjs.org/');
72
+ if (status !== '200') throw Error(`HTTP ${status}`); return 'registry.npmjs.org';
73
+ });
69
74
  await check('git', true, () => bash('git --version'));
70
75
  await check('git commit with the member\'s identity', false, async () => {
71
76
  await bash('git init -q . && git add -A');
@@ -34,7 +34,7 @@ const HELPER_USES = {
34
34
  research: roles => roles.includes('scout') ? 'research: anything on the web (current information, news, library or API documentation, releases, error messages), with the code when needed. It searches and reads the pages; you get its summary with sources.'
35
35
  : 'research: anything on the web (current information, news, documentation; it searches and reads the pages, you get its summary with sources), and before changing an area you do not know that spans more files than you can read in a few batches (where something is computed and every caller, how a flow crosses modules); your own context stays free for the change.',
36
36
  verify: () => 'verify: an independent check of a result: it runs the repository checks on the current code (offline, and the project is read-only for its commands) and confirms claims, yours or a helper\'s, against the code and primary sources. It answers pass, fail or unverifiable per claim, with evidence.',
37
- review: () => 'review: an independent review of the current patch.',
37
+ review: () => 'review: an independent review of the current patch (this turn\'s changes, commits made in the turn included). To review work committed in earlier turns, pass since: the commit, branch or HEAD~N it started from; when this turn changed nothing, it reviews the branch\'s commits since it left the default branch.',
38
38
  };
39
39
  export function delegateDescription(roles) {
40
40
  return `Hand work to a helper with its own context. Helpers read the project but cannot change files or call helpers. ${roles.map(role => HELPER_USES[role](roles)).join(' ')} A lookup that one grep or read answers stays with you. Write the task as a brief the helper can act on alone: the goal, what to find or check, where to start and what you already know or ruled out, and the answer you need (file:line facts). One call per helper runs at a time; each can be called again, up to ${HELPER_CALLS} times per user message, for example a second review after fixing what the first one found.`;
@@ -8,7 +8,7 @@ import { ManagedToolBoundary } from './tool-boundary.mjs';
8
8
  import { managedResourceLoader } from './resource-loader.mjs';
9
9
  import { shareGrant, releaseGrant } from './grant-share.mjs';
10
10
  import { describeFailure, failureCode, failureText } from '../runtime/managed-failure.mjs';
11
- import { reviewParts, mergeReviews, REVIEW_PARALLEL } from './patch-snapshot.mjs';
11
+ import { reviewParts, mergeReviews, REVIEW_PARALLEL, emptySnapshot, readBranchBase, readPatchDigest, readPatchSnapshot, resolveReviewBase } from './patch-snapshot.mjs';
12
12
  import { reviewFindings, briefIssues } from './workflow.mjs';
13
13
  import { anchorText, relayObjection, recordVerdicts } from './objections.mjs';
14
14
  import { HELPER_CALLS, HELPER_SETUP, READ_TOOLS, countedCheck } from './helper-roles.mjs';
@@ -21,8 +21,24 @@ const textContent = result => result.content?.filter(p => p.type === 'text').map
21
21
  const HELPER_SWITCH = new Set(['upstream_rate_limited', 'session_account_not_ready_retry_later', 'session_account_unavailable_start_new_session', 'account_capacity_unavailable', 'execution_capability_unavailable']);
22
22
  const HELPER_ATTEMPTS = 2;
23
23
 
24
- export async function runHelper(managed, role, task, signal, harness, { verifyGrant, patiently, title }) {
25
- const snapshot = role === 'review' ? await managed.patchSnapshot() : null, parts = snapshot ? reviewParts(snapshot) : null;
24
+ // The patch a review reads: this turn's change; the commits since `since`
25
+ // (a commit before HEAD the main agent names); or, when the turn changed
26
+ // nothing and the main agent asked (a review of work committed in earlier
27
+ // turns), the branch's commits since it left the default branch.
28
+ async function reviewSnapshot(managed, harness, since) {
29
+ if (since) {
30
+ const base = await resolveReviewBase(managed.boundary, since);
31
+ if (!base) throw Error(`managed-review-base-unknown: "${String(since).slice(0, 80)}" is not a commit HEAD descends from in this repository; name a commit, a branch or HEAD~N`);
32
+ return Object.assign(await readPatchSnapshot(managed.boundary, null, base), { from: { kind: 'since', ref: since, base } });
33
+ }
34
+ const snapshot = await managed.patchSnapshot();
35
+ if (harness || !emptySnapshot(snapshot)) return snapshot;
36
+ const branch = await readBranchBase(managed.boundary);
37
+ return branch ? Object.assign(await readPatchSnapshot(managed.boundary, null, branch.base), { from: { kind: 'branch', ref: branch.ref, base: branch.base } }) : snapshot;
38
+ }
39
+
40
+ export async function runHelper(managed, role, task, signal, harness, { verifyGrant, patiently, title, since }) {
41
+ const snapshot = role === 'review' ? await reviewSnapshot(managed, harness, since) : null, parts = snapshot ? reviewParts(snapshot) : null;
26
42
  // A helper whose company model account is out of usage, or resting after
27
43
  // a refusal, starts once more: Studio then gives the new helper the next
28
44
  // model of its role that serves (an auto role's list). Helpers read and
@@ -98,15 +114,17 @@ async function helperAttempt(managed, role, task, signal, harness, snapshot, par
98
114
  review = mergeReviews(parts, answers, { findingsOf: reviewFindings, issuesOf: briefIssues });
99
115
  ({ reply, usage } = review);
100
116
  }
101
- const stale = snapshot ? (await managed.patchDigest()) !== snapshot.digest : false;
102
- const details = { role, runID: grant.run_id, tokens: usage, patchDigest: snapshot?.digest, stale };
117
+ const from = snapshot?.from ?? null;
118
+ const stale = snapshot ? (await (from ? readPatchDigest(managed.boundary, null, from.base) : managed.patchDigest())) !== snapshot.digest : false;
119
+ const details = { role, runID: grant.run_id, tokens: usage, patchDigest: snapshot?.digest, stale, ...(from ? { since: { kind: from.kind, ref: from.ref, base: from.base } } : {}) };
103
120
  if (snapshot) {
104
121
  // Severity-graded findings decide whether the harness sends the agent back.
105
122
  const findings = review ? review.findings : reviewFindings(reply);
106
123
  Object.assign(details, { parsed: review ? review.parsed : !!findings, findings: (findings ?? []).slice(0, 40), blocking: findings?.filter(f => f.severity === 'blocking').length ?? 0 });
107
124
  if (review) Object.assign(details, { parts: parts.parts.length, unread: parts.omitted.map(o => o.path).slice(0, 50) });
108
125
  if (managed.run) { managed.run.reviews += 1; managed.run.blocking += details.blocking; }
109
- managed.review = details;
126
+ // A review of earlier commits is not the review of this turn's change.
127
+ if (!from) managed.review = details;
110
128
  managed.session.sessionManager.appendCustomEntry('agent-watch-review', details);
111
129
  }
112
130
  const verdicts = role === 'verify' ? recordVerdicts(managed.run, reply, harness, details) : '';
@@ -52,7 +52,7 @@ export function credentialFiles(roots, { limit = SCAN_ENTRIES } = {}) {
52
52
  }
53
53
 
54
54
  const depth = file => file.split('/').length;
55
- export function bubblewrapArgs({ cwd, sdkRoot, runtimeRoot, temporary, empty, node, readOnly = false, repository = [], gitDirs = [], toolchains = [], skills = [], caches = [], references = [], network = false, credentials = [], docker = null }) {
55
+ export function bubblewrapArgs({ cwd, sdkRoot, runtimeRoot, temporary, empty, node, readOnly = false, repository = [], gitDirs = [], toolchains = [], skills = [], caches = [], references = [], network = false, credentials = [], docker = null, proxySocket = null }) {
56
56
  const exists = file => { try { fs.statSync(file); return true; } catch { return false; } };
57
57
  const readable = [...new Set([sdkRoot, runtimeRoot, path.dirname(node), ...repository, ...toolchains, ...skills, ...references, ...(readOnly ? [cwd, ...gitDirs] : [])])].filter(exists);
58
58
  const writable = [...new Set([...caches, ...(readOnly ? [] : [cwd, ...gitDirs])])].filter(exists);
@@ -68,6 +68,9 @@ export function bubblewrapArgs({ cwd, sdkRoot, runtimeRoot, temporary, empty, no
68
68
  ...credentials.filter(item => exists(item.path)).flatMap(item => item.directory ? ['--tmpfs', item.path] : ['--ro-bind', empty, item.path]),
69
69
  // An approved Docker command reaches the engine's socket (run_with_docker).
70
70
  ...(docker && exists(docker) ? ['--bind', docker, docker] : []),
71
+ // A command without network reaches the package proxy (package-proxy.mjs)
72
+ // through this socket; the worker forwards 127.0.0.1:3128 to it.
73
+ ...(proxySocket && exists(proxySocket) ? ['--bind', proxySocket, '/run/piagent-proxy.sock'] : []),
71
74
  '--remount-ro', '/', '--chdir', cwd,
72
75
  ];
73
76
  }
@@ -8,8 +8,8 @@ export const memberQuestions: {
8
8
  pulseMs: number;
9
9
  bindSurface(sessionId: string, onEvent: (event: MemberQuestionEvent) => void): () => void;
10
10
  hasSurface(sessionId: string): boolean;
11
- ask(input: { sessionId: string; toolCallId: string; questions: MemberQuestion[]; signal?: AbortSignal }): Promise<MemberAnswer>;
12
- pending(sessionId: string): Array<{ questionRef: string; askedAt: string; questions: MemberQuestion[] }>;
11
+ ask(input: { sessionId: string; toolCallId: string; questions: MemberQuestion[]; signal?: AbortSignal; role?: string }): Promise<MemberAnswer>;
12
+ pending(sessionId: string): Array<{ questionRef: string; askedAt: string; role: string; questions: MemberQuestion[] }>;
13
13
  answer(sessionId: string, questionRef: string, raw: unknown): { questionRef: string; state: "answered" };
14
14
  };
15
15
  export function askTool(managed: unknown): unknown;