@piagent/platform 1.48.5 → 1.49.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.
Files changed (91) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/docs/command-reference-vietnamese.md +2 -2
  3. package/docs/distribution-standard.md +6 -6
  4. package/docs/harness-conformance.md +3 -3
  5. package/docs/operator-manual-vietnamese.md +7 -7
  6. package/docs/publishing-for-teams.md +2 -0
  7. package/docs/release-install-policy.md +2 -2
  8. package/docs/team-onboarding.md +2 -2
  9. package/docs-site/architecture.html +1 -1
  10. package/docs-site/assets/search-en.json +1 -1
  11. package/docs-site/assets/search-vi.json +1 -1
  12. package/docs-site/benchmark.html +1 -1
  13. package/docs-site/commands.html +1 -1
  14. package/docs-site/context-engine.html +1 -1
  15. package/docs-site/ecosystem.html +1 -1
  16. package/docs-site/en/architecture.html +1 -1
  17. package/docs-site/en/benchmark.html +1 -1
  18. package/docs-site/en/commands.html +1 -1
  19. package/docs-site/en/context-engine.html +1 -1
  20. package/docs-site/en/ecosystem.html +1 -1
  21. package/docs-site/en/index.html +15 -15
  22. package/docs-site/en/mcp-approval.html +1 -1
  23. package/docs-site/en/mcp-auth.html +1 -1
  24. package/docs-site/en/mcp-servers.html +1 -1
  25. package/docs-site/en/mcp.html +1 -1
  26. package/docs-site/en/permissions.html +1 -1
  27. package/docs-site/en/profiles.html +1 -1
  28. package/docs-site/en/quickstart.html +1 -1
  29. package/docs-site/en/runtime.html +1 -1
  30. package/docs-site/en/scope.html +1 -1
  31. package/docs-site/en/security.html +1 -1
  32. package/docs-site/en/team.html +1 -1
  33. package/docs-site/en/update.html +3 -3
  34. package/docs-site/en/whats-new.html +55 -30
  35. package/docs-site/en/windows.html +1 -1
  36. package/docs-site/en/workflows.html +1 -1
  37. package/docs-site/index.html +15 -15
  38. package/docs-site/mcp-approval.html +1 -1
  39. package/docs-site/mcp-auth.html +1 -1
  40. package/docs-site/mcp-servers.html +1 -1
  41. package/docs-site/mcp.html +1 -1
  42. package/docs-site/permissions.html +1 -1
  43. package/docs-site/profiles.html +1 -1
  44. package/docs-site/quickstart.html +1 -1
  45. package/docs-site/releases.json +77 -0
  46. package/docs-site/runtime.html +1 -1
  47. package/docs-site/scope.html +1 -1
  48. package/docs-site/security.html +1 -1
  49. package/docs-site/team.html +1 -1
  50. package/docs-site/update.html +3 -3
  51. package/docs-site/whats-new.html +55 -30
  52. package/docs-site/windows.html +1 -1
  53. package/docs-site/workflows.html +1 -1
  54. package/evals/private-holdout-v1/public-exposure.v2.json +2 -2
  55. package/package.json +1 -1
  56. package/packages/piagent-core/managed/check-questions.mjs +133 -0
  57. package/packages/piagent-core/managed/command-tools.mjs +2 -0
  58. package/packages/piagent-core/managed/fast-mode.mjs +19 -16
  59. package/packages/piagent-core/managed/helper-run.mjs +20 -3
  60. package/packages/piagent-core/managed/repository-checks.mjs +80 -0
  61. package/packages/piagent-core/managed/request-stream.mjs +2 -2
  62. package/packages/piagent-core/managed/session.mjs +2 -2
  63. package/packages/piagent-core/managed/workflow.mjs +41 -88
  64. package/packages/piagent-core/package.json +1 -1
  65. package/packages/piagent-webui/dist/client/assets/ActivityPanel-CmjxPmNT.js +1 -0
  66. package/packages/piagent-webui/dist/client/assets/{App-DqN7B1JR.js → App-JrRb7jgn.js} +1 -1
  67. package/packages/piagent-webui/dist/client/assets/GatewayApp-BmkqeXih.js +63 -0
  68. package/packages/piagent-webui/dist/client/assets/{InspectionApp-BKQiA0Ce.js → InspectionApp-9jBsgkVJ.js} +1 -1
  69. package/packages/piagent-webui/dist/client/assets/{SessionAgentWorkspace-ZAdRbxVA.js → SessionAgentWorkspace-RQ0N65d9.js} +1 -1
  70. package/packages/piagent-webui/dist/client/assets/{SessionInspectorDrawer-B2e4-1FG.js → SessionInspectorDrawer-BWyEN4bf.js} +2 -2
  71. package/packages/piagent-webui/dist/client/assets/{SettingsNavigation-C3MC0X4P.js → SettingsNavigation-Wx-q5QQk.js} +1 -1
  72. package/packages/piagent-webui/dist/client/assets/{SettingsPage-D4os5vHj.js → SettingsPage-xAt6kSyZ.js} +1 -1
  73. package/packages/piagent-webui/dist/client/assets/{TerminalPanel-CVYUiDkn.js → TerminalPanel-DzHdSjpN.js} +1 -1
  74. package/packages/piagent-webui/dist/client/assets/{WorkspaceCodeEditor-E0Zopcke.js → WorkspaceCodeEditor-CEuPTTBd.js} +1 -1
  75. package/packages/piagent-webui/dist/client/assets/{WorkspaceExplorer-DldLoJyf.js → WorkspaceExplorer-B_NwYf4S.js} +3 -3
  76. package/packages/piagent-webui/dist/client/assets/{index-CjM70M7D.css → index-De7bIeMc.css} +1 -1
  77. package/packages/piagent-webui/dist/client/assets/{index-DcrpSyBK.js → index-fUpioGMZ.js} +2 -2
  78. package/packages/piagent-webui/dist/client/index.html +2 -2
  79. package/packages/piagent-webui/gateway/gateway-session-stream.ts +28 -0
  80. package/packages/piagent-webui/gateway/managed-projection.ts +8 -1
  81. package/packages/piagent-webui/gateway/session-catalog.ts +2 -2
  82. package/packages/piagent-webui/server/tool-call-summary.ts +13 -0
  83. package/packages/piagent-webui/server/transcript-projection.ts +13 -5
  84. package/packages/piagent-webui/shared/process-copy.ts +3 -2
  85. package/schemas/piagent-webui/gateway-protocol-v1.schema.json +2 -0
  86. package/schemas/piagent-webui/session-catalog-v1.schema.json +1 -1
  87. package/schemas/piagent-webui/transcript-v1.schema.json +18 -1
  88. package/scripts/company-terminal.mjs +2 -2
  89. package/scripts/runtime-advisory-exceptions.json +8 -0
  90. package/packages/piagent-webui/dist/client/assets/ActivityPanel-Bv-jc1s3.js +0 -1
  91. package/packages/piagent-webui/dist/client/assets/GatewayApp-B3--WjSd.js +0 -63
@@ -1,4 +1,81 @@
1
1
  [
2
+ {
3
+ "version": "1.49.1",
4
+ "date": "2026-11-01",
5
+ "status": "stable",
6
+ "title": {
7
+ "en": "A running turn says what it waits for",
8
+ "vi": "Lượt chạy nói rõ đang chờ gì"
9
+ },
10
+ "summary": {
11
+ "en": "The status and Activity show what the main agent and each subagent are doing, answers before a review are marked, and Fast follows each model.",
12
+ "vi": "Trạng thái và Activity cho thấy main agent và từng subagent đang làm gì, câu trả lời trước review được đánh dấu, và Fast áp dụng theo từng model."
13
+ },
14
+ "requires": null,
15
+ "items": [
16
+ {
17
+ "kind": "fixed",
18
+ "en": "A long end-of-turn review no longer looks stuck: the status names the reviewer and how long it has run, with the step it is on.",
19
+ "vi": "Review cuối lượt chạy lâu không còn trông như bị treo: dòng trạng thái nói reviewer đang chạy, đã bao lâu và đang làm bước nào."
20
+ },
21
+ {
22
+ "kind": "improved",
23
+ "en": "Activity lists every call by agent: the main agent and each subagent, with tool, target and time, live and after the turn.",
24
+ "vi": "Activity liệt kê mọi lời gọi theo từng agent: main agent và mỗi subagent, gồm tool, đối tượng và giờ gọi, cả khi đang chạy lẫn sau khi xong."
25
+ },
26
+ {
27
+ "kind": "improved",
28
+ "en": "An answer written before the review is marked a draft and folded once a later one exists; the last one says it is the final result.",
29
+ "vi": "Câu trả lời viết trước review được đánh dấu bản nháp và thu gọn khi có câu mới hơn; câu cuối ghi rõ là kết quả cuối."
30
+ },
31
+ {
32
+ "kind": "improved",
33
+ "en": "You can write while a turn runs: Enter sends the message when it ends. A turn that fails or is stopped keeps the message for you.",
34
+ "vi": "Soạn được tin trong lúc lượt đang chạy: Enter sẽ gửi khi lượt xong. Lượt lỗi hoặc bị dừng thì tin vẫn được giữ để bạn quyết định."
35
+ },
36
+ {
37
+ "kind": "improved",
38
+ "en": "Fast follows each model, subagents too: a Claude main agent with Codex subagents runs them fast. Tokens run fast count 2.5 times toward the quota.",
39
+ "vi": "Fast áp dụng theo từng model, cả subagent: main agent Claude với subagent Codex thì subagent chạy Fast. Token chạy Fast tính vào quota gấp 2,5 lần."
40
+ },
41
+ {
42
+ "kind": "fixed",
43
+ "en": "A model that refuses Fast goes back to standard speed alone; the rest of the conversation keeps Fast.",
44
+ "vi": "Model từ chối Fast chỉ riêng nó về tốc độ thường; phần còn lại của cuộc trò chuyện vẫn giữ Fast."
45
+ }
46
+ ]
47
+ },
48
+ {
49
+ "version": "1.49.0",
50
+ "date": "2026-10-31",
51
+ "status": "stable",
52
+ "title": {
53
+ "en": "You choose how much to test",
54
+ "vi": "Bạn chọn chạy bao nhiêu test"
55
+ },
56
+ "summary": {
57
+ "en": "The company Harness no longer pushes a whole test suite after a small change: it asks you to run the related part, all of it or nothing.",
58
+ "vi": "Harness công ty không còn ép chạy cả bộ test sau một thay đổi nhỏ: bạn chọn chạy phần liên quan, toàn bộ hay không chạy."
59
+ },
60
+ "requires": null,
61
+ "items": [
62
+ {
63
+ "kind": "improved",
64
+ "en": "The Harness asks how much to test at the end of a change, before a whole test suite and before a push, on the dashboard card or in the Terminal.",
65
+ "vi": "Harness hỏi chạy bao nhiêu test ở cuối thay đổi, trước khi chạy cả bộ test và trước khi push, trên thẻ ở dashboard hoặc trong Terminal."
66
+ },
67
+ {
68
+ "kind": "improved",
69
+ "en": "Bypass runs the tests that cover the change without asking and still asks before a push. With no screen to ask on, nothing is asked.",
70
+ "vi": "Bypass tự chạy test liên quan, không hỏi, nhưng vẫn hỏi trước push. Khi không có màn hình để hỏi thì không hỏi gì."
71
+ },
72
+ {
73
+ "kind": "fixed",
74
+ "en": "When only part of the tests ran, the turn says so instead of saying a check passed.",
75
+ "vi": "Khi chỉ chạy một phần test, lượt ghi rõ điều đó thay vì báo check đã pass."
76
+ }
77
+ ]
78
+ },
2
79
  {
3
80
  "version": "1.48.5",
4
81
  "date": "2026-10-31",
@@ -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.5" title="v1.48.5 docs · VI"><span class="dot"></span>v1.48.5</a>
40
+ <a class="version-badge" href="/whats-new" data-docs-version="v1.49.1" title="v1.49.1 docs · VI"><span class="dot"></span>v1.49.1</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>
@@ -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.5" title="v1.48.5 docs · VI"><span class="dot"></span>v1.48.5</a>
40
+ <a class="version-badge" href="/whats-new" data-docs-version="v1.49.1" title="v1.49.1 docs · VI"><span class="dot"></span>v1.49.1</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>
@@ -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.5" title="v1.48.5 docs · VI"><span class="dot"></span>v1.48.5</a>
40
+ <a class="version-badge" href="/whats-new" data-docs-version="v1.49.1" title="v1.49.1 docs · VI"><span class="dot"></span>v1.49.1</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>
@@ -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.5" title="v1.48.5 docs · VI"><span class="dot"></span>v1.48.5</a>
40
+ <a class="version-badge" href="/whats-new" data-docs-version="v1.49.1" title="v1.49.1 docs · VI"><span class="dot"></span>v1.49.1</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>
@@ -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.5" title="v1.48.5 docs · VI"><span class="dot"></span>v1.48.5</a>
40
+ <a class="version-badge" href="/whats-new" data-docs-version="v1.49.1" title="v1.49.1 docs · VI"><span class="dot"></span>v1.49.1</a>
41
41
  <nav class="top-nav" aria-label="Tài liệu">
42
42
  <a href="/quickstart">Tài liệu</a>
43
43
  <a href="/whats-new">Changelog</a>
@@ -153,9 +153,9 @@
153
153
  <h2 id="pin-cho-team">Pin một bản cho cả team</h2>
154
154
  <p>Khi rollout cho team, cài đúng bản Pi host và đúng bản platform, rồi chạy installer ở chế độ stable. Bước <code>--dry-run</code> in ra những gì sẽ đổi.</p>
155
155
  <div class="command-card">
156
- <header><span>Cài pin theo bản</span><button class="clip-btn" data-clip="npm install -g --ignore-scripts @earendil-works/pi-coding-agent@1.3.0&#10;npm install -g --ignore-scripts @piagent/platform@1.48.5&#10;piagent-install --stable --dry-run&#10;piagent-install --stable">Lấy lệnh</button></header>
156
+ <header><span>Cài pin theo bản</span><button class="clip-btn" data-clip="npm install -g --ignore-scripts @earendil-works/pi-coding-agent@1.3.0&#10;npm install -g --ignore-scripts @piagent/platform@1.49.1&#10;piagent-install --stable --dry-run&#10;piagent-install --stable">Lấy lệnh</button></header>
157
157
  <pre><code>npm install -g --ignore-scripts @earendil-works/pi-coding-agent@1.3.0
158
- npm install -g --ignore-scripts @piagent/platform@1.48.5
158
+ npm install -g --ignore-scripts @piagent/platform@1.49.1
159
159
  piagent-install --stable --dry-run
160
160
  piagent-install --stable</code></pre>
161
161
  </div>
@@ -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.5" title="v1.48.5 docs · VI"><span class="dot"></span>v1.48.5</a>
40
+ <a class="version-badge" href="/whats-new" data-docs-version="v1.49.1" title="v1.49.1 docs · VI"><span class="dot"></span>v1.49.1</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,34 @@
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-5" data-release>
111
+ <article class="release latest" id="v1-49-1" data-release>
112
+ <div class="release-meta"><a class="release-version" href="#v1-49-1">v1.49.1</a><time class="release-date" datetime="2026-11-01">01/11/2026</time></div>
113
+ <div class="release-body">
114
+ <h3 data-toc-skip id="v1-49-1-title">Lượt chạy nói rõ đang chờ gì</h3>
115
+ <p>Trạng thái và Activity cho thấy main agent và từng subagent đang làm gì, câu trả lời trước review được đánh dấu, và Fast áp dụng theo từng model.</p>
116
+ <ul class="release-items">
117
+ <li data-kind="improved"><span class="kind kind-improved">Cải thiện</span><span>Activity liệt kê mọi lời gọi theo từng agent: main agent và mỗi subagent, gồm tool, đối tượng và giờ gọi, cả khi đang chạy lẫn sau khi xong.</span></li>
118
+ <li data-kind="improved"><span class="kind kind-improved">Cải thiện</span><span>Câu trả lời viết trước review được đánh dấu bản nháp và thu gọn khi có câu mới hơn; câu cuối ghi rõ là kết quả cuối.</span></li>
119
+ <li data-kind="improved"><span class="kind kind-improved">Cải thiện</span><span>Soạn được tin trong lúc lượt đang chạy: Enter sẽ gửi khi lượt xong. Lượt lỗi hoặc bị dừng thì tin vẫn được giữ để bạn quyết định.</span></li>
120
+ <li data-kind="improved"><span class="kind kind-improved">Cải thiện</span><span>Fast áp dụng theo từng model, cả subagent: main agent Claude với subagent Codex thì subagent chạy Fast. Token chạy Fast tính vào quota gấp 2,5 lần.</span></li>
121
+ <li data-kind="fixed"><span class="kind kind-fixed">Sửa lỗi</span><span>Review cuối lượt chạy lâu không còn trông như bị treo: dòng trạng thái nói reviewer đang chạy, đã bao lâu và đang làm bước nào.</span></li>
122
+ <li data-kind="fixed"><span class="kind kind-fixed">Sửa lỗi</span><span>Model từ chối Fast chỉ riêng nó về tốc độ thường; phần còn lại của cuộc trò chuyện vẫn giữ Fast.</span></li>
123
+ </ul>
124
+ </div>
125
+ </article>
126
+ <article class="release" id="v1-49-0" data-release>
127
+ <div class="release-meta"><a class="release-version" href="#v1-49-0">v1.49.0</a><time class="release-date" datetime="2026-10-31">31/10/2026</time></div>
128
+ <div class="release-body">
129
+ <h3 data-toc-skip id="v1-49-0-title">Bạn chọn chạy bao nhiêu test</h3>
130
+ <p>Harness công ty không còn ép chạy cả bộ test sau một thay đổi nhỏ: bạn chọn chạy phần liên quan, toàn bộ hay không chạy.</p>
131
+ <ul class="release-items">
132
+ <li data-kind="improved"><span class="kind kind-improved">Cải thiện</span><span>Harness hỏi chạy bao nhiêu test ở cuối thay đổi, trước khi chạy cả bộ test và trước khi push, trên thẻ ở dashboard hoặc trong Terminal.</span></li>
133
+ <li data-kind="improved"><span class="kind kind-improved">Cải thiện</span><span>Bypass tự chạy test liên quan, không hỏi, nhưng vẫn hỏi trước push. Khi không có màn hình để hỏi thì không hỏi gì.</span></li>
134
+ <li data-kind="fixed"><span class="kind kind-fixed">Sửa lỗi</span><span>Khi chỉ chạy một phần test, lượt ghi rõ điều đó thay vì báo check đã pass.</span></li>
135
+ </ul>
136
+ </div>
137
+ </article>
138
+ <article class="release" id="v1-48-5" data-release>
112
139
  <div class="release-meta"><a class="release-version" href="#v1-48-5">v1.48.5</a><time class="release-date" datetime="2026-10-31">31/10/2026</time></div>
113
140
  <div class="release-body">
114
141
  <h3 data-toc-skip id="v1-48-5-title">Task đang chạy vẫn dùng được tool khi Piagent được cập nhật</h3>
@@ -394,39 +421,37 @@
394
421
  </ul>
395
422
  </div>
396
423
  </article>
397
- <article class="release" id="v1-43-5" data-release>
398
- <div class="release-meta"><a class="release-version" href="#v1-43-5">v1.43.5</a><time class="release-date" datetime="2026-10-27">27/10/2026</time></div>
399
- <div class="release-body">
400
- <h3 data-toc-skip id="v1-43-5-title">Xem trước ảnh và đính kèm file văn phòng</h3>
401
- <p>Ảnh đã gửi hiện thành hình, bảng tính và slide đính kèm trực tiếp được, task chụp nhiều screenshot không còn dừng vì giới hạn dung lượng request của Studio.</p>
402
- <ul class="release-items">
403
- <li data-kind="new"><span class="kind kind-new">Mới</span><span>Ảnh đính kèm và ảnh đã gửi hiện thành hình trong hội thoại, bấm để xem cỡ lớn; chip trong khung soạn có ảnh thu nhỏ.</span></li>
404
- <li data-kind="new"><span class="kind kind-new">Mới</span><span>Đính kèm được .xlsx, .xlsm, .pptx, .odt, .ods, .odp, .rtf: bảng tính thành các dòng cách nhau bằng tab, slide theo thứ tự kèm ghi chú, không cần cài converter.</span></li>
405
- <li data-kind="fixed"><span class="kind kind-fixed">Sửa lỗi</span><span>Task công ty chụp nhiều screenshot không còn lỗi request_too_large: mỗi request chỉ gửi lại ảnh mới nhất trong 2 MiB, ảnh cũ hơn thành một dòng ghi chú.</span></li>
406
- <li data-kind="fixed"><span class="kind kind-fixed">Sửa lỗi</span><span>Câu trả lời đang stream của lượt có dùng tool xuống đoạn theo từng message của model, không còn dính các câu thành một khối.</span></li>
407
- </ul>
408
- </div>
409
- </article>
410
- <article class="release" id="v1-43-4" data-release>
411
- <div class="release-meta"><a class="release-version" href="#v1-43-4">v1.43.4</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-4-title">Dev chọn workflow, harness hỗ trợ BE/FE theo project</h3>
414
- <p>Cuộc trò chuyện công ty nhận diện project và kế thừa skill, plan, nguồn requirement của Dev, không ép thêm phương pháp mới. Giữ nguyên quyền hạn và cơ chế duyệt.</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
- <li data-kind="fixed"><span class="kind kind-fixed">Sửa lỗi</span><span>Tải lịch sử kiểm tra lại vị trí cuộn và phản hồi khi cuộn lên, tránh sự kiện observer cũ tải thừa trang hoặc làm lịch sử ngừng tải.</span></li>
420
- <li data-kind="fixed"><span class="kind kind-fixed">Sửa lỗi</span><span>Review chạy được ở thư mục chứa nhiều repo: mỗi file thay đổi được review trong repo của nó, file ngoài repo được ghi rõ là không review.</span></li>
421
- </ul>
422
- </div>
423
- </article>
424
424
  </section>
425
425
  <section class="section archive">
426
426
  <h2 id="archive">Các bản trước</h2>
427
427
  <p class="section-lead">Bản cũ hơn, gom theo tháng. Mở một tháng để xem từng thay đổi.</p>
428
428
  <details class="release-archive">
429
- <summary><span>Tháng 10/2026</span><span class="archive-count">68</span></summary>
429
+ <summary><span>Tháng 10/2026</span><span class="archive-count">70</span></summary>
430
+ <div class="archive-row" id="v1-43-5">
431
+ <div class="archive-head"><a class="release-version" href="#v1-43-5">v1.43.5</a> <span class="release-date">27/10/2026</span></div>
432
+ <div class="archive-body">
433
+ <p><strong>Xem trước ảnh và đính kèm file văn phòng</strong> — Ảnh đã gửi hiện thành hình, bảng tính và slide đính kèm trực tiếp được, task chụp nhiều screenshot không còn dừng vì giới hạn dung lượng request của Studio.</p>
434
+ <ul class="release-items">
435
+ <li data-kind="new"><span class="kind kind-new">Mới</span><span>Ảnh đính kèm và ảnh đã gửi hiện thành hình trong hội thoại, bấm để xem cỡ lớn; chip trong khung soạn có ảnh thu nhỏ.</span></li>
436
+ <li data-kind="new"><span class="kind kind-new">Mới</span><span>Đính kèm được .xlsx, .xlsm, .pptx, .odt, .ods, .odp, .rtf: bảng tính thành các dòng cách nhau bằng tab, slide theo thứ tự kèm ghi chú, không cần cài converter.</span></li>
437
+ <li data-kind="fixed"><span class="kind kind-fixed">Sửa lỗi</span><span>Task công ty chụp nhiều screenshot không còn lỗi request_too_large: mỗi request chỉ gửi lại ảnh mới nhất trong 2 MiB, ảnh cũ hơn thành một dòng ghi chú.</span></li>
438
+ <li data-kind="fixed"><span class="kind kind-fixed">Sửa lỗi</span><span>Câu trả lời đang stream của lượt có dùng tool xuống đoạn theo từng message của model, không còn dính các câu thành một khối.</span></li>
439
+ </ul>
440
+ </div>
441
+ </div>
442
+ <div class="archive-row" id="v1-43-4">
443
+ <div class="archive-head"><a class="release-version" href="#v1-43-4">v1.43.4</a> <span class="release-date">26/10/2026</span></div>
444
+ <div class="archive-body">
445
+ <p><strong>Dev chọn workflow, harness hỗ trợ BE/FE theo project</strong> — Cuộc trò chuyện công ty nhận diện project và kế thừa skill, plan, nguồn requirement của Dev, không ép thêm phương pháp mới. Giữ nguyên quyền hạn và cơ chế duyệt.</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
+ <li data-kind="fixed"><span class="kind kind-fixed">Sửa lỗi</span><span>Tải lịch sử kiểm tra lại vị trí cuộn và phản hồi khi cuộn lên, tránh sự kiện observer cũ tải thừa trang hoặc làm lịch sử ngừng tải.</span></li>
451
+ <li data-kind="fixed"><span class="kind kind-fixed">Sửa lỗi</span><span>Review chạy được ở thư mục chứa nhiều repo: mỗi file thay đổi được review trong repo của nó, file ngoài repo được ghi rõ là không review.</span></li>
452
+ </ul>
453
+ </div>
454
+ </div>
430
455
  <div class="archive-row" id="v1-43-3">
431
456
  <div class="archive-head"><a class="release-version" href="#v1-43-3">v1.43.3</a> <span class="release-date">26/10/2026</span></div>
432
457
  <div class="archive-body">
@@ -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.5" title="v1.48.5 docs · VI"><span class="dot"></span>v1.48.5</a>
40
+ <a class="version-badge" href="/whats-new" data-docs-version="v1.49.1" title="v1.49.1 docs · VI"><span class="dot"></span>v1.49.1</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>
@@ -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.5" title="v1.48.5 docs · VI"><span class="dot"></span>v1.48.5</a>
40
+ <a class="version-badge" href="/whats-new" data-docs-version="v1.49.1" title="v1.49.1 docs · VI"><span class="dot"></span>v1.49.1</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": 644,
260
- "sha256": "789b3bf1850c8e31f08a737575ba3f0c67f45f6f1913c665c350f07f85ed9c20"
259
+ "fileCount": 648,
260
+ "sha256": "c71a827c77f6d8a2557cc956bd7cc4b9d1863c81503b584784f130fe466932e7"
261
261
  }
262
262
  ],
263
263
  "custodianComparisonRules": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piagent/platform",
3
- "version": "1.48.5",
3
+ "version": "1.49.1",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -0,0 +1,133 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { memberQuestions } from './member-questions.mjs';
3
+ import { effectivePermission } from './permission.mjs';
4
+
5
+ // How much to test is the member's decision: a one-line fix should not cost a
6
+ // thirty-minute suite. The harness asks at the end of a change, before a whole
7
+ // suite and before a push, on the dashboard card or in the Terminal. Bypass skips
8
+ // the questions about the work (it runs the tests that cover the change) but not
9
+ // the one before a push. With no screen nobody can answer: nothing is asked.
10
+
11
+ // How much of the suite a test command covers: 'partial' (a filter, one file, one
12
+ // package), 'suite' (the whole test command) or null when it runs no tests.
13
+ const VERSION_ONLY = /(?:^|\s)(?:--version|-V|--help)(?:\s|$)/;
14
+ const FILTERED = /(?:^|\s)(?:-k|-t|--grep|--testNamePattern|--test-name-pattern|--filter|--only|--testPathPattern)(?:[\s=]|$)|\bgo\s+test\b.*\s-run[\s=]|\bpytest\b.*\s::|\.only\b/;
15
+ const TEST_RUNNER = /\b(npm|pnpm|yarn|bun)\s+(run\s+)?test(:\S+)?(?=\s|$)|\bnode\s+--test\b|\b(vitest|jest|mocha|pytest|rspec|phpunit)\b|\bplaywright\s+test\b|\bpython3?\s+-m\s+(pytest|unittest)\b|\b(go|cargo|swift|dotnet|deno)\s+test\b|\bmake\s+test\b|\bxcodebuild\b.*\btest\b|\b(gradle|gradlew|mvn|mvnw)\b.*\b(test|verify)\b/;
16
+ // Test commands start a command: `npm install -D vitest` or `rm jest.config.ts` only name one.
17
+ const TEST_START = new RegExp(`^(?:${TEST_RUNNER.source})`);
18
+ const LAUNCHER = /^(?:\w+=\S*\s+|time\s+|nohup\s+|timeout\s+\S+\s+|npx\s+(?:-\S+\s+)*|bunx\s+|pnpm\s+(?:exec|dlx)\s+|uv\s+run\s+|poetry\s+run\s+|yarn\s+(?=jest|vitest|mocha|playwright)|node_modules\/\.bin\/|\.\/)+/;
19
+ const partsOf = bare => bare.split(/\s*(?:\|\|?|&&|;)\s*/).map(part => part.replace(/\s\d*>.*$/, '').trim());
20
+ const NOT_TESTS = /\b(lint|typecheck|type-check|tsc|eslint|biome|ruff|mypy|pyright|vet|clippy|format|build)\b/;
21
+ const TARGET_FLAG = /^(-p|--package|-w|--workspace|--tests)$|^(-Dtest=|-only-testing:|--workspace=|--package=)/;
22
+ const VALUE_FLAG = /^(-c|--config|--reporter|--project|--workers|--timeout|--rootDir|--root|-f|--file|-o|--output)$/;
23
+ // A file or folder named on the command line; a flag's own value names none.
24
+ const targetsPart = args => args.some((a, i) => {
25
+ if (TARGET_FLAG.test(a)) return true;
26
+ if (a.startsWith('-') || VALUE_FLAG.test(args[i - 1] ?? '') || a.includes('*') || a.endsWith('...')) return false;
27
+ return a.includes('/') || /\.(test|spec)\.\w+$|_test\.\w+$|^test_\w+\.\w+$/.test(a);
28
+ });
29
+ // What a command runs, without the text it merely quotes (a commit message, an echo).
30
+ const unquoted = text => text.replace(/"[^"]*"|'[^']*'/g, '""');
31
+ export function checkScope(command, declared = []) {
32
+ const text = String(command ?? '').replace(/\s+/g, ' ').trim();
33
+ if (!text || /^(echo|printf|grep|rg|cat|sed|awk|head|tail|less|git|ls|find)\b/.test(text) || VERSION_ONLY.test(text) || /--collect-only/.test(text)) return null;
34
+ const bare = unquoted(text);
35
+ for (const part of partsOf(bare)) {
36
+ const own = part.replace(LAUNCHER, ''), runner = own.match(TEST_START);
37
+ if (!runner) continue;
38
+ if (FILTERED.test(own)) return 'partial';
39
+ return !declared.includes(text) && targetsPart(own.slice(runner[0].length).split(' ').filter(Boolean)) ? 'partial' : 'suite';
40
+ }
41
+ // A script the repository declares: it cannot be narrowed from here.
42
+ const named = declared.find(d => partsOf(bare).some(part => part.startsWith(unquoted(d))));
43
+ return named && !NOT_TESTS.test(named) ? 'suite' : null;
44
+ }
45
+ // The member's own words asking for the whole suite: nothing to ask them then.
46
+ const RUN_ALL = String.raw`(?:full|all(?:\s+the)?|whole(?:\s+the)?|entire(?:\s+the)?|the\s+(?:full|whole|entire))`;
47
+ const FULL_WORDS = new RegExp([
48
+ String.raw`(?:^|\s)(?:run|rerun|execute)\s+${RUN_ALL}\s+(?:tests?|suite|checks?)(?:\s|$|[.,!?])`,
49
+ String.raw`(?:^|\s)(?:chạy|chay)(?:\s+lại)?\s+(?:full|toàn bộ|toan bo|tất cả|tat ca|hết|het)(?:\s+các)?\s+(?:tests?|suite|kiểm thử|kiểm tra)`,
50
+ String.raw`(?:^|\s)(?:chạy|chay)(?:\s+lại)?\s+tests?\s+(?:full|toàn bộ|toan bo|tất cả|tat ca|hết|het)`,
51
+ ].join('|'), 'i');
52
+ const NOT_WORDS = /(không|khong|đừng|chưa|\bno\b|\bnot\b|don't|skip|bỏ qua)[^.!?\n]{0,24}$/i;
53
+ export function asksFullChecks(text) {
54
+ const value = String(text ?? ''), hit = value.match(FULL_WORDS);
55
+ return !!hit && !NOT_WORDS.test(value.slice(0, hit.index));
56
+ }
57
+ // A push the harness can see; handoff-policy still asks for its own approval.
58
+ export const isGitPush = command => /(^|[\s;&|(])git\s+(?:(?:-C|-c)\s+\S+\s+|--\S+\s+)*push\b/.test(unquoted(String(command ?? '')));
59
+
60
+ const PART = 'Chỉ chạy phần liên quan', KEEP = 'Giữ kết quả phần đã chạy', FULL = 'Chạy toàn bộ', NONE = 'Không chạy lúc này';
61
+ const PUSH_FULL = 'Chạy toàn bộ rồi mới push', PUSH_NOW = 'Push luôn, không chạy thêm';
62
+ const GATEWAY_UI = Symbol.for('piagent.webui.gateway-runtime-ui.v1');
63
+
64
+ // The screen to ask on: the dashboard's card, else the Terminal. The turn's end
65
+ // has no tool context, so the Terminal comes from the extension runner there.
66
+ function surfaceOf(self, ctx) {
67
+ const sessionId = self.session?.sessionManager?.getSessionId?.();
68
+ if (sessionId && memberQuestions.hasSurface(sessionId)) return { sessionId };
69
+ const runner = self.session?.extensionRunner, ui = ctx ? ctx.hasUI && ctx.ui : runner?.hasUI?.() && runner.getUIContext?.();
70
+ return ui?.select && !ui[GATEWAY_UI] ? { ui } : null;
71
+ }
72
+ // The label picked, { other } for the member's own words, undefined for no answer (Esc, stopped).
73
+ async function ask(surface, question, labels, signal) {
74
+ if (surface.ui) return surface.ui.select(question, labels, { signal });
75
+ const questions = [{ header: 'Test', question, options: labels.map(label => ({ label, description: '' })), multiSelect: false }];
76
+ const answer = await memberQuestions.ask({ sessionId: surface.sessionId, toolCallId: `check-scope-${randomUUID()}`, questions, signal }).catch(() => null);
77
+ if (!answer || answer.skipped) return undefined;
78
+ const { selected, other } = answer.answers[0];
79
+ return other ? { other } : labels[selected[0]];
80
+ }
81
+
82
+ // At the end of a change: { choice: 'partial' | 'full' | 'skip', words? }, or null
83
+ // when nobody can answer. No answer takes the cheap choice: the status says only part ran.
84
+ export async function askCheckScope(self, { partPassed = false, signal } = {}) {
85
+ const surface = surfaceOf(self);
86
+ if (!surface) return null;
87
+ if (effectivePermission(self) === 'bypass') return { choice: 'partial' };
88
+ const part = partPassed ? KEEP : PART;
89
+ const picked = await ask(surface, partPassed ? 'Code đã đổi, phần test liên quan đã pass. Có chạy toàn bộ test không?' : 'Code đã đổi, chưa chạy test. Chạy phần nào?', [part, FULL, NONE], signal);
90
+ if (signal?.aborted) return null;
91
+ if (picked?.other) return { choice: 'partial', words: picked.other };
92
+ return { choice: picked === FULL ? 'full' : picked === NONE ? 'skip' : 'partial' };
93
+ }
94
+
95
+ const PARTIAL_REASON = 'managed-check-scope: the member chose to run only the tests that cover the change. Run one file, a filter or one package, not the whole suite. If this project has no way to run part of it, say so and repeat the command: the member is asked.';
96
+ const BYPASS_REASON = 'managed-check-scope: run the tests that cover the change (one file, a filter, one package). A whole-suite run needs the member to ask for it, or their answer before a push. If this project has no way to run part of it, say so and repeat the command.';
97
+ const PUSH_REASON = 'managed-check-scope: the member wants the whole suite before this push. Run it, fix what fails, then push.';
98
+ const STOPPED_REASON = 'managed-check-scope: the member did not answer whether to push now. Do not push; say so and let them decide.';
99
+ const WORDS_REASON = words => `managed-check-scope: this command did not run. The member answered about testing: "${String(words).slice(0, 500)}". Follow that.`;
100
+
101
+ // Before a whole suite or a push: returns to let it run, throws to send the agent another way.
102
+ export async function beforeCheckOrPush(self, command, ctx, signal) {
103
+ const run = self.run;
104
+ if (!run || run.policy?.verify === 'off') return;
105
+ // Calls sent together wait for one answer: the next one sees it.
106
+ const turn = (run.scopeQuestion ?? Promise.resolve()).then(() => holdOrLet(self, run, command, ctx, signal));
107
+ run.scopeQuestion = turn.catch(() => {});
108
+ return turn;
109
+ }
110
+ async function holdOrLet(self, run, command, ctx, signal) {
111
+ const push = isGitPush(command);
112
+ if (!push && (checkScope(command, run.checks.commands) !== 'suite' || run.fullAsked || run.scopeChoice === 'full')) return;
113
+ const surface = surfaceOf(self, ctx);
114
+ if (!surface) return;
115
+ if (push) {
116
+ const now = await self.digest();
117
+ if (run.suitePassedOn(now) || (run.pushAllowedOn !== undefined && run.pushAllowedOn === now)) return;
118
+ const picked = await ask(surface, 'Sắp push code nhưng toàn bộ test chưa pass trên code này. Làm gì?', [PUSH_FULL, PUSH_NOW], signal);
119
+ if (picked === PUSH_NOW) { run.pushAllowedOn = now; return; }
120
+ if (picked?.other) throw Error(WORDS_REASON(picked.other));
121
+ if (picked !== PUSH_FULL) throw Error(STOPPED_REASON);
122
+ run.scopeChoice = 'full';
123
+ throw Error(PUSH_REASON);
124
+ }
125
+ // Sent another way once; the same command again means the part did not do: the member decides (Bypass: it runs).
126
+ const held = run.scopeHeld ??= new Set(), again = held.has(command); held.add(command);
127
+ if (effectivePermission(self) === 'bypass') { if (again) return; throw Error(BYPASS_REASON); }
128
+ if (run.scopeChoice === 'partial' && !again) throw Error(PARTIAL_REASON);
129
+ const picked = await ask(surface, `Agent muốn chạy toàn bộ test: ${String(command).slice(0, 160)}`, [PART, FULL], signal);
130
+ if (picked === FULL) { run.scopeChoice = 'full'; return; }
131
+ run.scopeChoice = 'partial';
132
+ throw Error(picked?.other ? WORDS_REASON(picked.other) : PARTIAL_REASON);
133
+ }
@@ -1,4 +1,5 @@
1
1
  import { spawn } from 'node:child_process';
2
+ import { beforeCheckOrPush } from './check-questions.mjs';
2
3
  import { asksOnlyHandoff, broadAccess, effectivePermission, networkConfirmation } from './permission.mjs';
3
4
  import { handoffReason } from '../runtime/policy/handoff-policy.mjs';
4
5
  import { engineAlive, engineLauncher } from './container-engine.mjs';
@@ -105,6 +106,7 @@ function commandTool(self, kind) {
105
106
  if (memberOnly) throw Error(MEMBER_ONLY(memberOnly));
106
107
  const notSimulator = kind === 'simulator' ? simulatorCommandReason(args.command, self.cwd) : null;
107
108
  if (notSimulator) throw Error(`managed-simulator-command: ${notSimulator}.`);
109
+ if (kind !== 'simulator') await beforeCheckOrPush(self, args.command, ctx, signal); // how much to test, before the command's own approval
108
110
  const confirm = await spec.confirm(self, args.command);
109
111
  if (confirm !== null) {
110
112
  const why = confirm === 'ask' ? null : stillAsks(self, confirm), handoff = await handoffReason(args.command);
@@ -1,10 +1,12 @@
1
1
  // Fast mode of a company conversation. The member turns it on per
2
2
  // conversation, and only where the team's Harness in Studio allows it
3
3
  // (harness.configuration.fast_mode); a conversation starts with it off. While
4
- // on, the main agent's calls ask a Codex model for the priority service tier:
5
- // the same model and thinking level, answered faster, and charged to the
6
- // key's quota at a higher rate (Studio decides the rate and refuses the tier
7
- // for a run whose Harness does not allow it). Subagents run as usual.
4
+ // on, every call of the conversation, the main agent's and its subagents', asks
5
+ // for the priority service tier where its model serves it: the same model and
6
+ // thinking level, answered faster, and charged to the key's quota at a higher
7
+ // rate (Studio decides the rate and refuses the tier for a run whose Harness
8
+ // does not allow it). A model that does not serve it runs at standard speed,
9
+ // so a Claude main agent with Codex subagents still gets it on the subagents.
8
10
  const ENTRY = 'agent-watch-fast';
9
11
  export const NOTICE = 'agent-watch-fast-notice';
10
12
  export const FAST_SERVICE_TIER = 'priority';
@@ -27,7 +29,7 @@ export function fastSetter(owner, manager) {
27
29
  return enabled => {
28
30
  if (enabled && !fastAllowed(owner.manifest)) throw Error('managed-fast-unavailable');
29
31
  if (Boolean(enabled) === Boolean(owner.fast)) return false;
30
- owner.fast = Boolean(enabled); manager.appendCustomEntry(ENTRY, { enabled: owner.fast, allowed: fastAllowed(owner.manifest), at: new Date().toISOString() });
32
+ owner.fast = Boolean(enabled); owner.fastDeclined?.clear(); manager.appendCustomEntry(ENTRY, { enabled: owner.fast, allowed: fastAllowed(owner.manifest), at: new Date().toISOString() });
31
33
  return true;
32
34
  };
33
35
  }
@@ -44,21 +46,22 @@ export const fastServed = route => route?.provider === 'codex' && CODEX_FAST.has
44
46
  // Studio's refusal of the tier (the Harness or the model list changed), or
45
47
  // the provider's of a request carrying it: asked again once without it.
46
48
  export const fastRefusal = code => code === 'policy_denied' || code === 'upstream_request_rejected';
47
- // A fast request was refused before the model did any work: the conversation
48
- // goes back to standard speed and says so, instead of failing the turn.
49
- export function declineFast(owner, code) {
50
- owner.fast = false;
49
+ // A fast request was refused before the model did any work: that model goes
50
+ // back to standard speed and the others keep fast mode. Only the main agent's
51
+ // model says so; turning Fast off and on asks again.
52
+ export function declineFast(owner, code, role = 'main', route = null) {
53
+ (owner.fastDeclined ??= new Set()).add(route?.native ?? '');
54
+ if (role !== 'main') return;
51
55
  try {
52
- recordFast(owner, owner.session.sessionManager);
53
56
  void owner.session.sendCustomMessage?.({ customType: NOTICE, display: true, details: { code },
54
- content: `Fast mode was refused for this request [${code}]: this conversation continues at standard speed. Turn Fast on again to retry.` }, { triggerTurn: false })?.catch?.(() => {});
57
+ content: `Fast mode was refused for this model [${code}]: its calls continue at standard speed. Turn Fast off and on again to retry.` }, { triggerTurn: false })?.catch?.(() => {});
55
58
  } catch { /* the request goes on at standard speed either way */ }
56
59
  }
57
- // The payload hook of one request: the main agent's call on a model that
58
- // serves fast mode carries the tier while fast mode is on and still allowed
59
- // (the Harness may have turned it off since the member turned it on).
60
- export function fastPayload(owner, role, route, onPayload) {
61
- if (role !== 'main' || !owner.fast || !fastAllowed(owner.manifest) || !fastServed(route)) return onPayload;
60
+ // The payload hook of one request: a call on a model that serves fast mode
61
+ // carries the tier while fast mode is on and still allowed (the Harness may
62
+ // have turned it off since the member turned it on).
63
+ export function fastPayload(owner, route, onPayload) {
64
+ if (!owner.fast || !fastAllowed(owner.manifest) || !fastServed(route) || owner.fastDeclined?.has(route.native)) return onPayload;
62
65
  return async (payload, model) => {
63
66
  const next = (onPayload ? await onPayload(payload, model) : undefined) ?? payload;
64
67
  return next && typeof next === 'object' && !Array.isArray(next) ? { ...next, service_tier: FAST_SERVICE_TIER } : next;
@@ -144,7 +144,7 @@ async function answerUnder(managed, key, task, signal, harness, snapshot, parts,
144
144
  (managed.jobTitles ??= new WeakMap()).set(runtime, jobTitle(task, title));
145
145
  const setup = HELPER_SETUP[role], memory = helperMemoryTools(managed, () => grant, renew);
146
146
  boundary = new ManagedToolBoundary({ cwd: managed.cwd, sdkRoot: managed.sdk, protectedRoots: managed.protectedRoots, readOnly: true, commands: setup.commands });
147
- 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'}.` : '';
147
+ 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'}.${managed.run?.fullAsked || managed.run?.scopeChoice === 'full' ? '' : ' Run only the tests that cover the claims: the member decides about a whole-suite run.'}` : '';
148
148
  // One helper session answers the brief (with one part of the patch for a
149
149
  // review in parts); its reply and tokens. A helper runs at its role's
150
150
  // level from Studio (fixed by the Harness, or its main agent's).
@@ -201,7 +201,7 @@ async function answerUnder(managed, key, task, signal, harness, snapshot, parts,
201
201
  for (const session of sessions) session.dispose(); await boundary?.dispose();
202
202
  await settleHelper(managed, renew.lease, report); // with the role's current token, before it closes
203
203
  await closeRole(managed, key);
204
- for (const who of watched) helperState(managed, who, null);
204
+ for (const who of watched) { helperState(managed, who, null); saveHelperSteps(managed, who); }
205
205
  }
206
206
  }
207
207
 
@@ -216,12 +216,29 @@ export function helperState(managed, key, state) {
216
216
  // latest tool step, told to listeners as it changes (company-terminal.mjs).
217
217
  function watchHelper(managed, key, session, task, title) {
218
218
  const job = String(title || task.split('\n').find(line => line.trim()) || '').trim().slice(0, 120);
219
+ const logs = managed.helperLogs ??= new Map();
220
+ const log = logs.get(key) ?? { job, startedAt: new Date().toISOString(), total: 0, steps: [] }; logs.set(key, log);
219
221
  helperState(managed, key, { job, step: 'đọc brief' });
220
222
  session.subscribe?.(event => {
221
223
  if (event?.type !== 'tool_execution_start') return;
222
- helperState(managed, key, { job, step: stepText(event.toolName, event.args) });
224
+ const args = stepArgs(event.args);
225
+ log.total += 1; log.steps.push({ tool: String(event.toolName), args, at: new Date().toISOString() }); log.steps.splice(0, log.steps.length - HELPER_STEPS_KEPT);
226
+ helperState(managed, key, { job, step: stepText(event.toolName, event.args), tool: String(event.toolName), args });
223
227
  });
224
228
  }
229
+ // A helper's session lives in memory: what it called is kept here so the
230
+ // dashboard's Activity can still show it once the helper has ended.
231
+ const HELPER_STEPS_KEPT = 200;
232
+ function saveHelperSteps(managed, key) {
233
+ const log = managed.helperLogs?.get(key); if (!log) return;
234
+ managed.helperLogs.delete(key);
235
+ managed.session?.sessionManager?.appendCustomEntry?.('agent-watch-helper-steps', { agent: key, ...log, endedAt: new Date().toISOString() });
236
+ }
237
+ function stepArgs(args = {}) {
238
+ const out = {};
239
+ for (const name of ['path', 'pattern', 'command', 'query', 'url']) if (typeof args?.[name] === 'string') out[name] = args[name].slice(0, 300);
240
+ return out;
241
+ }
225
242
  const STEP = { read: 'đọc', grep: 'tìm', find: 'tìm file', ls: 'xem thư mục', bash: 'chạy', web_search: 'tìm trên web', web_fetch: 'đọc trang', memory_search: 'tra team memory', memory_ask: 'hỏi team memory' };
226
243
  export function stepText(tool, args = {}) {
227
244
  const what = args?.path ?? args?.pattern ?? args?.command ?? args?.query ?? args?.url ?? '';