@piagent/platform 1.27.0 → 1.28.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.
- package/CHANGELOG.md +33 -0
- package/README.md +1 -1
- package/README.vi.md +1 -1
- package/docs/command-reference-vietnamese.md +2 -2
- package/docs/distribution-standard.md +6 -6
- package/docs/languages.json +2 -2
- package/docs/operator-manual-vietnamese.md +7 -7
- package/docs/publishing-for-teams.md +2 -0
- package/docs/release-install-policy.md +2 -2
- package/docs/team-onboarding.md +2 -2
- package/docs-site/architecture.html +1 -1
- package/docs-site/benchmark.html +1 -1
- package/docs-site/commands.html +1 -1
- package/docs-site/content/en/index.html +4 -4
- package/docs-site/content/en/whats-new.html +12 -0
- package/docs-site/content/index.html +4 -4
- package/docs-site/content/whats-new.html +12 -0
- package/docs-site/context-engine.html +1 -1
- package/docs-site/ecosystem.html +1 -1
- package/docs-site/en/architecture.html +1 -1
- package/docs-site/en/benchmark.html +1 -1
- package/docs-site/en/commands.html +1 -1
- package/docs-site/en/context-engine.html +1 -1
- package/docs-site/en/ecosystem.html +1 -1
- package/docs-site/en/index.html +5 -5
- package/docs-site/en/mcp-approval.html +1 -1
- package/docs-site/en/mcp-auth.html +1 -1
- package/docs-site/en/mcp-servers.html +1 -1
- package/docs-site/en/mcp.html +1 -1
- package/docs-site/en/permissions.html +1 -1
- package/docs-site/en/profiles.html +1 -1
- package/docs-site/en/quickstart.html +1 -1
- package/docs-site/en/runtime.html +1 -1
- package/docs-site/en/scope.html +1 -1
- package/docs-site/en/security.html +1 -1
- package/docs-site/en/team.html +1 -1
- package/docs-site/en/whats-new.html +13 -1
- package/docs-site/en/windows.html +1 -1
- package/docs-site/en/workflows.html +1 -1
- package/docs-site/index.html +5 -5
- package/docs-site/mcp-approval.html +1 -1
- package/docs-site/mcp-auth.html +1 -1
- package/docs-site/mcp-servers.html +1 -1
- package/docs-site/mcp.html +1 -1
- package/docs-site/permissions.html +1 -1
- package/docs-site/profiles.html +1 -1
- package/docs-site/quickstart.html +1 -1
- package/docs-site/runtime.html +1 -1
- package/docs-site/scope.html +1 -1
- package/docs-site/security.html +1 -1
- package/docs-site/team.html +1 -1
- package/docs-site/whats-new.html +13 -1
- package/docs-site/windows.html +1 -1
- package/docs-site/workflows.html +1 -1
- package/evals/private-holdout-v1/public-exposure.v2.json +2 -2
- package/package.json +1 -1
- package/packages/piagent-core/managed/helper-run.mjs +2 -2
- package/packages/piagent-core/managed/memory-lessons.mjs +63 -0
- package/packages/piagent-core/managed/memory-outbox.mjs +93 -0
- package/packages/piagent-core/managed/memory.mjs +258 -89
- package/packages/piagent-core/managed/session.mjs +7 -7
- package/packages/piagent-core/package.json +1 -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.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.28.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>
|
package/docs-site/en/team.html
CHANGED
|
@@ -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.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.28.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.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.28.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,18 @@
|
|
|
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.28.0 · 2026-10-17</p>
|
|
109
|
+
<h2 id="v1-28-0">Team memory v2: each round once, a fresh brief after compaction</h2>
|
|
110
|
+
<p class="section-lead">With Agent Studio 0.28.0, each company round has its own ID and is written to an outbox before it is sent, so a lost acknowledgement, a crash or two sessions at once never extract it twice. After compaction the agent gets a fresh brief for the latest request. A round carries up to three observed “check failed → change → passed” recoveries (no guessed cause). An empty brief is not asked again every round, a round closes within 4.5 seconds when the engine does not answer, and on Studio 0.27.x memory still grows.</p>
|
|
111
|
+
</section>
|
|
112
|
+
|
|
113
|
+
<section class="section">
|
|
114
|
+
<p class="section-kicker">v1.27.1 · 2026-10-17</p>
|
|
115
|
+
<h2 id="v1-27-1">Team memory keeps working while the agent calls the model</h2>
|
|
116
|
+
<p class="section-lead">A model request renews its run token and voids the one before, so the commits sent right after the brief, and a <span class="inline-code">memory_search</span> beside a request, were refused by Studio (401): the repository’s commits never reached memory. A memory call refused this way now renews the token (shared with that role’s requests, as web search does) and asks once more. Needs Agent Studio 0.27.1 (0.27.0 was withdrawn).</p>
|
|
117
|
+
</section>
|
|
118
|
+
|
|
107
119
|
<section class="section">
|
|
108
120
|
<p class="section-kicker">v1.27.0 · 2026-10-17</p>
|
|
109
121
|
<h2 id="v1-27-0">Team memory: company agents remember what the team settled in each repository</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.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.28.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.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.28.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>
|
package/docs-site/index.html
CHANGED
|
@@ -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.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.28.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.
|
|
97
|
+
<p class="eyebrow">Piagent 1.28.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.
|
|
111
|
+
<a class="button" href="/whats-new">Có gì mới trong 1.28.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.
|
|
187
|
+
<span class="card-kicker">Bản 1.28.0</span>
|
|
188
188
|
<h3>Có gì mới</h3>
|
|
189
|
-
<p>Team memory: agent công ty nhớ quyết định và quy tắc của team trong từng repo; ở 1.26.1: đổi tên cuộc trò chuyện được cả khi lượt đang chạy; ở 1.26.0: lệnh / và phím tắt của Terminal ngay trên dashboard, /undo hoàn tác lượt trước của agent, dev server chạy qua nhiều lượt; ở 1.25.0: thiếu môi trường thì agent xin quyền và chạy được sau khi bạn duyệt (run_on_host ngoài sandbox, start_docker mở Docker ngay trong cuộc trò chuyện); ở 1.24.0: chọn Fast ngay trên trang tạo cuộc trò chuyện công ty, nút Fast đổi màu vàng khi đang bật; ở 1.23.1: câu hỏi của agent luôn nằm cuối cuộc trò chuyện, ngay trên ô nhập, và dễ đọc hơn; ở 1.23.0: nút Fast cho cuộc trò chuyện công ty khi Harness cho phép, tool lỗi được báo về Studio (Logs → Tool lỗi), cuộc trò chuyện mới gửi được tin nhắn đầu sau khi Harness đổi; ở 1.22.1: dashboard không còn quay mãi khi project nằm trên ổ Windows nhìn từ WSL (/mnt/d…) hoặc phiên công ty không trả lời; ở 1.22.0: đính kèm được mọi file code và văn bản (.html, .css, .ts, .py, Dockerfile…); ở 1.21.1: bật Bypass được cả khi lượt đang chạy và được giữ sau khi tải lại; ở 1.21.0: 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>
|
|
189
|
+
<p>Team memory v2: mỗi lượt vào memory đúng một lần, tóm tắt lại sau compaction, ghi nhận check fail → pass; ở 1.27.1: team memory chạy ổn cả khi agent đang gọi model; ở 1.27.0: team memory, agent công ty nhớ quyết định và quy tắc của team trong từng repo; ở 1.26.1: đổi tên cuộc trò chuyện được cả khi lượt đang chạy; ở 1.26.0: lệnh / và phím tắt của Terminal ngay trên dashboard, /undo hoàn tác lượt trước của agent, dev server chạy qua nhiều lượt; ở 1.25.0: thiếu môi trường thì agent xin quyền và chạy được sau khi bạn duyệt (run_on_host ngoài sandbox, start_docker mở Docker ngay trong cuộc trò chuyện); ở 1.24.0: chọn Fast ngay trên trang tạo cuộc trò chuyện công ty, nút Fast đổi màu vàng khi đang bật; ở 1.23.1: câu hỏi của agent luôn nằm cuối cuộc trò chuyện, ngay trên ô nhập, và dễ đọc hơn; ở 1.23.0: nút Fast cho cuộc trò chuyện công ty khi Harness cho phép, tool lỗi được báo về Studio (Logs → Tool lỗi), cuộc trò chuyện mới gửi được tin nhắn đầu sau khi Harness đổi; ở 1.22.1: dashboard không còn quay mãi khi project nằm trên ổ Windows nhìn từ WSL (/mnt/d…) hoặc phiên công ty không trả lời; ở 1.22.0: đính kèm được mọi file code và văn bản (.html, .css, .ts, .py, Dockerfile…); ở 1.21.1: bật Bypass được cả khi lượt đang chạy và được giữ sau khi tải lại; ở 1.21.0: 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.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.28.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>
|
package/docs-site/mcp-auth.html
CHANGED
|
@@ -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.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.28.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.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.28.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>
|
package/docs-site/mcp.html
CHANGED
|
@@ -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.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.28.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.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.28.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>
|
package/docs-site/profiles.html
CHANGED
|
@@ -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.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.28.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.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.28.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>
|
package/docs-site/runtime.html
CHANGED
|
@@ -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.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.28.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>
|
package/docs-site/scope.html
CHANGED
|
@@ -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.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.28.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>
|
package/docs-site/security.html
CHANGED
|
@@ -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.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.28.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>
|
package/docs-site/team.html
CHANGED
|
@@ -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.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.28.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>
|
package/docs-site/whats-new.html
CHANGED
|
@@ -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.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.28.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,18 @@
|
|
|
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.28.0 · 2026-10-17</p>
|
|
109
|
+
<h2 id="v1-28-0">Team memory v2: ghi mỗi lượt đúng một lần, tóm tắt lại sau compaction</h2>
|
|
110
|
+
<p class="section-lead">Với Agent Studio 0.28.0, mỗi lượt của cuộc trò chuyện công ty có mã riêng và được ghi vào outbox trước khi gửi, nên mất phản hồi, máy tắt ngang hay hai phiên cùng lúc cũng không làm memory rút kiến thức hai lần. Sau compaction, agent nhận lại bản tóm tắt mới cho yêu cầu gần nhất. Mỗi lượt kèm tối đa ba lần “check fail → sửa → pass” quan sát được (không đoán nguyên nhân). Bản tóm tắt rỗng không bị hỏi lại mỗi lượt, cuối lượt chờ tối đa 4,5 giây khi engine không trả lời, và trên Studio 0.27.x memory vẫn được ghi.</p>
|
|
111
|
+
</section>
|
|
112
|
+
|
|
113
|
+
<section class="section">
|
|
114
|
+
<p class="section-kicker">v1.27.1 · 2026-10-17</p>
|
|
115
|
+
<h2 id="v1-27-1">Team memory chạy ổn cả khi agent đang gọi model</h2>
|
|
116
|
+
<p class="section-lead">Một lời gọi model làm mới token của run và huỷ token cũ, nên các commit gửi ngay sau bản tóm tắt và lệnh <span class="inline-code">memory_search</span> chạy cạnh một request bị Studio từ chối (401): commit của repo không vào được memory. Giờ lời gọi memory bị từ chối như vậy sẽ làm mới token (dùng chung với các request của role đó, như web search) rồi gửi lại một lần. Cần Agent Studio 0.27.1 (bản 0.27.0 đã rút lại).</p>
|
|
117
|
+
</section>
|
|
118
|
+
|
|
107
119
|
<section class="section">
|
|
108
120
|
<p class="section-kicker">v1.27.0 · 2026-10-17</p>
|
|
109
121
|
<h2 id="v1-27-0">Team memory: agent công ty nhớ những gì team đã chốt trong từng repo</h2>
|
package/docs-site/windows.html
CHANGED
|
@@ -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.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.28.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>
|
package/docs-site/workflows.html
CHANGED
|
@@ -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.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.28.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":
|
|
260
|
-
"sha256": "
|
|
259
|
+
"fileCount": 552,
|
|
260
|
+
"sha256": "138f8245842ea6ca59fcab4650aecab6d6c94b9e5c262438afb55a1cb4dffa61"
|
|
261
261
|
}
|
|
262
262
|
],
|
|
263
263
|
"custodianComparisonRules": {
|
package/package.json
CHANGED
|
@@ -13,7 +13,7 @@ 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';
|
|
15
15
|
import { trackToolFailures } from './tool-failures.mjs';
|
|
16
|
-
import { helperMemoryTools } from './memory.mjs';
|
|
16
|
+
import { helperMemoryTools, renewer } from './memory.mjs';
|
|
17
17
|
|
|
18
18
|
const textContent = result => result.content?.filter(p => p.type === 'text').map(p => p.text).join('\n') ?? '';
|
|
19
19
|
// A helper failure that another model of its role may not have: the model's
|
|
@@ -75,7 +75,7 @@ async function helperAttempt(managed, role, task, signal, harness, snapshot, par
|
|
|
75
75
|
const model = managed.installModel(runtime, grant); managed.wrapStreams(runtime, role);
|
|
76
76
|
// Studio's logs name the job by its title (the brief's first line without one).
|
|
77
77
|
(managed.jobTitles ??= new WeakMap()).set(runtime, jobTitle(task, title));
|
|
78
|
-
const setup = HELPER_SETUP[role], memory = helperMemoryTools(managed, () => grant);
|
|
78
|
+
const setup = HELPER_SETUP[role], memory = helperMemoryTools(managed, () => grant, renewer(managed, role, g => verifyGrant(g, managed.manifest, role)));
|
|
79
79
|
boundary = new ManagedToolBoundary({ cwd: managed.cwd, sdkRoot: managed.sdk, protectedRoots: managed.protectedRoots, readOnly: true, commands: setup.commands });
|
|
80
80
|
const checks = role === 'verify' ? `\n\nRepository checks: ${managed.checks.commands.length ? managed.checks.commands.map(c => '`' + c + '`').join(', ') : 'none declared or detected; choose the tests, type check or build that cover the claims'}.` : '';
|
|
81
81
|
// One helper session answers the brief (with one part of the patch for a
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Evidence-only lessons from main-agent checks. No model call, raw tool log,
|
|
2
|
+
// or guessed root cause. A passing rerun needs an intervening edit/runner
|
|
3
|
+
// change; otherwise a flaky check would become a supposed fix.
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import { createHash } from 'node:crypto';
|
|
6
|
+
import { redactedText } from '../runtime/managed-failure.mjs';
|
|
7
|
+
import { isCheckCommand } from './workflow.mjs';
|
|
8
|
+
|
|
9
|
+
const COMMAND_TOOLS = new Set(['bash','run_with_network','run_with_docker','run_on_host']);
|
|
10
|
+
const textOf = result => (result?.content ?? []).filter(p => p?.type === 'text').map(p => p.text ?? '').join('\n');
|
|
11
|
+
const clean = (value, max) => redactedText(String(value ?? '')).replace(/\bas_(?:live|run)_[A-Za-z0-9_-]{8,}/g,'[REDACTED_SECRET]').split(os.homedir()).join('~').replace(/\s+/g,' ').trim().slice(0,max);
|
|
12
|
+
const hash = s => createHash('sha256').update(s).digest('hex').slice(0,20);
|
|
13
|
+
function check(command, declared) {
|
|
14
|
+
const raw=String(command ?? '').trim();
|
|
15
|
+
// Simple check commands only. Permit one explicit working-directory prefix;
|
|
16
|
+
// no pipelines, redirected results, fallback exits or shell substitution.
|
|
17
|
+
const body=raw.replace(/^cd\s+(?:"[\w ./-]+"|'[\w ./-]+'|[\w./-]+)\s*&&\s*/, '');
|
|
18
|
+
if (/[\n\r;|&<>`$]/.test(body) || /(?:skip|only|grep|testNamePattern|testPathPattern|--filter|--list|dry.run|--passWithNoTests|--watch|--collect-only|--exclude|-Dtest=)/i.test(body)) return null;
|
|
19
|
+
if (!isCheckCommand(body,declared) && !/^(?:\.\/)?mvnw?\s+(?:[^;&]*\s)?(?:test|verify)\b/.test(body)) return null;
|
|
20
|
+
return { command: clean(raw,220), key: hash(raw), family: hash(raw.replace(/(?:^|\s)-P[\w,.-]+/g,'')) };
|
|
21
|
+
}
|
|
22
|
+
function total(output) { const m=/(?:Tests run:\s*|\btests\s+)(\d+)/i.exec(output);return m?Number(m[1]):null; }
|
|
23
|
+
function passed(output) {
|
|
24
|
+
const withoutZeros=output.replace(/\b(?:fail(?:ed|ures?)?|errors?|skipped)\s*[:=]?\s*0\b|\b0\s+(?:failed|failures?|errors?|skipped)\b/gi,'');
|
|
25
|
+
if(/\b(?:FAIL(?:ED|URES?)?|ERRORS?|SKIPPED)\b/i.test(withoutZeros)) return false;
|
|
26
|
+
if (/\b(?:[1-9]\d*\s+(?:skipped|failed)|(?:skip|skipped|fail|failed)\s*[:=]?\s*[1-9]\d*|no tests|tests? (?:were )?not (?:run|found)|collected 0|aborted|cancelled|timed out)\b/i.test(output)) return false;
|
|
27
|
+
return /(?:\bPASS\b|\b[1-9]\d* (?:passed|passing)\b|\bTests run:\s*[1-9]\d*,\s*Failures:\s*0,\s*Errors:\s*0|\bpass\s+[1-9]\d*|BUILD SUCCESS|Build succeeded|\bok\s+\S+)/i.test(output);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class CheckLessons {
|
|
31
|
+
constructor(declared = [], now = Date.now) { this.declared=declared;this.now=now;this.calls=new Map();this.failures=new Map();this.edits=[];this.lessons=[];this.failed=0;this.passed=0; }
|
|
32
|
+
event(event) {
|
|
33
|
+
if(event?.type==='tool_execution_start') {
|
|
34
|
+
if(this.calls.size>=32)this.calls.delete(this.calls.keys().next().value);
|
|
35
|
+
this.calls.set(event.toolCallId,{tool:event.toolName,check:COMMAND_TOOLS.has(event.toolName)?check(event.args?.command,this.declared):null,path:clean(event.args?.path,120)});return;
|
|
36
|
+
}
|
|
37
|
+
if(event?.type!=='tool_execution_end')return;
|
|
38
|
+
const call=this.calls.get(event.toolCallId);this.calls.delete(event.toolCallId);if(!call)return;
|
|
39
|
+
if(['write','edit'].includes(call.tool)&&!event.isError){this.edits.push(call.path);if(this.edits.length>100)this.edits.shift();return;}
|
|
40
|
+
if(!call.check)return;
|
|
41
|
+
const output=textOf(event.result), c=call.check;
|
|
42
|
+
if(event.isError || (event.result?.details?.exitCode !== undefined && event.result.details.exitCode!==0)) {
|
|
43
|
+
if(/managed[-_](?:tool[-_]cancelled|operation[-_]denied|helper[-_]cancelled)|Command aborted|Command timed out/i.test(output))return;
|
|
44
|
+
this.failed++;
|
|
45
|
+
const diagnostic=output.split('\n').find(l=>/error|fail|exception|not found|EADDRINUSE/i.test(l)) ?? 'Check exited unsuccessfully';
|
|
46
|
+
if(this.failures.size>=20)this.failures.delete(this.failures.keys().next().value);
|
|
47
|
+
this.failures.set(c.key,{...c,tool:call.tool,id:event.toolCallId,error:clean(diagnostic,180),edits:this.edits.length,total:total(output)});return;
|
|
48
|
+
}
|
|
49
|
+
if(!passed(output))return;
|
|
50
|
+
this.passed++;
|
|
51
|
+
let prior=this.failures.get(c.key);
|
|
52
|
+
// A Maven profile change may alter coverage: accept it only when both
|
|
53
|
+
// executions explicitly report the same positive test count.
|
|
54
|
+
if(!prior)prior=[...this.failures.values()].find(f=>f.family===c.family&&f.total>0&&f.total===total(output));
|
|
55
|
+
if(!prior || this.lessons.length>=3 || (prior.total>0 && total(output)!==null && total(output)<prior.total))return;
|
|
56
|
+
const files=this.edits.slice(prior.edits), changedRunner=prior.tool!==call.tool, changedCommand=prior.command!==c.command;
|
|
57
|
+
if(!files.length&&!changedRunner&&!changedCommand)return;
|
|
58
|
+
const fixed=files.length?`edited ${[...new Set(files)].slice(0,3).join(', ')}`:changedRunner?`changed runner from ${prior.tool} to ${call.tool}`:'changed the command profile (same reported test count)';
|
|
59
|
+
const line=clean(`Observed check recovery: ${prior.command} failed (${prior.error}); after ${fixed}, ${c.command} passed via ${call.tool}. Root cause not established; verify against current code.`,900);
|
|
60
|
+
this.lessons.push({line,fingerprint:hash(prior.command+'\0'+prior.error),failed_call:prior.id,passed_call:event.toolCallId,at:new Date(this.now()).toISOString()});
|
|
61
|
+
this.failures.delete(prior.key);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// Each event is its own durable file. The scope includes the source key and
|
|
2
|
+
// Studio authority; old unscoped JSONL files are deliberately never replayed.
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
6
|
+
|
|
7
|
+
const UUID = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i;
|
|
8
|
+
const LIMIT = 2 * 1024 * 1024, LEASE_MS = 60_000;
|
|
9
|
+
export function memoryScope(origin, manifest) {
|
|
10
|
+
const a = manifest?.authority;
|
|
11
|
+
if (!manifest?.key_id || !manifest?.user?.id || !a?.studio_instance_id || !a?.dataset_epoch || !Number.isInteger(a.auth_generation)) return null;
|
|
12
|
+
// Agent Watch encodes UUIDs in upper case; Studio's are lower case.
|
|
13
|
+
return { origin, key_id: String(manifest.key_id).toLowerCase(), user_id: String(manifest.user.id).toLowerCase(), studio_instance_id: a.studio_instance_id, dataset_epoch: a.dataset_epoch, auth_generation: a.auth_generation };
|
|
14
|
+
}
|
|
15
|
+
export const scopeID = scope => scope ? createHash('sha256').update(JSON.stringify(scope)).digest('hex') : null;
|
|
16
|
+
|
|
17
|
+
function syncDirectory(directory) {
|
|
18
|
+
// Windows does not expose POSIX directory fsync through Node. The file is
|
|
19
|
+
// still flushed before the atomic rename; server deduplication covers replay.
|
|
20
|
+
if (process.platform === 'win32') return;
|
|
21
|
+
const fd = fs.openSync(directory, 'r'); try { fs.fsyncSync(fd); } finally { fs.closeSync(fd); }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function atomic(file, value) {
|
|
25
|
+
const tmp = `${file}.${randomUUID()}.tmp`;
|
|
26
|
+
let fd;
|
|
27
|
+
try {
|
|
28
|
+
fd = fs.openSync(tmp, 'wx', 0o600); fs.writeFileSync(fd, JSON.stringify(value)); fs.fsyncSync(fd); fs.closeSync(fd); fd = null;
|
|
29
|
+
fs.renameSync(tmp, file);
|
|
30
|
+
// Persist the directory entry as well as the payload (POSIX/WSL).
|
|
31
|
+
syncDirectory(path.dirname(file));
|
|
32
|
+
} finally { if (fd !== null && fd !== undefined) fs.closeSync(fd); try { fs.rmSync(tmp, { force: true }); } catch {} }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class MemoryOutbox {
|
|
36
|
+
constructor(stateDir, scope, now = Date.now) {
|
|
37
|
+
this.scope = scopeID(scope); this.now = now; this.quarantined = 0;
|
|
38
|
+
this.directory = stateDir && this.scope ? path.join(stateDir, 'memory-outbox-v2', this.scope) : null;
|
|
39
|
+
}
|
|
40
|
+
files() { try { return fs.readdirSync(this.directory).filter(n => UUID.test(n.replace(/\.json$/, '')) && n.endsWith('.json')).sort(); } catch { return []; } }
|
|
41
|
+
put(item) {
|
|
42
|
+
if (!this.directory || !UUID.test(item.event_id)) return false;
|
|
43
|
+
try {
|
|
44
|
+
fs.mkdirSync(this.directory, { recursive: true, mode: 0o700 });
|
|
45
|
+
const file = path.join(this.directory, `${item.event_id}.json`);
|
|
46
|
+
if (fs.existsSync(file)) return JSON.stringify(JSON.parse(fs.readFileSync(file, 'utf8')).item) === JSON.stringify(item);
|
|
47
|
+
const size = fs.readdirSync(this.directory).filter(n => !n.endsWith('.lock')).reduce((n, f) => { try { return n + fs.statSync(path.join(this.directory, f)).size; } catch { return n; } }, 0);
|
|
48
|
+
if (size + Buffer.byteLength(JSON.stringify(item)) > LIMIT) return false;
|
|
49
|
+
// link is exclusive: concurrent producers never overwrite an event.
|
|
50
|
+
const tmp = path.join(this.directory, `${randomUUID()}.tmp`);
|
|
51
|
+
try {
|
|
52
|
+
atomic(tmp, { scope: this.scope, item, attempts: 0, next_at: 0 });
|
|
53
|
+
fs.linkSync(tmp, file);
|
|
54
|
+
syncDirectory(this.directory);
|
|
55
|
+
} finally { try { fs.rmSync(tmp, { force: true }); } catch {} }
|
|
56
|
+
return true;
|
|
57
|
+
} catch { return false; }
|
|
58
|
+
}
|
|
59
|
+
// A crashed consumer leaves the event intact; the short claim expires.
|
|
60
|
+
claim(name) {
|
|
61
|
+
if (!this.directory || !this.files().includes(name)) return null;
|
|
62
|
+
const file = path.join(this.directory, name), lock = `${file}.lock`;
|
|
63
|
+
let owned = false;
|
|
64
|
+
try {
|
|
65
|
+
try { fs.mkdirSync(lock, { mode: 0o700 }); }
|
|
66
|
+
catch {
|
|
67
|
+
if (this.now() - fs.statSync(lock).mtimeMs < LEASE_MS) return null;
|
|
68
|
+
fs.rmdirSync(lock); fs.mkdirSync(lock, { mode: 0o700 });
|
|
69
|
+
}
|
|
70
|
+
owned = true;
|
|
71
|
+
let record;
|
|
72
|
+
try { record = JSON.parse(fs.readFileSync(file, 'utf8')); }
|
|
73
|
+
catch { try { fs.renameSync(file, `${file}.quarantine`); this.quarantined++; } catch {} fs.rmdirSync(lock); return null; }
|
|
74
|
+
if (record.scope !== this.scope || !UUID.test(record.item?.event_id) || name !== `${record.item.event_id}.json`) {
|
|
75
|
+
fs.renameSync(file, `${file}.quarantine`); this.quarantined++; fs.rmdirSync(lock); return null;
|
|
76
|
+
}
|
|
77
|
+
if (record.attempts >= 30 || this.now() - Date.parse(record.item.occurred_at) > 30 * 86400_000) { fs.renameSync(file, `${file}.quarantine`); this.quarantined++; fs.rmdirSync(lock); return null; }
|
|
78
|
+
if (record.next_at > this.now()) { fs.rmdirSync(lock); return null; }
|
|
79
|
+
return { file, lock, record };
|
|
80
|
+
} catch { if (owned) try { fs.rmdirSync(lock); } catch {} return null; }
|
|
81
|
+
}
|
|
82
|
+
finish(claim, outcome) {
|
|
83
|
+
try {
|
|
84
|
+
if (outcome === 'ack' || outcome === 'discard') fs.rmSync(claim.file, { force: true });
|
|
85
|
+
else if (outcome === 'quarantine') fs.renameSync(claim.file, `${claim.file}.quarantine`);
|
|
86
|
+
else {
|
|
87
|
+
const attempts = (claim.record.attempts || 0) + 1;
|
|
88
|
+
atomic(claim.file, { ...claim.record, attempts, next_at: this.now() + Math.min(300_000, 5_000 * 2 ** Math.min(attempts - 1, 6)) });
|
|
89
|
+
}
|
|
90
|
+
syncDirectory(this.directory);
|
|
91
|
+
} finally { try { fs.rmdirSync(claim.lock); } catch {} }
|
|
92
|
+
}
|
|
93
|
+
}
|