@piagent/platform 1.14.0 → 1.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/README.md +1 -1
- package/README.vi.md +1 -1
- package/docs/command-reference-vietnamese.md +2 -2
- package/docs/distribution-standard.md +6 -6
- package/docs/languages.json +2 -2
- package/docs/operator-manual-vietnamese.md +7 -7
- package/docs/publishing-for-teams.md +2 -0
- package/docs/release-install-policy.md +2 -2
- package/docs/team-onboarding.md +2 -2
- package/docs-site/architecture.html +1 -1
- package/docs-site/benchmark.html +1 -1
- package/docs-site/commands.html +1 -1
- package/docs-site/content/en/index.html +4 -4
- package/docs-site/content/en/whats-new.html +24 -0
- package/docs-site/content/index.html +4 -4
- package/docs-site/content/whats-new.html +24 -0
- package/docs-site/context-engine.html +1 -1
- package/docs-site/ecosystem.html +1 -1
- package/docs-site/en/architecture.html +1 -1
- package/docs-site/en/benchmark.html +1 -1
- package/docs-site/en/commands.html +1 -1
- package/docs-site/en/context-engine.html +1 -1
- package/docs-site/en/ecosystem.html +1 -1
- package/docs-site/en/index.html +5 -5
- package/docs-site/en/mcp-approval.html +1 -1
- package/docs-site/en/mcp-auth.html +1 -1
- package/docs-site/en/mcp-servers.html +1 -1
- package/docs-site/en/mcp.html +1 -1
- package/docs-site/en/permissions.html +1 -1
- package/docs-site/en/profiles.html +1 -1
- package/docs-site/en/quickstart.html +1 -1
- package/docs-site/en/runtime.html +1 -1
- package/docs-site/en/scope.html +1 -1
- package/docs-site/en/security.html +1 -1
- package/docs-site/en/team.html +1 -1
- package/docs-site/en/whats-new.html +25 -1
- package/docs-site/en/windows.html +1 -1
- package/docs-site/en/workflows.html +1 -1
- package/docs-site/index.html +5 -5
- package/docs-site/mcp-approval.html +1 -1
- package/docs-site/mcp-auth.html +1 -1
- package/docs-site/mcp-servers.html +1 -1
- package/docs-site/mcp.html +1 -1
- package/docs-site/permissions.html +1 -1
- package/docs-site/profiles.html +1 -1
- package/docs-site/quickstart.html +1 -1
- package/docs-site/runtime.html +1 -1
- package/docs-site/scope.html +1 -1
- package/docs-site/security.html +1 -1
- package/docs-site/team.html +1 -1
- package/docs-site/whats-new.html +25 -1
- package/docs-site/windows.html +1 -1
- package/docs-site/workflows.html +1 -1
- package/evals/private-holdout-v1/public-exposure.v2.json +2 -2
- package/package.json +1 -1
- package/packages/piagent-core/managed/broker-client.mjs +7 -1
- package/packages/piagent-core/managed/call-purpose.mjs +39 -0
- package/packages/piagent-core/managed/helper-roles.mjs +7 -3
- package/packages/piagent-core/managed/helper-run.mjs +8 -5
- package/packages/piagent-core/managed/patch-snapshot.mjs +29 -7
- package/packages/piagent-core/managed/request-stream.mjs +4 -1
- package/packages/piagent-core/managed/review-snapshot-worker.mjs +27 -3
- package/packages/piagent-core/managed/sandbox-diagnostic.mjs +7 -1
- package/packages/piagent-core/managed/session.mjs +42 -14
- package/packages/piagent-core/managed/tool-boundary.mjs +5 -1
- package/packages/piagent-core/managed/web-search.mjs +1 -1
- package/packages/piagent-core/managed/workflow.mjs +19 -5
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/runtime/managed-failure.mjs +9 -1
- package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-CyT2G1H2.js → MarkdownMessage-DjA3OGyI.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/{index-BBEa7Ger.js → index-bE2DKmc1.js} +12 -12
- package/packages/piagent-webui/dist/client/index.html +1 -1
- package/packages/piagent-webui/gateway/pi-host.ts +16 -2
- package/packages/piagent-webui/gateway/session-command-controller.ts +11 -0
- package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +2 -2
- package/packages/piagent-webui/ownership/session-lease-store.ts +20 -1
- package/scripts/piagent-cli.mjs +5 -1
- package/scripts/update-global.mjs +7 -1
package/docs-site/en/team.html
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<span>Pi Agent Platform</span>
|
|
31
31
|
</a>
|
|
32
32
|
<div class="top-actions">
|
|
33
|
-
<span class="pill"><span class="pill-dot"></span>v1.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.15.1 docs · EN</span>
|
|
34
34
|
<nav class="language-switch" aria-label="Language">
|
|
35
35
|
<a class="language-option" href="/team" hreflang="vi" lang="vi">VI</a>
|
|
36
36
|
<a class="language-option current" href="/en/team" hreflang="en" lang="en" aria-current="true">EN</a>
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<span>Pi Agent Platform</span>
|
|
31
31
|
</a>
|
|
32
32
|
<div class="top-actions">
|
|
33
|
-
<span class="pill"><span class="pill-dot"></span>v1.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.15.1 docs · EN</span>
|
|
34
34
|
<nav class="language-switch" aria-label="Language">
|
|
35
35
|
<a class="language-option" href="/whats-new" hreflang="vi" lang="vi">VI</a>
|
|
36
36
|
<a class="language-option current" href="/en/whats-new" hreflang="en" lang="en" aria-current="true">EN</a>
|
|
@@ -104,6 +104,30 @@
|
|
|
104
104
|
<h1>1.9.0: ask in plain language, the team's Harness checks</h1>
|
|
105
105
|
<p>The task contract and workflow commands are gone. Company mode asks for checks and a review on its own; Stop, network commands on approval and clearer approval cards.</p>
|
|
106
106
|
</div>
|
|
107
|
+
<section class="section">
|
|
108
|
+
<p class="section-kicker">v1.15.1 · 2026-10-07</p>
|
|
109
|
+
<h2 id="v1-15-1">Studio sees what each model call is for</h2>
|
|
110
|
+
<p class="section-lead">Every company model call tells Studio what it is for: the answer to your message, a step after tools (with the tools just run), a turn the harness started, a conversation summary, a subagent's brief (with a one-line title of its job) or a web search. No other prompt text is sent. Studio 0.20.0 shows them in Logs → "Lượt agent".</p>
|
|
111
|
+
<ul>
|
|
112
|
+
<li>Several company conversations in one folder no longer take each other's edits for their own: checks, reviews and the plan guard look only at the files that conversation changed.</li>
|
|
113
|
+
<li>A new company conversation whose runtime fails to open says why, and Archive or Restart work on it without restarting the dashboard.</li>
|
|
114
|
+
<li>A blocked <span class="inline-code">mvnw</span>/<span class="inline-code">gradlew</span> download points to <span class="inline-code">run_with_network</span>; <span class="inline-code">piagent doctor</span> runs <span class="inline-code">piagent-doctor</span>.</li>
|
|
115
|
+
</ul>
|
|
116
|
+
<p>Updating from 1.14.x or 1.15.0 needs no re-import in Agent Watch.</p>
|
|
117
|
+
</section>
|
|
118
|
+
|
|
119
|
+
<section class="section">
|
|
120
|
+
<p class="section-kicker">v1.15.0 · 2026-10-07</p>
|
|
121
|
+
<h2 id="v1-15-0">A refused subagent says why</h2>
|
|
122
|
+
<p class="section-lead">When Studio refuses a company subagent, Piagent now gives Studio's reason instead of always saying the configuration changed. For example: no company AI account serves this role's model, and an administrator has to link or enable one. Needs Agent Watch 0.15.0; with Studio 0.19.0, a subagent that had no account when the run started takes a model that serves when it is called.</p>
|
|
123
|
+
<ul>
|
|
124
|
+
<li>Long commands that print a lot (test suites, builds) no longer stop with <span class="inline-code">managed-tool-output-too-large</span>.</li>
|
|
125
|
+
<li>A company command that writes to <span class="inline-code">/tmp</span> is pointed to <span class="inline-code">$TMPDIR</span> or the project.</li>
|
|
126
|
+
<li><span class="inline-code">piagent-update</span> started from <span class="inline-code">npm exec</span> installs a lasting helper; on Windows, Pi is found in custom npm prefixes.</li>
|
|
127
|
+
</ul>
|
|
128
|
+
<p>Updating from 1.14.x needs no new import in Agent Watch.</p>
|
|
129
|
+
</section>
|
|
130
|
+
|
|
107
131
|
<section class="section">
|
|
108
132
|
<p class="section-kicker">v1.14.0 · 2026-10-06</p>
|
|
109
133
|
<h2 id="v1-14-0">Updates keep your model choices</h2>
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<span>Pi Agent Platform</span>
|
|
31
31
|
</a>
|
|
32
32
|
<div class="top-actions">
|
|
33
|
-
<span class="pill"><span class="pill-dot"></span>v1.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.15.1 docs · EN</span>
|
|
34
34
|
<nav class="language-switch" aria-label="Language">
|
|
35
35
|
<a class="language-option" href="/windows" hreflang="vi" lang="vi">VI</a>
|
|
36
36
|
<a class="language-option current" href="/en/windows" hreflang="en" lang="en" aria-current="true">EN</a>
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<span>Pi Agent Platform</span>
|
|
31
31
|
</a>
|
|
32
32
|
<div class="top-actions">
|
|
33
|
-
<span class="pill"><span class="pill-dot"></span>v1.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.15.1 docs · EN</span>
|
|
34
34
|
<nav class="language-switch" aria-label="Language">
|
|
35
35
|
<a class="language-option" href="/workflows" hreflang="vi" lang="vi">VI</a>
|
|
36
36
|
<a class="language-option current" href="/en/workflows" hreflang="en" lang="en" aria-current="true">EN</a>
|
package/docs-site/index.html
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<span>Pi Agent Platform</span>
|
|
31
31
|
</a>
|
|
32
32
|
<div class="top-actions">
|
|
33
|
-
<span class="pill"><span class="pill-dot"></span>v1.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.15.1 docs · VI</span>
|
|
34
34
|
<nav class="language-switch" aria-label="Ngôn ngữ">
|
|
35
35
|
<a class="language-option current" href="/" hreflang="vi" lang="vi" aria-current="true">VI</a>
|
|
36
36
|
<a class="language-option" href="/en/" hreflang="en" lang="en">EN</a>
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
<main>
|
|
95
95
|
<div class="content-shell" data-article>
|
|
96
96
|
<section class="hero">
|
|
97
|
-
<p class="eyebrow">Piagent 1.
|
|
97
|
+
<p class="eyebrow">Piagent 1.15.1</p>
|
|
98
98
|
<h1>Coding agent cho cả team, kiểm tra từng bước.</h1>
|
|
99
99
|
<p>
|
|
100
100
|
Một dashboard cho Terminal và trình duyệt. Dùng model bằng tài khoản của bạn, hoặc model của công ty qua Agent Watch và Agent Studio. Guard kiểm tra từng tool call; ở chế độ công ty, Harness của team tự yêu cầu chạy check, cho subagent review và báo kết quả ngay dưới câu trả lời.
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
<div class="hero-actions">
|
|
109
109
|
<a class="button primary" href="/quickstart">Bắt đầu</a>
|
|
110
110
|
<a class="button" href="/ecosystem">Hệ sinh thái</a>
|
|
111
|
-
<a class="button" href="/whats-new">Có gì mới trong 1.
|
|
111
|
+
<a class="button" href="/whats-new">Có gì mới trong 1.15.1</a>
|
|
112
112
|
<a class="button" href="/commands">Xem command</a>
|
|
113
113
|
</div>
|
|
114
114
|
</section>
|
|
@@ -184,9 +184,9 @@ piagent-setup</code></pre>
|
|
|
184
184
|
<h2 id="bat-dau-tu-dau">Bắt đầu từ đâu</h2>
|
|
185
185
|
<div class="card-grid">
|
|
186
186
|
<a class="doc-card" href="/whats-new">
|
|
187
|
-
<span class="card-kicker">Bản 1.
|
|
187
|
+
<span class="card-kicker">Bản 1.15.1</span>
|
|
188
188
|
<h3>Có gì mới</h3>
|
|
189
|
-
<p>
|
|
189
|
+
<p>Studio thấy mỗi call của model dùng để làm gì, nhiều cuộc trò chuyện trong một folder không nhận nhầm thay đổi của nhau; ở 1.15.0: subagent bị từ chối nói đúng lý do; ở 1.14: cập nhật giữ model bạn đã chọn; ở 1.13: cuộc trò chuyện giữ chỗ đang đọc, chữ của agent có định dạng, Piagent trên Windows qua WSL2: sandbox công ty bằng bubblewrap và Agent Watch cho Windows; ở 1.12: @ chọn file và folder trên máy, skill và command dùng bằng /, nhánh cuộc trò chuyện công ty gửi được tin nhắn.</p>
|
|
190
190
|
</a>
|
|
191
191
|
<a class="doc-card" href="/ecosystem">
|
|
192
192
|
<span class="card-kicker">Get started</span>
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<span>Pi Agent Platform</span>
|
|
31
31
|
</a>
|
|
32
32
|
<div class="top-actions">
|
|
33
|
-
<span class="pill"><span class="pill-dot"></span>v1.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.15.1 docs · VI</span>
|
|
34
34
|
<nav class="language-switch" aria-label="Ngôn ngữ">
|
|
35
35
|
<a class="language-option current" href="/mcp-approval" hreflang="vi" lang="vi" aria-current="true">VI</a>
|
|
36
36
|
<a class="language-option" href="/en/mcp-approval" hreflang="en" lang="en">EN</a>
|
package/docs-site/mcp-auth.html
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<span>Pi Agent Platform</span>
|
|
31
31
|
</a>
|
|
32
32
|
<div class="top-actions">
|
|
33
|
-
<span class="pill"><span class="pill-dot"></span>v1.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.15.1 docs · VI</span>
|
|
34
34
|
<nav class="language-switch" aria-label="Ngôn ngữ">
|
|
35
35
|
<a class="language-option current" href="/mcp-auth" hreflang="vi" lang="vi" aria-current="true">VI</a>
|
|
36
36
|
<a class="language-option" href="/en/mcp-auth" hreflang="en" lang="en">EN</a>
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<span>Pi Agent Platform</span>
|
|
31
31
|
</a>
|
|
32
32
|
<div class="top-actions">
|
|
33
|
-
<span class="pill"><span class="pill-dot"></span>v1.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.15.1 docs · VI</span>
|
|
34
34
|
<nav class="language-switch" aria-label="Ngôn ngữ">
|
|
35
35
|
<a class="language-option current" href="/mcp-servers" hreflang="vi" lang="vi" aria-current="true">VI</a>
|
|
36
36
|
<a class="language-option" href="/en/mcp-servers" hreflang="en" lang="en">EN</a>
|
package/docs-site/mcp.html
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<span>Pi Agent Platform</span>
|
|
31
31
|
</a>
|
|
32
32
|
<div class="top-actions">
|
|
33
|
-
<span class="pill"><span class="pill-dot"></span>v1.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.15.1 docs · VI</span>
|
|
34
34
|
<nav class="language-switch" aria-label="Ngôn ngữ">
|
|
35
35
|
<a class="language-option current" href="/mcp" hreflang="vi" lang="vi" aria-current="true">VI</a>
|
|
36
36
|
<a class="language-option" href="/en/mcp" hreflang="en" lang="en">EN</a>
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<span>Pi Agent Platform</span>
|
|
31
31
|
</a>
|
|
32
32
|
<div class="top-actions">
|
|
33
|
-
<span class="pill"><span class="pill-dot"></span>v1.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.15.1 docs · VI</span>
|
|
34
34
|
<nav class="language-switch" aria-label="Ngôn ngữ">
|
|
35
35
|
<a class="language-option current" href="/permissions" hreflang="vi" lang="vi" aria-current="true">VI</a>
|
|
36
36
|
<a class="language-option" href="/en/permissions" hreflang="en" lang="en">EN</a>
|
package/docs-site/profiles.html
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<span>Pi Agent Platform</span>
|
|
31
31
|
</a>
|
|
32
32
|
<div class="top-actions">
|
|
33
|
-
<span class="pill"><span class="pill-dot"></span>v1.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.15.1 docs · VI</span>
|
|
34
34
|
<nav class="language-switch" aria-label="Ngôn ngữ">
|
|
35
35
|
<a class="language-option current" href="/profiles" hreflang="vi" lang="vi" aria-current="true">VI</a>
|
|
36
36
|
<a class="language-option" href="/en/profiles" hreflang="en" lang="en">EN</a>
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<span>Pi Agent Platform</span>
|
|
31
31
|
</a>
|
|
32
32
|
<div class="top-actions">
|
|
33
|
-
<span class="pill"><span class="pill-dot"></span>v1.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.15.1 docs · VI</span>
|
|
34
34
|
<nav class="language-switch" aria-label="Ngôn ngữ">
|
|
35
35
|
<a class="language-option current" href="/quickstart" hreflang="vi" lang="vi" aria-current="true">VI</a>
|
|
36
36
|
<a class="language-option" href="/en/quickstart" hreflang="en" lang="en">EN</a>
|
package/docs-site/runtime.html
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<span>Pi Agent Platform</span>
|
|
31
31
|
</a>
|
|
32
32
|
<div class="top-actions">
|
|
33
|
-
<span class="pill"><span class="pill-dot"></span>v1.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.15.1 docs · VI</span>
|
|
34
34
|
<nav class="language-switch" aria-label="Ngôn ngữ">
|
|
35
35
|
<a class="language-option current" href="/runtime" hreflang="vi" lang="vi" aria-current="true">VI</a>
|
|
36
36
|
<a class="language-option" href="/en/runtime" hreflang="en" lang="en">EN</a>
|
package/docs-site/scope.html
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<span>Pi Agent Platform</span>
|
|
31
31
|
</a>
|
|
32
32
|
<div class="top-actions">
|
|
33
|
-
<span class="pill"><span class="pill-dot"></span>v1.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.15.1 docs · VI</span>
|
|
34
34
|
<nav class="language-switch" aria-label="Ngôn ngữ">
|
|
35
35
|
<a class="language-option current" href="/scope" hreflang="vi" lang="vi" aria-current="true">VI</a>
|
|
36
36
|
<a class="language-option" href="/en/scope" hreflang="en" lang="en">EN</a>
|
package/docs-site/security.html
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<span>Pi Agent Platform</span>
|
|
31
31
|
</a>
|
|
32
32
|
<div class="top-actions">
|
|
33
|
-
<span class="pill"><span class="pill-dot"></span>v1.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.15.1 docs · VI</span>
|
|
34
34
|
<nav class="language-switch" aria-label="Ngôn ngữ">
|
|
35
35
|
<a class="language-option current" href="/security" hreflang="vi" lang="vi" aria-current="true">VI</a>
|
|
36
36
|
<a class="language-option" href="/en/security" hreflang="en" lang="en">EN</a>
|
package/docs-site/team.html
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<span>Pi Agent Platform</span>
|
|
31
31
|
</a>
|
|
32
32
|
<div class="top-actions">
|
|
33
|
-
<span class="pill"><span class="pill-dot"></span>v1.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.15.1 docs · VI</span>
|
|
34
34
|
<nav class="language-switch" aria-label="Ngôn ngữ">
|
|
35
35
|
<a class="language-option current" href="/team" hreflang="vi" lang="vi" aria-current="true">VI</a>
|
|
36
36
|
<a class="language-option" href="/en/team" hreflang="en" lang="en">EN</a>
|
package/docs-site/whats-new.html
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<span>Pi Agent Platform</span>
|
|
31
31
|
</a>
|
|
32
32
|
<div class="top-actions">
|
|
33
|
-
<span class="pill"><span class="pill-dot"></span>v1.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.15.1 docs · VI</span>
|
|
34
34
|
<nav class="language-switch" aria-label="Ngôn ngữ">
|
|
35
35
|
<a class="language-option current" href="/whats-new" hreflang="vi" lang="vi" aria-current="true">VI</a>
|
|
36
36
|
<a class="language-option" href="/en/whats-new" hreflang="en" lang="en">EN</a>
|
|
@@ -104,6 +104,30 @@
|
|
|
104
104
|
<h1>Bản 1.9.0: nói việc bằng lời, Harness của team kiểm tra</h1>
|
|
105
105
|
<p>Bỏ task contract và lệnh workflow. Chế độ công ty tự yêu cầu check và review; nút Dừng, lệnh có mạng theo lần duyệt và thẻ duyệt dễ đọc hơn.</p>
|
|
106
106
|
</div>
|
|
107
|
+
<section class="section">
|
|
108
|
+
<p class="section-kicker">v1.15.1 · 2026-10-07</p>
|
|
109
|
+
<h2 id="v1-15-1">Studio thấy mỗi call của model dùng để làm gì</h2>
|
|
110
|
+
<p class="section-lead">Mỗi call model trong phiên công ty báo cho Studio nó dùng để làm gì: trả lời tin nhắn, bước tiếp sau tool (kèm tên tool vừa chạy), lượt harness yêu cầu, tóm tắt hội thoại, subagent nhận việc (kèm tiêu đề một dòng của việc) hoặc tìm web. Không gửi thêm nội dung prompt nào khác. Studio 0.20.0 hiện chúng ở Logs → "Lượt agent".</p>
|
|
111
|
+
<ul>
|
|
112
|
+
<li>Nhiều cuộc trò chuyện công ty trong cùng một folder không còn nhận nhầm thay đổi của nhau: check, review và kế hoạch chỉ xét các file chính cuộc trò chuyện đó đã sửa.</li>
|
|
113
|
+
<li>Cuộc trò chuyện công ty mới không mở được runtime nói rõ lý do, và Lưu trữ hoặc Khởi động lại dùng được mà không cần khởi động lại dashboard.</li>
|
|
114
|
+
<li><span class="inline-code">mvnw</span>/<span class="inline-code">gradlew</span> tải bị chặn được chỉ sang <span class="inline-code">run_with_network</span>; <span class="inline-code">piagent doctor</span> chạy <span class="inline-code">piagent-doctor</span>.</li>
|
|
115
|
+
</ul>
|
|
116
|
+
<p>Cập nhật từ 1.14.x hoặc 1.15.0 không cần nhập lại cấu hình trong Agent Watch.</p>
|
|
117
|
+
</section>
|
|
118
|
+
|
|
119
|
+
<section class="section">
|
|
120
|
+
<p class="section-kicker">v1.15.0 · 2026-10-07</p>
|
|
121
|
+
<h2 id="v1-15-0">Subagent bị từ chối nói đúng lý do</h2>
|
|
122
|
+
<p class="section-lead">Khi Studio từ chối một subagent công ty, Piagent nay báo lý do của Studio thay vì luôn báo "cấu hình vừa thay đổi". Ví dụ: không tài khoản AI nào của công ty phục vụ model của vai trò này, và quản trị viên cần gắn hoặc bật một tài khoản. Cần Agent Watch 0.15.0; với Studio 0.19.0, subagent thiếu tài khoản lúc bắt đầu lượt sẽ tự lấy model đang phục vụ khi được gọi.</p>
|
|
123
|
+
<ul>
|
|
124
|
+
<li>Lệnh in nhiều và chạy lâu (bộ test, build) không còn bị dừng với <span class="inline-code">managed-tool-output-too-large</span>.</li>
|
|
125
|
+
<li>Lệnh ghi vào <span class="inline-code">/tmp</span> ở chế độ công ty được chỉ sang <span class="inline-code">$TMPDIR</span> hoặc thư mục project.</li>
|
|
126
|
+
<li><span class="inline-code">piagent-update</span> chạy từ <span class="inline-code">npm exec</span> cài helper lâu dài; trên Windows, Pi được tìm thấy trong npm prefix tùy chỉnh.</li>
|
|
127
|
+
</ul>
|
|
128
|
+
<p>Cập nhật từ 1.14.x không cần nhập lại cấu hình trong Agent Watch.</p>
|
|
129
|
+
</section>
|
|
130
|
+
|
|
107
131
|
<section class="section">
|
|
108
132
|
<p class="section-kicker">v1.14.0 · 2026-10-06</p>
|
|
109
133
|
<h2 id="v1-14-0">Cập nhật giữ lựa chọn model của bạn</h2>
|
package/docs-site/windows.html
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<span>Pi Agent Platform</span>
|
|
31
31
|
</a>
|
|
32
32
|
<div class="top-actions">
|
|
33
|
-
<span class="pill"><span class="pill-dot"></span>v1.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.15.1 docs · VI</span>
|
|
34
34
|
<nav class="language-switch" aria-label="Ngôn ngữ">
|
|
35
35
|
<a class="language-option current" href="/windows" hreflang="vi" lang="vi" aria-current="true">VI</a>
|
|
36
36
|
<a class="language-option" href="/en/windows" hreflang="en" lang="en">EN</a>
|
package/docs-site/workflows.html
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<span>Pi Agent Platform</span>
|
|
31
31
|
</a>
|
|
32
32
|
<div class="top-actions">
|
|
33
|
-
<span class="pill"><span class="pill-dot"></span>v1.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.15.1 docs · VI</span>
|
|
34
34
|
<nav class="language-switch" aria-label="Ngôn ngữ">
|
|
35
35
|
<a class="language-option current" href="/workflows" hreflang="vi" lang="vi" aria-current="true">VI</a>
|
|
36
36
|
<a class="language-option" href="/en/workflows" hreflang="en" lang="en">EN</a>
|
|
@@ -256,8 +256,8 @@
|
|
|
256
256
|
},
|
|
257
257
|
{
|
|
258
258
|
"path": "tests",
|
|
259
|
-
"fileCount":
|
|
260
|
-
"sha256": "
|
|
259
|
+
"fileCount": 524,
|
|
260
|
+
"sha256": "905a1c9999775af0f40d20589e23f047bb0ba5974b258cb5d7c2dcb255c56086"
|
|
261
261
|
}
|
|
262
262
|
],
|
|
263
263
|
"custodianComparisonRules": {
|
package/package.json
CHANGED
|
@@ -26,7 +26,13 @@ export class ManagedBrokerClient {
|
|
|
26
26
|
const result = JSON.parse(line), item = this.#pending.get(result.id);
|
|
27
27
|
if (!item) throw Error('unexpected-broker-response');
|
|
28
28
|
clearTimeout(item.timer); this.#pending.delete(result.id);
|
|
29
|
-
if (result.error)
|
|
29
|
+
if (result.error) {
|
|
30
|
+
// Agent Watch's coarse error stays the message (start and renewal
|
|
31
|
+
// decide on it); Studio's own reason rides along for the member.
|
|
32
|
+
const error = Error(`managed-broker:${String(result.error).replace(/[^a-zA-Z_]/g, '').slice(0, 80)}`);
|
|
33
|
+
if (typeof result.studio_code === 'string' && /^[a-z0-9_]{1,64}$/.test(result.studio_code)) error.studioCode = result.studio_code;
|
|
34
|
+
item.reject(error);
|
|
35
|
+
}
|
|
30
36
|
else item.resolve(result.result);
|
|
31
37
|
} catch { this.#child.kill(); fail(); }
|
|
32
38
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// What a company model call is for, for Studio's logs (Logs → "Lượt agent"):
|
|
2
|
+
// the kind of call, the tools the answer before it called, and a subagent
|
|
3
|
+
// job's one-line title. No other prompt text leaves in these headers.
|
|
4
|
+
const SUMMARY_PROMPT = 'You are a context summarization assistant';
|
|
5
|
+
const TOOL = /^[a-z0-9_]{1,40}$/;
|
|
6
|
+
const TITLE_CHARS = 100;
|
|
7
|
+
|
|
8
|
+
// "read:3,bash": the tools of the assistant answer before this call, most
|
|
9
|
+
// used first, at most eight names (an unknown name counts as "other").
|
|
10
|
+
export function afterTools(message) {
|
|
11
|
+
const counts = new Map();
|
|
12
|
+
for (const part of message?.role === 'assistant' && Array.isArray(message.content) ? message.content : []) {
|
|
13
|
+
if (part?.type !== 'toolCall') continue;
|
|
14
|
+
const name = TOOL.test(String(part.name)) ? part.name : 'other';
|
|
15
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
16
|
+
}
|
|
17
|
+
return [...counts].sort((a, b) => b[1] - a[1]).slice(0, 8).map(([name, n]) => n > 1 ? `${name}:${Math.min(n, 9999)}` : name).join(',');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// A subagent job's title: the one the main agent gave, else the brief's
|
|
21
|
+
// first line; one line, at most TITLE_CHARS characters.
|
|
22
|
+
export function jobTitle(task, title) {
|
|
23
|
+
const line = [title, ...String(task ?? '').split('\n')].map(s => String(s ?? '').replace(/[\p{Cc}\s]+/gu, ' ').trim()).find(Boolean) ?? '';
|
|
24
|
+
return line.length > TITLE_CHARS ? line.slice(0, TITLE_CHARS - 1).trimEnd() + '…' : line;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// The headers of one call. `owner.nextPurpose` is set by the harness when it
|
|
28
|
+
// sends the main agent back (checks, review findings, a missing plan).
|
|
29
|
+
export function purposeHeaders(owner, runtime, role, context) {
|
|
30
|
+
const messages = context?.messages ?? [], last = messages.at(-1);
|
|
31
|
+
let purpose, after = '';
|
|
32
|
+
if (typeof context?.systemPrompt === 'string' && context.systemPrompt.startsWith(SUMMARY_PROMPT)) purpose = 'summary';
|
|
33
|
+
else if (last?.role === 'toolResult') { purpose = 'tool_step'; after = afterTools(messages.findLast(m => m.role === 'assistant')); }
|
|
34
|
+
else if (role !== 'main') purpose = 'brief';
|
|
35
|
+
else if (owner.nextPurpose === 'harness') { purpose = 'harness'; owner.nextPurpose = null; }
|
|
36
|
+
else purpose = 'answer';
|
|
37
|
+
const title = purpose === 'brief' ? owner.jobTitles?.get(runtime) : '';
|
|
38
|
+
return { 'X-Agent-Purpose': purpose, ...(after ? { 'X-Agent-After': after } : {}), ...(title ? { 'X-Agent-Task': encodeURIComponent(title) } : {}) };
|
|
39
|
+
}
|
|
@@ -58,12 +58,16 @@ export const HELPER_SETUP = {
|
|
|
58
58
|
export function countedCheck(managed, tool) {
|
|
59
59
|
if (tool.name !== 'bash') return tool;
|
|
60
60
|
return { ...tool, execute: async (...args) => {
|
|
61
|
-
|
|
61
|
+
// Whether anything in the folder moved during the check (another
|
|
62
|
+
// conversation's edit too: the suite ran on the whole folder); the check
|
|
63
|
+
// then counts for this conversation's own code.
|
|
64
|
+
const folder = async () => { try { return await (managed.folderDigest ?? managed.digest).call(managed); } catch { return null; } };
|
|
65
|
+
const counted = managed.run && isCheckCommand(args[1]?.command, managed.checks.commands), before = counted ? await folder() : null;
|
|
62
66
|
let ok = false;
|
|
63
67
|
try { const result = await tool.execute(...args); ok = !result?.isError; return result; }
|
|
64
68
|
finally {
|
|
65
|
-
const after = counted && before !== null ? await
|
|
66
|
-
if (after !== null && after === before) await managed.run.afterTool('bash', args[1], ok,
|
|
69
|
+
const after = counted && before !== null ? await folder() : null;
|
|
70
|
+
if (after !== null && after === before) await managed.run.afterTool('bash', args[1], ok, () => managed.digest());
|
|
67
71
|
}
|
|
68
72
|
} };
|
|
69
73
|
}
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
// A helper whose company model account is out of usage, or resting after a
|
|
4
4
|
// refusal, starts once more: Studio then gives the new helper the next model
|
|
5
5
|
// of its role that serves (an auto role's list).
|
|
6
|
+
import { jobTitle } from './call-purpose.mjs';
|
|
6
7
|
import { ManagedToolBoundary } from './tool-boundary.mjs';
|
|
7
8
|
import { managedResourceLoader } from './resource-loader.mjs';
|
|
8
9
|
import { shareGrant, releaseGrant } from './grant-share.mjs';
|
|
9
|
-
import { describeFailure, failureCode } from '../runtime/managed-failure.mjs';
|
|
10
|
+
import { describeFailure, failureCode, failureText } from '../runtime/managed-failure.mjs';
|
|
10
11
|
import { reviewParts, mergeReviews, REVIEW_PARALLEL } from './patch-snapshot.mjs';
|
|
11
12
|
import { reviewFindings, briefIssues } from './workflow.mjs';
|
|
12
13
|
import { anchorText, relayObjection, recordVerdicts } from './objections.mjs';
|
|
@@ -20,20 +21,20 @@ const textContent = result => result.content?.filter(p => p.type === 'text').map
|
|
|
20
21
|
const HELPER_SWITCH = new Set(['upstream_rate_limited', 'session_account_not_ready_retry_later', 'session_account_unavailable_start_new_session', 'account_capacity_unavailable', 'execution_capability_unavailable']);
|
|
21
22
|
const HELPER_ATTEMPTS = 2;
|
|
22
23
|
|
|
23
|
-
export async function runHelper(managed, role, task, signal, harness, { verifyGrant, patiently }) {
|
|
24
|
+
export async function runHelper(managed, role, task, signal, harness, { verifyGrant, patiently, title }) {
|
|
24
25
|
const snapshot = role === 'review' ? await managed.patchSnapshot() : null, parts = snapshot ? reviewParts(snapshot) : null;
|
|
25
26
|
// A helper whose company model account is out of usage, or resting after
|
|
26
27
|
// a refusal, starts once more: Studio then gives the new helper the next
|
|
27
28
|
// model of its role that serves (an auto role's list). Helpers read and
|
|
28
29
|
// run checks only, so starting over repeats no change.
|
|
29
30
|
for (let attempt = 1; ; attempt += 1) {
|
|
30
|
-
try { return await helperAttempt(managed, role, task, signal, harness, snapshot, parts, { verifyGrant, patiently }); }
|
|
31
|
+
try { return await helperAttempt(managed, role, task, signal, harness, snapshot, parts, { verifyGrant, patiently, title }); }
|
|
31
32
|
catch (error) {
|
|
32
33
|
if (attempt >= HELPER_ATTEMPTS || signal?.aborted || !HELPER_SWITCH.has(failureCode(error?.message)) || (managed.helperCalls.get(role) ?? 0) >= HELPER_CALLS) throw error;
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
|
-
async function helperAttempt(managed, role, task, signal, harness, snapshot, parts, { verifyGrant, patiently }) {
|
|
37
|
+
async function helperAttempt(managed, role, task, signal, harness, snapshot, parts, { verifyGrant, patiently, title }) {
|
|
37
38
|
// A long local tool step may outlive the main lease. Refresh authority
|
|
38
39
|
// before asking for a child; the broker serializes concurrent renewals,
|
|
39
40
|
// and main's own searches running beside it share managed renew.
|
|
@@ -48,12 +49,14 @@ async function helperAttempt(managed, role, task, signal, harness, snapshot, par
|
|
|
48
49
|
managed.helperCalls.set(role, (managed.helperCalls.get(role) ?? 0) + 1);
|
|
49
50
|
try { verifyGrant(grant, managed.manifest, role); }
|
|
50
51
|
catch (error) { try { await managed.broker.request('close', { role }); } catch { /* closes with the run */ } throw error; }
|
|
51
|
-
} catch (error) { throw Error(`managed-helper-failed: ${describeFailure(role, error
|
|
52
|
+
} catch (error) { throw Error(`managed-helper-failed: ${describeFailure(role, failureText(error))}`); }
|
|
52
53
|
let boundary;
|
|
53
54
|
const sessions = new Set();
|
|
54
55
|
try {
|
|
55
56
|
const runtime = await managed.api.ModelRuntime.create({ credentials: new managed.ai.InMemoryCredentialStore(), modelsPath: null, refreshOnCreate: false, allowModelNetwork: false });
|
|
56
57
|
const model = managed.installModel(runtime, grant); managed.wrapStreams(runtime, role);
|
|
58
|
+
// Studio's logs name the job by its title (the brief's first line without one).
|
|
59
|
+
(managed.jobTitles ??= new WeakMap()).set(runtime, jobTitle(task, title));
|
|
57
60
|
const setup = HELPER_SETUP[role];
|
|
58
61
|
boundary = new ManagedToolBoundary({ cwd: managed.cwd, sdkRoot: managed.sdk, protectedRoots: managed.protectedRoots, readOnly: true, commands: setup.commands });
|
|
59
62
|
const checks = role === 'verify' ? `\n\nRepository checks: ${managed.checks.commands.length ? managed.checks.commands.map(c => '`' + c + '`').join(', ') : 'none declared or detected; choose the tests, type check or build that cover the claims'}.` : '';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { createHash } from 'node:crypto';
|
|
3
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
|
|
6
6
|
const quote = value => "'" + value.replaceAll("'", "'\"'\"'") + "'";
|
|
@@ -9,8 +9,16 @@ const WORKER = fileURLToPath(new URL('./review-snapshot-worker.mjs', import.meta
|
|
|
9
9
|
const MAX_SNAPSHOT = 16 * 1024 * 1024;
|
|
10
10
|
|
|
11
11
|
// The worker runs inside the tool boundary; what it prints is one short line.
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
// `scope`: only these paths (relative to the project) make the patch.
|
|
13
|
+
async function worker(boundary, mode, scope = null) {
|
|
14
|
+
let scopeName = null;
|
|
15
|
+
if (scope) {
|
|
16
|
+
scopeName = `scope-${randomUUID()}.json`;
|
|
17
|
+
fs.writeFileSync(path.join(boundary.temporary, scopeName), JSON.stringify([...scope]), { mode: 0o600, flag: 'wx' });
|
|
18
|
+
}
|
|
19
|
+
let result;
|
|
20
|
+
try { result = await boundary.invoke('bash', { command: `${quote(process.execPath)} ${quote(WORKER)} ${mode}${scopeName ? ' ' + scopeName : ''}`, timeout: 60 }); }
|
|
21
|
+
finally { if (scopeName) fs.rmSync(path.join(boundary.temporary, scopeName), { force: true }); }
|
|
14
22
|
const text = textContent(result).trim();
|
|
15
23
|
if (result.details?.truncation?.truncated || text.includes('\n')) throw Error(/review-(patch|file|untracked)-limit/.exec(text)?.[0] ?? 'managed-review-patch-unreadable');
|
|
16
24
|
const [digest, bytes, name] = text.split(' ');
|
|
@@ -20,15 +28,29 @@ async function worker(boundary, mode) {
|
|
|
20
28
|
|
|
21
29
|
// The digest of the patch a reviewer would see (diff against HEAD plus
|
|
22
30
|
// untracked files): whether a review is still current. Nothing else moves.
|
|
23
|
-
export async function readPatchDigest(boundary) { return (await worker(boundary, 'digest')).digest; }
|
|
31
|
+
export async function readPatchDigest(boundary, scope = null) { return (await worker(boundary, 'digest', scope)).digest; }
|
|
24
32
|
|
|
25
33
|
// The patch a reviewer sees, read inside the tool boundary. The worker writes
|
|
26
34
|
// it to the boundary's temporary directory and names it, with its digest, on
|
|
27
35
|
// its output; the file is read once, must match that digest (a file changed
|
|
28
36
|
// meanwhile never passes) and is removed.
|
|
29
|
-
export async function readPatchSnapshot(boundary) {
|
|
30
|
-
const { digest, bytes, name } = await worker(boundary, 'file');
|
|
37
|
+
export async function readPatchSnapshot(boundary, scope = null) {
|
|
38
|
+
const { digest, bytes, name } = await worker(boundary, 'file', scope);
|
|
31
39
|
if (!/^review-[0-9a-f-]{36}\.json$/.test(name ?? '')) throw Error('managed-review-patch-unreadable');
|
|
40
|
+
return { patch: readOnce(boundary, name, bytes, digest).toString('utf8'), digest };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// What each changed or new file of the project holds now: { path: hash }.
|
|
44
|
+
// Compared before and after a command, it names the files that command
|
|
45
|
+
// changed, so a conversation answers for its own changes, not for another
|
|
46
|
+
// conversation's in the same folder.
|
|
47
|
+
export async function readChangedFiles(boundary) {
|
|
48
|
+
const { digest, bytes, name } = await worker(boundary, 'files');
|
|
49
|
+
if (!/^files-[0-9a-f-]{36}\.json$/.test(name ?? '')) throw Error('managed-review-patch-unreadable');
|
|
50
|
+
return new Map(Object.entries(JSON.parse(readOnce(boundary, name, bytes, digest).toString('utf8'))));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function readOnce(boundary, name, bytes, digest) {
|
|
32
54
|
const file = path.join(boundary.temporary, name);
|
|
33
55
|
let data;
|
|
34
56
|
try {
|
|
@@ -40,7 +62,7 @@ export async function readPatchSnapshot(boundary) {
|
|
|
40
62
|
} finally { fs.closeSync(fd); }
|
|
41
63
|
} finally { fs.rmSync(file, { force: true }); }
|
|
42
64
|
if (createHash('sha256').update(data).digest('hex') !== digest) throw Error('managed-review-patch-changed');
|
|
43
|
-
return
|
|
65
|
+
return data;
|
|
44
66
|
}
|
|
45
67
|
|
|
46
68
|
// Why a required review could not run, in one word for the member.
|
|
@@ -9,6 +9,7 @@ import { setTimeout as sleep } from 'node:timers/promises';
|
|
|
9
9
|
import { describeFailure, failureCode, failureIsAdmissionRefusal, failureIsBriefRefusal, failureReason, unnamedFailureText } from '../runtime/managed-failure.mjs';
|
|
10
10
|
import { CLIENT_AGENT } from './client-agent.mjs';
|
|
11
11
|
import { shareGrant, releaseGrant } from './grant-share.mjs';
|
|
12
|
+
import { purposeHeaders } from './call-purpose.mjs';
|
|
12
13
|
|
|
13
14
|
// How long one request waits in line for a free company model account.
|
|
14
15
|
const CAPACITY_WAIT_MS = 20 * 60_000;
|
|
@@ -25,6 +26,8 @@ export function wrapRoleStreams(owner, runtime, role, { provider, verifyGrant })
|
|
|
25
26
|
runtime[method] = (model, context, options) => {
|
|
26
27
|
// Tools run between two answers: the count names the answer they came from.
|
|
27
28
|
if (role === 'main') owner.mainAnswers = (owner.mainAnswers ?? 0) + 1;
|
|
29
|
+
// What this call is for, named once: an ask again after a refusal is the same call.
|
|
30
|
+
const purpose = purposeHeaders(owner, runtime, role, context);
|
|
28
31
|
const out = owner.ai.createAssistantMessageEventStream(), signal = options?.signal;
|
|
29
32
|
void (async () => {
|
|
30
33
|
let wait = null, noted = false, brief = 0, refused = null, rebinds = 0, share = null;
|
|
@@ -59,7 +62,7 @@ export function wrapRoleStreams(owner, runtime, role, { provider, verifyGrant })
|
|
|
59
62
|
// same company account and prompt cache; a helper names its role.
|
|
60
63
|
const conversation = role === 'main' ? conversationSession(owner) : null;
|
|
61
64
|
const stream = original({ ...model, id: route.native }, context, { ...options, apiKey: grant.token, sessionId: conversation ?? grant.role_id, maxRetries: 0,
|
|
62
|
-
headers: { ...options?.headers, 'User-Agent': CLIENT_AGENT, 'X-Session-Id': grant.role_id, ...(conversation ? { 'X-Claude-Code-Session-Id': conversation } : {}) } });
|
|
65
|
+
headers: { ...options?.headers, ...purpose, 'User-Agent': CLIENT_AGENT, 'X-Session-Id': grant.role_id, ...(conversation ? { 'X-Claude-Code-Session-Id': conversation } : {}) } });
|
|
63
66
|
// Whether Studio admitted the request is known at its first event
|
|
64
67
|
// after "start": until then nothing is passed on, so a refused
|
|
65
68
|
// request leaves no trace in the conversation.
|
|
@@ -12,8 +12,33 @@ import { managedGit } from './toolchain.mjs';
|
|
|
12
12
|
const MAX_SNAPSHOT = 16 * 1024 * 1024, MAX_UNTRACKED = 2000, READ_FILE = 400_000, BINARY_INLINE = 120_000, HASHED_FILE = 256 * 1024 * 1024;
|
|
13
13
|
const git = args => execFileSync(managedGit(), ['--no-pager', ...args], { encoding: 'utf8', maxBuffer: MAX_SNAPSHOT + 1, env: { ...process.env, GIT_TERMINAL_PROMPT: '0', GIT_CONFIG_NOSYSTEM: '1' } });
|
|
14
14
|
const sha256 = data => createHash('sha256').update(data).digest('hex');
|
|
15
|
-
const
|
|
16
|
-
|
|
15
|
+
const mode = process.argv[2];
|
|
16
|
+
// "files": what each changed or new file holds now (a hash, "deleted"), so
|
|
17
|
+
// the runtime can tell which files one conversation's command changed.
|
|
18
|
+
if (mode === 'files') {
|
|
19
|
+
const changed = git(['diff', '--name-only', '--no-renames', '--relative', '-z', 'HEAD', '--', '.']).split('\0').filter(Boolean);
|
|
20
|
+
const fresh = git(['ls-files', '--others', '--exclude-standard', '-z']).split('\0').filter(Boolean);
|
|
21
|
+
if (changed.length + fresh.length > MAX_UNTRACKED * 4) throw Error('review-untracked-limit');
|
|
22
|
+
const print = name => {
|
|
23
|
+
let stat; try { stat = fs.lstatSync(name); } catch { return 'deleted'; }
|
|
24
|
+
if (stat.isSymbolicLink()) return 'link:' + sha256(fs.readlinkSync(name));
|
|
25
|
+
if (stat.isDirectory()) return 'dir:' + sha256(fs.readdirSync(name).sort().join('\0'));
|
|
26
|
+
return stat.size > HASHED_FILE ? `size:${stat.size}:${stat.mtimeMs}` : sha256(fs.readFileSync(name)) + ':' + (stat.mode & 0o777);
|
|
27
|
+
};
|
|
28
|
+
const files = Object.fromEntries([...new Set([...changed, ...fresh])].sort().map(name => [name, print(name)]));
|
|
29
|
+
const name = `files-${randomUUID()}.json`, data = Buffer.from(JSON.stringify(files));
|
|
30
|
+
fs.writeFileSync(path.join(os.tmpdir(), name), data, { mode: 0o600, flag: 'wx' });
|
|
31
|
+
process.stdout.write(`${sha256(data)} ${data.length} ${name}`);
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}
|
|
34
|
+
// A scope (a file the runtime wrote in this boundary's temporary directory):
|
|
35
|
+
// only these paths, the ones this conversation changed, make the patch.
|
|
36
|
+
const scopeName = process.argv[3];
|
|
37
|
+
if (scopeName !== undefined && !/^scope-[0-9a-f-]{36}\.json$/.test(scopeName)) throw Error('review-scope-invalid');
|
|
38
|
+
const scope = scopeName ? new Set(JSON.parse(fs.readFileSync(path.join(os.tmpdir(), scopeName), 'utf8'))) : null;
|
|
39
|
+
if (scope && [...scope].some(p => typeof p !== 'string' || !p || path.isAbsolute(p) || p.split('/').includes('..'))) throw Error('review-scope-invalid');
|
|
40
|
+
const patch = scope && !scope.size ? '' : git(['diff', '--binary', '--no-ext-diff', '--no-textconv', '--no-renames', 'HEAD', '--', ...(scope ? [...scope].sort().map(p => ':(literal)' + p) : ['.'])]);
|
|
41
|
+
const paths = git(['ls-files', '--others', '--exclude-standard', '-z']).split('\0').filter(Boolean).filter(p => !scope || scope.has(p)).sort();
|
|
17
42
|
if (paths.length > MAX_UNTRACKED) throw Error('review-untracked-limit');
|
|
18
43
|
const untracked = paths.map(name => {
|
|
19
44
|
const stat = fs.lstatSync(name);
|
|
@@ -36,7 +61,6 @@ if (snapshot.length > MAX_SNAPSHOT) throw Error('review-patch-limit');
|
|
|
36
61
|
// the snapshot goes to a new file in this boundary's temporary directory,
|
|
37
62
|
// named on stdout with its digest; the runtime reads it once, checks the
|
|
38
63
|
// digest and removes it. No argument: the snapshot itself.
|
|
39
|
-
const mode = process.argv[2];
|
|
40
64
|
if (mode === 'digest') process.stdout.write(`${sha256(snapshot)} ${snapshot.length}`);
|
|
41
65
|
else if (mode === 'file') {
|
|
42
66
|
const name = `review-${randomUUID()}.json`;
|