@piagent/platform 1.48.2 → 1.48.4
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 +28 -0
- package/docs/command-reference-vietnamese.md +2 -2
- package/docs/distribution-standard.md +6 -6
- 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 +51 -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 +72 -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 +51 -27
- package/docs-site/windows.html +1 -1
- package/docs-site/workflows.html +1 -1
- package/evals/private-holdout-v1/public-exposure.v2.json +1 -1
- package/package.json +1 -1
- package/packages/piagent-core/extensions/workspace-inspection.ts +4 -1
- package/packages/piagent-core/managed/dist/workspace/manifest.json +1 -1
- package/packages/piagent-core/managed/dist/workspace/worker.mjs +7 -1
- package/packages/piagent-core/managed/patch-snapshot.mjs +28 -13
- package/packages/piagent-core/managed/review-snapshot-request.mjs +1 -1
- package/packages/piagent-core/managed/review-snapshot-worker.mjs +52 -11
- package/packages/piagent-core/managed/session.mjs +7 -7
- package/packages/piagent-core/managed/workflow.mjs +4 -3
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/runtime/update/terminal-reload.mjs +23 -13
- package/packages/piagent-webui/dist/client/assets/{ActivityPanel-DxQ3_dyT.js → ActivityPanel-Bv-jc1s3.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/App-DqN7B1JR.js +5 -0
- package/packages/piagent-webui/dist/client/assets/AttachmentImage-eZKqLYZ2.js +1 -0
- package/packages/piagent-webui/dist/client/assets/GatewayApp-B3--WjSd.js +63 -0
- package/packages/piagent-webui/dist/client/assets/{InspectionApp-CEilR0FG.js → InspectionApp-BKQiA0Ce.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/{LineReview-C_0ZpgCH.js → LineReview-CB3mQe7m.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/MarkdownMessage-zw5RxDmZ.js +29 -0
- package/packages/piagent-webui/dist/client/assets/{SessionAgentWorkspace-yh0py16t.js → SessionAgentWorkspace-ZAdRbxVA.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/SessionInspectorDrawer-B2e4-1FG.js +2 -0
- package/packages/piagent-webui/dist/client/assets/SettingsNavigation-C3MC0X4P.js +3 -0
- package/packages/piagent-webui/dist/client/assets/SettingsPage-D4os5vHj.js +1 -0
- package/packages/piagent-webui/dist/client/assets/{TerminalPanel-B2-YAz2x.js → TerminalPanel-CVYUiDkn.js} +2 -2
- package/packages/piagent-webui/dist/client/assets/{TerminalView-yaIg6RCs.js → TerminalView-C3upxbKw.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/{WorkspaceCodeEditor-DAdMuw6x.js → WorkspaceCodeEditor-E0Zopcke.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/WorkspaceExplorer-DldLoJyf.js +5 -0
- package/packages/piagent-webui/dist/client/assets/WorkspaceFilePreview-CvIBYige.js +1 -0
- package/packages/piagent-webui/dist/client/assets/{WorkspaceQuickOpen-CJmMBmK8.js → WorkspaceQuickOpen-Bi-kTEEU.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/index-DcrpSyBK.js +37 -0
- package/packages/piagent-webui/dist/client/assets/{tab-close-ITV4Cf4X.js → tab-close-BwkkhMH7.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/transcript-view-model-acWtHGyE.js +3 -0
- package/packages/piagent-webui/dist/client/assets/{ui-preferences-0waeSO-S.js → ui-preferences-OfS30-ZV.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/{vendor-CSDKB7mU.js → vendor-DNZbaWhT.js} +62 -26
- package/packages/piagent-webui/dist/client/assets/{workspace-api-D8sJkiZB.js → workspace-api-CFBR73lU.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/workspace-navigation-DW8zhH6U.js +1 -0
- package/packages/piagent-webui/dist/client/index.html +3 -3
- package/packages/piagent-webui/shared/process-copy.ts +2 -1
- package/schemas/piagent-webui/workspace-media-v1.schema.json +6 -0
- package/scripts/company-terminal.mjs +10 -5
- package/packages/piagent-webui/dist/client/assets/App-Dg6Firq1.js +0 -5
- package/packages/piagent-webui/dist/client/assets/GatewayApp-CCczgphI.js +0 -99
- package/packages/piagent-webui/dist/client/assets/MarkdownMessage-DGt5B-6D.js +0 -29
- package/packages/piagent-webui/dist/client/assets/SessionInspectorDrawer-C-18TcGH.js +0 -2
- package/packages/piagent-webui/dist/client/assets/SettingsNavigation-BFfqMcuL.js +0 -3
- package/packages/piagent-webui/dist/client/assets/SettingsPage-CxpZ7cdM.js +0 -1
- package/packages/piagent-webui/dist/client/assets/WorkspaceExplorer-97MLNtEa.js +0 -5
- package/packages/piagent-webui/dist/client/assets/index-B8tL7HFz.js +0 -37
- package/packages/piagent-webui/dist/client/assets/transcript-view-model-DqO4xO12.js +0 -3
- package/packages/piagent-webui/dist/client/assets/workspace-navigation-DkVobhwg.js +0 -1
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.48.
|
|
40
|
+
<a class="version-badge" href="/whats-new" data-docs-version="v1.48.4" title="v1.48.4 docs · VI"><span class="dot"></span>v1.48.4</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,33 @@
|
|
|
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-48-
|
|
111
|
+
<article class="release latest" id="v1-48-4" data-release>
|
|
112
|
+
<div class="release-meta"><a class="release-version" href="#v1-48-4">v1.48.4</a><time class="release-date" datetime="2026-10-30">30/10/2026</time></div>
|
|
113
|
+
<div class="release-body">
|
|
114
|
+
<h3 data-toc-skip id="v1-48-4-title">/reload không còn làm hỏng Terminal công ty; nạp bản mới không làm mất tin nhắn</h3>
|
|
115
|
+
<p>Tool vẫn chạy sau /reload trong piagent studio, bản cập nhật yêu cầu trước /reload vẫn được nạp, và Terminal chờ lúc rảnh hẳn mới nạp bản mới.</p>
|
|
116
|
+
<ul class="release-items">
|
|
117
|
+
<li data-kind="fixed"><span class="kind kind-fixed">Sửa lỗi</span><span>/reload trong piagent studio không còn làm hỏng cuộc trò chuyện: trước đây tool báo lỗi ctx cũ sau lệnh này.</span></li>
|
|
118
|
+
<li data-kind="fixed"><span class="kind kind-fixed">Sửa lỗi</span><span>Tin nhắn gõ ngay sau khi bấm Esc không còn bị mất: Terminal chỉ nạp bản mới sau 10 giây rảnh liên tục và ô nhập trống.</span></li>
|
|
119
|
+
<li data-kind="fixed"><span class="kind kind-fixed">Sửa lỗi</span><span>Bản cập nhật yêu cầu trước /reload vẫn được nạp, và yêu cầu đã nạp không làm Terminal nạp lại lần nữa.</span></li>
|
|
120
|
+
</ul>
|
|
121
|
+
</div>
|
|
122
|
+
</article>
|
|
123
|
+
<article class="release" id="v1-48-3" data-release>
|
|
124
|
+
<div class="release-meta"><a class="release-version" href="#v1-48-3">v1.48.3</a><time class="release-date" datetime="2026-10-30">30/10/2026</time></div>
|
|
125
|
+
<div class="release-body">
|
|
126
|
+
<h3 data-toc-skip id="v1-48-3-title">Agent gửi thẳng hình và file vào chat; review chạy được trong thư mục nhiều repo</h3>
|
|
127
|
+
<p>Câu trả lời hiện hình và thẻ file ngay trong chat, SVG mở ra hình, ảnh đính kèm lớn và bấm xem được, thư mục chứa nhiều repo được review bình thường.</p>
|
|
128
|
+
<ul class="release-items">
|
|
129
|
+
<li data-kind="new"><span class="kind kind-new">Mới</span><span>Agent gửi hình hoặc file vào chat bằng : hình hiện ngay và mở to được, file khác là thẻ để mở hoặc tải về.</span></li>
|
|
130
|
+
<li data-kind="improved"><span class="kind kind-improved">Cải thiện</span><span>SVG mở ra hình, có nút Xem mã bên cạnh; mọi file đang mở đều có nút Tải về.</span></li>
|
|
131
|
+
<li data-kind="improved"><span class="kind kind-improved">Cải thiện</span><span>Ảnh đính kèm là thumbnail lớn, bấm để xem to, kể cả ở trang cuộc trò chuyện mới.</span></li>
|
|
132
|
+
<li data-kind="fixed"><span class="kind kind-fixed">Sửa lỗi</span><span>Review không còn lỗi not_git trong thư mục chứa nhiều repo: thay đổi do lệnh tạo ra, đã commit hay chưa, được nhận đúng theo từng repo.</span></li>
|
|
133
|
+
<li data-kind="fixed"><span class="kind kind-fixed">Sửa lỗi</span><span>Review dừng vì Piagent được cập nhật giữa cuộc trò chuyện sẽ nói rõ và nhắc mở lại; lỗi chưa có mã giữ nội dung đã che trong log máy.</span></li>
|
|
134
|
+
</ul>
|
|
135
|
+
</div>
|
|
136
|
+
</article>
|
|
137
|
+
<article class="release" id="v1-48-2" data-release>
|
|
112
138
|
<div class="release-meta"><a class="release-version" href="#v1-48-2">v1.48.2</a><time class="release-date" datetime="2026-10-30">30/10/2026</time></div>
|
|
113
139
|
<div class="release-body">
|
|
114
140
|
<h3 data-toc-skip id="v1-48-2-title">Terminal nạp bản mới không cần đóng; chỉ member được bỏ qua kiểm tra</h3>
|
|
@@ -396,36 +422,34 @@
|
|
|
396
422
|
</ul>
|
|
397
423
|
</div>
|
|
398
424
|
</article>
|
|
399
|
-
<article class="release" id="v1-43-2" data-release>
|
|
400
|
-
<div class="release-meta"><a class="release-version" href="#v1-43-2">v1.43.2</a><time class="release-date" datetime="2026-10-26">26/10/2026</time></div>
|
|
401
|
-
<div class="release-body">
|
|
402
|
-
<h3 data-toc-skip id="v1-43-2-title">Dev chọn workflow, harness hỗ trợ BE/FE theo project <span class="tag">không phát hành</span></h3>
|
|
403
|
-
<p>Đã tag nhưng chưa publish: wording prompt trùng chuỗi lệnh bị cấm trong test native session. Thay bằng 1.43.4.</p>
|
|
404
|
-
<ul class="release-items">
|
|
405
|
-
<li data-kind="new"><span class="kind kind-new">Mới</span><span>Tự nhận diện repo/package FE, BE trong thư mục hội thoại và gợi ý checks phù hợp, làm mới theo task.</span></li>
|
|
406
|
-
<li data-kind="new"><span class="kind kind-new">Mới</span><span>Context workflow tùy chọn nhớ nguồn requirement, plan và tasks của công việc khi mở lại hội thoại, truyền chỉ đọc cho helper và hỗ trợ đổi hoặc xóa.</span></li>
|
|
407
|
-
<li data-kind="improved"><span class="kind kind-improved">Cải thiện</span><span>Lifecycle là hướng dẫn theo workflow và điểm dừng Dev chọn, không ép file spec hoặc tự tạo gate release. Skill được chọn không cấp quyền hay bỏ qua kiểm chứng, review và duyệt deploy.</span></li>
|
|
408
|
-
</ul>
|
|
409
|
-
</div>
|
|
410
|
-
</article>
|
|
411
|
-
<article class="release" id="v1-43-1" data-release>
|
|
412
|
-
<div class="release-meta"><a class="release-version" href="#v1-43-1">v1.43.1</a><time class="release-date" datetime="2026-10-26">26/10/2026</time></div>
|
|
413
|
-
<div class="release-body">
|
|
414
|
-
<h3 data-toc-skip id="v1-43-1-title">Dev chọn workflow, harness hỗ trợ BE/FE theo project <span class="tag">không phát hành</span></h3>
|
|
415
|
-
<p>Đã tag nhưng chưa publish: một bullet release note vượt giới hạn độ dài. Thay bằng 1.43.4.</p>
|
|
416
|
-
<ul class="release-items">
|
|
417
|
-
<li data-kind="new"><span class="kind kind-new">Mới</span><span>Tự nhận diện repo/package FE, BE trong thư mục hội thoại và gợi ý checks phù hợp, làm mới theo task.</span></li>
|
|
418
|
-
<li data-kind="new"><span class="kind kind-new">Mới</span><span>Context workflow tùy chọn nhớ nguồn requirement, plan và tasks của công việc khi mở lại hội thoại, truyền chỉ đọc cho helper và hỗ trợ đổi hoặc xóa.</span></li>
|
|
419
|
-
<li data-kind="improved"><span class="kind kind-improved">Cải thiện</span><span>Lifecycle là hướng dẫn theo workflow và điểm dừng Dev chọn, không ép file spec hoặc tự tạo gate release. Skill được chọn không cấp quyền hay bỏ qua kiểm chứng, review và duyệt deploy.</span></li>
|
|
420
|
-
</ul>
|
|
421
|
-
</div>
|
|
422
|
-
</article>
|
|
423
425
|
</section>
|
|
424
426
|
<section class="section archive">
|
|
425
427
|
<h2 id="archive">Các bản trước</h2>
|
|
426
428
|
<p class="section-lead">Bản cũ hơn, gom theo tháng. Mở một tháng để xem từng thay đổi.</p>
|
|
427
429
|
<details class="release-archive">
|
|
428
|
-
<summary><span>Tháng 10/2026</span><span class="archive-count">
|
|
430
|
+
<summary><span>Tháng 10/2026</span><span class="archive-count">67</span></summary>
|
|
431
|
+
<div class="archive-row" id="v1-43-2">
|
|
432
|
+
<div class="archive-head"><a class="release-version" href="#v1-43-2">v1.43.2</a> <span class="release-date">26/10/2026</span></div>
|
|
433
|
+
<div class="archive-body">
|
|
434
|
+
<p><strong>Dev chọn workflow, harness hỗ trợ BE/FE theo project</strong> <span class="tag">không phát hành</span> — Đã tag nhưng chưa publish: wording prompt trùng chuỗi lệnh bị cấm trong test native session. Thay bằng 1.43.4.</p>
|
|
435
|
+
<ul class="release-items">
|
|
436
|
+
<li data-kind="new"><span class="kind kind-new">Mới</span><span>Tự nhận diện repo/package FE, BE trong thư mục hội thoại và gợi ý checks phù hợp, làm mới theo task.</span></li>
|
|
437
|
+
<li data-kind="new"><span class="kind kind-new">Mới</span><span>Context workflow tùy chọn nhớ nguồn requirement, plan và tasks của công việc khi mở lại hội thoại, truyền chỉ đọc cho helper và hỗ trợ đổi hoặc xóa.</span></li>
|
|
438
|
+
<li data-kind="improved"><span class="kind kind-improved">Cải thiện</span><span>Lifecycle là hướng dẫn theo workflow và điểm dừng Dev chọn, không ép file spec hoặc tự tạo gate release. Skill được chọn không cấp quyền hay bỏ qua kiểm chứng, review và duyệt deploy.</span></li>
|
|
439
|
+
</ul>
|
|
440
|
+
</div>
|
|
441
|
+
</div>
|
|
442
|
+
<div class="archive-row" id="v1-43-1">
|
|
443
|
+
<div class="archive-head"><a class="release-version" href="#v1-43-1">v1.43.1</a> <span class="release-date">26/10/2026</span></div>
|
|
444
|
+
<div class="archive-body">
|
|
445
|
+
<p><strong>Dev chọn workflow, harness hỗ trợ BE/FE theo project</strong> <span class="tag">không phát hành</span> — Đã tag nhưng chưa publish: một bullet release note vượt giới hạn độ dài. Thay bằng 1.43.4.</p>
|
|
446
|
+
<ul class="release-items">
|
|
447
|
+
<li data-kind="new"><span class="kind kind-new">Mới</span><span>Tự nhận diện repo/package FE, BE trong thư mục hội thoại và gợi ý checks phù hợp, làm mới theo task.</span></li>
|
|
448
|
+
<li data-kind="new"><span class="kind kind-new">Mới</span><span>Context workflow tùy chọn nhớ nguồn requirement, plan và tasks của công việc khi mở lại hội thoại, truyền chỉ đọc cho helper và hỗ trợ đổi hoặc xóa.</span></li>
|
|
449
|
+
<li data-kind="improved"><span class="kind kind-improved">Cải thiện</span><span>Lifecycle là hướng dẫn theo workflow và điểm dừng Dev chọn, không ép file spec hoặc tự tạo gate release. Skill được chọn không cấp quyền hay bỏ qua kiểm chứng, review và duyệt deploy.</span></li>
|
|
450
|
+
</ul>
|
|
451
|
+
</div>
|
|
452
|
+
</div>
|
|
429
453
|
<div class="archive-row" id="v1-43-0">
|
|
430
454
|
<div class="archive-head"><a class="release-version" href="#v1-43-0">v1.43.0</a> <span class="release-date">26/10/2026</span></div>
|
|
431
455
|
<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.48.
|
|
40
|
+
<a class="version-badge" href="/whats-new" data-docs-version="v1.48.4" title="v1.48.4 docs · VI"><span class="dot"></span>v1.48.4</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.48.
|
|
40
|
+
<a class="version-badge" href="/whats-new" data-docs-version="v1.48.4" title="v1.48.4 docs · VI"><span class="dot"></span>v1.48.4</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>
|
|
@@ -257,7 +257,7 @@
|
|
|
257
257
|
{
|
|
258
258
|
"path": "tests",
|
|
259
259
|
"fileCount": 643,
|
|
260
|
-
"sha256": "
|
|
260
|
+
"sha256": "ee1cd916b71678c7f3b2eda3f9b90d0661b636c81634fb3cfbd18372b2b53ba6"
|
|
261
261
|
}
|
|
262
262
|
],
|
|
263
263
|
"custodianComparisonRules": {
|
package/package.json
CHANGED
|
@@ -140,7 +140,10 @@ export function inspectWorkspaceFile(root: string, relative: string, options: Wo
|
|
|
140
140
|
// An image the agent made or names, shown as a picture in the explorer (the
|
|
141
141
|
// page takes data: images only). Protected files are never read.
|
|
142
142
|
const IMAGE_MIMES: Record<string, string> = { png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", gif: "image/gif",
|
|
143
|
-
webp: "image/webp", bmp: "image/bmp", ico: "image/x-icon", avif: "image/avif"
|
|
143
|
+
webp: "image/webp", bmp: "image/bmp", ico: "image/x-icon", avif: "image/avif", svg: "image/svg+xml",
|
|
144
|
+
// Not drawn: their bytes are what "Tải về" on a file the agent handed over saves.
|
|
145
|
+
pdf: "application/pdf", docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
146
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation", zip: "application/zip" };
|
|
144
147
|
const MAX_MEDIA_BYTES = 8 << 20;
|
|
145
148
|
export function inspectWorkspaceMedia(root: string, relative: string, options: WorkspacePolicy) {
|
|
146
149
|
validPath(relative);
|
|
@@ -526,7 +526,13 @@ var IMAGE_MIMES = {
|
|
|
526
526
|
webp: "image/webp",
|
|
527
527
|
bmp: "image/bmp",
|
|
528
528
|
ico: "image/x-icon",
|
|
529
|
-
avif: "image/avif"
|
|
529
|
+
avif: "image/avif",
|
|
530
|
+
svg: "image/svg+xml",
|
|
531
|
+
pdf: "application/pdf",
|
|
532
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
533
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
534
|
+
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
535
|
+
zip: "application/zip"
|
|
530
536
|
};
|
|
531
537
|
var MAX_MEDIA_BYTES = 8 << 20;
|
|
532
538
|
function inspectWorkspaceMedia(root, relative, options) {
|
|
@@ -11,16 +11,20 @@ const MAX_SNAPSHOT = 16 * 1024 * 1024;
|
|
|
11
11
|
// `base`: the commit the patch starts from (the turn's starting HEAD), so a
|
|
12
12
|
// commit made during the turn stays part of its change; none: HEAD.
|
|
13
13
|
const BASE = /^[0-9a-f]{40}([0-9a-f]{24})?$/;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
// `bases`: in a folder of several repositories, where each started the turn.
|
|
15
|
+
async function worker(boundary, mode, scope = null, base = null, bases = null) {
|
|
16
|
+
const written = [];
|
|
17
|
+
const write = (prefix, value) => {
|
|
18
|
+
const name = `${prefix}-${randomUUID()}.json`;
|
|
19
|
+
fs.writeFileSync(path.join(boundary.temporary, name), JSON.stringify(value), { mode: 0o600, flag: 'wx' });
|
|
20
|
+
written.push(name);
|
|
21
|
+
};
|
|
22
|
+
if (scope) write('scope', [...scope]);
|
|
23
|
+
if (bases && Object.keys(bases).length) write('bases', bases);
|
|
20
24
|
let result;
|
|
21
|
-
const extra = [...
|
|
25
|
+
const extra = [...written, ...(typeof base === 'string' && BASE.test(base) ? [base] : [])];
|
|
22
26
|
try { result = await invokeReviewSnapshot(boundary, mode, extra); }
|
|
23
|
-
finally {
|
|
27
|
+
finally { for (const name of written) fs.rmSync(path.join(boundary.temporary, name), { force: true }); }
|
|
24
28
|
const text = textContent(result).trim();
|
|
25
29
|
if (result.details?.truncation?.truncated || text.includes('\n')) throw Error(/review-(patch|file|untracked)-limit/.exec(text)?.[0] ?? 'managed-review-patch-unreadable');
|
|
26
30
|
const [digest, bytes, name] = text.split(' ');
|
|
@@ -30,7 +34,17 @@ async function worker(boundary, mode, scope = null, base = null) {
|
|
|
30
34
|
|
|
31
35
|
// The digest of the patch a reviewer would see (diff against HEAD plus
|
|
32
36
|
// untracked files): whether a review is still current. Nothing else moves.
|
|
33
|
-
export async function readPatchDigest(boundary, scope = null, base = null) { return (await worker(boundary, 'digest', scope, base)).digest; }
|
|
37
|
+
export async function readPatchDigest(boundary, scope = null, base = null, bases = null) { return (await worker(boundary, 'digest', scope, base, bases)).digest; }
|
|
38
|
+
|
|
39
|
+
// Where each repository below a folder that is none itself stands now:
|
|
40
|
+
// { repository: commit }; empty inside one repository.
|
|
41
|
+
export async function readRepositoryHeads(boundary) {
|
|
42
|
+
try {
|
|
43
|
+
const { digest, bytes, name } = await worker(boundary, 'heads');
|
|
44
|
+
if (!/^heads-[0-9a-f-]{36}\.json$/.test(name ?? '')) return null;
|
|
45
|
+
return JSON.parse(readOnce(boundary, name, bytes, digest).toString('utf8'));
|
|
46
|
+
} catch { return null; }
|
|
47
|
+
}
|
|
34
48
|
|
|
35
49
|
// The commit HEAD names now, or null (no repository, no commit yet).
|
|
36
50
|
export async function readHead(boundary) {
|
|
@@ -69,8 +83,8 @@ export function emptySnapshot(snapshot) {
|
|
|
69
83
|
// it to the boundary's temporary directory and names it, with its digest, on
|
|
70
84
|
// its output; the file is read once, must match that digest (a file changed
|
|
71
85
|
// meanwhile never passes) and is removed.
|
|
72
|
-
export async function readPatchSnapshot(boundary, scope = null, base = null) {
|
|
73
|
-
const { digest, bytes, name } = await worker(boundary, 'file', scope, base);
|
|
86
|
+
export async function readPatchSnapshot(boundary, scope = null, base = null, bases = null) {
|
|
87
|
+
const { digest, bytes, name } = await worker(boundary, 'file', scope, base, bases);
|
|
74
88
|
if (!/^review-[0-9a-f-]{36}\.json$/.test(name ?? '')) throw Error('managed-review-patch-unreadable');
|
|
75
89
|
return { patch: readOnce(boundary, name, bytes, digest).toString('utf8'), digest };
|
|
76
90
|
}
|
|
@@ -79,8 +93,8 @@ export async function readPatchSnapshot(boundary, scope = null, base = null) {
|
|
|
79
93
|
// Compared before and after a command, it names the files that command
|
|
80
94
|
// changed, so a conversation answers for its own changes, not for another
|
|
81
95
|
// conversation's in the same folder.
|
|
82
|
-
export async function readChangedFiles(boundary, base = null) {
|
|
83
|
-
const { digest, bytes, name } = await worker(boundary, 'files', null, base);
|
|
96
|
+
export async function readChangedFiles(boundary, base = null, bases = null) {
|
|
97
|
+
const { digest, bytes, name } = await worker(boundary, 'files', null, base, bases);
|
|
84
98
|
if (!/^files-[0-9a-f-]{36}\.json$/.test(name ?? '')) throw Error('managed-review-patch-unreadable');
|
|
85
99
|
return new Map(Object.entries(JSON.parse(readOnce(boundary, name, bytes, digest).toString('utf8'))));
|
|
86
100
|
}
|
|
@@ -106,6 +120,7 @@ export function reviewUnavailableReason(message) {
|
|
|
106
120
|
if (/review-(patch|file|untracked)-limit|managed-review-patch-too-large/.test(text)) return 'too_large';
|
|
107
121
|
if (/not a git repository|Could not access 'HEAD'|managed-review-patch-unreadable/i.test(text)) return 'not_git';
|
|
108
122
|
if (/managed-helper-limit|helper_run_limit_reached/.test(text)) return 'limit';
|
|
123
|
+
if (/managed-runtime-changed/.test(text)) return 'runtime_changed';
|
|
109
124
|
return 'failed';
|
|
110
125
|
}
|
|
111
126
|
|
|
@@ -7,7 +7,7 @@ const quote = value => "'" + value.replaceAll("'", "'\"'\"'") + "'";
|
|
|
7
7
|
// Only fixed runtime snapshot reads preserve provenance. JSON tool arguments
|
|
8
8
|
// cannot forge this identity; freezing prevents substitution before dispatch.
|
|
9
9
|
export function invokeReviewSnapshot(boundary, mode, extra = []) {
|
|
10
|
-
if (!['head', 'since', 'branch-base', 'digest', 'file', 'files'].includes(mode)
|
|
10
|
+
if (!['head', 'heads', 'since', 'branch-base', 'digest', 'file', 'files'].includes(mode)
|
|
11
11
|
|| !Array.isArray(extra) || extra.some(value => typeof value !== 'string')) throw Error('review-request-invalid');
|
|
12
12
|
const args = Object.freeze({ command: [process.execPath, worker, mode, ...extra].map(quote).join(' '), timeout: 60 });
|
|
13
13
|
requests.add(args);
|
|
@@ -27,12 +27,40 @@ function repositoryOf(file) {
|
|
|
27
27
|
for (let dir = path.posix.dirname(file); dir !== '.'; dir = path.posix.dirname(dir)) if (fs.existsSync(path.join(dir, '.git'))) return dir;
|
|
28
28
|
return null;
|
|
29
29
|
}
|
|
30
|
+
// The repositories below a folder that is none itself (clones side by side),
|
|
31
|
+
// relative to the folder. A repository's own folders are not searched.
|
|
32
|
+
const SEARCH_DEPTH = 3, MAX_REPOSITORIES = 64, NOT_SEARCHED = new Set(['node_modules', 'vendor', 'dist', 'build', 'target']);
|
|
33
|
+
function childRepositories() {
|
|
34
|
+
const found = [];
|
|
35
|
+
const search = (dir, depth) => {
|
|
36
|
+
let entries; try { entries = fs.readdirSync(dir || '.', { withFileTypes: true }); } catch { return; }
|
|
37
|
+
for (const entry of entries.sort((a, b) => a.name < b.name ? -1 : 1)) {
|
|
38
|
+
if (!entry.isDirectory() || entry.name.startsWith('.') || NOT_SEARCHED.has(entry.name) || found.length >= MAX_REPOSITORIES) continue;
|
|
39
|
+
const child = dir ? `${dir}/${entry.name}` : entry.name;
|
|
40
|
+
if (fs.existsSync(path.join(child, '.git'))) found.push(child);
|
|
41
|
+
else if (depth < SEARCH_DEPTH) search(child, depth + 1);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
search('', 1);
|
|
45
|
+
return found;
|
|
46
|
+
}
|
|
47
|
+
const repoGit = (repository, args) => git(['-C', repository, ...args]);
|
|
48
|
+
const repoHead = repository => { try { return repoGit(repository, ['rev-parse', '--verify', '-q', 'HEAD^{commit}']).trim(); } catch { return ''; } };
|
|
30
49
|
const mode = process.argv[2];
|
|
31
50
|
// The arguments after the mode: a scope file, then the commit the patch starts
|
|
32
51
|
// from (the turn's starting HEAD; commits made since are part of the change).
|
|
33
52
|
// While HEAD has not moved, the patch is the plain one against HEAD.
|
|
34
53
|
const head = () => { try { return git(['rev-parse', '--verify', '-q', 'HEAD^{commit}']).trim(); } catch { return ''; } };
|
|
35
54
|
if (mode === 'head') { process.stdout.write(head() || 'none'); process.exit(0); }
|
|
55
|
+
// "heads": where each repository below the folder stands, so a commit made
|
|
56
|
+
// during the turn stays part of its change, as `base` does in one repository.
|
|
57
|
+
if (mode === 'heads') {
|
|
58
|
+
const heads = insideWorkTree() ? {} : Object.fromEntries(childRepositories().map(r => [r, repoHead(r)]).filter(([, sha]) => sha));
|
|
59
|
+
const name = `heads-${randomUUID()}.json`, data = Buffer.from(JSON.stringify(heads));
|
|
60
|
+
fs.writeFileSync(path.join(os.tmpdir(), name), data, { mode: 0o600, flag: 'wx' });
|
|
61
|
+
process.stdout.write(`${sha256(data)} ${data.length} ${name}`);
|
|
62
|
+
process.exit(0);
|
|
63
|
+
}
|
|
36
64
|
// "since <ref>": the commit a reference names, when HEAD descends from it (a
|
|
37
65
|
// review of the commits made since); "branch-base": where this branch left
|
|
38
66
|
// the default branch (or, on it, its upstream), when commits came since.
|
|
@@ -53,19 +81,29 @@ if (mode === 'branch-base') {
|
|
|
53
81
|
}
|
|
54
82
|
const extra = process.argv.slice(3), given = extra.find(a => /^[0-9a-f]{40}([0-9a-f]{24})?$/.test(a));
|
|
55
83
|
const base = given && given !== head() ? given : 'HEAD';
|
|
84
|
+
// `bases-….json` (from "heads"): the commit each repository below the folder
|
|
85
|
+
// started the turn on; a repository not named is compared with its HEAD.
|
|
86
|
+
const basesName = extra.find(a => a.startsWith('bases-'));
|
|
87
|
+
if (basesName !== undefined && !/^bases-[0-9a-f-]{36}\.json$/.test(basesName)) throw Error('review-scope-invalid');
|
|
88
|
+
const bases = basesName ? JSON.parse(fs.readFileSync(path.join(os.tmpdir(), basesName), 'utf8')) : {};
|
|
89
|
+
const baseOf = repository => /^[0-9a-f]{40}([0-9a-f]{24})?$/.test(bases[repository] ?? '') ? bases[repository] : 'HEAD';
|
|
56
90
|
// "files": what each changed or new file holds now (a hash, "deleted"), so
|
|
57
91
|
// the runtime can tell which files one conversation's command changed.
|
|
58
92
|
if (mode === 'files') {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
93
|
+
// In a folder of several repositories each one answers for its own files,
|
|
94
|
+
// named from the folder. Without this a command's changes there have no
|
|
95
|
+
// owner and a review of them cannot run (seen 2026-10-30: `not_git`).
|
|
96
|
+
const repositories = insideWorkTree() ? [] : childRepositories();
|
|
97
|
+
const listed = (run, from, prefix) => [...run(['diff', '--name-only', '--no-renames', '--relative', '-z', from, '--', '.']).split('\0'), ...run(['ls-files', '--others', '--exclude-standard', '-z']).split('\0')].filter(Boolean).map(name => prefix + name);
|
|
98
|
+
const all = repositories.length ? repositories.flatMap(r => { try { return listed(args => repoGit(r, args), baseOf(r), `${r}/`); } catch { return []; } }) : listed(git, base, '');
|
|
99
|
+
if (all.length > MAX_UNTRACKED * 4) throw Error('review-untracked-limit');
|
|
62
100
|
const print = name => {
|
|
63
101
|
let stat; try { stat = fs.lstatSync(name); } catch { return 'deleted'; }
|
|
64
102
|
if (stat.isSymbolicLink()) return 'link:' + sha256(fs.readlinkSync(name));
|
|
65
103
|
if (stat.isDirectory()) return 'dir:' + sha256(fs.readdirSync(name).sort().join('\0'));
|
|
66
104
|
return stat.size > HASHED_FILE ? `size:${stat.size}:${stat.mtimeMs}` : sha256(fs.readFileSync(name)) + ':' + (stat.mode & 0o777);
|
|
67
105
|
};
|
|
68
|
-
const files = Object.fromEntries([...new Set(
|
|
106
|
+
const files = Object.fromEntries([...new Set(all)].sort().map(name => [name, print(name)]));
|
|
69
107
|
const name = `files-${randomUUID()}.json`, data = Buffer.from(JSON.stringify(files));
|
|
70
108
|
fs.writeFileSync(path.join(os.tmpdir(), name), data, { mode: 0o600, flag: 'wx' });
|
|
71
109
|
process.stdout.write(`${sha256(data)} ${data.length} ${name}`);
|
|
@@ -73,20 +111,23 @@ if (mode === 'files') {
|
|
|
73
111
|
}
|
|
74
112
|
// A scope (a file the runtime wrote in this boundary's temporary directory):
|
|
75
113
|
// only these paths, the ones this conversation changed, make the patch.
|
|
76
|
-
const scopeName = extra.find(a => a !== given);
|
|
114
|
+
const scopeName = extra.find(a => a !== given && a !== basesName);
|
|
77
115
|
if (scopeName !== undefined && !/^scope-[0-9a-f-]{36}\.json$/.test(scopeName)) throw Error('review-scope-invalid');
|
|
78
116
|
const scope = scopeName ? new Set(JSON.parse(fs.readFileSync(path.join(os.tmpdir(), scopeName), 'utf8'))) : null;
|
|
79
117
|
if (scope && [...scope].some(p => typeof p !== 'string' || !p || path.isAbsolute(p) || p.split('/').includes('..'))) throw Error('review-scope-invalid');
|
|
80
118
|
// A folder that is not a repository but holds several (clones side by side):
|
|
81
119
|
// each changed path is diffed in the repository that holds it, against that
|
|
82
120
|
// repository's HEAD, with its paths kept relative to the folder. A path no
|
|
83
|
-
// repository holds is named, not read. With no scope
|
|
84
|
-
//
|
|
121
|
+
// repository holds is named, not read. With no scope, every repository's
|
|
122
|
+
// changes make the patch; a conversation with no change yet has an empty one.
|
|
85
123
|
const repositories = [], outside = [];
|
|
86
124
|
let patch = '', paths;
|
|
87
|
-
|
|
125
|
+
const several = insideWorkTree() ? [] : childRepositories();
|
|
126
|
+
if (scope && !scope.size && several.length) paths = [];
|
|
127
|
+
else if ((scope ? scope.size : several.length) && !insideWorkTree()) {
|
|
88
128
|
const groups = new Map();
|
|
89
|
-
for (const
|
|
129
|
+
if (!scope) for (const repository of several) groups.set(repository, ['.']);
|
|
130
|
+
for (const file of [...(scope ?? [])].sort()) {
|
|
90
131
|
const repository = repositoryOf(file);
|
|
91
132
|
if (repository === null) { outside.push(file); continue; }
|
|
92
133
|
if (!groups.has(repository)) groups.set(repository, []);
|
|
@@ -95,9 +136,9 @@ if (scope && scope.size && !insideWorkTree()) {
|
|
|
95
136
|
if (!groups.size) throw Error('managed-review-patch-unreadable: not a git repository');
|
|
96
137
|
paths = [];
|
|
97
138
|
for (const [repository, files] of groups) {
|
|
98
|
-
const spec = files.map(p => ':(literal)' + p), run = args =>
|
|
139
|
+
const spec = files.map(p => p === '.' ? '.' : ':(literal)' + p), run = args => repoGit(repository, args);
|
|
99
140
|
repositories.push(repository);
|
|
100
|
-
patch += run(['diff', '--binary', '--no-ext-diff', '--no-textconv', '--no-renames', `--src-prefix=a/${repository}/`, `--dst-prefix=b/${repository}/`,
|
|
141
|
+
patch += run(['diff', '--binary', '--no-ext-diff', '--no-textconv', '--no-renames', `--src-prefix=a/${repository}/`, `--dst-prefix=b/${repository}/`, baseOf(repository), '--', ...spec]);
|
|
101
142
|
paths.push(...run(['ls-files', '--others', '--exclude-standard', '-z', '--', ...spec]).split('\0').filter(Boolean).map(p => `${repository}/${p}`));
|
|
102
143
|
}
|
|
103
144
|
paths.sort();
|
|
@@ -20,7 +20,7 @@ import { runHelper } from './helper-run.mjs';
|
|
|
20
20
|
import { askTool } from './member-questions.mjs';
|
|
21
21
|
import { setTimeout as sleep } from 'node:timers/promises';
|
|
22
22
|
import { stageCompaction, compactEarly, backOffFailedCompaction, trackCompaction, boundRequestImages } from './compaction.mjs';
|
|
23
|
-
import { readPatchDigest, readPatchSnapshot, readChangedFiles, readHead } from './patch-snapshot.mjs';
|
|
23
|
+
import { readPatchDigest, readPatchSnapshot, readChangedFiles, readHead, readRepositoryHeads } from './patch-snapshot.mjs';
|
|
24
24
|
import { memberTurn } from './continuation.mjs';
|
|
25
25
|
import { workflowPolicy, repositoryChecks, sessionPlanTool, currentPlan, workflowPrompt, RunProcess, completionGate, pendingBaseline, pendingPaths, pendingHead, processEditTools } from './workflow.mjs';
|
|
26
26
|
import { workflowContextTool, workflowContextExtension } from './workflow-context.mjs';
|
|
@@ -35,7 +35,7 @@ const PROVIDER = 'agent_watch_managed';
|
|
|
35
35
|
export const HELPER_BEAT_MS = 60_000;
|
|
36
36
|
// Who the agent is: the model account may put another product's name in an
|
|
37
37
|
// earlier system line; the member is talking to Piagent.
|
|
38
|
-
const BASE_PROMPT = 'You are Piagent, the company coding assistant. If an earlier system line gives you another product name, that line belongs to the model account: when asked who you are, say you are Piagent, the company coding assistant. Work directly on the user request. Do not require task contracts or workflow commands. ' + UNTRUSTED_RULE + ' Never claim a stale review covers changed code. When the request leaves a decision open that changes the result and you cannot settle it from the code or a subagent, ask the member with ask_user before you act on a guess. To
|
|
38
|
+
const BASE_PROMPT = 'You are Piagent, the company coding assistant. If an earlier system line gives you another product name, that line belongs to the model account: when asked who you are, say you are Piagent, the company coding assistant. Work directly on the user request. Do not require task contracts or workflow commands. ' + UNTRUSTED_RULE + ' Never claim a stale review covers changed code. When the request leaves a decision open that changes the result and you cannot settle it from the code or a subagent, ask the member with ask_user before you act on a guess. To send the member a picture or a file in the chat, keep it in the project and write  in your answer, such as  or : the web view draws a picture in place and shows any other file as a card to open or download; say the path too, for the Terminal. To point at code, link it, such as [app.ts:42](src/app.ts:42): the web view opens it at the line.';
|
|
39
39
|
// Folders outside the project the member points to (an @ mention, a folder
|
|
40
40
|
// they name) are readable for reference; the sandbox keeps them read-only.
|
|
41
41
|
const referencePrompt = home => ` The member may point you to folders outside the project for reference (an @path mention or a folder they name): read them with read, ls, grep and find. They are read-only: changes go in the project only. In those tools ~ is the member's home folder (${home}); in bash ~ is a private empty folder, so use absolute paths there.`;
|
|
@@ -264,7 +264,7 @@ export class ManagedSession {
|
|
|
264
264
|
// during the turn is still its change.
|
|
265
265
|
const pending = pendingBaseline(manager);
|
|
266
266
|
self.ownPaths = new Set(pending !== null ? pendingPaths(manager) : []);
|
|
267
|
-
self.base = (pending !== null ? pendingHead(manager) : null) ?? await readHead(self.boundary);
|
|
267
|
+
self.base = (pending !== null ? pendingHead(manager) : null) ?? await readHead(self.boundary); self.repositoryBases = await readRepositoryHeads(self.boundary);
|
|
268
268
|
self.run.startDigest = pending ?? await self.digest(); await openRound(self, manager, text); // sends what waited; a new conversation gets the team brief
|
|
269
269
|
} catch (error) { self.preflight = describeFailure('main', failureText(error)); }
|
|
270
270
|
self.activePrompt = true;
|
|
@@ -430,14 +430,14 @@ export class ManagedSession {
|
|
|
430
430
|
// Whether this conversation changed code, and whether a check ran on it,
|
|
431
431
|
// look only at the files it changed: another conversation (or the member)
|
|
432
432
|
// changing other files in the same folder is not this run's change.
|
|
433
|
-
async changeDigest() { return readPatchDigest(this.boundary, this.ownPaths, this.base); }
|
|
433
|
+
async changeDigest() { return readPatchDigest(this.boundary, this.ownPaths, this.base, this.repositoryBases); }
|
|
434
434
|
async folderDigest() { try { return await readPatchDigest(this.boundary); } catch { return null; } }
|
|
435
435
|
// What a reviewer reads: this conversation's files; before it changed any,
|
|
436
436
|
// the whole folder (the member's own changes, asked to be reviewed).
|
|
437
437
|
reviewScope() { return this.ownPaths.size ? this.ownPaths : null; }
|
|
438
|
-
async patchSnapshot() { return readPatchSnapshot(this.boundary, this.reviewScope(), this.base); }
|
|
439
|
-
async patchDigest() { return readPatchDigest(this.boundary, this.reviewScope(), this.base); }
|
|
440
|
-
async changedFiles() { try { return await readChangedFiles(this.boundary, this.base); } catch { return null; } }
|
|
438
|
+
async patchSnapshot() { return readPatchSnapshot(this.boundary, this.reviewScope(), this.base, this.repositoryBases); }
|
|
439
|
+
async patchDigest() { return readPatchDigest(this.boundary, this.reviewScope(), this.base, this.repositoryBases); }
|
|
440
|
+
async changedFiles() { try { return await readChangedFiles(this.boundary, this.base, this.repositoryBases); } catch { return null; } }
|
|
441
441
|
// A write or edit names its file; a command, the files it changed.
|
|
442
442
|
claimPath(raw) {
|
|
443
443
|
if (typeof raw !== 'string' || !raw) return;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { reviewUnavailableReason } from './patch-snapshot.mjs';
|
|
4
|
-
import { failureCode } from '../runtime/managed-failure.mjs';
|
|
4
|
+
import { failureCode, unnamedFailureText } from '../runtime/managed-failure.mjs';
|
|
5
5
|
import { TsDiagnostics } from './ts-diagnostics.mjs';
|
|
6
6
|
import { checkpointsOf } from './checkpoints.mjs';
|
|
7
7
|
import { discoverDeliveryContext } from './delivery-context.mjs';
|
|
@@ -367,8 +367,9 @@ export async function completionGate(managed, run, signal) {
|
|
|
367
367
|
try { await managed.delegate({ role: 'review', task: brief }, signal, { harness: true }); }
|
|
368
368
|
catch (error) {
|
|
369
369
|
run.reviewUnavailable = !stopped(); run.reviewUnavailableReason = reviewUnavailableReason(error?.message); interrupted = stopped();
|
|
370
|
-
// Why the review could not run
|
|
371
|
-
|
|
370
|
+
// Why the review could not run: its code, and the redacted words of a
|
|
371
|
+
// failure no code names (the report says only "failed").
|
|
372
|
+
if (run.reviewUnavailable) try { session.sessionManager.appendCustomEntry('agent-watch-failure-detail', { role: 'review', code: failureCode(error?.message), ...(unnamedFailureText(error?.message) ? { detail: unnamedFailureText(error?.message) } : {}) }); } catch { /* the report still says it */ }
|
|
372
373
|
break;
|
|
373
374
|
} finally { managed.reviewFocus = null; }
|
|
374
375
|
continue;
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
//
|
|
3
3
|
// `piagent reload` (and `piagent-update` once it installed a release) writes
|
|
4
4
|
// one request file. Each Terminal started before that request takes it when it
|
|
5
|
-
// is idle: no run going, nothing queued, nothing typed in the editor
|
|
6
|
-
// progress is never cut: the footer says the reload
|
|
7
|
-
// the run
|
|
5
|
+
// is idle: no run going, nothing queued, nothing typed in the editor, and quiet
|
|
6
|
+
// for a moment. A run in progress is never cut: the footer says the reload
|
|
7
|
+
// waits, and it happens once the run has settled. How a Terminal reloads is its own: the personal Terminal
|
|
8
8
|
// runs Pi's /reload (Piagent's extensions are read again from disk), the
|
|
9
9
|
// company Terminal starts again in place on the same conversation, since its
|
|
10
10
|
// company code is loaded once per process.
|
|
@@ -14,7 +14,15 @@ import path from "node:path";
|
|
|
14
14
|
|
|
15
15
|
const REQUEST_FILE = "piagent-reload.json";
|
|
16
16
|
const POLL_MS = 2_000;
|
|
17
|
+
// A member who stops a run with Esc usually types the next message right away:
|
|
18
|
+
// a Terminal that starts again in that moment loses what they type.
|
|
19
|
+
const QUIET_MS = 10_000;
|
|
17
20
|
const STATUS_KEY = "piagent-reload";
|
|
21
|
+
// Per process, not per extension runtime: /reload runs this module again, and a
|
|
22
|
+
// request sent before it (the company Terminal's /reload loads no company code)
|
|
23
|
+
// must still be taken, while one already taken must not reload twice.
|
|
24
|
+
const APPLIED_AT = "PIAGENT_RELOAD_APPLIED_AT";
|
|
25
|
+
const processStartedAt = () => Date.now() - process.uptime() * 1000;
|
|
18
26
|
|
|
19
27
|
const requestPath = (home) => path.join(home ?? os.homedir(), ".pi", REQUEST_FILE);
|
|
20
28
|
|
|
@@ -47,8 +55,8 @@ function idle(ctx) {
|
|
|
47
55
|
|
|
48
56
|
// `apply(ctx, request)` reloads this Terminal; it runs at most once per
|
|
49
57
|
// process. Only Terminals with a UI take requests: scripted runs keep theirs.
|
|
50
|
-
export function watchTerminalReload(pi, { apply, home, startedAt =
|
|
51
|
-
let ctx = null, applied = false, waiting = false, timer = null;
|
|
58
|
+
export function watchTerminalReload(pi, { apply, home, startedAt = processStartedAt(), pollMs = POLL_MS, quietMs = QUIET_MS }) {
|
|
59
|
+
let ctx = null, applied = false, waiting = false, timer = null, busyAt = 0;
|
|
52
60
|
// A ctx kept from an earlier session (replaced, reloaded) throws on any use:
|
|
53
61
|
// a check that meets one does nothing, and the next session_start brings a new one.
|
|
54
62
|
const check = async () => {
|
|
@@ -56,17 +64,19 @@ export function watchTerminalReload(pi, { apply, home, startedAt = Date.now(), p
|
|
|
56
64
|
try {
|
|
57
65
|
if (applied || !ctx?.hasUI) return;
|
|
58
66
|
request = readReloadRequest(home);
|
|
59
|
-
if (!request || request.at <= startedAt) return;
|
|
60
|
-
|
|
61
|
-
|
|
67
|
+
if (!request || request.at <= Math.max(startedAt, Number(process.env[APPLIED_AT]) || 0)) return;
|
|
68
|
+
const quiet = idle(ctx);
|
|
69
|
+
if (!quiet) busyAt = Date.now();
|
|
70
|
+
if (!quiet || Date.now() - busyAt < quietMs) {
|
|
71
|
+
if (!waiting) { waiting = true; ctx.ui?.setStatus?.(STATUS_KEY, `⟳ Nạp Piagent${request.version ? ` ${request.version}` : " mới"} khi rảnh`); }
|
|
62
72
|
return;
|
|
63
73
|
}
|
|
64
|
-
applied = true;
|
|
74
|
+
applied = true; process.env[APPLIED_AT] = String(request.at);
|
|
65
75
|
ctx.ui?.setStatus?.(STATUS_KEY, undefined);
|
|
66
76
|
} catch { return; }
|
|
67
77
|
try { await apply(ctx, request); }
|
|
68
78
|
catch (error) {
|
|
69
|
-
applied = false;
|
|
79
|
+
applied = false; delete process.env[APPLIED_AT];
|
|
70
80
|
try { ctx.ui?.notify?.(`Chưa nạp được Piagent mới: ${error instanceof Error ? error.message : String(error)}`, "warning"); } catch { /* the session is gone */ }
|
|
71
81
|
}
|
|
72
82
|
};
|
|
@@ -75,9 +85,9 @@ export function watchTerminalReload(pi, { apply, home, startedAt = Date.now(), p
|
|
|
75
85
|
keep(event, next);
|
|
76
86
|
if (!timer) { timer = setInterval(() => { void check(); }, pollMs); timer.unref?.(); }
|
|
77
87
|
});
|
|
78
|
-
pi.on("agent_start", keep);
|
|
79
|
-
// The
|
|
80
|
-
pi.on("agent_settled", (event, next) => { keep(event, next);
|
|
88
|
+
pi.on("agent_start", (event, next) => { keep(event, next); busyAt = Date.now(); });
|
|
89
|
+
// The next check after the quiet time takes a request that waits.
|
|
90
|
+
pi.on("agent_settled", (event, next) => { keep(event, next); busyAt = Date.now(); });
|
|
81
91
|
pi.on("session_shutdown", () => { if (timer) clearInterval(timer); timer = null; ctx = null; });
|
|
82
92
|
return { check };
|
|
83
93
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e,r as t}from"./rolldown-runtime-hePW80VL.js";import{
|
|
1
|
+
import{n as e,r as t}from"./rolldown-runtime-hePW80VL.js";import{An as n,Rn as r}from"./vendor-DNZbaWhT.js";import{i,n as a}from"./ui-preferences-OfS30-ZV.js";import{B as o,x as s}from"./api-BQS4Dtrr.js";import{c}from"./view-model-CWNiS4JN.js";var l=t(r(),1);function u(e,t=`vi`){let n=e.state===`running`?`running`:e.state===`failed`?`failed`:`passed`,r=n===`running`?t===`vi`?`đang chạy`:`running`:n===`failed`?t===`vi`?`thất bại`:`failed`:t===`vi`?`hoàn tất`:`completed`,i=e.startedAt??e.finishedAt??``;return{activityRef:`live.${e.toolCallRef}`,kind:[`bash`,`shell`,`exec`].includes(e.toolLabel)?`command`:`tool`,state:n,label:`${e.toolLabel} ${r}`,preview:t===`vi`?`Cập nhật trực tiếp từ Gateway`:`Live update from the Gateway`,toolCallId:e.toolCallRef,toolName:e.toolLabel,commandDigest:null,logRef:null,exitCode:null,exitCodeExact:!1,startedAt:i,finishedAt:n===`running`?null:e.finishedAt??i}}function d(e,t=`vi`){let n=e.state===`failed`?t===`vi`?`Lượt chạy gặp lỗi`:`Operation failed`:e.state===`blocked`?t===`vi`?`Lượt chạy bị chặn`:`Operation blocked`:e.state===`aborted`?t===`vi`?`Lượt chạy đã dừng`:`Operation stopped`:t===`vi`?`Chưa xác định kết quả lượt chạy`:`Operation outcome unknown`;return{activityRef:e.activityRef,kind:`system`,state:e.state,label:n,preview:e.reasonCode,toolCallId:null,toolName:null,commandDigest:null,logRef:null,exitCode:null,exitCodeExact:!1,startedAt:e.settledAt,finishedAt:e.settledAt}}function f(e,t,n,r=`vi`,i){let a=[...e,...t],o=new Set(a.map(e=>e.activityRef)),s=n.filter(e=>!o.has(e.activityRef)).map(e=>d(e,r));if(i===void 0)return{rows:[...e,...s,...t],terminalCount:s.length,runningCount:e.length};let c=i.filter(e=>e.state===`running`).map(e=>u(e,r));return{rows:[...c,...s,...t],terminalCount:s.length,runningCount:c.length}}function p(e,t=`vi`){return e.state===`passed`&&/ recovered$/i.test(e.label)?t===`vi`?`Đã khôi phục`:`Recovered`:e.state===`passed`&&/ warning$/i.test(e.label)?t===`vi`?`Đã xử lý cảnh báo`:`Handled warning`:e.state===`passed`&&e.exitCodeExact?`Pass · exit ${e.exitCode??0}`:e.state===`failed`&&e.exitCodeExact?`Fail · exit ${e.exitCode??`?`}`:e.state===`blocked`?`Blocked`:e.state===`aborted`?t===`vi`?`Đã dừng`:`Stopped`:e.state===`unknown`?t===`vi`?`Chưa xác định`:`Unknown`:e.state===`failed`?t===`vi`?`Thất bại`:`Failed`:e.state===`running`?t===`vi`?`Đang chạy`:`Running`:e.state.replaceAll(`-`,` `)}function m(e,t=`vi`){let n=Date.parse(e.startedAt),r=e.finishedAt?Date.parse(e.finishedAt):Date.now();if(!Number.isFinite(n)||!Number.isFinite(r)||r<n)return t===`vi`?`Thời gian chưa xác định`:`Unknown duration`;let i=Math.round((r-n)/1e3);return i<60?`${i}s`:`${Math.floor(i/60)}m ${i%60}s`}var h=e({ActivityPanel:()=>v}),g=n();function _({activity:e,preview:t,onPreview:n,locale:r}){return(0,g.jsxs)(`article`,{className:`activity-row`,children:[(0,g.jsx)(`span`,{className:`activity-state tone-${c(e.state)}`,"aria-label":e.state}),(0,g.jsxs)(`div`,{className:`activity-copy`,children:[(0,g.jsxs)(`div`,{children:[(0,g.jsx)(`strong`,{children:e.label}),(0,g.jsx)(`span`,{children:e.kind})]}),(0,g.jsx)(`p`,{children:e.preview||a(r,`Không có mô tả ngắn`,`No short description`)}),t&&(0,g.jsxs)(`div`,{className:`log-preview ${t.state===`error`?`error-state`:``}`,children:[t.state===`loading`?a(r,`Đang tải log ngắn…`,`Loading log preview…`):t.state===`error`?a(r,`Log preview không còn sẵn sàng.`,`Log preview is no longer available.`):t.value?.preview??`${a(r,`Không có nội dung log`,`No log content`)} · ${t.value?.reasonCode??`unavailable`}`,t.value?.truncated&&(0,g.jsx)(`small`,{children:a(r,`Preview đã được rút gọn`,`Preview truncated`)})]})]}),(0,g.jsxs)(`div`,{className:`activity-result`,children:[(0,g.jsx)(`strong`,{children:p(e,r)}),(0,g.jsx)(`span`,{children:m(e,r)}),e.logRef&&n&&(0,g.jsx)(`button`,{type:`button`,onClick:n,"aria-expanded":!!t,children:t?a(r,`Ẩn log`,`Hide log`):a(r,`Xem log`,`View log`)})]})]})}function v({snapshot:e,sessionRef:t,terminalActivities:n=[],liveActivities:r}){let{locale:c}=i(),[u,d]=(0,l.useState)({}),p=e?.activity.running??[],m=e?.activity.recent??[],{rows:h,terminalCount:v,runningCount:y}=f(p,m,n,c,r),b=e=>{if(u[e.activityRef]){d(t=>({...t,[e.activityRef]:void 0}));return}d(t=>({...t,[e.activityRef]:{state:`loading`}})),(t?o(t,e.activityRef):s(e.activityRef)).then(t=>d(n=>({...n,[e.activityRef]:{state:`ready`,value:t}}))).catch(()=>d(t=>({...t,[e.activityRef]:{state:`error`}})))};return(0,g.jsxs)(`section`,{className:`activity-panel surface`,"aria-labelledby":`activity-title`,children:[(0,g.jsxs)(`header`,{className:`panel-heading`,children:[(0,g.jsxs)(`div`,{children:[(0,g.jsx)(`p`,{className:`section-kicker`,children:`Activity`}),(0,g.jsx)(`h2`,{id:`activity-title`,children:a(c,`Tool, command và kết quả lượt chạy`,`Tools, commands, and operation outcomes`)})]}),(0,g.jsxs)(`div`,{children:[(0,g.jsxs)(`span`,{className:`running-count`,children:[y,` `,a(c,`đang chạy`,`running`)]}),(0,g.jsxs)(`span`,{children:[m.length+v,` `,a(c,`gần đây`,`recent`)]})]})]}),(0,g.jsxs)(`div`,{className:`activity-list`,children:[h.map(e=>(0,g.jsx)(_,{activity:e,preview:u[e.activityRef],onPreview:e.logRef?()=>b(e):void 0,locale:c},e.activityRef)),h.length===0&&(0,g.jsx)(`div`,{className:`activity-empty`,children:a(c,`Chưa có tool, command hoặc kết quả lượt chạy nào trong timeline hiện tại.`,`No tools, commands, or operation outcomes in the current timeline.`)})]}),e?.activity.page.truncated&&(0,g.jsx)(`footer`,{className:`panel-footnote`,children:a(c,`Timeline đã được rút gọn theo giới hạn local.`,`The timeline was truncated to local limits.`)})]})}export{h as n,v as t};
|