@piagent/platform 1.35.0 → 1.36.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 +32 -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/repository-memory.js +27 -2
- package/packages/piagent-core/managed/continuation.mjs +23 -2
- package/packages/piagent-core/managed/memory-lessons.mjs +34 -6
- package/packages/piagent-core/managed/memory-serves.mjs +17 -0
- package/packages/piagent-core/managed/memory.mjs +12 -10
- package/packages/piagent-core/managed/request-stream.mjs +8 -2
- package/packages/piagent-core/managed/skill-feedback.mjs +79 -0
- package/packages/piagent-core/managed/studio-link.mjs +18 -0
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/runtime/managed-failure.mjs +8 -0
- package/packages/piagent-webui/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.36.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>
|
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.36.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.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.36.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.36.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.36.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.36.0 · 2026-10-21</p>
|
|
109
|
+
<h2 id="v1-36-0">Team memory learns from what went wrong</h2>
|
|
110
|
+
<p class="section-lead">What memory handed a round (briefs, <code>memory_search</code> answers) goes back to Studio with that round, so Studio rates it by the member's next words and stops handing over notes members said were wrong (needs Agent Studio 0.37.0). A command that fails the same way twice in a round becomes a lesson; repository memory skips tasks that add nothing new; skills that keep ending rounds badly are proposed for review in <code>.pi/piagent-state/skill-proposals</code>, and Piagent never edits a skill itself.</p>
|
|
111
|
+
</section>
|
|
112
|
+
|
|
113
|
+
<section class="section">
|
|
114
|
+
<p class="section-kicker">v1.35.1 · 2026-10-21</p>
|
|
115
|
+
<h2 id="v1-35-1">A passing model provider error no longer stops a long task</h2>
|
|
116
|
+
<p class="section-lead">When the model's server (GPT, Claude) reports an error in the middle of an answer, Piagent asks again a few seconds later; if the whole round is cut, the next one starts by itself after a pause (at most 8 times, about 23 minutes per message of yours), the agent is told why and goes on from where it was. Each ask again is written to the log on the machine.</p>
|
|
117
|
+
</section>
|
|
118
|
+
|
|
107
119
|
<section class="section">
|
|
108
120
|
<p class="section-kicker">v1.35.0 · 2026-10-21</p>
|
|
109
121
|
<h2 id="v1-35-0">Long tasks survive a Studio update</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.36.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.36.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.36.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.36.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.36.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.36.0</span>
|
|
188
188
|
<h3>Có gì mới</h3>
|
|
189
|
-
<p>
|
|
189
|
+
<p>Team memory học từ chỗ làm sai: Studio chấm những gì memory đã đưa, lệnh lỗi lặp lại thành bài học; ở 1.35: task dài không dừng khi Studio cập nhật hay model báo lỗi tạm thời; ở 1.34: helper báo kết quả, Agent Watch báo khi cần bạn; ở 1.33.0: chạy trên Pi 1.3.0; ở 1.32.1: Bypass được giữ sau khi tải lại, tab đang dùng không còn bị đăng xuất sau 8 giờ; ở 1.32.0: lượt lỗi hoặc bấm Stop được dừng cả ở Studio; ở 1.31.1: <code>memory_ask</code> có ngay sau khi cập nhật; ở 1.31.0: Memory của team đi theo branch và luật của team, có <code>memory_ask</code>; ở 1.30.1: Terminal công ty không còn hiện tên model, mở <code>piagent studio</code> ở thư mục home báo rõ cần vào thư mục dự án; ở 1.30.0: context lớn đi qua mạng chậm, Terminal công ty gọn như dashboard; ở 1.29.0: Terminal công ty có giao diện riêng, dừng lượt không còn làm hỏng cuộc trò chuyện; ở 1.28.3: Agent Watch tự cập nhật không còn làm gián đoạn Terminal công ty; ở 1.28.2: không còn lỗi “binding changed” ngay sau khi cập nhật; ở 1.28.1: 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.36.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.36.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.36.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.36.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.36.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.36.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.36.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.36.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.36.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.36.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.36.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.36.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.36.0 · 2026-10-21</p>
|
|
109
|
+
<h2 id="v1-36-0">Team memory học từ chỗ làm sai</h2>
|
|
110
|
+
<p class="section-lead">Những gì memory đưa cho một lượt (brief, kết quả <code>memory_search</code>) được gửi lại Studio cùng lượt đó, để Studio chấm theo câu trả lời tiếp theo của member và thôi đưa những ghi chú member nói là sai (cần Agent Studio 0.37.0). Một lệnh lỗi giống nhau hai lần trong một lượt thành bài học; memory của repo bỏ qua task không thêm gì mới; skill hay làm hỏng lượt được đề xuất xem lại trong <code>.pi/piagent-state/skill-proposals</code>, Piagent không tự sửa skill.</p>
|
|
111
|
+
</section>
|
|
112
|
+
|
|
113
|
+
<section class="section">
|
|
114
|
+
<p class="section-kicker">v1.35.1 · 2026-10-21</p>
|
|
115
|
+
<h2 id="v1-35-1">Lỗi tạm thời của nhà cung cấp model không còn làm dừng task dài</h2>
|
|
116
|
+
<p class="section-lead">Khi máy chủ của model (GPT, Claude) báo lỗi ngay giữa câu trả lời, Piagent hỏi lại sau vài giây; nếu cả vòng bị cắt, vòng sau tự bắt đầu sau một lúc chờ (tối đa 8 lần, khoảng 23 phút cho mỗi tin nhắn của bạn), agent được báo lý do và làm tiếp từ chỗ dở. Mỗi lần hỏi lại được ghi vào log trên máy.</p>
|
|
117
|
+
</section>
|
|
118
|
+
|
|
107
119
|
<section class="section">
|
|
108
120
|
<p class="section-kicker">v1.35.0 · 2026-10-21</p>
|
|
109
121
|
<h2 id="v1-35-0">Task dài chạy qua được lúc Studio cập nhật</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.36.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.36.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": 567,
|
|
260
|
+
"sha256": "3d50d7636c175e88a1ec7cbd491086417b26bb4a157e5d0261b3adaf6b36d35b"
|
|
261
261
|
}
|
|
262
262
|
],
|
|
263
263
|
"custodianComparisonRules": {
|
package/package.json
CHANGED
|
@@ -105,13 +105,38 @@ export function appendRepositoryMemoryFact(cwd, input, options = {}) {
|
|
|
105
105
|
return record;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
// Files whose change says nothing durable about the repository: build
|
|
109
|
+
// output, caches, logs, snapshots and Piagent's own state.
|
|
110
|
+
const DISPOSABLE_PATH = /(?:^|\/)(?:tmp|temp|node_modules|dist|build|coverage|test-results|\.pi|\.cache|\.next|out)\/|\.(?:log|snap|tmp)$/i;
|
|
111
|
+
|
|
112
|
+
// A completed task is written only when it adds something (after Cognee's
|
|
113
|
+
// session distillation): "not_durable" when every changed file is disposable,
|
|
114
|
+
// "already_known" when an unexpired record already cites the same files at
|
|
115
|
+
// the same digests. Returns the reason to skip, or null.
|
|
116
|
+
export function completedTaskSkipReason(cwd, citations, options = {}) {
|
|
117
|
+
const durable = citations.filter((citation) => !DISPOSABLE_PATH.test(normalizePath(citation.path)));
|
|
118
|
+
if (durable.length === 0) return "not_durable";
|
|
119
|
+
// Digests as they are stored: only a current working-tree digest is kept.
|
|
120
|
+
const key = (list) => list.map((citation) => `${normalizePath(citation.path)}@${isCurrentWorkingTreeDigest(citation.digest) ? citation.digest : ""}`).sort().join("\n");
|
|
121
|
+
const wanted = key(durable);
|
|
122
|
+
const known = readRepositoryMemoryFacts(cwd, { limit: 200, now: options.now })
|
|
123
|
+
.some((record) => record.kind === "retrieval-feedback" && key(record.citations.filter((citation) => !DISPOSABLE_PATH.test(citation.path))) === wanted);
|
|
124
|
+
return known ? "already_known" : null;
|
|
125
|
+
}
|
|
126
|
+
|
|
108
127
|
export function recordCompletedTaskMemory(cwd, task, options = {}) {
|
|
109
|
-
const
|
|
128
|
+
const all = (Array.isArray(task?.changedFiles) ? task.changedFiles : []).map((file) => ({
|
|
110
129
|
path: file,
|
|
111
130
|
reason: "Changed file verified by the task completion gate.",
|
|
112
131
|
digest: task?.finalFileDigests?.[file]
|
|
113
132
|
}));
|
|
114
|
-
if (
|
|
133
|
+
if (all.length === 0) return undefined;
|
|
134
|
+
const skip = completedTaskSkipReason(cwd, all, { now: options.now });
|
|
135
|
+
if (skip) {
|
|
136
|
+
options.onSkip?.(skip);
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
const citations = all.filter((citation) => !DISPOSABLE_PATH.test(normalizePath(citation.path))).slice(0, 8);
|
|
115
140
|
const nowMs = Number.isFinite(options.nowMs) ? options.nowMs : Date.now();
|
|
116
141
|
return appendRepositoryMemoryFact(cwd, {
|
|
117
142
|
kind: "retrieval-feedback",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { checkpointsOf } from './checkpoints.mjs';
|
|
2
2
|
import { unfinished, planText, currentPlan, workflowPolicy } from './workflow.mjs';
|
|
3
3
|
import { failureCode } from '../runtime/managed-failure.mjs';
|
|
4
|
-
import { resumeMessage } from './studio-link.mjs';
|
|
4
|
+
import { providerResumeMessage, resumeMessage } from './studio-link.mjs';
|
|
5
5
|
|
|
6
6
|
// A main agent often ends its turn between the steps of its own checklist
|
|
7
7
|
// ("STEP06 done, STEP07 next"), though the member asked for all of them.
|
|
@@ -15,6 +15,14 @@ export const MAX_ROUNDS = 30;
|
|
|
15
15
|
// How the member's message ended, shown under the last answer.
|
|
16
16
|
export const TURN_END_ENTRY = 'agent-watch-turn-end';
|
|
17
17
|
export const TURN_END_STATES = ['done', 'midway', 'failed', 'cancelled'];
|
|
18
|
+
// A round cut by a passing failure of the model provider (its error event in
|
|
19
|
+
// the middle of an answer, an answer cut or never sent) is asked again by the
|
|
20
|
+
// turn's own retry; where that retry does not run (the WebUI does not replay a
|
|
21
|
+
// round once a tool ran), the next round starts by itself after a pause: at
|
|
22
|
+
// most PROVIDER_RESUME_DELAYS_MS.length times per member message, 15 s
|
|
23
|
+
// doubling to 5 min (about 23 min in all); a round that ends well resets it.
|
|
24
|
+
export const PROVIDER_RESUME_CODES = new Set(['upstream_server_error', 'upstream_interrupted', 'upstream_incomplete', 'upstream_timeout', 'upstream_unavailable', 'connector_execution_failed', 'output_limit_exceeded']);
|
|
25
|
+
export const PROVIDER_RESUME_DELAYS_MS = [15_000, 30_000, 60_000, 120_000, 240_000, 300_000, 300_000, 300_000];
|
|
18
26
|
|
|
19
27
|
const completed = plan => plan?.plan?.filter(p => p.status === 'completed').length ?? 0;
|
|
20
28
|
const deferred = plan => plan?.plan?.filter(p => p.status === 'deferred').length ?? 0;
|
|
@@ -86,7 +94,7 @@ export async function memberTurn(self, { manager, prompt, runRound }, text, deli
|
|
|
86
94
|
await self.session.sendCustomMessage({ customType: 'agent-watch-process', content, display: true, details: { phase: 'undo', undo } }, { triggerTurn: false });
|
|
87
95
|
return;
|
|
88
96
|
}
|
|
89
|
-
let continuation = fresh(self, manager, text), result, waiting = null, ended = null;
|
|
97
|
+
let continuation = fresh(self, manager, text), result, waiting = null, ended = null, providerResumes = 0;
|
|
90
98
|
self.stopRequested = false; self.queued = [];
|
|
91
99
|
try {
|
|
92
100
|
for (let first = true; ; first = false) {
|
|
@@ -111,6 +119,19 @@ export async function memberTurn(self, { manager, prompt, runRound }, text, deli
|
|
|
111
119
|
stopped ||= self.stopRequested;
|
|
112
120
|
if (round.planUpdated) continuation.touched = true;
|
|
113
121
|
}
|
|
122
|
+
const delays = self.providerResumeDelaysMs ?? PROVIDER_RESUME_DELAYS_MS;
|
|
123
|
+
if (!failure) providerResumes = 0;
|
|
124
|
+
else if (!resume && !stopped && !self.queued.length && PROVIDER_RESUME_CODES.has(failure.code) && providerResumes < delays.length) {
|
|
125
|
+
const delay = delays[providerResumes++], conversation = manager.getSessionId?.() ?? null;
|
|
126
|
+
self.studioLink?.log({ kind: 'provider_retry', code: failure.code, attempt: providerResumes, of: delays.length, delay_ms: delay, conversation });
|
|
127
|
+
self.notify({ type: 'managed_provider_retry', code: failure.code, attempt: providerResumes, of: delays.length, delayMs: delay });
|
|
128
|
+
self.studioLink?.status(`The model provider failed while answering (${failure.code}). Going on in ${Math.round(delay / 1000)} s (${providerResumes}/${delays.length}). Esc stops.`);
|
|
129
|
+
try { await (self.studioLink ? self.studioLink.hold(delay) : new Promise(r => setTimeout(r, delay))); resume = providerResumeMessage(failure.code, providerResumes, delays.length); }
|
|
130
|
+
catch { /* stopped while waiting */ }
|
|
131
|
+
stopped ||= self.stopRequested;
|
|
132
|
+
if (round.planUpdated) continuation.touched = true;
|
|
133
|
+
} else if (PROVIDER_RESUME_CODES.has(failure.code) && providerResumes >= delays.length)
|
|
134
|
+
self.studioLink?.log({ kind: 'provider_retry', code: failure.code, outcome: 'gave_up', attempts: providerResumes, conversation: manager.getSessionId?.() ?? null });
|
|
114
135
|
const plan = currentPlan(manager), message = resume && !stopped ? resume : continuation.next({ plan, planUpdated: round.planUpdated, changed: round.changed, stopped, failed: !!failure });
|
|
115
136
|
if (self.queued.length) {
|
|
116
137
|
// The member wrote while the round closed: theirs is the next round,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Evidence-only lessons from main-agent checks. No model call, raw tool log,
|
|
2
2
|
// or guessed root cause. A passing rerun needs an intervening edit/runner
|
|
3
|
-
// change; otherwise a flaky check would become a supposed fix.
|
|
3
|
+
// change; otherwise a flaky check would become a supposed fix. A command
|
|
4
|
+
// that failed the same way twice in a round is a lesson too (after Cognee's
|
|
5
|
+
// live failure lessons): its first error line, sanitized, without a cause.
|
|
4
6
|
import os from 'node:os';
|
|
5
7
|
import { createHash } from 'node:crypto';
|
|
6
8
|
import { redactedText } from '../runtime/managed-failure.mjs';
|
|
@@ -38,19 +40,45 @@ function passed(output) {
|
|
|
38
40
|
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);
|
|
39
41
|
}
|
|
40
42
|
|
|
43
|
+
// What an error says, without what varies between runs: numbers, ids,
|
|
44
|
+
// temporary paths and quoted values.
|
|
45
|
+
export const errorShape = text => clean(text,180).replace(/\/(?:tmp|var\/folders|private)\/\S*/g,'<tmp>').replace(/\b[0-9a-f]{8,}\b/gi,'<id>').replace(/\d+/g,'N').replace(/(["'`])[^"'`]{1,80}\1/g,'$1…$1');
|
|
46
|
+
const ERROR_LINE = /error|fail|exception|not found|no such|denied|refused|cannot|unknown (?:option|flag|command)|invalid|EADDRINUSE|ENOENT|EACCES/i;
|
|
47
|
+
const MAX_LESSONS = 3;
|
|
48
|
+
|
|
41
49
|
export class CheckLessons {
|
|
42
|
-
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;this.last='none'; }
|
|
50
|
+
constructor(declared = [], now = Date.now) { this.declared=declared;this.now=now;this.calls=new Map();this.failures=new Map();this.errors=new Map();this.edits=[];this.lessons=[];this.failed=0;this.passed=0;this.last='none'; }
|
|
51
|
+
// A non-check command that failed: the second time with the same error
|
|
52
|
+
// shape it becomes a failure lesson for the team memory.
|
|
53
|
+
#toolError(call,event,output) {
|
|
54
|
+
if(!COMMAND_TOOLS.has(call.tool)||call.check||this.lessons.length>=MAX_LESSONS)return;
|
|
55
|
+
if(/managed[-_](?:tool[-_]cancelled|operation[-_]denied|helper[-_]cancelled)|Command aborted|Command timed out/i.test(output))return;
|
|
56
|
+
const line=output.split('\n').find(l=>ERROR_LINE.test(l));
|
|
57
|
+
if(!line)return;
|
|
58
|
+
const command=clean(call.command,160).split(' ')[0];
|
|
59
|
+
if(!command)return;
|
|
60
|
+
const shape=errorShape(line), key=hash(command+'\0'+shape), seen=this.errors.get(key);
|
|
61
|
+
if(!seen){if(this.errors.size>=40)this.errors.delete(this.errors.keys().next().value);this.errors.set(key,{id:event.toolCallId,command:clean(call.command,160),error:clean(line,180),done:false});return;}
|
|
62
|
+
if(seen.done)return;
|
|
63
|
+
seen.done=true;
|
|
64
|
+
this.lessons.push({kind:'tool_error',line:clean(`Observed repeated tool error: ${seen.command} failed twice in one round with "${seen.error}". Cause not established; check the command, its flags and the environment before running it again.`,900),
|
|
65
|
+
fingerprint:hash('tool\0'+command+'\0'+shape),failed_call:seen.id,passed_call:event.toolCallId,at:new Date(this.now()).toISOString()});
|
|
66
|
+
}
|
|
43
67
|
// How the round's checks ended, for memory: passed, failed or none.
|
|
44
68
|
get summary() { return { passed:this.passed, failed:this.failed, final:this.last }; }
|
|
45
69
|
event(event) {
|
|
46
70
|
if(event?.type==='tool_execution_start') {
|
|
47
71
|
if(this.calls.size>=32)this.calls.delete(this.calls.keys().next().value);
|
|
48
|
-
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;
|
|
72
|
+
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),command:COMMAND_TOOLS.has(event.toolName)?String(event.args?.command??''):''});return;
|
|
49
73
|
}
|
|
50
74
|
if(event?.type!=='tool_execution_end')return;
|
|
51
75
|
const call=this.calls.get(event.toolCallId);this.calls.delete(event.toolCallId);if(!call)return;
|
|
52
76
|
if(['write','edit'].includes(call.tool)&&!event.isError){this.edits.push(call.path);if(this.edits.length>100)this.edits.shift();return;}
|
|
53
|
-
if(!call.check)
|
|
77
|
+
if(!call.check){
|
|
78
|
+
const failed=event.isError || (event.result?.details?.exitCode !== undefined && event.result.details.exitCode!==0);
|
|
79
|
+
if(failed)this.#toolError(call,event,textOf(event.result));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
54
82
|
const output=textOf(event.result), c=call.check;
|
|
55
83
|
const exitFailed=event.isError || (event.result?.details?.exitCode !== undefined && event.result.details.exitCode!==0);
|
|
56
84
|
if(exitFailed || (c.piped && failedOutput(output))) {
|
|
@@ -66,12 +94,12 @@ export class CheckLessons {
|
|
|
66
94
|
// A Maven profile change may alter coverage: accept it only when both
|
|
67
95
|
// executions explicitly report the same positive test count.
|
|
68
96
|
if(!prior)prior=[...this.failures.values()].find(f=>f.family===c.family&&f.total>0&&f.total===total(output));
|
|
69
|
-
if(!prior || this.lessons.length>=
|
|
97
|
+
if(!prior || this.lessons.length>=MAX_LESSONS || (prior.total>0 && total(output)!==null && total(output)<prior.total))return;
|
|
70
98
|
const files=this.edits.slice(prior.edits), changedRunner=prior.tool!==call.tool, changedCommand=prior.key!==c.key;
|
|
71
99
|
if(!files.length&&!changedRunner&&!changedCommand)return;
|
|
72
100
|
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)';
|
|
73
101
|
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);
|
|
74
|
-
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()});
|
|
102
|
+
this.lessons.push({kind:'check_recovery',line,fingerprint:hash(prior.command+'\0'+prior.error),failed_call:prior.id,passed_call:event.toolCallId,at:new Date(this.now()).toISOString()});
|
|
75
103
|
this.failures.delete(prior.key);
|
|
76
104
|
}
|
|
77
105
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Studio protocol 4: a brief or memory_search names what it handed over
|
|
2
|
+
// (serve_id); the round that used it sends the ids back with its retain, so
|
|
3
|
+
// the member's next words can rate those notes. Kept per repository until
|
|
4
|
+
// the round is queued.
|
|
5
|
+
export const FEEDBACK_PROTOCOL = 4, MAX_SERVES = 20;
|
|
6
|
+
|
|
7
|
+
export class RoundServes {
|
|
8
|
+
constructor() { this.byRepo = new Map(); }
|
|
9
|
+
add(repoKey, id) {
|
|
10
|
+
if (typeof id !== 'string' || !/^[0-9a-f-]{36}$/i.test(id)) return;
|
|
11
|
+
const list = this.byRepo.get(repoKey) ?? [];
|
|
12
|
+
if (!list.includes(id) && list.length < MAX_SERVES) list.push(id);
|
|
13
|
+
this.byRepo.set(repoKey, list);
|
|
14
|
+
}
|
|
15
|
+
of(repoKey) { return this.byRepo.get(repoKey)?.slice(0, MAX_SERVES) ?? []; }
|
|
16
|
+
clear() { this.byRepo.clear(); }
|
|
17
|
+
}
|