@piagent/platform 1.49.0 → 1.50.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/docs/command-reference-vietnamese.md +2 -2
- package/docs/distribution-standard.md +6 -6
- package/docs/harness-conformance.md +3 -3
- 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/assets/search-en.json +1 -1
- package/docs-site/assets/search-vi.json +1 -1
- package/docs-site/benchmark.html +1 -1
- package/docs-site/commands.html +1 -1
- 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 +15 -15
- 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/update.html +3 -3
- package/docs-site/en/whats-new.html +56 -27
- package/docs-site/en/windows.html +1 -1
- package/docs-site/en/workflows.html +1 -1
- package/docs-site/index.html +15 -15
- 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/releases.json +97 -0
- 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/update.html +3 -3
- package/docs-site/whats-new.html +56 -27
- 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/approval-refusal.mjs +9 -0
- package/packages/piagent-core/managed/check-evidence.mjs +134 -0
- package/packages/piagent-core/managed/check-questions.mjs +32 -14
- package/packages/piagent-core/managed/command-tools.mjs +7 -3
- package/packages/piagent-core/managed/fast-mode.mjs +19 -16
- package/packages/piagent-core/managed/helper-roles.mjs +1 -1
- package/packages/piagent-core/managed/helper-run.mjs +21 -3
- package/packages/piagent-core/managed/ledger.mjs +5 -5
- package/packages/piagent-core/managed/memory-lessons.mjs +2 -2
- package/packages/piagent-core/managed/repository-operation.mjs +2 -1
- package/packages/piagent-core/managed/request-stream.mjs +2 -2
- package/packages/piagent-core/managed/session.mjs +2 -2
- package/packages/piagent-core/managed/tool-boundary.mjs +1 -1
- package/packages/piagent-core/managed/tool-failures.mjs +1 -1
- package/packages/piagent-core/managed/workflow.mjs +14 -8
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/runtime/inspection/approval-broker.ts +2 -2
- package/packages/piagent-webui/dist/client/assets/ActivityPanel-CmjxPmNT.js +1 -0
- package/packages/piagent-webui/dist/client/assets/{App-CU-wndsb.js → App-JrRb7jgn.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/GatewayApp-BmkqeXih.js +63 -0
- package/packages/piagent-webui/dist/client/assets/{InspectionApp-Bfvr2S8S.js → InspectionApp-9jBsgkVJ.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/{SessionAgentWorkspace-0Xowe-sG.js → SessionAgentWorkspace-RQ0N65d9.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/{SessionInspectorDrawer-72Z5908P.js → SessionInspectorDrawer-BWyEN4bf.js} +2 -2
- package/packages/piagent-webui/dist/client/assets/{SettingsNavigation-Bu1A7x4F.js → SettingsNavigation-Wx-q5QQk.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/{SettingsPage-DxGxE0AF.js → SettingsPage-xAt6kSyZ.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/{TerminalPanel-BKgx9SjR.js → TerminalPanel-DzHdSjpN.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/{WorkspaceCodeEditor-DFrh3qs2.js → WorkspaceCodeEditor-CEuPTTBd.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/{WorkspaceExplorer-D6ufO-eP.js → WorkspaceExplorer-B_NwYf4S.js} +3 -3
- package/packages/piagent-webui/dist/client/assets/{index-CjM70M7D.css → index-De7bIeMc.css} +1 -1
- package/packages/piagent-webui/dist/client/assets/{index-C3CJv1wM.js → index-fUpioGMZ.js} +2 -2
- package/packages/piagent-webui/dist/client/index.html +2 -2
- package/packages/piagent-webui/gateway/gateway-session-stream.ts +28 -0
- package/packages/piagent-webui/gateway/managed-projection.ts +8 -1
- package/packages/piagent-webui/gateway/session-catalog.ts +2 -2
- package/packages/piagent-webui/server/tool-call-summary.ts +13 -0
- package/packages/piagent-webui/server/transcript-projection.ts +11 -4
- package/schemas/piagent-webui/gateway-protocol-v1.schema.json +2 -0
- package/schemas/piagent-webui/session-catalog-v1.schema.json +1 -1
- package/schemas/piagent-webui/transcript-v1.schema.json +17 -1
- package/scripts/company-terminal.mjs +2 -2
- package/packages/piagent-webui/dist/client/assets/ActivityPanel-Bv-jc1s3.js +0 -1
- package/packages/piagent-webui/dist/client/assets/GatewayApp-MIvgr7kn.js +0 -63
package/docs-site/releases.json
CHANGED
|
@@ -1,4 +1,101 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "1.50.0",
|
|
4
|
+
"date": "2026-11-01",
|
|
5
|
+
"status": "stable",
|
|
6
|
+
"title": {
|
|
7
|
+
"en": "A push is judged repository by repository and reviewed first",
|
|
8
|
+
"vi": "Push được xét theo từng repo và review trước"
|
|
9
|
+
},
|
|
10
|
+
"summary": {
|
|
11
|
+
"en": "From feedback on a real three-repository task: the company Harness knows what passed where, says it before a push, and reviews the code before it leaves.",
|
|
12
|
+
"vi": "Từ góp ý trên một task ba repo thật: Harness công ty biết phần nào đã pass ở đâu, ghi rõ trước khi push và review code trước khi code rời máy."
|
|
13
|
+
},
|
|
14
|
+
"requires": null,
|
|
15
|
+
"items": [
|
|
16
|
+
{
|
|
17
|
+
"kind": "improved",
|
|
18
|
+
"en": "A required review now runs before a push. A finding holds that push until it is fixed or rejected, so it no longer costs an extra release.",
|
|
19
|
+
"vi": "Review bắt buộc giờ chạy trước push. Ý kiến review giữ push lại tới khi được sửa hoặc bác bỏ, nên không còn tốn thêm một bản phát hành."
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"kind": "improved",
|
|
23
|
+
"en": "Before a push the question says which repositories passed in full, which only in part, how many files changed and how long the last full run took.",
|
|
24
|
+
"vi": "Câu hỏi trước push ghi rõ repo nào đã pass toàn bộ, repo nào mới pass một phần, đổi bao nhiêu file và lần chạy toàn bộ gần nhất mất bao lâu."
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"kind": "fixed",
|
|
28
|
+
"en": "A whole-suite pass counts per repository: a later small run no longer erases it, and a pass in one repository no longer clears a push of another.",
|
|
29
|
+
"vi": "Kết quả chạy toàn bộ tính theo từng repo: lần chạy nhỏ sau không xóa nó, và pass ở repo này không còn mở khóa push của repo khác."
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"kind": "fixed",
|
|
33
|
+
"en": "An approval card nobody answered now says it expired instead of reading as a refusal. A push card waits up to 30 minutes instead of 5.",
|
|
34
|
+
"vi": "Thẻ duyệt không ai trả lời giờ báo là hết hạn, không còn giống bị từ chối. Thẻ push chờ tối đa 30 phút thay vì 5 phút."
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"kind": "fixed",
|
|
38
|
+
"en": "Findings of a later review in the same turn reach the agent, and the status names any objection left without a decision by its id.",
|
|
39
|
+
"vi": "Ý kiến của lần review sau trong cùng lượt nay đến được agent, và dòng trạng thái ghi mã của ý kiến chưa có quyết định."
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"kind": "fixed",
|
|
43
|
+
"en": "Test scope reads go test -p as parallelism, not a package, and a shell function or loop that picks its files as a part of the suite.",
|
|
44
|
+
"vi": "Phạm vi test hiểu go test -p là số package chạy song song, không phải tên package, và hiểu hàm shell hoặc vòng lặp tự chọn file là chạy một phần."
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"kind": "improved",
|
|
48
|
+
"en": "The checks the Harness names are those of the repositories that changed. Bypass no longer sends a quick suite another way, and tests may write .env files in their own temporary folder.",
|
|
49
|
+
"vi": "Harness chỉ nêu check của repo có thay đổi. Bypass không còn bắt đi đường vòng với bộ test chạy nhanh, và test được ghi file .env trong thư mục tạm của chính nó."
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"version": "1.49.1",
|
|
55
|
+
"date": "2026-11-01",
|
|
56
|
+
"status": "stable",
|
|
57
|
+
"title": {
|
|
58
|
+
"en": "A running turn says what it waits for",
|
|
59
|
+
"vi": "Lượt chạy nói rõ đang chờ gì"
|
|
60
|
+
},
|
|
61
|
+
"summary": {
|
|
62
|
+
"en": "The status and Activity show what the main agent and each subagent are doing, answers before a review are marked, and Fast follows each model.",
|
|
63
|
+
"vi": "Trạng thái và Activity cho thấy main agent và từng subagent đang làm gì, câu trả lời trước review được đánh dấu, và Fast áp dụng theo từng model."
|
|
64
|
+
},
|
|
65
|
+
"requires": null,
|
|
66
|
+
"items": [
|
|
67
|
+
{
|
|
68
|
+
"kind": "fixed",
|
|
69
|
+
"en": "A long end-of-turn review no longer looks stuck: the status names the reviewer and how long it has run, with the step it is on.",
|
|
70
|
+
"vi": "Review cuối lượt chạy lâu không còn trông như bị treo: dòng trạng thái nói reviewer đang chạy, đã bao lâu và đang làm bước nào."
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"kind": "improved",
|
|
74
|
+
"en": "Activity lists every call by agent: the main agent and each subagent, with tool, target and time, live and after the turn.",
|
|
75
|
+
"vi": "Activity liệt kê mọi lời gọi theo từng agent: main agent và mỗi subagent, gồm tool, đối tượng và giờ gọi, cả khi đang chạy lẫn sau khi xong."
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"kind": "improved",
|
|
79
|
+
"en": "An answer written before the review is marked a draft and folded once a later one exists; the last one says it is the final result.",
|
|
80
|
+
"vi": "Câu trả lời viết trước review được đánh dấu bản nháp và thu gọn khi có câu mới hơn; câu cuối ghi rõ là kết quả cuối."
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"kind": "improved",
|
|
84
|
+
"en": "You can write while a turn runs: Enter sends the message when it ends. A turn that fails or is stopped keeps the message for you.",
|
|
85
|
+
"vi": "Soạn được tin trong lúc lượt đang chạy: Enter sẽ gửi khi lượt xong. Lượt lỗi hoặc bị dừng thì tin vẫn được giữ để bạn quyết định."
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"kind": "improved",
|
|
89
|
+
"en": "Fast follows each model, subagents too: a Claude main agent with Codex subagents runs them fast. Tokens run fast count 2.5 times toward the quota.",
|
|
90
|
+
"vi": "Fast áp dụng theo từng model, cả subagent: main agent Claude với subagent Codex thì subagent chạy Fast. Token chạy Fast tính vào quota gấp 2,5 lần."
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"kind": "fixed",
|
|
94
|
+
"en": "A model that refuses Fast goes back to standard speed alone; the rest of the conversation keeps Fast.",
|
|
95
|
+
"vi": "Model từ chối Fast chỉ riêng nó về tốc độ thường; phần còn lại của cuộc trò chuyện vẫn giữ Fast."
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
},
|
|
2
99
|
{
|
|
3
100
|
"version": "1.49.0",
|
|
4
101
|
"date": "2026-10-31",
|
package/docs-site/runtime.html
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
<span class="brand-mark"><img src="/assets/piagent-logo.svg" alt="" width="28" height="28" /></span>
|
|
38
38
|
<span class="brand-name"><b>Piagent</b> <span>Platform</span></span>
|
|
39
39
|
</a>
|
|
40
|
-
<a class="version-badge" href="/whats-new" data-docs-version="v1.
|
|
40
|
+
<a class="version-badge" href="/whats-new" data-docs-version="v1.50.0" title="v1.50.0 docs · VI"><span class="dot"></span>v1.50.0</a>
|
|
41
41
|
<nav class="top-nav" aria-label="Tài liệu">
|
|
42
42
|
<a href="/quickstart">Tài liệu</a>
|
|
43
43
|
<a href="/whats-new">Changelog</a>
|
package/docs-site/scope.html
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
<span class="brand-mark"><img src="/assets/piagent-logo.svg" alt="" width="28" height="28" /></span>
|
|
38
38
|
<span class="brand-name"><b>Piagent</b> <span>Platform</span></span>
|
|
39
39
|
</a>
|
|
40
|
-
<a class="version-badge" href="/whats-new" data-docs-version="v1.
|
|
40
|
+
<a class="version-badge" href="/whats-new" data-docs-version="v1.50.0" title="v1.50.0 docs · VI"><span class="dot"></span>v1.50.0</a>
|
|
41
41
|
<nav class="top-nav" aria-label="Tài liệu">
|
|
42
42
|
<a href="/quickstart">Tài liệu</a>
|
|
43
43
|
<a href="/whats-new">Changelog</a>
|
package/docs-site/security.html
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
<span class="brand-mark"><img src="/assets/piagent-logo.svg" alt="" width="28" height="28" /></span>
|
|
38
38
|
<span class="brand-name"><b>Piagent</b> <span>Platform</span></span>
|
|
39
39
|
</a>
|
|
40
|
-
<a class="version-badge" href="/whats-new" data-docs-version="v1.
|
|
40
|
+
<a class="version-badge" href="/whats-new" data-docs-version="v1.50.0" title="v1.50.0 docs · VI"><span class="dot"></span>v1.50.0</a>
|
|
41
41
|
<nav class="top-nav" aria-label="Tài liệu">
|
|
42
42
|
<a href="/quickstart">Tài liệu</a>
|
|
43
43
|
<a href="/whats-new">Changelog</a>
|
package/docs-site/team.html
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
<span class="brand-mark"><img src="/assets/piagent-logo.svg" alt="" width="28" height="28" /></span>
|
|
38
38
|
<span class="brand-name"><b>Piagent</b> <span>Platform</span></span>
|
|
39
39
|
</a>
|
|
40
|
-
<a class="version-badge" href="/whats-new" data-docs-version="v1.
|
|
40
|
+
<a class="version-badge" href="/whats-new" data-docs-version="v1.50.0" title="v1.50.0 docs · VI"><span class="dot"></span>v1.50.0</a>
|
|
41
41
|
<nav class="top-nav" aria-label="Tài liệu">
|
|
42
42
|
<a href="/quickstart">Tài liệu</a>
|
|
43
43
|
<a href="/whats-new">Changelog</a>
|
package/docs-site/update.html
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
<span class="brand-mark"><img src="/assets/piagent-logo.svg" alt="" width="28" height="28" /></span>
|
|
38
38
|
<span class="brand-name"><b>Piagent</b> <span>Platform</span></span>
|
|
39
39
|
</a>
|
|
40
|
-
<a class="version-badge" href="/whats-new" data-docs-version="v1.
|
|
40
|
+
<a class="version-badge" href="/whats-new" data-docs-version="v1.50.0" title="v1.50.0 docs · VI"><span class="dot"></span>v1.50.0</a>
|
|
41
41
|
<nav class="top-nav" aria-label="Tài liệu">
|
|
42
42
|
<a href="/quickstart">Tài liệu</a>
|
|
43
43
|
<a href="/whats-new">Changelog</a>
|
|
@@ -153,9 +153,9 @@
|
|
|
153
153
|
<h2 id="pin-cho-team">Pin một bản cho cả team</h2>
|
|
154
154
|
<p>Khi rollout cho team, cài đúng bản Pi host và đúng bản platform, rồi chạy installer ở chế độ stable. Bước <code>--dry-run</code> in ra những gì sẽ đổi.</p>
|
|
155
155
|
<div class="command-card">
|
|
156
|
-
<header><span>Cài pin theo bản</span><button class="clip-btn" data-clip="npm install -g --ignore-scripts @earendil-works/pi-coding-agent@1.3.0 npm install -g --ignore-scripts @piagent/platform@1.
|
|
156
|
+
<header><span>Cài pin theo bản</span><button class="clip-btn" data-clip="npm install -g --ignore-scripts @earendil-works/pi-coding-agent@1.3.0 npm install -g --ignore-scripts @piagent/platform@1.50.0 piagent-install --stable --dry-run piagent-install --stable">Lấy lệnh</button></header>
|
|
157
157
|
<pre><code>npm install -g --ignore-scripts @earendil-works/pi-coding-agent@1.3.0
|
|
158
|
-
npm install -g --ignore-scripts @piagent/platform@1.
|
|
158
|
+
npm install -g --ignore-scripts @piagent/platform@1.50.0
|
|
159
159
|
piagent-install --stable --dry-run
|
|
160
160
|
piagent-install --stable</code></pre>
|
|
161
161
|
</div>
|
package/docs-site/whats-new.html
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
<span class="brand-mark"><img src="/assets/piagent-logo.svg" alt="" width="28" height="28" /></span>
|
|
38
38
|
<span class="brand-name"><b>Piagent</b> <span>Platform</span></span>
|
|
39
39
|
</a>
|
|
40
|
-
<a class="version-badge" href="/whats-new" data-docs-version="v1.
|
|
40
|
+
<a class="version-badge" href="/whats-new" data-docs-version="v1.50.0" title="v1.50.0 docs · VI"><span class="dot"></span>v1.50.0</a>
|
|
41
41
|
<nav class="top-nav" aria-label="Tài liệu">
|
|
42
42
|
<a href="/quickstart">Tài liệu</a>
|
|
43
43
|
<a href="/whats-new" aria-current="page">Changelog</a>
|
|
@@ -108,7 +108,38 @@
|
|
|
108
108
|
<div class="release-filter" role="toolbar" aria-label="Lọc theo loại thay đổi"><button type="button" data-filter-kind="all" aria-pressed="true">Tất cả</button><button type="button" data-filter-kind="new" aria-pressed="false">Mới</button><button type="button" data-filter-kind="improved" aria-pressed="false">Cải thiện</button><button type="button" data-filter-kind="fixed" aria-pressed="false">Sửa lỗi</button><button type="button" data-filter-kind="security" aria-pressed="false">Bảo mật</button></div>
|
|
109
109
|
<section class="section releases" data-releases>
|
|
110
110
|
<h2 id="latest" class="sr-only">Mới nhất</h2>
|
|
111
|
-
<article class="release latest" id="v1-
|
|
111
|
+
<article class="release latest" id="v1-50-0" data-release>
|
|
112
|
+
<div class="release-meta"><a class="release-version" href="#v1-50-0">v1.50.0</a><time class="release-date" datetime="2026-11-01">01/11/2026</time></div>
|
|
113
|
+
<div class="release-body">
|
|
114
|
+
<h3 data-toc-skip id="v1-50-0-title">Push được xét theo từng repo và review trước</h3>
|
|
115
|
+
<p>Từ góp ý trên một task ba repo thật: Harness công ty biết phần nào đã pass ở đâu, ghi rõ trước khi push và review code trước khi code rời máy.</p>
|
|
116
|
+
<ul class="release-items">
|
|
117
|
+
<li data-kind="improved"><span class="kind kind-improved">Cải thiện</span><span>Review bắt buộc giờ chạy trước push. Ý kiến review giữ push lại tới khi được sửa hoặc bác bỏ, nên không còn tốn thêm một bản phát hành.</span></li>
|
|
118
|
+
<li data-kind="improved"><span class="kind kind-improved">Cải thiện</span><span>Câu hỏi trước push ghi rõ repo nào đã pass toàn bộ, repo nào mới pass một phần, đổi bao nhiêu file và lần chạy toàn bộ gần nhất mất bao lâu.</span></li>
|
|
119
|
+
<li data-kind="improved"><span class="kind kind-improved">Cải thiện</span><span>Harness chỉ nêu check của repo có thay đổi. Bypass không còn bắt đi đường vòng với bộ test chạy nhanh, và test được ghi file .env trong thư mục tạm của chính nó.</span></li>
|
|
120
|
+
<li data-kind="fixed"><span class="kind kind-fixed">Sửa lỗi</span><span>Kết quả chạy toàn bộ tính theo từng repo: lần chạy nhỏ sau không xóa nó, và pass ở repo này không còn mở khóa push của repo khác.</span></li>
|
|
121
|
+
<li data-kind="fixed"><span class="kind kind-fixed">Sửa lỗi</span><span>Thẻ duyệt không ai trả lời giờ báo là hết hạn, không còn giống bị từ chối. Thẻ push chờ tối đa 30 phút thay vì 5 phút.</span></li>
|
|
122
|
+
<li data-kind="fixed"><span class="kind kind-fixed">Sửa lỗi</span><span>Ý kiến của lần review sau trong cùng lượt nay đến được agent, và dòng trạng thái ghi mã của ý kiến chưa có quyết định.</span></li>
|
|
123
|
+
<li data-kind="fixed"><span class="kind kind-fixed">Sửa lỗi</span><span>Phạm vi test hiểu go test -p là số package chạy song song, không phải tên package, và hiểu hàm shell hoặc vòng lặp tự chọn file là chạy một phần.</span></li>
|
|
124
|
+
</ul>
|
|
125
|
+
</div>
|
|
126
|
+
</article>
|
|
127
|
+
<article class="release" id="v1-49-1" data-release>
|
|
128
|
+
<div class="release-meta"><a class="release-version" href="#v1-49-1">v1.49.1</a><time class="release-date" datetime="2026-11-01">01/11/2026</time></div>
|
|
129
|
+
<div class="release-body">
|
|
130
|
+
<h3 data-toc-skip id="v1-49-1-title">Lượt chạy nói rõ đang chờ gì</h3>
|
|
131
|
+
<p>Trạng thái và Activity cho thấy main agent và từng subagent đang làm gì, câu trả lời trước review được đánh dấu, và Fast áp dụng theo từng model.</p>
|
|
132
|
+
<ul class="release-items">
|
|
133
|
+
<li data-kind="improved"><span class="kind kind-improved">Cải thiện</span><span>Activity liệt kê mọi lời gọi theo từng agent: main agent và mỗi subagent, gồm tool, đối tượng và giờ gọi, cả khi đang chạy lẫn sau khi xong.</span></li>
|
|
134
|
+
<li data-kind="improved"><span class="kind kind-improved">Cải thiện</span><span>Câu trả lời viết trước review được đánh dấu bản nháp và thu gọn khi có câu mới hơn; câu cuối ghi rõ là kết quả cuối.</span></li>
|
|
135
|
+
<li data-kind="improved"><span class="kind kind-improved">Cải thiện</span><span>Soạn được tin trong lúc lượt đang chạy: Enter sẽ gửi khi lượt xong. Lượt lỗi hoặc bị dừng thì tin vẫn được giữ để bạn quyết định.</span></li>
|
|
136
|
+
<li data-kind="improved"><span class="kind kind-improved">Cải thiện</span><span>Fast áp dụng theo từng model, cả subagent: main agent Claude với subagent Codex thì subagent chạy Fast. Token chạy Fast tính vào quota gấp 2,5 lần.</span></li>
|
|
137
|
+
<li data-kind="fixed"><span class="kind kind-fixed">Sửa lỗi</span><span>Review cuối lượt chạy lâu không còn trông như bị treo: dòng trạng thái nói reviewer đang chạy, đã bao lâu và đang làm bước nào.</span></li>
|
|
138
|
+
<li data-kind="fixed"><span class="kind kind-fixed">Sửa lỗi</span><span>Model từ chối Fast chỉ riêng nó về tốc độ thường; phần còn lại của cuộc trò chuyện vẫn giữ Fast.</span></li>
|
|
139
|
+
</ul>
|
|
140
|
+
</div>
|
|
141
|
+
</article>
|
|
142
|
+
<article class="release" id="v1-49-0" data-release>
|
|
112
143
|
<div class="release-meta"><a class="release-version" href="#v1-49-0">v1.49.0</a><time class="release-date" datetime="2026-10-31">31/10/2026</time></div>
|
|
113
144
|
<div class="release-body">
|
|
114
145
|
<h3 data-toc-skip id="v1-49-0-title">Bạn chọn chạy bao nhiêu test</h3>
|
|
@@ -395,36 +426,34 @@
|
|
|
395
426
|
</ul>
|
|
396
427
|
</div>
|
|
397
428
|
</article>
|
|
398
|
-
<article class="release" id="v1-43-6" data-release>
|
|
399
|
-
<div class="release-meta"><a class="release-version" href="#v1-43-6">v1.43.6</a><time class="release-date" datetime="2026-10-27">27/10/2026</time></div>
|
|
400
|
-
<div class="release-body">
|
|
401
|
-
<h3 data-toc-skip id="v1-43-6-title">Xem thay đổi của agent ngay khi đang chạy</h3>
|
|
402
|
-
<p>Thay đổi và Review cập nhật theo lượt đang chạy, thấy sai thì dừng sớm được, và lượt còn chạy không còn trông như đã xong.</p>
|
|
403
|
-
<ul class="release-items">
|
|
404
|
-
<li data-kind="improved"><span class="kind kind-improved">Cải thiện</span><span>Panel Workspace liệt kê file thay đổi và mở được Review khi agent đang chạy, tự làm mới vài giây một lần; Review cũng cập nhật theo.</span></li>
|
|
405
|
-
<li data-kind="fixed"><span class="kind kind-fixed">Sửa lỗi</span><span>Câu trả lời bị harness trả lại để chạy check được ghi là giữa chừng, và lượt vẫn hiện đang chạy kèm nút Dừng cho tới khi check xong.</span></li>
|
|
406
|
-
</ul>
|
|
407
|
-
</div>
|
|
408
|
-
</article>
|
|
409
|
-
<article class="release" id="v1-43-5" data-release>
|
|
410
|
-
<div class="release-meta"><a class="release-version" href="#v1-43-5">v1.43.5</a><time class="release-date" datetime="2026-10-27">27/10/2026</time></div>
|
|
411
|
-
<div class="release-body">
|
|
412
|
-
<h3 data-toc-skip id="v1-43-5-title">Xem trước ảnh và đính kèm file văn phòng</h3>
|
|
413
|
-
<p>Ảnh đã gửi hiện thành hình, bảng tính và slide đính kèm trực tiếp được, task chụp nhiều screenshot không còn dừng vì giới hạn dung lượng request của Studio.</p>
|
|
414
|
-
<ul class="release-items">
|
|
415
|
-
<li data-kind="new"><span class="kind kind-new">Mới</span><span>Ảnh đính kèm và ảnh đã gửi hiện thành hình trong hội thoại, bấm để xem cỡ lớn; chip trong khung soạn có ảnh thu nhỏ.</span></li>
|
|
416
|
-
<li data-kind="new"><span class="kind kind-new">Mới</span><span>Đính kèm được .xlsx, .xlsm, .pptx, .odt, .ods, .odp, .rtf: bảng tính thành các dòng cách nhau bằng tab, slide theo thứ tự kèm ghi chú, không cần cài converter.</span></li>
|
|
417
|
-
<li data-kind="fixed"><span class="kind kind-fixed">Sửa lỗi</span><span>Task công ty chụp nhiều screenshot không còn lỗi request_too_large: mỗi request chỉ gửi lại ảnh mới nhất trong 2 MiB, ảnh cũ hơn thành một dòng ghi chú.</span></li>
|
|
418
|
-
<li data-kind="fixed"><span class="kind kind-fixed">Sửa lỗi</span><span>Câu trả lời đang stream của lượt có dùng tool xuống đoạn theo từng message của model, không còn dính các câu thành một khối.</span></li>
|
|
419
|
-
</ul>
|
|
420
|
-
</div>
|
|
421
|
-
</article>
|
|
422
429
|
</section>
|
|
423
430
|
<section class="section archive">
|
|
424
431
|
<h2 id="archive">Các bản trước</h2>
|
|
425
432
|
<p class="section-lead">Bản cũ hơn, gom theo tháng. Mở một tháng để xem từng thay đổi.</p>
|
|
426
433
|
<details class="release-archive">
|
|
427
|
-
<summary><span>Tháng 10/2026</span><span class="archive-count">
|
|
434
|
+
<summary><span>Tháng 10/2026</span><span class="archive-count">71</span></summary>
|
|
435
|
+
<div class="archive-row" id="v1-43-6">
|
|
436
|
+
<div class="archive-head"><a class="release-version" href="#v1-43-6">v1.43.6</a> <span class="release-date">27/10/2026</span></div>
|
|
437
|
+
<div class="archive-body">
|
|
438
|
+
<p><strong>Xem thay đổi của agent ngay khi đang chạy</strong> — Thay đổi và Review cập nhật theo lượt đang chạy, thấy sai thì dừng sớm được, và lượt còn chạy không còn trông như đã xong.</p>
|
|
439
|
+
<ul class="release-items">
|
|
440
|
+
<li data-kind="improved"><span class="kind kind-improved">Cải thiện</span><span>Panel Workspace liệt kê file thay đổi và mở được Review khi agent đang chạy, tự làm mới vài giây một lần; Review cũng cập nhật theo.</span></li>
|
|
441
|
+
<li data-kind="fixed"><span class="kind kind-fixed">Sửa lỗi</span><span>Câu trả lời bị harness trả lại để chạy check được ghi là giữa chừng, và lượt vẫn hiện đang chạy kèm nút Dừng cho tới khi check xong.</span></li>
|
|
442
|
+
</ul>
|
|
443
|
+
</div>
|
|
444
|
+
</div>
|
|
445
|
+
<div class="archive-row" id="v1-43-5">
|
|
446
|
+
<div class="archive-head"><a class="release-version" href="#v1-43-5">v1.43.5</a> <span class="release-date">27/10/2026</span></div>
|
|
447
|
+
<div class="archive-body">
|
|
448
|
+
<p><strong>Xem trước ảnh và đính kèm file văn phòng</strong> — Ảnh đã gửi hiện thành hình, bảng tính và slide đính kèm trực tiếp được, task chụp nhiều screenshot không còn dừng vì giới hạn dung lượng request của Studio.</p>
|
|
449
|
+
<ul class="release-items">
|
|
450
|
+
<li data-kind="new"><span class="kind kind-new">Mới</span><span>Ảnh đính kèm và ảnh đã gửi hiện thành hình trong hội thoại, bấm để xem cỡ lớn; chip trong khung soạn có ảnh thu nhỏ.</span></li>
|
|
451
|
+
<li data-kind="new"><span class="kind kind-new">Mới</span><span>Đính kèm được .xlsx, .xlsm, .pptx, .odt, .ods, .odp, .rtf: bảng tính thành các dòng cách nhau bằng tab, slide theo thứ tự kèm ghi chú, không cần cài converter.</span></li>
|
|
452
|
+
<li data-kind="fixed"><span class="kind kind-fixed">Sửa lỗi</span><span>Task công ty chụp nhiều screenshot không còn lỗi request_too_large: mỗi request chỉ gửi lại ảnh mới nhất trong 2 MiB, ảnh cũ hơn thành một dòng ghi chú.</span></li>
|
|
453
|
+
<li data-kind="fixed"><span class="kind kind-fixed">Sửa lỗi</span><span>Câu trả lời đang stream của lượt có dùng tool xuống đoạn theo từng message của model, không còn dính các câu thành một khối.</span></li>
|
|
454
|
+
</ul>
|
|
455
|
+
</div>
|
|
456
|
+
</div>
|
|
428
457
|
<div class="archive-row" id="v1-43-4">
|
|
429
458
|
<div class="archive-head"><a class="release-version" href="#v1-43-4">v1.43.4</a> <span class="release-date">26/10/2026</span></div>
|
|
430
459
|
<div class="archive-body">
|
package/docs-site/windows.html
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
<span class="brand-mark"><img src="/assets/piagent-logo.svg" alt="" width="28" height="28" /></span>
|
|
38
38
|
<span class="brand-name"><b>Piagent</b> <span>Platform</span></span>
|
|
39
39
|
</a>
|
|
40
|
-
<a class="version-badge" href="/whats-new" data-docs-version="v1.
|
|
40
|
+
<a class="version-badge" href="/whats-new" data-docs-version="v1.50.0" title="v1.50.0 docs · VI"><span class="dot"></span>v1.50.0</a>
|
|
41
41
|
<nav class="top-nav" aria-label="Tài liệu">
|
|
42
42
|
<a href="/quickstart">Tài liệu</a>
|
|
43
43
|
<a href="/whats-new">Changelog</a>
|
package/docs-site/workflows.html
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
<span class="brand-mark"><img src="/assets/piagent-logo.svg" alt="" width="28" height="28" /></span>
|
|
38
38
|
<span class="brand-name"><b>Piagent</b> <span>Platform</span></span>
|
|
39
39
|
</a>
|
|
40
|
-
<a class="version-badge" href="/whats-new" data-docs-version="v1.
|
|
40
|
+
<a class="version-badge" href="/whats-new" data-docs-version="v1.50.0" title="v1.50.0 docs · VI"><span class="dot"></span>v1.50.0</a>
|
|
41
41
|
<nav class="top-nav" aria-label="Tài liệu">
|
|
42
42
|
<a href="/quickstart">Tài liệu</a>
|
|
43
43
|
<a href="/whats-new">Changelog</a>
|
|
@@ -256,8 +256,8 @@
|
|
|
256
256
|
},
|
|
257
257
|
{
|
|
258
258
|
"path": "tests",
|
|
259
|
-
"fileCount":
|
|
260
|
-
"sha256": "
|
|
259
|
+
"fileCount": 648,
|
|
260
|
+
"sha256": "35ef07c0ab53282fccd92472562e92fa1e505305c4a47f7c527391eecf1cafec"
|
|
261
261
|
}
|
|
262
262
|
],
|
|
263
263
|
"custodianComparisonRules": {
|
package/package.json
CHANGED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Why an approval did not allow the action, in words the agent can act on: a card
|
|
2
|
+
// nobody answered in time is not a refusal, and asking again is then the right move.
|
|
3
|
+
export function approvalRefusal(decision) {
|
|
4
|
+
const receipt = decision?.receipt;
|
|
5
|
+
if (receipt?.state === 'expired') return Error('managed-operation-expired: the approval card ran out of time with no answer. Nobody refused it and no policy blocked it; the command did not run. Tell the member it is waiting for them, or send the same command again when they are back.');
|
|
6
|
+
if (receipt?.state === 'cancelled') return Error('managed-operation-cancelled: the approval was cancelled because the runtime restarted or stopped the task; nobody refused it and the command did not run.');
|
|
7
|
+
if (receipt?.decision === 'deny') return Error('managed-operation-denied: the member refused this exact command on the approval card; it did not run. Do not send it again unless they say so.');
|
|
8
|
+
return Error('managed-operation-denied: the command was not approved and did not run.');
|
|
9
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { readPatchDigest } from './patch-snapshot.mjs';
|
|
3
|
+
import { itemText, undecided } from './ledger.mjs';
|
|
4
|
+
import { changeLane, lanePolicy, patchStats } from './risk-lanes.mjs';
|
|
5
|
+
|
|
6
|
+
// What passed on which code, repository by repository: a folder can hold several,
|
|
7
|
+
// a later small run must not erase a whole-suite pass, and a pass in one repository
|
|
8
|
+
// says nothing about a push of another. Also what a push needs first: its review.
|
|
9
|
+
|
|
10
|
+
const FAST_SUITE_MS = 60_000, MAX_PARTS = 20, PUSH_REVIEWS = 2;
|
|
11
|
+
const repositoryNames = self => Object.keys(self.repositoryBases ?? {});
|
|
12
|
+
const plain = word => word.replace(/^["']|["']$/g, '');
|
|
13
|
+
|
|
14
|
+
// The repositories a command goes to, from its `cd` and `git -C`. `known` is false
|
|
15
|
+
// when one is a variable: a loop or a function decides it, not the text.
|
|
16
|
+
export function commandRepositories(self, command) {
|
|
17
|
+
const names = repositoryNames(self);
|
|
18
|
+
if (!names.length) return { repositories: ['.'], known: true };
|
|
19
|
+
const found = new Set(); let dir = self.cwd, known = true;
|
|
20
|
+
const visit = (word, stay) => {
|
|
21
|
+
const target = plain(word);
|
|
22
|
+
if (target.includes('$')) { known = false; return; }
|
|
23
|
+
const next = path.resolve(dir, target), first = path.relative(self.cwd, next).split(path.sep)[0];
|
|
24
|
+
if (names.includes(first)) found.add(first);
|
|
25
|
+
if (!stay) dir = next;
|
|
26
|
+
};
|
|
27
|
+
for (const match of String(command ?? '').matchAll(/(?:^|[\s;&|(])(cd|git\s+-C)\s+("[^"]*"|'[^']*'|[^\s;&|)]+)/g)) visit(match[2], match[1] !== 'cd');
|
|
28
|
+
return { repositories: [...found], known };
|
|
29
|
+
}
|
|
30
|
+
const changedRepositories = self => {
|
|
31
|
+
const names = repositoryNames(self);
|
|
32
|
+
return [...new Set([...(self.ownPaths ?? [])].map(file => String(file).split('/')[0]))].filter(name => names.includes(name));
|
|
33
|
+
};
|
|
34
|
+
// What a push sends: the repositories it names, else the ones this conversation changed.
|
|
35
|
+
export function pushRepositories(self, command) {
|
|
36
|
+
const { repositories, known } = commandRepositories(self, command);
|
|
37
|
+
return known && repositories.length ? repositories : changedRepositories(self);
|
|
38
|
+
}
|
|
39
|
+
// The code of one repository: this conversation's files in it, compared with where it started.
|
|
40
|
+
async function repositoryDigest(self, repository) {
|
|
41
|
+
if (repository === '.') return self.digest();
|
|
42
|
+
const scope = new Set([...(self.ownPaths ?? [])].filter(file => String(file).startsWith(`${repository}/`)));
|
|
43
|
+
try { return await (self.scopedDigest ?? (paths => readPatchDigest(self.boundary, paths, self.base, self.repositoryBases)))(scope); } catch { return null; }
|
|
44
|
+
}
|
|
45
|
+
// Reading a digest starts a sandboxed worker: one question reads each once.
|
|
46
|
+
export function digestsOnce(self) {
|
|
47
|
+
const known = new Map();
|
|
48
|
+
return repository => { if (!known.has(repository)) known.set(repository, repositoryDigest(self, repository)); return known.get(repository); };
|
|
49
|
+
}
|
|
50
|
+
const entryOf = (run, repository) => { const passes = run.passes ??= new Map(); if (!passes.has(repository)) passes.set(repository, { suite: null, parts: [] }); return passes.get(repository); };
|
|
51
|
+
|
|
52
|
+
// One test command ended. `part` names its test step, for how long that step takes.
|
|
53
|
+
export async function recordCheck(run, self, command, scope, ok, part) {
|
|
54
|
+
if (!self || !scope) return;
|
|
55
|
+
const started = run.checkStarts?.get(command), took = started ? Date.now() - started : null;
|
|
56
|
+
run.checkStarts?.delete(command);
|
|
57
|
+
for (const repository of commandRepositories(self, command).repositories) {
|
|
58
|
+
const digest = await repositoryDigest(self, repository), entry = entryOf(run, repository);
|
|
59
|
+
if (scope === 'suite') {
|
|
60
|
+
entry.suite = ok ? { digest, command } : null;
|
|
61
|
+
if (ok) run.fullFor?.delete(repository);
|
|
62
|
+
if (ok && took !== null) (self.checkTimes ??= new Map()).set(`${repository}\u0000${part}`, took);
|
|
63
|
+
} else if (ok) entry.parts = [...entry.parts, { digest, command }].slice(-MAX_PARTS);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export function markCheckStart(run, command) { (run.checkStarts ??= new Map()).set(command, Date.now()); }
|
|
67
|
+
|
|
68
|
+
// A whole suite this conversation has seen pass quickly is not worth a detour.
|
|
69
|
+
export function knownFast(self, command, part) {
|
|
70
|
+
const times = self.checkTimes, { repositories } = commandRepositories(self, command);
|
|
71
|
+
return !!times && repositories.length > 0 && repositories.every(repository => times.get(`${repository}\u0000${part}`) < FAST_SUITE_MS);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// The repositories whose whole suite has not passed on their current code.
|
|
75
|
+
export async function missingSuites(digestOf, run, repositories) {
|
|
76
|
+
const missing = [];
|
|
77
|
+
for (const repository of repositories) {
|
|
78
|
+
const digest = await digestOf(repository), pass = run.passes?.get(repository)?.suite;
|
|
79
|
+
if (!(pass && digest !== null && pass.digest === digest)) missing.push(repository);
|
|
80
|
+
}
|
|
81
|
+
return missing;
|
|
82
|
+
}
|
|
83
|
+
const minutes = ms => Math.max(1, Math.round(ms / 60_000));
|
|
84
|
+
// The member decides from this: what ran, what did not, what changed, how long the rest takes.
|
|
85
|
+
export async function pushQuestion(self, digestOf, run, repositories, missing) {
|
|
86
|
+
const name = repository => repository === '.' ? 'repo này' : repository;
|
|
87
|
+
const lines = [`Sắp push${repositories.length && repositories[0] !== '.' ? ` ${repositories.join(', ')}` : ' code'} nhưng toàn bộ test chưa pass trên code này.`];
|
|
88
|
+
const passed = repositories.filter(repository => !missing.includes(repository));
|
|
89
|
+
if (passed.length) lines.push(`Đã pass toàn bộ: ${passed.map(name).join(', ')}.`);
|
|
90
|
+
const partly = [], none = [];
|
|
91
|
+
for (const repository of missing) {
|
|
92
|
+
const digest = await digestOf(repository), count = (run.passes?.get(repository)?.parts ?? []).filter(p => digest !== null && p.digest === digest).length;
|
|
93
|
+
if (count) partly.push(`${name(repository)} (${count} lệnh)`); else none.push(name(repository));
|
|
94
|
+
}
|
|
95
|
+
if (partly.length) lines.push(`Mới pass một phần: ${partly.join(', ')}.`);
|
|
96
|
+
if (none.length) lines.push(`Chưa có test nào pass trên code hiện tại: ${none.join(', ')}.`);
|
|
97
|
+
const files = self.ownPaths?.size ?? 0;
|
|
98
|
+
if (files) lines.push(`Phiên này đổi ${files} file.`);
|
|
99
|
+
const took = [...(self.checkTimes ?? new Map())].filter(([key]) => missing.includes(key.split('\u0000')[0])).map(([, ms]) => ms);
|
|
100
|
+
if (took.length) lines.push(`Lần chạy toàn bộ gần nhất mất khoảng ${minutes(Math.max(...took))} phút.`);
|
|
101
|
+
return lines.join('\n');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// The checks worth naming: those of the repositories this conversation changed,
|
|
105
|
+
// not of every repository that happens to sit in the folder.
|
|
106
|
+
export function relevantChecks(self, commands) {
|
|
107
|
+
const changed = new Set([...(self.ownPaths ?? [])].map(file => String(file).split('/')[0]));
|
|
108
|
+
if (!changed.size) return commands;
|
|
109
|
+
const kept = commands.filter(command => { const dir = String(command).match(/^cd '([^']+)'/)?.[1]; return !dir || changed.has(dir.split('/')[0]); });
|
|
110
|
+
return kept.length ? kept : commands;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Before a push under a required review: a finding is cheap now and costs a release
|
|
114
|
+
// after. The patch the turn's end would review is the same, so it is reviewed once.
|
|
115
|
+
export async function reviewBeforePush(self, digestOf, run, signal) {
|
|
116
|
+
if (!self.delegate || !self.patchDigest || run.reviewSettled) return;
|
|
117
|
+
const code = await digestOf('.');
|
|
118
|
+
if (run.startDigest === null || code === null || code === run.startDigest) return;
|
|
119
|
+
let policy = run.policy;
|
|
120
|
+
if (policy.lanes) { let stats = null; try { stats = patchStats(await self.patchSnapshot?.()); } catch { /* unreadable: standard */ } policy = lanePolicy(policy, changeLane(stats, run)); }
|
|
121
|
+
if (policy.review !== 'require') return;
|
|
122
|
+
let patch = null; try { patch = await self.patchDigest(); } catch { return; }
|
|
123
|
+
const fresh = !!self.review && !self.review.stale && self.review.patchDigest === patch;
|
|
124
|
+
if (!fresh) {
|
|
125
|
+
if ((run.pushReviews ?? 0) >= PUSH_REVIEWS) return;
|
|
126
|
+
run.pushReviews = (run.pushReviews ?? 0) + 1;
|
|
127
|
+
self.session?.sessionManager?.appendCustomEntry?.('agent-watch-review-start', {});
|
|
128
|
+
// A review that cannot run does not hold the push: the turn's status says so.
|
|
129
|
+
try { await self.delegate({ role: 'review', task: "Review the current patch against the member's request. The main agent is about to push it: report what must change before it leaves this machine." }, signal, { harness: true }); }
|
|
130
|
+
catch (error) { if (signal?.aborted) throw error; return; }
|
|
131
|
+
}
|
|
132
|
+
const pending = undecided(run).filter(item => item.source === 'review');
|
|
133
|
+
if (pending.length) throw Error(`managed-review-before-push: this command did not run. The review of the code you are pushing has ${pending.length} finding(s) with no decision:\n${pending.map(item => '- ' + itemText(item)).join('\n')}\nFix each, or reject it with resolve_objection (what you checked and why), then push again.`);
|
|
134
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { memberQuestions } from './member-questions.mjs';
|
|
3
3
|
import { effectivePermission } from './permission.mjs';
|
|
4
|
+
import { digestsOnce, knownFast, markCheckStart, missingSuites, pushQuestion, pushRepositories, reviewBeforePush } from './check-evidence.mjs';
|
|
4
5
|
|
|
5
6
|
// How much to test is the member's decision: a one-line fix should not cost a
|
|
6
7
|
// thirty-minute suite. The harness asks at the end of a change, before a whole
|
|
@@ -15,19 +16,26 @@ const FILTERED = /(?:^|\s)(?:-k|-t|--grep|--testNamePattern|--test-name-pattern|
|
|
|
15
16
|
const TEST_RUNNER = /\b(npm|pnpm|yarn|bun)\s+(run\s+)?test(:\S+)?(?=\s|$)|\bnode\s+--test\b|\b(vitest|jest|mocha|pytest|rspec|phpunit)\b|\bplaywright\s+test\b|\bpython3?\s+-m\s+(pytest|unittest)\b|\b(go|cargo|swift|dotnet|deno)\s+test\b|\bmake\s+test\b|\bxcodebuild\b.*\btest\b|\b(gradle|gradlew|mvn|mvnw)\b.*\b(test|verify)\b/;
|
|
16
17
|
// Test commands start a command: `npm install -D vitest` or `rm jest.config.ts` only name one.
|
|
17
18
|
const TEST_START = new RegExp(`^(?:${TEST_RUNNER.source})`);
|
|
18
|
-
|
|
19
|
-
const
|
|
19
|
+
// A function body, a group or a loop body opens a part as well.
|
|
20
|
+
const LAUNCHER = /^(?:[\w-]+\s*\(\)\s*\{?\s*|\{\s+|\(\s*|(?:do|then|else)\s+|\w+=\S*\s+|time\s+|nohup\s+|timeout\s+\S+\s+|npx\s+(?:-\S+\s+)*|bunx\s+|pnpm\s+(?:exec|dlx)\s+|uv\s+run\s+|poetry\s+run\s+|yarn\s+(?=jest|vitest|mocha|playwright)|node_modules\/\.bin\/|\.\/)+/;
|
|
21
|
+
const partsOf = bare => bare.split(/\s*(?:\|\|?|&&|;)\s*/).map(part => part.replace(/\s\d*>.*$/, '').replace(/[\s)}]+$/, '').trim());
|
|
20
22
|
const NOT_TESTS = /\b(lint|typecheck|type-check|tsc|eslint|biome|ruff|mypy|pyright|vet|clippy|format|build)\b/;
|
|
21
23
|
const TARGET_FLAG = /^(-p|--package|-w|--workspace|--tests)$|^(-Dtest=|-only-testing:|--workspace=|--package=)/;
|
|
24
|
+
// `go test -p 4` sets how many packages run at once; for cargo -p names a package.
|
|
25
|
+
const GO_TEST = /^go\s+test\b/;
|
|
22
26
|
const VALUE_FLAG = /^(-c|--config|--reporter|--project|--workers|--timeout|--rootDir|--root|-f|--file|-o|--output)$/;
|
|
23
27
|
// A file or folder named on the command line; a flag's own value names none.
|
|
24
|
-
const targetsPart = args => args.some((a, i) => {
|
|
28
|
+
const targetsPart = (args, go = false) => args.some((a, i) => {
|
|
29
|
+
// A variable: a function's arguments, a loop's file, $(go list …). The caller chose them, so this is not the whole command.
|
|
30
|
+
if (a.startsWith('$')) return true;
|
|
31
|
+
if (go && a === '-p') return false;
|
|
25
32
|
if (TARGET_FLAG.test(a)) return true;
|
|
33
|
+
if (go && args[i - 1] === '-p') return false;
|
|
26
34
|
if (a.startsWith('-') || VALUE_FLAG.test(args[i - 1] ?? '') || a.includes('*') || a.endsWith('...')) return false;
|
|
27
35
|
return a.includes('/') || /\.(test|spec)\.\w+$|_test\.\w+$|^test_\w+\.\w+$/.test(a);
|
|
28
36
|
});
|
|
29
37
|
// What a command runs, without the text it merely quotes (a commit message, an echo).
|
|
30
|
-
const unquoted = text => text.replace(/"[^"]*"|'[^']*'/g, '""');
|
|
38
|
+
const unquoted = text => text.replace(/"[^"]*"|'[^']*'/g, quoted => quoted.startsWith('"$') ? '$' : '""');
|
|
31
39
|
export function checkScope(command, declared = []) {
|
|
32
40
|
const text = String(command ?? '').replace(/\s+/g, ' ').trim();
|
|
33
41
|
if (!text || /^(echo|printf|grep|rg|cat|sed|awk|head|tail|less|git|ls|find)\b/.test(text) || VERSION_ONLY.test(text) || /--collect-only/.test(text)) return null;
|
|
@@ -36,12 +44,17 @@ export function checkScope(command, declared = []) {
|
|
|
36
44
|
const own = part.replace(LAUNCHER, ''), runner = own.match(TEST_START);
|
|
37
45
|
if (!runner) continue;
|
|
38
46
|
if (FILTERED.test(own)) return 'partial';
|
|
39
|
-
return !declared.includes(text) && targetsPart(own.slice(runner[0].length).split(' ').filter(Boolean)) ? 'partial' : 'suite';
|
|
47
|
+
return !declared.includes(text) && targetsPart(own.slice(runner[0].length).split(' ').filter(Boolean), GO_TEST.test(own)) ? 'partial' : 'suite';
|
|
40
48
|
}
|
|
41
49
|
// A script the repository declares: it cannot be narrowed from here.
|
|
42
50
|
const named = declared.find(d => partsOf(bare).some(part => part.startsWith(unquoted(d))));
|
|
43
51
|
return named && !NOT_TESTS.test(named) ? 'suite' : null;
|
|
44
52
|
}
|
|
53
|
+
// The test step of a command, without what surrounds it: the same step costs the same.
|
|
54
|
+
export function suitePart(command) {
|
|
55
|
+
for (const part of partsOf(unquoted(String(command ?? '').replace(/\s+/g, ' ').trim()))) { const own = part.replace(LAUNCHER, ''); if (TEST_START.test(own)) return own; }
|
|
56
|
+
return String(command ?? '').trim();
|
|
57
|
+
}
|
|
45
58
|
// The member's own words asking for the whole suite: nothing to ask them then.
|
|
46
59
|
const RUN_ALL = String.raw`(?:full|all(?:\s+the)?|whole(?:\s+the)?|entire(?:\s+the)?|the\s+(?:full|whole|entire))`;
|
|
47
60
|
const FULL_WORDS = new RegExp([
|
|
@@ -94,18 +107,19 @@ export async function askCheckScope(self, { partPassed = false, signal } = {}) {
|
|
|
94
107
|
|
|
95
108
|
const PARTIAL_REASON = 'managed-check-scope: the member chose to run only the tests that cover the change. Run one file, a filter or one package, not the whole suite. If this project has no way to run part of it, say so and repeat the command: the member is asked.';
|
|
96
109
|
const BYPASS_REASON = 'managed-check-scope: run the tests that cover the change (one file, a filter, one package). A whole-suite run needs the member to ask for it, or their answer before a push. If this project has no way to run part of it, say so and repeat the command.';
|
|
97
|
-
const PUSH_REASON =
|
|
110
|
+
const PUSH_REASON = missing => `managed-check-scope: the member wants the whole suite before this push${missing.length && missing[0] !== '.' ? ` (still needed in: ${missing.join(', ')})` : ''}. Run it, fix what fails, then push.`;
|
|
98
111
|
const STOPPED_REASON = 'managed-check-scope: the member did not answer whether to push now. Do not push; say so and let them decide.';
|
|
99
112
|
const WORDS_REASON = words => `managed-check-scope: this command did not run. The member answered about testing: "${String(words).slice(0, 500)}". Follow that.`;
|
|
100
113
|
|
|
101
114
|
// Before a whole suite or a push: returns to let it run, throws to send the agent another way.
|
|
102
115
|
export async function beforeCheckOrPush(self, command, ctx, signal) {
|
|
103
116
|
const run = self.run;
|
|
104
|
-
if (!run || run.policy?.verify === 'off') return;
|
|
117
|
+
if (!run || (run.policy?.verify === 'off' && !isGitPush(command))) return;
|
|
105
118
|
// Calls sent together wait for one answer: the next one sees it.
|
|
106
119
|
const turn = (run.scopeQuestion ?? Promise.resolve()).then(() => holdOrLet(self, run, command, ctx, signal));
|
|
107
120
|
run.scopeQuestion = turn.catch(() => {});
|
|
108
|
-
|
|
121
|
+
await turn;
|
|
122
|
+
markCheckStart(run, command);
|
|
109
123
|
}
|
|
110
124
|
async function holdOrLet(self, run, command, ctx, signal) {
|
|
111
125
|
const push = isGitPush(command);
|
|
@@ -113,18 +127,22 @@ async function holdOrLet(self, run, command, ctx, signal) {
|
|
|
113
127
|
const surface = surfaceOf(self, ctx);
|
|
114
128
|
if (!surface) return;
|
|
115
129
|
if (push) {
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
130
|
+
const digestOf = digestsOnce(self);
|
|
131
|
+
await reviewBeforePush(self, digestOf, run, signal);
|
|
132
|
+
if (run.policy?.verify === 'off') return;
|
|
133
|
+
const now = await digestOf('.'), repositories = pushRepositories(self, command), single = !repositories.length || repositories[0] === '.';
|
|
134
|
+
const missing = await missingSuites(digestOf, run, repositories.length ? repositories : ['.']);
|
|
135
|
+
if ((single ? run.suitePassedOn(now) : false) || !missing.length || (run.pushAllowedOn !== undefined && run.pushAllowedOn === now)) return;
|
|
136
|
+
const picked = await ask(surface, await pushQuestion(self, digestOf, run, repositories, missing), [PUSH_FULL, PUSH_NOW], signal);
|
|
119
137
|
if (picked === PUSH_NOW) { run.pushAllowedOn = now; return; }
|
|
120
138
|
if (picked?.other) throw Error(WORDS_REASON(picked.other));
|
|
121
139
|
if (picked !== PUSH_FULL) throw Error(STOPPED_REASON);
|
|
122
|
-
run.scopeChoice = 'full';
|
|
123
|
-
throw Error(PUSH_REASON);
|
|
140
|
+
run.scopeChoice = 'full'; run.fullFor = new Set(missing);
|
|
141
|
+
throw Error(PUSH_REASON(missing));
|
|
124
142
|
}
|
|
125
143
|
// Sent another way once; the same command again means the part did not do: the member decides (Bypass: it runs).
|
|
126
144
|
const held = run.scopeHeld ??= new Set(), again = held.has(command); held.add(command);
|
|
127
|
-
if (effectivePermission(self) === 'bypass') { if (again) return; throw Error(BYPASS_REASON); }
|
|
145
|
+
if (effectivePermission(self) === 'bypass') { if (again || knownFast(self, command, suitePart(command))) return; throw Error(BYPASS_REASON); }
|
|
128
146
|
if (run.scopeChoice === 'partial' && !again) throw Error(PARTIAL_REASON);
|
|
129
147
|
const picked = await ask(surface, `Agent muốn chạy toàn bộ test: ${String(command).slice(0, 160)}`, [PART, FULL], signal);
|
|
130
148
|
if (picked === FULL) { run.scopeChoice = 'full'; return; }
|