@piagent/platform 1.17.0 → 1.19.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 (98) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +1 -1
  3. package/README.vi.md +1 -1
  4. package/catalog/claude-model-additions.json +49 -2
  5. package/docs/command-reference-vietnamese.md +2 -2
  6. package/docs/distribution-standard.md +6 -6
  7. package/docs/languages.json +2 -2
  8. package/docs/operator-manual-vietnamese.md +7 -7
  9. package/docs/publishing-for-teams.md +2 -0
  10. package/docs/release-install-policy.md +2 -2
  11. package/docs/team-onboarding.md +2 -2
  12. package/docs-site/architecture.html +1 -1
  13. package/docs-site/benchmark.html +1 -1
  14. package/docs-site/commands.html +1 -1
  15. package/docs-site/content/en/index.html +4 -4
  16. package/docs-site/content/en/whats-new.html +13 -0
  17. package/docs-site/content/index.html +4 -4
  18. package/docs-site/content/whats-new.html +13 -0
  19. package/docs-site/context-engine.html +1 -1
  20. package/docs-site/ecosystem.html +1 -1
  21. package/docs-site/en/architecture.html +1 -1
  22. package/docs-site/en/benchmark.html +1 -1
  23. package/docs-site/en/commands.html +1 -1
  24. package/docs-site/en/context-engine.html +1 -1
  25. package/docs-site/en/ecosystem.html +1 -1
  26. package/docs-site/en/index.html +5 -5
  27. package/docs-site/en/mcp-approval.html +1 -1
  28. package/docs-site/en/mcp-auth.html +1 -1
  29. package/docs-site/en/mcp-servers.html +1 -1
  30. package/docs-site/en/mcp.html +1 -1
  31. package/docs-site/en/permissions.html +1 -1
  32. package/docs-site/en/profiles.html +1 -1
  33. package/docs-site/en/quickstart.html +1 -1
  34. package/docs-site/en/runtime.html +1 -1
  35. package/docs-site/en/scope.html +1 -1
  36. package/docs-site/en/security.html +1 -1
  37. package/docs-site/en/team.html +1 -1
  38. package/docs-site/en/whats-new.html +14 -1
  39. package/docs-site/en/windows.html +1 -1
  40. package/docs-site/en/workflows.html +1 -1
  41. package/docs-site/index.html +5 -5
  42. package/docs-site/mcp-approval.html +1 -1
  43. package/docs-site/mcp-auth.html +1 -1
  44. package/docs-site/mcp-servers.html +1 -1
  45. package/docs-site/mcp.html +1 -1
  46. package/docs-site/permissions.html +1 -1
  47. package/docs-site/profiles.html +1 -1
  48. package/docs-site/quickstart.html +1 -1
  49. package/docs-site/runtime.html +1 -1
  50. package/docs-site/scope.html +1 -1
  51. package/docs-site/security.html +1 -1
  52. package/docs-site/team.html +1 -1
  53. package/docs-site/whats-new.html +14 -1
  54. package/docs-site/windows.html +1 -1
  55. package/docs-site/workflows.html +1 -1
  56. package/evals/private-holdout-v1/public-exposure.v2.json +3 -3
  57. package/evals/runtime-conformance-v1/runner.mjs +13 -17
  58. package/package.json +1 -1
  59. package/packages/piagent-core/managed/call-purpose.mjs +3 -2
  60. package/packages/piagent-core/managed/command-tools.mjs +59 -0
  61. package/packages/piagent-core/managed/container-engine.mjs +102 -0
  62. package/packages/piagent-core/managed/continuation.mjs +131 -0
  63. package/packages/piagent-core/managed/doctor.mjs +7 -0
  64. package/packages/piagent-core/managed/language-environment.mjs +57 -0
  65. package/packages/piagent-core/managed/linux-sandbox.mjs +3 -1
  66. package/packages/piagent-core/managed/native-catalog.mjs +14 -1
  67. package/packages/piagent-core/managed/patch-snapshot.mjs +25 -9
  68. package/packages/piagent-core/managed/repository-operation.mjs +20 -0
  69. package/packages/piagent-core/managed/request-stream.mjs +28 -0
  70. package/packages/piagent-core/managed/review-snapshot-worker.mjs +11 -4
  71. package/packages/piagent-core/managed/sandbox-diagnostic.mjs +6 -1
  72. package/packages/piagent-core/managed/session.mjs +52 -63
  73. package/packages/piagent-core/managed/tool-boundary.mjs +21 -10
  74. package/packages/piagent-core/managed/tool-worker.mjs +3 -3
  75. package/packages/piagent-core/managed/workflow.mjs +20 -20
  76. package/packages/piagent-core/package.json +1 -1
  77. package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-CeQ79h30.js → MarkdownMessage-nggiOt0N.js} +1 -1
  78. package/packages/piagent-webui/dist/client/assets/{index-BNQK7rHr.js → index-CM9cs-gP.js} +16 -14
  79. package/packages/piagent-webui/dist/client/index.html +1 -1
  80. package/packages/piagent-webui/gateway/company-relay.ts +14 -2
  81. package/packages/piagent-webui/gateway/gateway-service.ts +38 -0
  82. package/packages/piagent-webui/gateway/git-branches.ts +94 -0
  83. package/packages/piagent-webui/gateway/git-repositories.ts +46 -0
  84. package/packages/piagent-webui/gateway/managed-projection.ts +9 -2
  85. package/packages/piagent-webui/gateway/project-registry.ts +10 -6
  86. package/packages/piagent-webui/gateway/session-catalog.ts +13 -6
  87. package/packages/piagent-webui/gateway/session-operation-lifecycle.ts +10 -3
  88. package/packages/piagent-webui/server/loopback-server.ts +9 -2
  89. package/packages/piagent-webui/server/member-routes.ts +41 -0
  90. package/packages/piagent-webui/server/tool-call-summary.ts +3 -1
  91. package/packages/piagent-webui/server/transcript-projection.ts +20 -4
  92. package/packages/piagent-webui/shared/process-copy.ts +3 -1
  93. package/packages/piagent-webui/shared/turn-end.ts +68 -0
  94. package/schemas/piagent-webui/session-catalog-v1.schema.json +11 -1
  95. package/schemas/piagent-webui/transcript-v1.schema.json +23 -2
  96. package/scripts/company-terminal.mjs +15 -0
  97. package/scripts/configure-model-scope.sh +1 -0
  98. package/templates/global/settings.json +2 -1
@@ -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.17.0 docs · EN</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.19.0 docs · EN</span>
34
34
  <nav class="language-switch" aria-label="Language">
35
35
  <a class="language-option" href="/permissions" hreflang="vi" lang="vi">VI</a>
36
36
  <a class="language-option current" href="/en/permissions" 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.17.0 docs · EN</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.19.0 docs · EN</span>
34
34
  <nav class="language-switch" aria-label="Language">
35
35
  <a class="language-option" href="/profiles" hreflang="vi" lang="vi">VI</a>
36
36
  <a class="language-option current" href="/en/profiles" 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.17.0 docs · EN</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.19.0 docs · EN</span>
34
34
  <nav class="language-switch" aria-label="Language">
35
35
  <a class="language-option" href="/quickstart" hreflang="vi" lang="vi">VI</a>
36
36
  <a class="language-option current" href="/en/quickstart" 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.17.0 docs · EN</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.19.0 docs · EN</span>
34
34
  <nav class="language-switch" aria-label="Language">
35
35
  <a class="language-option" href="/runtime" hreflang="vi" lang="vi">VI</a>
36
36
  <a class="language-option current" href="/en/runtime" 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.17.0 docs · EN</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.19.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.17.0 docs · EN</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.19.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.17.0 docs · EN</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.19.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.17.0 docs · EN</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.19.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,19 @@
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.19.0 · 2026-10-08</p>
109
+ <h2 id="v1-19-0">Tasks with a checklist are carried to the end, and each turn says whether it finished</h2>
110
+ <p class="section-lead">In a company conversation, when the main agent stops while its checklist still has open steps (say, after STEP06 of the STEP22 you asked for) and the team's Harness requires a plan, the harness sends it on to the next open step, as if you had sent “continue”. It stops when the checklist is done, when you press Stop or write, when a round fails, or after 2 rounds in a row that finish no step. Every turn ends with one line: Done, Stopped midway with x of y steps open (and why), Turn stopped by a failure, or You stopped this turn, with Continue when the task is not finished; the conversation list shows it too. Steps you leave for later are marked deferred, never completed; “continue” takes up the open checklist. A turn whose change the agent committed is still checked and reviewed.</p>
111
+ <p class="section-lead">Docker in the company sandbox: when Docker runs on the machine (Docker Desktop, Colima, OrbStack…), the agent has <span class="inline-code">run_with_docker</span> to run one command you approve with internet and Docker, such as a test database with <span class="inline-code">docker compose up -d</span>, <span class="inline-code">docker build</span> or Testcontainers tests. Docker gets no registry credentials; Bypass still asks when a command or compose file mounts a folder outside the project. <span class="inline-code">./gradlew</span> and <span class="inline-code">./mvnw</span> reuse the Gradle and Maven already on the machine, without asking for network. A folder that holds several repositories (front end, back end…, each its own Git) shows “N repo”: click it to see each repository, its branch and changed files, and switch branches there; a full folder list makes room instead of failing to add a folder.</p>
112
+ </section>
113
+
114
+ <section class="section">
115
+ <p class="section-kicker">v1.18.0 · 2026-10-08</p>
116
+ <h2 id="v1-18-0">Switch the Git branch from the dashboard, retries for passing failures, Claude Haiku 5.5</h2>
117
+ <p class="section-lead">Click the branch under the chat title or in the status bar to choose another branch or create one. The dashboard does not switch while a conversation in the folder runs, and shows Git's reason when it refuses (for example, uncommitted changes it would overwrite). When a model's answer fails for a passing reason (a cut stream, a model or Studio gateway that does not answer), Piagent asks again up to twice before showing the failure; quota, key and policy failures still show at once. Claude Haiku 5.5, Anthropic's fastest model, can be chosen in company and personal sessions.</p>
118
+ </section>
119
+
107
120
  <section class="section">
108
121
  <p class="section-kicker">v1.17.0 · 2026-10-08</p>
109
122
  <h2 id="v1-17-0">The dashboard shows each project's Git branch</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.17.0 docs · EN</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.19.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.17.0 docs · EN</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.19.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.17.0 docs · VI</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.19.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.17.0</p>
97
+ <p class="eyebrow">Piagent 1.19.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.17.0</a>
111
+ <a class="button" href="/whats-new">Có gì mới trong 1.19.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.17.0</span>
187
+ <span class="card-kicker">Bản 1.19.0</span>
188
188
  <h3>Có gì mới</h3>
189
- <p>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>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.17.0 docs · VI</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.19.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.17.0 docs · VI</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.19.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.17.0 docs · VI</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.19.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.17.0 docs · VI</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.19.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.17.0 docs · VI</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.19.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.17.0 docs · VI</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.19.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.17.0 docs · VI</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.19.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.17.0 docs · VI</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.19.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.17.0 docs · VI</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.19.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.17.0 docs · VI</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.19.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.17.0 docs · VI</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.19.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.17.0 docs · VI</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.19.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,19 @@
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.19.0 · 2026-10-08</p>
109
+ <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>
110
+ <p class="section-lead">Trong cuộc trò chuyện công ty, khi main agent tự dừng lúc checklist còn bước (ví dụ làm xong STEP06 rồi dừng, dù bạn giao đến STEP22) và Harness của team bắt buộc plan, Harness tự cho agent làm tiếp bước kế tiếp, như bạn gửi “tiếp tục”. Nó dừng khi checklist xong, khi bạn bấm Stop hoặc nhắn tin, khi gặp lỗi, hoặc sau 2 vòng liên tiếp không xong thêm bước nào. Cuối mỗi lượt có một dòng: Hoàn thành, Dừng giữa chừng còn x/y bước (kèm lý do), Lượt dừng vì lỗi, hoặc Bạn đã dừng lượt này, với nút Tiếp tục khi task chưa xong; danh sách cuộc trò chuyện cũng hiện trạng thái này. Bước bạn bảo để sau được đánh dấu “để sau”, không bao giờ bị ghi là xong; gửi “tiếp tục” là agent nhận lại checklist đang dở. Lượt mà agent đã commit vẫn được chạy check và review.</p>
111
+ <p class="section-lead">Sandbox công ty dùng được Docker: khi máy đang chạy Docker (Docker Desktop, Colima, OrbStack…), agent có <span class="inline-code">run_with_docker</span> để chạy một lệnh bạn duyệt với internet và Docker, ví dụ bật database cho test bằng <span class="inline-code">docker compose up -d</span>, <span class="inline-code">docker build</span> hay test Testcontainers. Docker không mang theo thông tin đăng nhập registry; Bypass vẫn hỏi khi lệnh hoặc file compose mount thư mục ngoài project. <span class="inline-code">./gradlew</span> và <span class="inline-code">./mvnw</span> dùng lại bản Gradle/Maven đã có trên máy nên không phải xin mạng. Folder chứa nhiều repo (FE, BE… mỗi folder một git) hiện “N repo”: bấm vào để xem từng repo, nhánh và số file thay đổi, rồi đổi nhánh trong repo đó; danh sách folder đầy thì tự bỏ folder đã mất hoặc lâu nhất, không còn báo “Không thể thêm thư mục”.</p>
112
+ </section>
113
+
114
+ <section class="section">
115
+ <p class="section-kicker">v1.18.0 · 2026-10-08</p>
116
+ <h2 id="v1-18-0">Đổi nhánh Git trên dashboard, tự thử lại khi lỗi tạm thời, Claude Haiku 5.5</h2>
117
+ <p class="section-lead">Bấm vào tên nhánh dưới tên cuộc trò chuyện hoặc ở thanh trạng thái để chọn nhánh khác hoặc tạo nhánh mới. Dashboard không cho đổi khi còn cuộc trò chuyện đang chạy trong folder, và báo đúng lý do khi Git từ chối (ví dụ thay đổi chưa commit sẽ bị ghi đè). Khi câu trả lời của model hỏng vì lý do tạm thời (stream bị cắt, model hoặc gateway của Studio không trả lời), Piagent tự hỏi lại tối đa 2 lần trước khi báo lỗi; lỗi quota, key hay chính sách vẫn báo ngay. Claude Haiku 5.5, model nhanh nhất của Anthropic, có thể chọn ở chế độ công ty và cá nhân.</p>
118
+ </section>
119
+
107
120
  <section class="section">
108
121
  <p class="section-kicker">v1.17.0 · 2026-10-08</p>
109
122
  <h2 id="v1-17-0">Dashboard hiện nhánh Git của project</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.17.0 docs · VI</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.19.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.17.0 docs · VI</span>
33
+ <span class="pill"><span class="pill-dot"></span>v1.19.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>
@@ -247,7 +247,7 @@
247
247
  {
248
248
  "path": "evals/runtime-conformance-v1",
249
249
  "fileCount": 5,
250
- "sha256": "f3ac30a6faef3bcae91314847f0a26b6fa74e2dbadd85b11740ebc854cbef4ce"
250
+ "sha256": "e5ec03530b09c3397f60ef247ce408bea2d21f36fcf5d5ec3ee1393431233f44"
251
251
  },
252
252
  {
253
253
  "path": "evals/scenarios",
@@ -256,8 +256,8 @@
256
256
  },
257
257
  {
258
258
  "path": "tests",
259
- "fileCount": 527,
260
- "sha256": "c9b28ccb6da35e951cdc534487cfb165feaa42e4fd422567e5cb9bb7f949dd41"
259
+ "fileCount": 529,
260
+ "sha256": "bf1347157d8de533df6eca703df76ab279f60abd03535ff8ab798f6bb8f2c2a8"
261
261
  }
262
262
  ],
263
263
  "custodianComparisonRules": {
@@ -262,27 +262,23 @@ runCase("context-orphan-protocol-fail-closed", "adaptive-context-governor", () =
262
262
  });
263
263
 
264
264
  runCase("operation-pristine-retry-bounded", "webui-operation-lifecycle", () => {
265
- assert.deepEqual(sessionOperationRetryPolicy(), { maximumAttempts: 1, maximumDelayMs: 8_000 });
265
+ assert.deepEqual(sessionOperationRetryPolicy(), { maximumAttempts: 2, maximumDelayMs: 8_000 });
266
266
  const lifecycle = new SessionOperationLifecycle({ operationRef: "operation-pristine" });
267
267
  lifecycle.observe({ type: "agent_start", operationRef: "operation-pristine" });
268
- const first = lifecycle.observe({
269
- type: "auto_retry_start",
270
- operationRef: "operation-pristine",
271
- attempt: 1,
272
- delayMs: 100
273
- });
268
+ const retry = (attempt) => lifecycle.observe({ type: "auto_retry_start", operationRef: "operation-pristine", attempt, delayMs: 100 });
269
+ const ended = (attempt) => lifecycle.observe({ type: "auto_retry_end", operationRef: "operation-pristine", attempt, success: false });
270
+ const first = retry(1);
274
271
  assert.equal(first.retry, "allowed");
275
272
  metrics.operations.pristineRetriesAllowed += 1;
276
- lifecycle.observe({ type: "auto_retry_end", operationRef: "operation-pristine", attempt: 1, success: false });
277
- const second = lifecycle.observe({
278
- type: "auto_retry_start",
279
- operationRef: "operation-pristine",
280
- attempt: 2,
281
- delayMs: 100
282
- });
283
- assert.equal(second.retry, "abort");
284
- assert.equal(second.reasonCode, "automatic-retry-attempt-limit");
285
- return { maximumAttempts: lifecycle.retryPolicy.maximumAttempts, firstRetry: first.retry, secondRetry: second.retry };
273
+ ended(1);
274
+ const second = retry(2);
275
+ assert.equal(second.retry, "allowed");
276
+ metrics.operations.pristineRetriesAllowed += 1;
277
+ ended(2);
278
+ const third = retry(3);
279
+ assert.equal(third.retry, "abort");
280
+ assert.equal(third.reasonCode, "automatic-retry-attempt-limit");
281
+ return { maximumAttempts: lifecycle.retryPolicy.maximumAttempts, firstRetry: first.retry, secondRetry: second.retry, thirdRetry: third.retry };
286
282
  });
287
283
 
288
284
  runCase("operation-replay-unsafe-and-exactly-once", "webui-operation-lifecycle", () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piagent/platform",
3
- "version": "1.17.0",
3
+ "version": "1.19.0",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -25,14 +25,15 @@ export function jobTitle(task, title) {
25
25
  }
26
26
 
27
27
  // The headers of one call. `owner.nextPurpose` is set by the harness when it
28
- // sends the main agent back (checks, review findings, a missing plan).
28
+ // sends the main agent back (checks, review findings: "harness"; a checklist
29
+ // with open steps: "continue").
29
30
  export function purposeHeaders(owner, runtime, role, context) {
30
31
  const messages = context?.messages ?? [], last = messages.at(-1);
31
32
  let purpose, after = '';
32
33
  if (systemText(context).startsWith(SUMMARY_PROMPT)) purpose = 'summary';
33
34
  else if (last?.role === 'toolResult') { purpose = 'tool_step'; after = afterTools(messages.findLast(m => m.role === 'assistant')); }
34
35
  else if (role !== 'main') purpose = 'brief';
35
- else if (owner.nextPurpose === 'harness') { purpose = 'harness'; owner.nextPurpose = null; }
36
+ else if (['harness', 'continue'].includes(owner.nextPurpose)) { purpose = owner.nextPurpose; owner.nextPurpose = null; }
36
37
  else purpose = 'answer';
37
38
  const title = purpose === 'brief' ? owner.jobTitles?.get(runtime) : '';
38
39
  return { 'X-Agent-Purpose': purpose, ...(after ? { 'X-Agent-After': after } : {}), ...(title ? { 'X-Agent-Task': encodeURIComponent(title) } : {}) };
@@ -0,0 +1,59 @@
1
+ import { mustConfirm } from '../runtime/policy/bypass-policy.mjs';
2
+ import { dockerConfirmation } from './container-engine.mjs';
3
+ import { networkConfirmation } from './permission.mjs';
4
+
5
+ // Commands that leave the plain sandbox, each after the member approves that
6
+ // exact command: run_with_network (internet, a local server or browser) and,
7
+ // 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.
11
+ 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
+
13
+ const KINDS = {
14
+ network: {
15
+ 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.',
17
+ allow: 'Run this exact command once, with internet access; a server it starts accepts connections while it runs.',
18
+ 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
+ confirm: (self, command) => networkConfirmation(self, command),
20
+ },
21
+ docker: {
22
+ name: 'run_with_docker', label: 'Run with Docker', scope: 'docker-command-once', provider: 'docker', riskClass: 'high',
23
+ 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
+ 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
+ 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 }),
28
+ },
29
+ };
30
+
31
+ function commandTool(self, kind) {
32
+ const spec = KINDS[kind];
33
+ return { name: spec.name, label: spec.label, description: spec.description, parameters: PARAMETERS,
34
+ execute: async (id, args, signal, onUpdate, ctx) => {
35
+ const confirm = await spec.confirm(self, args.command);
36
+ const decision = confirm === null ? { allowed: true, consume: () => true } : await (await import('../runtime/inspection/approval-broker.ts')).piApprovalBroker.request({ cwd: self.cwd, rawSessionId: self.session.sessionManager.getSessionId(), toolCallId: id,
37
+ action: { kind: 'external-provider-action', preconditionClass: 'runtime-only', toolName: spec.name, rawAction: { command: args.command }, commandPreview: String(args.command),
38
+ targetPaths: [self.cwd], provider: spec.provider, urlOrigin: null, requestedScope: spec.scope, reason: (confirm === 'ask' ? String(args.reason) : `Bypass still asks: ${confirm}. ${args.reason}`).slice(0, 300), riskClass: spec.riskClass,
39
+ allowConsequence: spec.allow, denyConsequence: 'The command does not run; the agent is told you declined.' },
40
+ terminalConfirm: () => ctx?.ui?.confirm?.(spec.title, `${args.command}\n\nLý do: ${args.reason}${confirm === 'ask' ? '' : `\nBypass vẫn hỏi: ${confirm}`}\n\nĐồng ý: ${spec.allowText}\nTừ chối: lệnh không chạy, agent được báo bạn đã từ chối.`) ?? Promise.resolve(false),
41
+ unavailableFallback: 'terminal-confirm', recheck: () => !signal?.aborted && Boolean(self.grant) });
42
+ if (!decision.allowed || !decision.consume() || signal?.aborted) throw Error('managed-operation-denied');
43
+ let ok = false;
44
+ const before = await self.changedFiles();
45
+ try { const result = await self.boundary.invoke('bash', { command: args.command, ...(args.timeout ? { timeout: args.timeout } : {}) }, signal, onUpdate, ctx?.model, kind === 'docker' ? { docker: true } : { network: true }); ok = !result?.isError; return result; }
46
+ finally { await self.claimChanges(before); await self.run?.afterTool('bash', args, ok, () => self.digest()); await self.refreshReview(); }
47
+ } };
48
+ }
49
+
50
+ export function commandTools(self) {
51
+ return [commandTool(self, 'network'), ...(self.boundary.engine ? [commandTool(self, 'docker')] : [])];
52
+ }
53
+
54
+ // What the system prompt says about services: Docker when the machine has an
55
+ // engine (run_with_docker), else the member starts them.
56
+ export function servicesPrompt(boundary) {
57
+ if (boundary.engine) return ' Docker is available through run_with_docker (the member approves each command): a database or service for tests (docker compose up -d, then the tests through bash or run_with_network), docker build, or Testcontainers tests. Stop what you started when done unless the member wants it kept. The iOS Simulator is not available in the sandbox: ask the member to run those tests.';
58
+ return ' Docker is not running on this machine and the iOS Simulator is not available in the sandbox: ask the member to start those services (or Docker, after which a new conversation has run_with_docker) or run those tests.';
59
+ }
@@ -0,0 +1,102 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ // The Docker engine a company command may use once the member approves it
5
+ // (run_with_docker): its socket, the CLI and its plugins (compose, buildx).
6
+ // The socket is the only thing opened beyond an approved network command:
7
+ // containers run in the engine (Docker Desktop's VM, Colima, OrbStack, the
8
+ // Linux daemon, Podman's compatible socket), not in this sandbox.
9
+ // The CLI gets a configuration folder of its own in the sandbox's home: no
10
+ // registry credentials (credsStore, auths), so public images are pulled and
11
+ // private registries are not, as with .npmrc.
12
+ const isSocket = file => { try { return fs.statSync(file).isSocket(); } catch { return false; } };
13
+ const real = file => { try { return fs.realpathSync(file); } catch { return null; } };
14
+
15
+ function socketCandidates(userHome, env, system) {
16
+ const host = /^unix:\/\/(\/.+)$/.exec(env.DOCKER_HOST ?? '')?.[1];
17
+ const runtime = env.XDG_RUNTIME_DIR;
18
+ return [host, path.join(userHome, '.docker/run/docker.sock'), ...(system ? ['/var/run/docker.sock'] : []), path.join(userHome, '.colima/default/docker.sock'),
19
+ path.join(userHome, '.orbstack/run/docker.sock'), ...(runtime ? [path.join(runtime, 'docker.sock'), path.join(runtime, 'podman/podman.sock')] : [])].filter(Boolean);
20
+ }
21
+
22
+ // The member's CLI plugins (links into the app bundle on macOS).
23
+ function cliPlugins(userHome, system) {
24
+ const dirs = [path.join(userHome, '.docker/cli-plugins'), ...!system ? [] : ['/usr/local/lib/docker/cli-plugins', '/usr/libexec/docker/cli-plugins', '/usr/lib/docker/cli-plugins',
25
+ '/Applications/Docker.app/Contents/Resources/cli-plugins', '/opt/homebrew/lib/docker/cli-plugins']];
26
+ const plugins = new Map();
27
+ for (const dir of dirs) {
28
+ let names = [];
29
+ try { names = fs.readdirSync(dir); } catch { continue; }
30
+ for (const name of names) {
31
+ if (!/^docker-[a-z0-9-]+$/.test(name) || plugins.has(name)) continue;
32
+ const file = real(path.join(dir, name));
33
+ try { if (file && fs.statSync(file).isFile()) plugins.set(name, file); } catch { /* broken link */ }
34
+ }
35
+ }
36
+ return plugins;
37
+ }
38
+
39
+ // `system`: also the machine-wide socket and plugin folders (tests leave them out).
40
+ export function containerEngine({ userHome, env = process.env, paths = ['/usr/local/bin', '/opt/homebrew/bin', '/usr/bin', '/Applications/Docker.app/Contents/Resources/bin'], system = true } = {}) {
41
+ const socket = socketCandidates(userHome, env, system).map(real).find(file => file && isSocket(file));
42
+ const cli = paths.map(dir => real(path.join(dir, 'docker'))).find(Boolean);
43
+ if (!socket || !cli) return null;
44
+ const plugins = cliPlugins(userHome, system);
45
+ // Read roots: the folders the CLI and plugins live in (/usr is open already).
46
+ const readRoots = [...new Set([cli, ...plugins.values()].map(file => {
47
+ const bundle = /^(\/Applications\/[^/]+\.app)\//.exec(file)?.[1];
48
+ return bundle ?? path.dirname(file);
49
+ }))].filter(dir => !/^\/(usr|bin|sbin|opt\/homebrew)(\/|$)/.test(dir));
50
+ return { socket, cli, plugins, readRoots };
51
+ }
52
+
53
+ // The CLI's configuration in the sandbox's home, and the environment that
54
+ // points it (and Testcontainers) at the engine.
55
+ export function engineEnvironment(engine, home) {
56
+ const config = path.join(home, '.docker');
57
+ fs.mkdirSync(path.join(config, 'cli-plugins'), { recursive: true, mode: 0o700 });
58
+ fs.writeFileSync(path.join(config, 'config.json'), '{}\n', { mode: 0o600 });
59
+ for (const [name, file] of engine.plugins) {
60
+ try { fs.symlinkSync(file, path.join(config, 'cli-plugins', name)); } catch { /* already there */ }
61
+ }
62
+ return {
63
+ DOCKER_HOST: `unix://${engine.socket}`, DOCKER_CONFIG: config, DOCKER_CLI_HINTS: 'false',
64
+ // Testcontainers' reaper mounts the engine's socket inside its VM, where
65
+ // Docker Desktop and Colima name it /var/run/docker.sock.
66
+ ...(process.platform === 'darwin' ? { TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE: '/var/run/docker.sock' } : {}),
67
+ };
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
+ }