@piagent/platform 1.27.1 → 1.28.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -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/extensions/company-session-guard.ts +36 -0
- package/packages/piagent-core/extensions/piagent-guard.ts +2 -0
- 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 +246 -93
- package/packages/piagent-core/managed/session.mjs +6 -6
- package/packages/piagent-core/package.json +1 -1
- package/scripts/company-terminal.mjs +12 -0
- package/scripts/install-global.sh +11 -0
- package/scripts/personal-pi-config.mjs +137 -0
- package/scripts/piagent-cli.mjs +8 -0
- package/scripts/piagent-studio.mjs +4 -0
- package/scripts/team-doctor.sh +4 -0
- package/scripts/update-global.mjs +9 -2
|
@@ -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.1 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>
|
package/docs-site/en/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.1 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.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.28.1 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.1 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.1 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.1 · 2026-10-18</p>
|
|
109
|
+
<h2 id="v1-28-1">The company Terminal and personal Pi stop stepping on each other</h2>
|
|
110
|
+
<p class="section-lead">Personal Pi no longer fails to start when Piagent is loaded twice (an extension pointing into another Piagent copy): <span class="inline-code">piagent-update</span> and the installer remove it (with a backup) and <span class="inline-code">piagent-doctor</span> reports it. Old Agent Watch company entries in personal Pi (<span class="inline-code">agent_watch_*</span> providers, a default model naming them) are cleaned up once the machine has a company binding. Personal Pi no longer continues a company conversation and says to open it with <span class="inline-code">piagent studio</span>; the company Terminal says how to reopen it, answers <span class="inline-code">/help</span> itself (no model request) and stops fully when stopped from outside.</p>
|
|
111
|
+
</section>
|
|
112
|
+
|
|
113
|
+
<section class="section">
|
|
114
|
+
<p class="section-kicker">v1.28.0 · 2026-10-17</p>
|
|
115
|
+
<h2 id="v1-28-0">Team memory v2: each round once, a fresh brief after compaction</h2>
|
|
116
|
+
<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>
|
|
117
|
+
</section>
|
|
118
|
+
|
|
107
119
|
<section class="section">
|
|
108
120
|
<p class="section-kicker">v1.27.1 · 2026-10-17</p>
|
|
109
121
|
<h2 id="v1-27-1">Team memory keeps working while the agent calls the model</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.1 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.1 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.1 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.1</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.1</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.1</span>
|
|
188
188
|
<h3>Có gì mới</h3>
|
|
189
|
-
<p>
|
|
189
|
+
<p>Terminal công ty và Pi cá nhân không còn giẫm chân nhau (không nạp Piagent hai lần, dọn cấu hình công ty cũ, cuộc trò chuyện công ty chỉ mở bằng piagent studio); ở 1.28.0: team memory v2; ở 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.1 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.1 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.1 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.1 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.1 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.1 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.1 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.1 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.1 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.1 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.1 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.1 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.1 · 2026-10-18</p>
|
|
109
|
+
<h2 id="v1-28-1">Terminal công ty và Pi cá nhân không còn giẫm chân nhau</h2>
|
|
110
|
+
<p class="section-lead">Pi cá nhân không còn chết khi Piagent bị nạp hai lần (extension trỏ vào một bản Piagent khác): <span class="inline-code">piagent-update</span> và installer tự gỡ (có backup), <span class="inline-code">piagent-doctor</span> báo lỗi. Các mục công ty cũ của Agent Watch trong Pi cá nhân (provider <span class="inline-code">agent_watch_*</span>, model mặc định trỏ vào đó) được dọn khi máy đã dùng binding công ty. Pi cá nhân không tiếp tục cuộc trò chuyện công ty nữa và chỉ cách mở bằng <span class="inline-code">piagent studio</span>; Terminal công ty cũng hướng dẫn mở lại đúng cách, trả lời <span class="inline-code">/help</span> tại chỗ (không gửi model), và dừng hẳn khi bị dừng từ bên ngoài.</p>
|
|
111
|
+
</section>
|
|
112
|
+
|
|
113
|
+
<section class="section">
|
|
114
|
+
<p class="section-kicker">v1.28.0 · 2026-10-17</p>
|
|
115
|
+
<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>
|
|
116
|
+
<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>
|
|
117
|
+
</section>
|
|
118
|
+
|
|
107
119
|
<section class="section">
|
|
108
120
|
<p class="section-kicker">v1.27.1 · 2026-10-17</p>
|
|
109
121
|
<h2 id="v1-27-1">Team memory chạy ổn cả khi agent đang gọi model</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.1 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.1 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": 553,
|
|
260
|
+
"sha256": "d948a7a9e0266c7f0cbd453310dd5c7498e1a664620b4696b8f845ebdea5e312"
|
|
261
261
|
}
|
|
262
262
|
],
|
|
263
263
|
"custodianComparisonRules": {
|
package/package.json
CHANGED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// A company conversation (Agent Watch's ManagedSessions store, or one that
|
|
2
|
+
// carries its run entries) belongs to `piagent studio`. Opened in personal Pi,
|
|
3
|
+
// for example through Pi's "To resume this session: pi --session-dir …", it
|
|
4
|
+
// would go on with the member's personal models, keys, extensions and
|
|
5
|
+
// settings, outside the team's Harness; personal Pi refuses it instead.
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
8
|
+
|
|
9
|
+
const COMPANY_PROVIDER = "agent_watch_managed";
|
|
10
|
+
const STORE = "/AgentWatch/ManagedSessions/";
|
|
11
|
+
|
|
12
|
+
export function isCompanySession(ctx: Pick<ExtensionContext, "sessionManager">): boolean {
|
|
13
|
+
const file = String(ctx.sessionManager?.getSessionFile?.() ?? "").split(path.sep).join("/");
|
|
14
|
+
if (file.includes(STORE)) return true;
|
|
15
|
+
try {
|
|
16
|
+
return ctx.sessionManager.getEntries().some((entry: any) => typeof entry?.customType === "string" && entry.customType.startsWith("agent-watch-run"));
|
|
17
|
+
} catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const quoted = (value: string) => /^[\w./~:-]+$/.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`;
|
|
23
|
+
export function companySessionNotice(cwd: string): string {
|
|
24
|
+
return `Đây là cuộc trò chuyện công ty: mở bằng \`piagent studio --project ${quoted(cwd)}\` rồi gõ /resume. `
|
|
25
|
+
+ "Pi cá nhân không tiếp tục nó, vì sẽ chạy bằng model, key và cấu hình cá nhân thay vì Harness của team.";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function registerCompanySessionGuard(pi: ExtensionAPI) {
|
|
29
|
+
pi.on("input", (_event, ctx) => {
|
|
30
|
+
if (ctx.model?.provider === COMPANY_PROVIDER || !isCompanySession(ctx)) return { action: "continue" };
|
|
31
|
+
const notice = companySessionNotice(ctx.cwd);
|
|
32
|
+
if (ctx.hasUI) ctx.ui.notify(notice, "error");
|
|
33
|
+
else { process.stderr.write(`${notice}\n`); process.exitCode = 1; }
|
|
34
|
+
return { action: "handled" };
|
|
35
|
+
});
|
|
36
|
+
}
|
|
@@ -24,6 +24,7 @@ import { extractShellWritePathCandidates } from "./shell-write-targets.js";
|
|
|
24
24
|
import { isProjectMutatingShellCommand } from "./readonly-inline-inspection.ts";
|
|
25
25
|
import { commandMatchesVerifyPlan, createBashResultLedger, findMatchingObservedBashResult, readObservedBashResults } from "./runtime-evidence.js";
|
|
26
26
|
import { findPackageRoot, findPlatformRoot, readJsonFile } from "./guard-io.js";
|
|
27
|
+
import { registerCompanySessionGuard } from "./company-session-guard.ts";
|
|
27
28
|
import {
|
|
28
29
|
extractDocument,
|
|
29
30
|
resolveDocumentPath,
|
|
@@ -3459,6 +3460,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
3459
3460
|
const modelSelectionProvenance = new ModelSelectionProvenanceTracker();
|
|
3460
3461
|
const serviceTierRuntime = new ServiceTierRuntime({ environmentValue: process.env.PIAGENT_FAST_MODE });
|
|
3461
3462
|
const trajectoryRuntime = new TrajectoryRuntime();
|
|
3463
|
+
registerCompanySessionGuard(pi);
|
|
3462
3464
|
|
|
3463
3465
|
pi.on("model_select", (event, ctx) => {
|
|
3464
3466
|
modelSelectionProvenance.observeModelSelection(ctx.sessionManager.getSessionId(), event.source);
|
|
@@ -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
|
+
}
|