@piagent/platform 1.48.1 → 1.48.2
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 +14 -0
- package/README.md +2 -0
- package/README.vi.md +2 -0
- package/docs/command-reference-vietnamese.md +4 -3
- package/docs/distribution-standard.md +6 -6
- package/docs/harness-conformance.md +4 -0
- package/docs/languages.json +2 -2
- package/docs/operator-manual-vietnamese.md +7 -7
- package/docs/publishing-for-teams.md +1 -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/content/en/update.html +8 -0
- package/docs-site/content/update.html +8 -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 +9 -9
- 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 +11 -3
- package/docs-site/en/whats-new.html +27 -15
- package/docs-site/en/windows.html +1 -1
- package/docs-site/en/workflows.html +1 -1
- package/docs-site/index.html +9 -9
- 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 +36 -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 +11 -3
- package/docs-site/whats-new.html +27 -15
- package/docs-site/windows.html +1 -1
- package/docs-site/workflows.html +1 -1
- package/evals/private-holdout-v1/public-exposure.v2.json +2 -2
- package/package.json +1 -1
- package/packages/piagent-core/extensions/piagent-guard.ts +4 -1
- package/packages/piagent-core/managed/check-waiver.mjs +35 -0
- package/packages/piagent-core/managed/command-tools.mjs +2 -2
- package/packages/piagent-core/managed/session.mjs +2 -2
- package/packages/piagent-core/managed/workflow.mjs +4 -4
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/runtime/update/terminal-reload.mjs +122 -0
- package/packages/piagent-core/runtime/update/terminal-update-offer.mjs +1 -1
- package/packages/piagent-webui/dist/client/assets/{App-fZeIRoaD.js → App-Dg6Firq1.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/{GatewayApp-CpC4lgYK.js → GatewayApp-CCczgphI.js} +3 -3
- package/packages/piagent-webui/dist/client/assets/{InspectionApp-pD-BqBXm.js → InspectionApp-CEilR0FG.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/{SessionAgentWorkspace-3tM7Q6HH.js → SessionAgentWorkspace-yh0py16t.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/{SessionInspectorDrawer-BJ94aQ6y.js → SessionInspectorDrawer-C-18TcGH.js} +2 -2
- package/packages/piagent-webui/dist/client/assets/{TerminalPanel-CtZyb0tj.js → TerminalPanel-B2-YAz2x.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/{WorkspaceCodeEditor-BS_ZrjPN.js → WorkspaceCodeEditor-DAdMuw6x.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/{WorkspaceExplorer-CkFBpcM7.js → WorkspaceExplorer-97MLNtEa.js} +3 -3
- package/packages/piagent-webui/dist/client/assets/{index-CNdhIouM.js → index-B8tL7HFz.js} +2 -2
- package/packages/piagent-webui/dist/client/assets/{transcript-view-model-CpEMY1xb.js → transcript-view-model-DqO4xO12.js} +1 -1
- package/packages/piagent-webui/dist/client/index.html +1 -1
- package/scripts/company-terminal.mjs +21 -1
- package/scripts/piagent-cli.mjs +44 -21
- package/scripts/piagent-studio.mjs +22 -2
- package/scripts/terminal-reload.mjs +15 -0
- package/scripts/update-global.mjs +10 -0
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.2" title="v1.48.2 docs · VI"><span class="dot"></span>v1.48.2</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,20 @@
|
|
|
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-2" data-release>
|
|
112
|
+
<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
|
+
<div class="release-body">
|
|
114
|
+
<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>
|
|
115
|
+
<p>Sau khi cập nhật, mọi Terminal đang mở tự nạp bản mới khi rảnh và giữ nguyên cuộc trò chuyện; kiểm tra bắt buộc của team chỉ được bỏ qua khi member xác nhận trên thẻ.</p>
|
|
116
|
+
<ul class="release-items">
|
|
117
|
+
<li data-kind="new"><span class="kind kind-new">Mới</span><span>piagent reload: mọi Terminal đang mở (piagent và piagent studio) nạp bản đã cài khi rảnh, giữ nguyên cuộc trò chuyện; piagent-update tự yêu cầu sau khi cài.</span></li>
|
|
118
|
+
<li data-kind="new"><span class="kind kind-new">Mới</span><span>Chỉ member được bỏ qua kiểm tra mà team bắt buộc: agent hỏi trên thẻ duyệt, một lần, cho đúng code lúc đó; code đổi sau đó phải kiểm tra lại.</span></li>
|
|
119
|
+
<li data-kind="improved"><span class="kind kind-improved">Cải thiện</span><span>Terminal đang chạy task không bị cắt ngang: nạp bản mới khi task xong, footer ghi rõ đang chờ.</span></li>
|
|
120
|
+
<li data-kind="improved"><span class="kind kind-improved">Cải thiện</span><span>Lượt được member bỏ qua kiểm tra ghi rõ trong trạng thái, tách khỏi lượt không có kiểm tra nào chạy; push vẫn hỏi như mọi chế độ.</span></li>
|
|
121
|
+
</ul>
|
|
122
|
+
</div>
|
|
123
|
+
</article>
|
|
124
|
+
<article class="release" id="v1-48-1" data-release>
|
|
112
125
|
<div class="release-meta"><a class="release-version" href="#v1-48-1">v1.48.1</a><time class="release-date" datetime="2026-10-30">30/10/2026</time></div>
|
|
113
126
|
<div class="release-body">
|
|
114
127
|
<h3 data-toc-skip id="v1-48-1-title">Dashboard trả lời nhanh hơn, Enter trong Terminal hiện tin nhắn ngay</h3>
|
|
@@ -407,24 +420,23 @@
|
|
|
407
420
|
</ul>
|
|
408
421
|
</div>
|
|
409
422
|
</article>
|
|
410
|
-
<article class="release" id="v1-43-0" data-release>
|
|
411
|
-
<div class="release-meta"><a class="release-version" href="#v1-43-0">v1.43.0</a><time class="release-date" datetime="2026-10-26">26/10/2026</time></div>
|
|
412
|
-
<div class="release-body">
|
|
413
|
-
<h3 data-toc-skip id="v1-43-0-title">Dev chọn workflow, harness hỗ trợ BE/FE theo project <span class="tag">không phát hành</span></h3>
|
|
414
|
-
<p>Đã tag nhưng chưa publish: gate wording tài liệu chặn phát hành. Thay bằng 1.43.4.</p>
|
|
415
|
-
<ul class="release-items">
|
|
416
|
-
<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>
|
|
417
|
-
<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>
|
|
418
|
-
<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>
|
|
419
|
-
</ul>
|
|
420
|
-
</div>
|
|
421
|
-
</article>
|
|
422
423
|
</section>
|
|
423
424
|
<section class="section archive">
|
|
424
425
|
<h2 id="archive">Các bản trước</h2>
|
|
425
426
|
<p class="section-lead">Bản cũ hơn, gom theo tháng. Mở một tháng để xem từng thay đổi.</p>
|
|
426
427
|
<details class="release-archive">
|
|
427
|
-
<summary><span>Tháng 10/2026</span><span class="archive-count">
|
|
428
|
+
<summary><span>Tháng 10/2026</span><span class="archive-count">65</span></summary>
|
|
429
|
+
<div class="archive-row" id="v1-43-0">
|
|
430
|
+
<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
|
+
<div class="archive-body">
|
|
432
|
+
<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: gate wording tài liệu chặn phát hành. Thay bằng 1.43.4.</p>
|
|
433
|
+
<ul class="release-items">
|
|
434
|
+
<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>
|
|
435
|
+
<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>
|
|
436
|
+
<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>
|
|
437
|
+
</ul>
|
|
438
|
+
</div>
|
|
439
|
+
</div>
|
|
428
440
|
<div class="archive-row" id="v1-42-1">
|
|
429
441
|
<div class="archive-head"><a class="release-version" href="#v1-42-1">v1.42.1</a> <span class="release-date">25/10/2026</span></div>
|
|
430
442
|
<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.2" title="v1.48.2 docs · VI"><span class="dot"></span>v1.48.2</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.2" title="v1.48.2 docs · VI"><span class="dot"></span>v1.48.2</a>
|
|
41
41
|
<nav class="top-nav" aria-label="Tài liệu">
|
|
42
42
|
<a href="/quickstart">Tài liệu</a>
|
|
43
43
|
<a href="/whats-new">Changelog</a>
|
|
@@ -256,8 +256,8 @@
|
|
|
256
256
|
},
|
|
257
257
|
{
|
|
258
258
|
"path": "tests",
|
|
259
|
-
"fileCount":
|
|
260
|
-
"sha256": "
|
|
259
|
+
"fileCount": 643,
|
|
260
|
+
"sha256": "da53dba99cba2d0d9f5adc6101fb1a49d058038cb5eca770de3bf8785de3290c"
|
|
261
261
|
}
|
|
262
262
|
],
|
|
263
263
|
"custodianComparisonRules": {
|
package/package.json
CHANGED
|
@@ -166,6 +166,7 @@ import { prefixCompletions, registerPiagentTool as registerToolDefinition, regis
|
|
|
166
166
|
import { registerTaskEvidenceTools } from "../runtime/registration/task-evidence-tools.ts";
|
|
167
167
|
import { registerAgentResources } from "../runtime/registration/agent-resources.ts";
|
|
168
168
|
import { registerTerminalUpdateOffer } from "../runtime/update/terminal-update-offer.mjs";
|
|
169
|
+
import { registerPersonalTerminalReload } from "../runtime/update/terminal-reload.mjs";
|
|
169
170
|
import { registerStyleCommand } from "../runtime/terminal/terminal-style.mjs";
|
|
170
171
|
import { userSkillFolders } from "../runtime/resources/agent-resources.mjs";
|
|
171
172
|
import { FRESH_COMMAND_ACTIONS, FRESH_COMMAND_HELP, ONBOARDING_COMMAND_ACTIONS, WORKFLOW_COMMAND_EXCLUSIONS } from "../runtime/registration/operator-catalogs.ts";
|
|
@@ -4052,7 +4053,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4052
4053
|
}
|
|
4053
4054
|
|
|
4054
4055
|
const grants = sessionGrants.get(ctx.sessionManager.getSessionId()) ?? new Set<string>();
|
|
4055
|
-
const grantable = Boolean(pendingHumanApproval) && !/handoff/.test(String(pendingHumanApproval!.action.requestedScope));
|
|
4056
|
+
const grantable = Boolean(pendingHumanApproval) && !/handoff|waiver/.test(String(pendingHumanApproval!.action.requestedScope));
|
|
4056
4057
|
const grant = grantKey(event.toolName, typeof pendingHumanApproval?.action.commandPreview === "string" ? pendingHumanApproval.action.commandPreview : null, preparedInput.input);
|
|
4057
4058
|
if (pendingHumanApproval && grantable && grants.has(grant)) pendingHumanApproval = null;
|
|
4058
4059
|
if (pendingHumanApproval) {
|
|
@@ -4192,6 +4193,8 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4192
4193
|
registerAgentResources(pi);
|
|
4193
4194
|
// A new release is asked about in the Terminal, as the dashboard does.
|
|
4194
4195
|
registerTerminalUpdateOffer(pi, { installed: installedPlatformVersion(), packageRoot: PLATFORM_ROOT, restart: "pi" });
|
|
4196
|
+
// `piagent reload` and a finished update reach this Terminal: it reloads when idle.
|
|
4197
|
+
registerPersonalTerminalReload(pi, { installed: readJsonFile<{ version?: string }>(path.join(PLATFORM_ROOT, "package.json"))?.version });
|
|
4195
4198
|
// The Terminal's look (/style): Pi keeps the pick in its own settings.
|
|
4196
4199
|
registerStyleCommand(pi);
|
|
4197
4200
|
registerSessionCommands(pi, { ...registrationDeps, ...contextCommandApi });
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// The member, never the agent, may waive the repository checks the Harness
|
|
2
|
+
// asks for ("bỏ qua test, đẩy luôn"). Before, the member's words changed
|
|
3
|
+
// nothing: under verify "require" the harness sent the agent back
|
|
4
|
+
// max_fix_loops times anyway, while the delivery prompt already said checks
|
|
5
|
+
// may be "explicitly human-waived". The agent now asks with waive_checks; the
|
|
6
|
+
// member answers on an approval card, once, for the code as it is then. A
|
|
7
|
+
// later change needs its checks again. The turn still reports that no check
|
|
8
|
+
// passed, and says the member waived them.
|
|
9
|
+
import { approve } from './command-tools.mjs';
|
|
10
|
+
|
|
11
|
+
const PARAMETERS = { type: 'object', properties: {
|
|
12
|
+
member_words: { type: 'string', minLength: 1, maxLength: 300, description: 'What the member said, quoted: the request to skip the checks.' },
|
|
13
|
+
reason: { type: 'string', minLength: 1, maxLength: 300, description: 'Which checks are skipped and what is left unverified.' } },
|
|
14
|
+
required: ['member_words', 'reason'], additionalProperties: false };
|
|
15
|
+
|
|
16
|
+
const text = value => ({ content: [{ type: 'text', text: value }], details: {} });
|
|
17
|
+
|
|
18
|
+
export function waiveChecksTool(self) {
|
|
19
|
+
return { name: 'waive_checks', label: 'Waive checks', parameters: PARAMETERS,
|
|
20
|
+
description: 'Ask the member to confirm that the repository checks are skipped for the code as it is now. Call it only when the member asked in this conversation to skip the tests or checks (quote them in member_words); never to end a turn sooner, never on your own judgment. The member answers on an approval card. Approved: the harness stops asking for checks on this exact code, and the turn reports that no check passed and that the member waived them. Change the code after it and the checks are required again. Pushing still asks the member as always.',
|
|
21
|
+
execute: async (id, args, signal, _onUpdate, ctx) => {
|
|
22
|
+
const run = self.run;
|
|
23
|
+
if (!run) throw Error('managed-checks-waiver-no-run: no member request is running.');
|
|
24
|
+
if (run.policy?.verify === 'off') return text('The Harness asks for no checks here: nothing to waive.');
|
|
25
|
+
const digest = await self.digest();
|
|
26
|
+
if (digest === null) throw Error('managed-checks-waiver-unreadable: the current code cannot be read, so a waiver could not be bound to it.');
|
|
27
|
+
await approve(self, id, signal, ctx, { toolName: 'waive_checks', rawAction: { digest }, commandPreview: `waive checks: ${args.member_words}`,
|
|
28
|
+
provider: 'harness', requestedScope: 'checks-waiver-once', reason: String(args.reason).slice(0, 300), riskClass: 'medium',
|
|
29
|
+
allowConsequence: 'The harness stops asking for checks on this exact code; the turn reports that no check passed and that you waived them.',
|
|
30
|
+
denyConsequence: 'The checks stay required; the agent runs them before it finishes.' },
|
|
31
|
+
{ title: 'Bỏ qua kiểm tra?', body: `Bạn đã nói: "${args.member_words}"\n\nBỏ qua: ${args.reason}\n\nĐồng ý: Harness không yêu cầu chạy kiểm tra cho đúng code hiện tại; lượt này ghi là chưa có kiểm tra nào qua và bạn đã miễn. Sửa code sau đó thì phải kiểm tra lại. Push vẫn hỏi bạn như thường.\nTừ chối: vẫn phải chạy kiểm tra trước khi xong.` });
|
|
32
|
+
run.waivedDigest = digest;
|
|
33
|
+
return text('The member waived the checks for the code as it is now. Do not run them again unless the member asks; say in your answer that the change is unverified.');
|
|
34
|
+
} };
|
|
35
|
+
}
|
|
@@ -75,11 +75,11 @@ export function stillAsks(self, reason) {
|
|
|
75
75
|
|
|
76
76
|
// One approval of the member (the dashboard card, else the Terminal). The
|
|
77
77
|
// member may allow it once, allow the same exact action for the rest of the
|
|
78
|
-
// conversation (never a handoff: its scope names it), deny it, or deny it and
|
|
78
|
+
// conversation (never a handoff or a waiver of checks: its scope names it), deny it, or deny it and
|
|
79
79
|
// stop the turn so the agent does not try again.
|
|
80
80
|
const CHOICES = { once: 'Đồng ý một lần', session: 'Đồng ý cho cả cuộc trò chuyện (đúng lệnh này)', deny: 'Từ chối', stop: 'Từ chối và dừng lượt này' };
|
|
81
81
|
export async function approve(self, id, signal, ctx, action, { title, body }) {
|
|
82
|
-
const grantable = !/handoff/.test(String(action.requestedScope ?? '')), key = `${action.toolName}\u0000${action.commandPreview ?? JSON.stringify(action.rawAction ?? {})}`;
|
|
82
|
+
const grantable = !/handoff|waiver/.test(String(action.requestedScope ?? '')), key = `${action.toolName}\u0000${action.commandPreview ?? JSON.stringify(action.rawAction ?? {})}`;
|
|
83
83
|
if (grantable && self.sessionApprovals?.has(key)) return;
|
|
84
84
|
const { piApprovalBroker } = await import('../runtime/inspection/approval-broker.ts');
|
|
85
85
|
const choices = ['once', ...(grantable ? ['session'] : []), 'deny', 'stop'];
|
|
@@ -29,7 +29,7 @@ import { HELPER_CALLS, HELPER_ROLES, helperRoles, helperPrompt, webPrompt, deleg
|
|
|
29
29
|
import { loadAgentResources } from './agent-skills.mjs';
|
|
30
30
|
import { loadPiagentThemes } from '../runtime/terminal/terminal-style.mjs';
|
|
31
31
|
import { restorePermission, recordBypass, permissionSetter } from './permission.mjs';
|
|
32
|
-
import { commandTools, servicesPrompt } from './command-tools.mjs'; import { attachMemory, memoryToolsFor, openRound, closeRound, prepareMemoryContext, trackMemoryLessons } from './memory.mjs';
|
|
32
|
+
import { commandTools, servicesPrompt } from './command-tools.mjs'; import { waiveChecksTool } from './check-waiver.mjs'; import { attachMemory, memoryToolsFor, openRound, closeRound, prepareMemoryContext, trackMemoryLessons } from './memory.mjs';
|
|
33
33
|
|
|
34
34
|
const PROVIDER = 'agent_watch_managed';
|
|
35
35
|
export const HELPER_BEAT_MS = 60_000;
|
|
@@ -129,7 +129,7 @@ export class ManagedSession {
|
|
|
129
129
|
if (roles.length) customTools.push({ name: 'delegate', label: 'Subagent', description: delegateDescription(roles) + SPEC_PROMPT,
|
|
130
130
|
parameters: delegateParameters(), // the brief and its structured fields (helper-settlement.mjs)
|
|
131
131
|
execute: (_id, args, signal) => self.delegate(delegateArgs(self, args), signal) });
|
|
132
|
-
customTools.push(sessionPlanTool(self), ...(roles.length ? [resolveTool(self)] : [])); // the objection ledger's decisions (ledger.mjs)
|
|
132
|
+
customTools.push(sessionPlanTool(self), waiveChecksTool(self), ...(roles.length ? [resolveTool(self)] : [])); // the member's waiver of checks (check-waiver.mjs), the objection ledger's decisions (ledger.mjs)
|
|
133
133
|
// The member decides what the request leaves open: numbered options, or their own words.
|
|
134
134
|
customTools.push(workflowContextTool(() => self.session.sessionManager));
|
|
135
135
|
customTools.push(askTool(self), ...memoryToolsFor(self, agentDir)); // team memory's tools, once Studio answered memory here (memory.mjs)
|
|
@@ -148,7 +148,7 @@ export function workflowPrompt(policy, checks) {
|
|
|
148
148
|
if (policy.plan !== 'off') lines.push(`- Plan: for work with several steps keep a checklist with update_plan and update it as you go. When the member asks for several items (plans, files, tickets), put every item in the checklist and work through all of them; do not end your turn to report between items.${policy.plan === 'require' ? ' For a complex task, file edits are refused until a plan exists. A checklist left with pending steps sends you back to the next one until every step is completed or deferred: if you cannot go on without the member, ask with ask_user. Mark a step deferred only when the member asked to leave it for later or it is no longer needed; never mark completed a step you did not do.' : ''}`);
|
|
149
149
|
if (policy.verify !== 'off') {
|
|
150
150
|
const list = checks.commands.length ? `${checks.commands.map(c => '`' + c + '`').join(', ')}${checks.source === 'detected' ? ' (detected; the repository declares none in AGENTS.md "## Checks")' : ''}` : 'none declared: choose the tests, type check or build that cover the change';
|
|
151
|
-
lines.push(`- Verify: after changing code, run the repository checks that cover the change and fix failures before you finish. Repository checks: ${list}.${policy.verify === 'require' ? ' The turn does not end until a check has passed on the final code.' : ''}
|
|
151
|
+
lines.push(`- Verify: after changing code, run the repository checks that cover the change and fix failures before you finish. Repository checks: ${list}.${policy.verify === 'require' ? ' The turn does not end until a check has passed on the final code.' : ''} Only the member may skip the checks: when they ask to, call waive_checks with their words; never skip them on your own.`);
|
|
152
152
|
}
|
|
153
153
|
// What each severity asks: blocking is fixed or argued, major decided, minor only listed.
|
|
154
154
|
const severities = ' Blocking findings: fix them, or reject one with resolve_objection and the reviewer judges your reason; a disagreement that remains goes to the member. Major findings: fix them or reject them with resolve_objection; no new review follows. Minor findings are listed for the member and need nothing.';
|
|
@@ -350,7 +350,7 @@ export async function completionGate(managed, run, signal) {
|
|
|
350
350
|
// The member declined the approval a check needed (a JDK, Docker, the
|
|
351
351
|
// network): asking again would only repeat the same answer.
|
|
352
352
|
if (policy.verify === 'require' && !run.verified && run.approvalDeclined && run.fixLoops > 0) verifyGaveUp = true;
|
|
353
|
-
if (policy.verify === 'require' && !run.verified && !run.docsOnly && !verifyGaveUp) {
|
|
353
|
+
if (policy.verify === 'require' && !run.verified && !run.docsOnly && !verifyGaveUp && run.waivedDigest !== digest) {
|
|
354
354
|
run.fixLoops += 1;
|
|
355
355
|
const list = run.checks.commands.length ? run.checks.commands.map(c => '`' + c + '`').join(', ') : 'the tests, type check or build that cover the change';
|
|
356
356
|
await send(`Harness process check: code changed in this turn, but no check has passed on the current code. Run the checks that cover your change (${list}) and fix what fails. If a check cannot run because the machine lacks something (a runtime such as a JDK, Docker, a database or another service, the network), request the approval that makes it run (run_with_network, run_with_docker, start_docker, run_on_host) or ask the member with ask_user, then run it; install or start nothing without that approval. If it fails for a reason unrelated to your change, say so plainly.`,
|
|
@@ -441,8 +441,8 @@ export async function completionGate(managed, run, signal) {
|
|
|
441
441
|
if (!run.changed && !run.disputes.length && !ledgerOf(run).length) return;
|
|
442
442
|
const finalPlan = currentPlan(session.sessionManager), planOpen = run.planUpdated ? finalPlan?.plan?.filter(p => p.status === 'pending' || p.status === 'in_progress').length ?? 0 : 0;
|
|
443
443
|
const status = { verified: run.verified, reviewed: run.reviewed, blockingOpen: run.blockingOpen, checks: run.checksRun, fixLoops: run.fixLoops,
|
|
444
|
-
...(planOpen ? { planOpen } : {}), ...(run.planSkipped ? { planSkipped: true } : {}), ...(run.disputes.length ? { disputes: run.disputes.length } : {}), ...(run.changed ? {} : { unchanged: true }) };
|
|
445
|
-
const words = [...(run.changed ? [run.verified ? 'a check passed on the final code' : policy.verify === 'off' ? null : run.docsOnly ? 'only documentation changed: no check needed' : 'no check passed on the final code',
|
|
444
|
+
...(planOpen ? { planOpen } : {}), ...(run.planSkipped ? { planSkipped: true } : {}), ...(!run.verified && run.waivedDigest === digest ? { checksWaived: true } : {}), ...(run.disputes.length ? { disputes: run.disputes.length } : {}), ...(run.changed ? {} : { unchanged: true }) };
|
|
445
|
+
const words = [...(run.changed ? [run.verified ? 'a check passed on the final code' : policy.verify === 'off' ? null : run.docsOnly ? 'only documentation changed: no check needed' : run.waivedDigest === digest ? 'no check passed on the final code: the member waived the checks' : 'no check passed on the final code',
|
|
446
446
|
run.reviewed ? (run.blockingOpen ? `review: ${run.blockingOpen} blocking issue(s) still open` : 'review: no blocking issue') : policy.review === 'off' ? null : run.reviewUnavailable ? `review could not run (${run.reviewUnavailableReason})` : 'not reviewed',
|
|
447
447
|
run.planSkipped ? 'code was changed before the required plan existed' : null, planOpen ? `plan: ${planOpen} step(s) not completed` : null] : []),
|
|
448
448
|
run.disputes.length ? `${run.disputes.length} disagreement(s) between the main agent and a subagent go to the member` : null, ledgerWords(run)].filter(Boolean);
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// Every open Terminal moves to a new Piagent release without being closed.
|
|
2
|
+
//
|
|
3
|
+
// `piagent reload` (and `piagent-update` once it installed a release) writes
|
|
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. A run in
|
|
6
|
+
// progress is never cut: the footer says the reload waits, and it happens when
|
|
7
|
+
// the run settles. How a Terminal reloads is its own: the personal Terminal
|
|
8
|
+
// runs Pi's /reload (Piagent's extensions are read again from disk), the
|
|
9
|
+
// company Terminal starts again in place on the same conversation, since its
|
|
10
|
+
// company code is loaded once per process.
|
|
11
|
+
import fs from "node:fs";
|
|
12
|
+
import os from "node:os";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
|
|
15
|
+
const REQUEST_FILE = "piagent-reload.json";
|
|
16
|
+
const POLL_MS = 2_000;
|
|
17
|
+
const STATUS_KEY = "piagent-reload";
|
|
18
|
+
|
|
19
|
+
const requestPath = (home) => path.join(home ?? os.homedir(), ".pi", REQUEST_FILE);
|
|
20
|
+
|
|
21
|
+
export function requestTerminalReload({ home, version = null, piHost = null, now = Date.now() } = {}) {
|
|
22
|
+
const file = requestPath(home);
|
|
23
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
24
|
+
const temporary = `${file}.${process.pid}.tmp`;
|
|
25
|
+
fs.writeFileSync(temporary, `${JSON.stringify({ version, piHost, at: now })}\n`, { mode: 0o600 });
|
|
26
|
+
fs.renameSync(temporary, file);
|
|
27
|
+
return { version, piHost, at: now };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function readReloadRequest(home) {
|
|
31
|
+
try {
|
|
32
|
+
const value = JSON.parse(fs.readFileSync(requestPath(home), "utf8"));
|
|
33
|
+
if (!Number.isFinite(value?.at)) return null;
|
|
34
|
+
return { at: value.at, version: typeof value.version === "string" ? value.version : null, piHost: typeof value.piHost === "string" ? value.piHost : null };
|
|
35
|
+
} catch { return null; }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Whether this Terminal may reload now without losing anything the member did.
|
|
39
|
+
function idle(ctx) {
|
|
40
|
+
try {
|
|
41
|
+
if (typeof ctx?.isIdle === "function" && !ctx.isIdle()) return false;
|
|
42
|
+
if (typeof ctx?.hasPendingMessages === "function" && ctx.hasPendingMessages()) return false;
|
|
43
|
+
const typed = typeof ctx?.ui?.getEditorText === "function" ? ctx.ui.getEditorText() : "";
|
|
44
|
+
return !String(typed ?? "").trim();
|
|
45
|
+
} catch { return false; }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// `apply(ctx, request)` reloads this Terminal; it runs at most once per
|
|
49
|
+
// process. Only Terminals with a UI take requests: scripted runs keep theirs.
|
|
50
|
+
export function watchTerminalReload(pi, { apply, home, startedAt = Date.now(), pollMs = POLL_MS }) {
|
|
51
|
+
let ctx = null, applied = false, waiting = false, timer = null;
|
|
52
|
+
// A ctx kept from an earlier session (replaced, reloaded) throws on any use:
|
|
53
|
+
// a check that meets one does nothing, and the next session_start brings a new one.
|
|
54
|
+
const check = async () => {
|
|
55
|
+
let request;
|
|
56
|
+
try {
|
|
57
|
+
if (applied || !ctx?.hasUI) return;
|
|
58
|
+
request = readReloadRequest(home);
|
|
59
|
+
if (!request || request.at <= startedAt) return;
|
|
60
|
+
if (!idle(ctx)) {
|
|
61
|
+
if (!waiting) { waiting = true; ctx.ui?.setStatus?.(STATUS_KEY, `⟳ Nạp Piagent${request.version ? ` ${request.version}` : " mới"} khi xong lượt này`); }
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
applied = true;
|
|
65
|
+
ctx.ui?.setStatus?.(STATUS_KEY, undefined);
|
|
66
|
+
} catch { return; }
|
|
67
|
+
try { await apply(ctx, request); }
|
|
68
|
+
catch (error) {
|
|
69
|
+
applied = false;
|
|
70
|
+
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
|
+
}
|
|
72
|
+
};
|
|
73
|
+
const keep = (_event, next) => { ctx = next; };
|
|
74
|
+
pi.on("session_start", (event, next) => {
|
|
75
|
+
keep(event, next);
|
|
76
|
+
if (!timer) { timer = setInterval(() => { void check(); }, pollMs); timer.unref?.(); }
|
|
77
|
+
});
|
|
78
|
+
pi.on("agent_start", keep);
|
|
79
|
+
// The moment a run settles is the moment a waiting reload can happen.
|
|
80
|
+
pi.on("agent_settled", (event, next) => { keep(event, next); setTimeout(() => { void check(); }, 300).unref?.(); });
|
|
81
|
+
pi.on("session_shutdown", () => { if (timer) clearInterval(timer); timer = null; ctx = null; });
|
|
82
|
+
return { check };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// The personal Terminal: Pi's /reload reads Piagent's extensions again from
|
|
86
|
+
// disk and keeps the conversation. A request naming another Pi host than the
|
|
87
|
+
// one running cannot be met that way: the member is told to open it again.
|
|
88
|
+
const RELOADED_FROM = "PIAGENT_RELOADED_FROM";
|
|
89
|
+
// The Pi this process runs: the package of the CLI that started it.
|
|
90
|
+
function runningPiVersion() {
|
|
91
|
+
try {
|
|
92
|
+
for (let dir = path.dirname(fs.realpathSync(process.argv[1])), depth = 0; depth < 6; depth += 1, dir = path.dirname(dir)) {
|
|
93
|
+
try { const manifest = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf8")); if (manifest.name === "@earendil-works/pi-coding-agent") return manifest.version; } catch { /* keep walking up */ }
|
|
94
|
+
}
|
|
95
|
+
} catch { /* unknown */ }
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
export function registerPersonalTerminalReload(pi, { installed, home = undefined }) {
|
|
99
|
+
pi.registerCommand("piagent-reload", { description: "Nạp lại Piagent (bản vừa cập nhật) mà không đóng Terminal",
|
|
100
|
+
handler: async (_args, ctx) => {
|
|
101
|
+
await ctx.waitForIdle();
|
|
102
|
+
process.env[RELOADED_FROM] = installed ?? "?";
|
|
103
|
+
await ctx.reload();
|
|
104
|
+
} });
|
|
105
|
+
pi.on("session_start", (_event, ctx) => {
|
|
106
|
+
const from = process.env[RELOADED_FROM];
|
|
107
|
+
if (from === undefined) return;
|
|
108
|
+
delete process.env[RELOADED_FROM];
|
|
109
|
+
// Said once the reloaded Terminal has drawn itself: a notice during the reload is lost.
|
|
110
|
+
const text = from === installed ? `Đã nạp lại Piagent ${installed}.` : `Đã nạp Piagent ${installed} (trước đó ${from}).`;
|
|
111
|
+
setTimeout(() => { try { ctx.ui?.notify?.(text, "info"); } catch { /* the Terminal closed meanwhile */ } }, 300).unref?.();
|
|
112
|
+
});
|
|
113
|
+
watchTerminalReload(pi, { home, apply: async (ctx, request) => {
|
|
114
|
+
if (ctx.mode !== "tui") return;
|
|
115
|
+
const VERSION = runningPiVersion();
|
|
116
|
+
if (request.piHost && VERSION && request.piHost !== VERSION) {
|
|
117
|
+
ctx.ui.notify(`Piagent ${request.version ?? "mới"} chạy trên Pi ${request.piHost}, Terminal này đang dùng Pi ${VERSION}: thoát rồi mở lại \`piagent\` để dùng bản mới.`, "warning");
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
await pi.sendUserMessage("/piagent-reload", { expandPromptTemplates: true });
|
|
121
|
+
} });
|
|
122
|
+
}
|
|
@@ -88,7 +88,7 @@ export function registerTerminalUpdateOffer(pi, { installed, packageRoot, restar
|
|
|
88
88
|
if (!result.ok) { ctx.ui.notify(`Chưa cập nhật được Piagent. Chi tiết trong ${result.log}. Có thể chạy \`piagent-update\` trong terminal.`, "error"); return; }
|
|
89
89
|
updated = version; status(undefined);
|
|
90
90
|
const extra = afterUpdate();
|
|
91
|
-
ctx.ui.notify(`Đã cập nhật Piagent ${installed} → ${version}.
|
|
91
|
+
ctx.ui.notify(`Đã cập nhật Piagent ${installed} → ${version}. Terminal này tự nạp bản mới khi rảnh và giữ nguyên cuộc trò chuyện (hoặc thoát rồi mở lại \`${restart}\`).${extra ? ` ${extra}` : ""}`, "info");
|
|
92
92
|
};
|
|
93
93
|
const offer = async (ctx, { force = false } = {}) => {
|
|
94
94
|
if (asking || updated || !ctx?.hasUI || ctx.ui?.[GATEWAY_UI] || !ctx.ui?.select) return;
|