@piagent/platform 1.29.0 → 1.30.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 (65) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +1 -1
  3. package/README.vi.md +1 -1
  4. package/docs/command-reference-vietnamese.md +2 -2
  5. package/docs/distribution-standard.md +6 -6
  6. package/docs/languages.json +2 -2
  7. package/docs/operator-manual-vietnamese.md +7 -7
  8. package/docs/publishing-for-teams.md +2 -0
  9. package/docs/release-install-policy.md +2 -2
  10. package/docs/team-onboarding.md +2 -2
  11. package/docs-site/architecture.html +1 -1
  12. package/docs-site/benchmark.html +1 -1
  13. package/docs-site/commands.html +1 -1
  14. package/docs-site/content/en/index.html +4 -4
  15. package/docs-site/content/en/whats-new.html +13 -0
  16. package/docs-site/content/index.html +4 -4
  17. package/docs-site/content/whats-new.html +13 -0
  18. package/docs-site/context-engine.html +1 -1
  19. package/docs-site/ecosystem.html +1 -1
  20. package/docs-site/en/architecture.html +1 -1
  21. package/docs-site/en/benchmark.html +1 -1
  22. package/docs-site/en/commands.html +1 -1
  23. package/docs-site/en/context-engine.html +1 -1
  24. package/docs-site/en/ecosystem.html +1 -1
  25. package/docs-site/en/index.html +5 -5
  26. package/docs-site/en/mcp-approval.html +1 -1
  27. package/docs-site/en/mcp-auth.html +1 -1
  28. package/docs-site/en/mcp-servers.html +1 -1
  29. package/docs-site/en/mcp.html +1 -1
  30. package/docs-site/en/permissions.html +1 -1
  31. package/docs-site/en/profiles.html +1 -1
  32. package/docs-site/en/quickstart.html +1 -1
  33. package/docs-site/en/runtime.html +1 -1
  34. package/docs-site/en/scope.html +1 -1
  35. package/docs-site/en/security.html +1 -1
  36. package/docs-site/en/team.html +1 -1
  37. package/docs-site/en/whats-new.html +14 -1
  38. package/docs-site/en/windows.html +1 -1
  39. package/docs-site/en/workflows.html +1 -1
  40. package/docs-site/index.html +5 -5
  41. package/docs-site/mcp-approval.html +1 -1
  42. package/docs-site/mcp-auth.html +1 -1
  43. package/docs-site/mcp-servers.html +1 -1
  44. package/docs-site/mcp.html +1 -1
  45. package/docs-site/permissions.html +1 -1
  46. package/docs-site/profiles.html +1 -1
  47. package/docs-site/quickstart.html +1 -1
  48. package/docs-site/runtime.html +1 -1
  49. package/docs-site/scope.html +1 -1
  50. package/docs-site/security.html +1 -1
  51. package/docs-site/team.html +1 -1
  52. package/docs-site/whats-new.html +14 -1
  53. package/docs-site/windows.html +1 -1
  54. package/docs-site/workflows.html +1 -1
  55. package/evals/private-holdout-v1/public-exposure.v2.json +2 -2
  56. package/package.json +1 -1
  57. package/packages/piagent-core/managed/compressed-fetch.mjs +28 -0
  58. package/packages/piagent-core/managed/request-stream.mjs +2 -1
  59. package/packages/piagent-core/package.json +1 -1
  60. package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-3vQTaeOO.js → MarkdownMessage-ZFXG_br8.js} +1 -1
  61. package/packages/piagent-webui/dist/client/assets/{index-Dve4GNQ7.js → index-B3ODBFGj.js} +3 -3
  62. package/packages/piagent-webui/dist/client/index.html +1 -1
  63. package/packages/piagent-webui/shared/company-copy.ts +1 -0
  64. package/scripts/company-terminal.mjs +75 -16
  65. package/scripts/piagent-studio.mjs +5 -1
@@ -7,7 +7,7 @@
7
7
  <meta name="csp-nonce" content="__PIAGENT_CSP_NONCE__" />
8
8
  <meta name="piagent-webui-mode" content="__PIAGENT_WEBUI_MODE__" />
9
9
  <title>Piagent WebUI</title>
10
- <script type="module" crossorigin src="/assets/index-Dve4GNQ7.js"></script>
10
+ <script type="module" crossorigin src="/assets/index-B3ODBFGj.js"></script>
11
11
  <link rel="stylesheet" crossorigin href="/assets/index-B5XOKQy-.css">
12
12
  </head>
13
13
  <body>
@@ -12,6 +12,7 @@ export const LAUNCH_REASONS: Record<string, [string, string]> = {
12
12
  "managed-key-unavailable": ["Agent Watch không đọc được key công ty trên máy này. Mở Agent Watch, dán lại mã kết nối, bấm Kết nối Piagent trong WSL rồi kết nối lại.", "Agent Watch cannot read the company key on this computer. Open Agent Watch, paste the connection code again, choose Connect Piagent in WSL, then connect again."],
13
13
  "managed-profile-disconnected": ["Cấu hình Piagent đang thuộc key công ty đã ngắt kết nối. Nhập lại cấu hình cho Piagent trong Agent Watch (trên Windows: Kết nối Piagent trong WSL).", "The Piagent configuration belongs to a disconnected company key. Import it again in Agent Watch (on Windows: Connect Piagent in WSL)."],
14
14
  "managed-launch-binding-changed": ["Agent Watch hoặc Piagent vừa cập nhật. Nhập lại cấu hình cho Piagent trong Agent Watch (trên Windows: Kết nối Piagent trong WSL).", "Agent Watch or Piagent changed. Import the Piagent configuration again in Agent Watch (on Windows: Connect Piagent in WSL)."],
15
+ "managed-project-contains-config": ["Không mở Piagent công ty ở thư mục này (thường là thư mục home ~) vì nó chứa cấu hình công ty. Vào thư mục dự án rồi chạy lại: cd ~/đường-dẫn-dự-án && piagent studio, hoặc piagent studio --project ~/đường-dẫn-dự-án.", "The company Piagent cannot open this folder (usually your home folder ~) because it holds the company configuration. Go to a project folder and run it again: cd ~/your-project && piagent studio, or piagent studio --project ~/your-project."],
15
16
  "managed-binding-update-pending": ["Agent Watch đang chạy nhưng chưa cập nhật cấu hình sau bản Piagent hoặc Agent Watch mới. Mở Agent Watch → Studio, bấm Áp dụng cho Piagent rồi mở lại.", "Agent Watch is running but has not updated the configuration after the Piagent or Agent Watch update yet. Open Agent Watch → Studio, apply for Piagent, then open it again."],
16
17
  "managed-binding-watch-not-running": ["Piagent hoặc Agent Watch vừa cập nhật nhưng Agent Watch không chạy nên chưa cập nhật cấu hình. Mở Agent Watch, chờ vài giây rồi mở lại.", "Piagent or Agent Watch was updated but Agent Watch is not running, so the configuration was not updated. Open Agent Watch, wait a few seconds, then open it again."],
17
18
  "managed-launch-version-mismatch": ["Agent Watch đang dùng một bản Piagent khác với dashboard này. Mở Agent Watch → Studio, bấm Áp dụng cho Piagent rồi kết nối lại.", "Agent Watch points at another Piagent install than this dashboard. Open Agent Watch → Studio, apply for Piagent, then connect again."],
@@ -5,6 +5,7 @@
5
5
  import fs from 'node:fs';
6
6
  import { createRequire } from 'node:module';
7
7
  import path from 'node:path';
8
+ import { execFile } from 'node:child_process';
8
9
  import { pathToFileURL } from 'node:url';
9
10
 
10
11
  import { registerTerminalUpdateOffer } from '../packages/piagent-core/runtime/update/terminal-update-offer.mjs';
@@ -16,6 +17,45 @@ import { headlineText, outcomeText, processItems } from '../packages/piagent-web
16
17
  import { turnEndCopy, turnEndOf } from '../packages/piagent-webui/shared/turn-end.ts';
17
18
 
18
19
  const LOCALE = 'vi';
20
+
21
+ const WORKING = 'Đang làm…';
22
+
23
+ // A typed value that is already complete offers nothing, so the first Enter runs the command.
24
+ export const argumentCompletions = (values) => (prefix) => {
25
+ const typed = String(prefix ?? '').trim();
26
+ return values.includes(typed) ? [] : values.filter((v) => v.startsWith(typed)).map((value) => ({ value, label: value }));
27
+ };
28
+
29
+ const compactTokens = (n) => n >= 1_000_000 ? `${(n / 1_000_000).toFixed(1)}M` : n >= 1000 ? `${Math.round(n / 1000)}k` : String(n);
30
+ const textOf = (content) => typeof content === 'string' ? content : Array.isArray(content) ? content.filter((p) => p?.type === 'text').map((p) => p.text ?? '').join('\n') : '';
31
+
32
+ // Model requests, tokens, time and output speed of the member's last message,
33
+ // as the dashboard writes them under a turn.
34
+ export function turnStats(messages, now = Date.now()) {
35
+ const list = Array.isArray(messages) ? messages : [];
36
+ const start = list.findLastIndex((m) => m?.role === 'user');
37
+ if (start < 0) return null;
38
+ const answers = list.slice(start + 1).filter((m) => m?.role === 'assistant');
39
+ if (!answers.length) return null;
40
+ const tokens = answers.reduce((n, m) => n + (m.usage?.totalTokens ?? 0), 0), output = answers.reduce((n, m) => n + (m.usage?.output ?? 0), 0);
41
+ const began = Number(list[start].timestamp) || null, seconds = began ? Math.max(0, Math.round((now - began) / 1000)) : null;
42
+ const parts = [`${answers.length} request`];
43
+ if (tokens) parts.push(`${compactTokens(tokens)} token`);
44
+ if (seconds) parts.push(seconds < 60 ? `${seconds} giây` : `${Math.floor(seconds / 60)} phút ${seconds % 60} giây`);
45
+ if (seconds && output) parts.push(`${Math.round(output / seconds)} tok/s`);
46
+ return parts.join(' · ');
47
+ }
48
+
49
+ // Team memory results stay short in the transcript (Ctrl+O shows all).
50
+ export function memoryResultLines(text, expanded = false) {
51
+ const lines = String(text ?? '').split('\n').map((l) => l.trimEnd()).filter((l) => l.trim());
52
+ if (expanded || lines.length <= 4) return lines;
53
+ const facts = lines.filter((l) => /^\s*-\s/.test(l)).length;
54
+ return [...lines.slice(0, 3), `… ${facts ? `${facts} ghi chú, ` : ''}${lines.length - 3} dòng nữa (Ctrl+O để xem hết)`];
55
+ }
56
+
57
+ const branchOf = (cwd) => new Promise((resolve) => execFile('git', ['-C', cwd, 'rev-parse', '--abbrev-ref', 'HEAD'], { timeout: 3000 },
58
+ (error, out) => resolve(error ? null : String(out).trim() || null)));
19
59
  export const HELP_TEXT = [
20
60
  'Cuộc trò chuyện công ty: /undo hoàn tác file lượt trước đã sửa · /bypass on|off|status quyền lệnh cần internet · /fast on|off|status (khi Harness cho phép) · /skill:<tên> hoặc /<lệnh> của bạn và của project.',
21
61
  'Pi: /session thông tin cuộc trò chuyện · /resume mở cuộc trò chuyện công ty khác · /new cuộc mới · /compact nén ngữ cảnh · /tree, /fork nhánh · /name đặt tên · /copy, /export · /hotkeys toàn bộ phím tắt · /quit thoát.',
@@ -33,15 +73,15 @@ const EFFORT = { off: 'Tắt', minimal: 'Tối thiểu', low: 'Thấp', medium:
33
73
  export const effortText = (level) => EFFORT[level] ?? level ?? '';
34
74
 
35
75
  // What the team's Harness decides for this conversation, as the header and
36
- // footer say it. `last` is the main agent's latest run (its model and
37
- // effort); before the first run, the Harness's first main model and its effort.
76
+ // footer say it: roles and effort only. A member never sees which model a
77
+ // role runs on, so no model id leaves this function. `last` is the main
78
+ // agent's latest run (its effort); before the first run, the Harness's effort.
38
79
  export function harnessSummary(managed, { last = null, version = null } = {}) {
39
80
  const manifest = managed?.manifest, config = manifest?.harness?.configuration ?? {};
40
- const review = config.workflow?.review !== 'off' && config.review?.model_ids?.[0] || null;
81
+ const review = config.workflow?.review !== 'off' && Boolean(config.review?.model_ids?.length);
41
82
  const fast = managed?.session?.managedFastState?.() ?? null;
42
83
  return {
43
84
  team: manifest?.user?.team_name || null,
44
- model: last?.model_id || config.main?.model_ids?.[0] || null,
45
85
  effort: last?.effort || config.main?.effort || managed?.session?.state?.thinkingLevel || null,
46
86
  review, permission: managed?.permission === 'trusted-full-access' ? 'Bypass' : 'Hỏi trước',
47
87
  fast: fast === null ? null : fast === 'on', memory: managed ? memoryOn(managed) : false, version,
@@ -49,8 +89,8 @@ export function harnessSummary(managed, { last = null, version = null } = {}) {
49
89
  }
50
90
  export function headerText(summary) {
51
91
  return ['Piagent công ty', summary.team && `team ${summary.team}`,
52
- summary.model && `main ${summary.model}${summary.effort ? ` (${effortText(summary.effort)})` : ''}`,
53
- summary.review && `review ${summary.review}`, summary.permission,
92
+ `Main agent${summary.effort ? ` (${effortText(summary.effort)})` : ''}`,
93
+ summary.review && 'Subagent review bật', summary.permission,
54
94
  summary.fast !== null && `Fast ${summary.fast ? 'bật' : 'tắt'}`, `memory ${summary.memory ? 'bật' : 'tắt'}`, summary.version].filter(Boolean).join(' · ');
55
95
  }
56
96
  export const HEADER_KEYS = 'Esc dừng lượt · Ctrl+C xoá ô nhập, hai lần để thoát · / lệnh · ! shell · Ctrl+O xem thêm · /help';
@@ -61,7 +101,7 @@ export const HEADER_KEYS = 'Esc dừng lượt · Ctrl+C xoá ô nhập, hai l
61
101
  // Ctrl+P, Shift+Tab) answer the same.
62
102
  const ADMIN = 'Admin đổi trong Agent Studio → Harness.';
63
103
  export function companyCommandText(name, summary = {}) {
64
- const main = summary.model ? ` (main ${summary.model}${summary.effort ? `, ${effortText(summary.effort)}` : ''})` : '';
104
+ const main = summary.effort ? ` (Main agent: ${effortText(summary.effort)})` : '';
65
105
  switch (name) {
66
106
  case 'share': return '/share bị tắt trong cuộc trò chuyện công ty: lệnh này đăng toàn bộ hội thoại lên GitHub Gist, ra ngoài công ty. Cần lưu lại thì dùng /export (file trên máy này).';
67
107
  case 'bug': return '/bug bị tắt trong cuộc trò chuyện công ty: báo cáo gửi hội thoại và code cho nhà phát triển Pi, ra ngoài công ty. Gặp lỗi, gửi admin của team mã lỗi trong [ ] và giờ xảy ra.';
@@ -128,7 +168,7 @@ export function failureLines(errorMessage) {
128
168
 
129
169
  const tokens = (n) => n < 1000 ? String(n) : n < 10000 ? `${(n / 1000).toFixed(1)}k` : n < 1e6 ? `${Math.round(n / 1000)}k` : `${(n / 1e6).toFixed(1)}M`;
130
170
  // Pi's footer without the USD cost (company accounts are not billed per
131
- // conversation) and with the Harness's main model and effort on the right.
171
+ // conversation) and with the main agent's effort on the right (never its model).
132
172
  export function footerLines({ session, summary, footerData, theme, width, truncateToWidth, visibleWidth }) {
133
173
  const dim = (text) => theme.fg('dim', text);
134
174
  if (!session) return [truncateToWidth(dim(headerText(summary)), width, dim('...'))];
@@ -148,7 +188,7 @@ export function footerLines({ session, summary, footerData, theme, width, trunca
148
188
  const parts = [sum.input && `↑${tokens(sum.input)}`, sum.output && `↓${tokens(sum.output)}`, sum.cacheRead && `R${tokens(sum.cacheRead)}`, sum.cacheWrite && `W${tokens(sum.cacheWrite)}`].filter(Boolean);
149
189
  const ctxText = `${shown === '?' ? '?' : shown + '%'}/${tokens(window)} (auto)`;
150
190
  let left = [...parts, ctxText].join(' ');
151
- const right = summary.model ? `main ${summary.model}${summary.effort ? ` • ${effortText(summary.effort)}` : ''}` : '';
191
+ const right = `Main agent${summary.effort ? ` • ${effortText(summary.effort)}` : ''}`;
152
192
  if (visibleWidth(left) > width) left = truncateToWidth(left, width, '...');
153
193
  const room = width - visibleWidth(left) - 2;
154
194
  const tail = room > 0 && right ? truncateToWidth(right, room, '') : '';
@@ -165,7 +205,10 @@ export function footerLines({ session, summary, footerData, theme, width, trunca
165
205
  export async function companyToolRenderers(sdkRoot) {
166
206
  const require = createRequire(path.join(sdkRoot, 'package.json'));
167
207
  const { Text } = await import(pathToFileURL(require.resolve('@earendil-works/pi-tui')).href);
168
- return { update_plan: {
208
+ const memory = (title) => ({
209
+ renderCall: (args, theme) => new Text(theme.fg('toolTitle', theme.bold(title)) + (args?.query || args?.name ? theme.fg('muted', ` ${args.query ?? args.name}`) : ''), 0, 0),
210
+ renderResult: (result, options, theme) => new Text(theme.fg('toolOutput', memoryResultLines((result?.content ?? []).map((c) => c.text ?? '').join('\n'), options?.expanded).join('\n')), 0, 0) });
211
+ return { memory_search: memory('Team memory'), memory_page: memory('Trang kiến thức'), update_plan: {
169
212
  renderCall: (_args, theme) => new Text(theme.fg('toolTitle', theme.bold('Plan')), 0, 0),
170
213
  renderResult: (result, _options, theme) => {
171
214
  const plan = result?.details?.plan;
@@ -184,6 +227,15 @@ export async function companyInteractiveMode(api, { sdkRoot, packageRoot, summar
184
227
  const changelog = () => { try { return fs.readFileSync(path.join(packageRoot, 'CHANGELOG.md'), 'utf8'); } catch { return ''; } };
185
228
  return companyMode(api.InteractiveMode, { summary, whatsNew: () => whatsNewMarkdown(changelog()), components: { Markdown, Spacer, DynamicBorder: api.DynamicBorder } });
186
229
  }
230
+ // Pi's own warnings, as the company Terminal says them: the crash notice
231
+ // without /bug, and no model id ("Could not restore model p/id").
232
+ export function companyWarning(message) {
233
+ const text = String(message ?? '');
234
+ const crash = /^\S+ crashed on (.+?) \((.*)\)\. Run \/bug/s.exec(text);
235
+ if (crash) return `Lần trước Piagent công ty dừng bất thường (${crash[1]}, ${crash[2]}). Cuộc trò chuyện vẫn còn: /resume để mở lại. Nếu lặp lại, gửi admin của team giờ xảy ra.`;
236
+ if (/^Could not restore model /.test(text)) return 'Cuộc trò chuyện này chạy theo Harness hiện tại của team.';
237
+ return text;
238
+ }
187
239
  export function companyMode(Base, { summary, whatsNew, components }) {
188
240
  const say = (mode, name, tone = 'warning') => mode.showExtensionNotify(companyCommandText(name, summary()), tone);
189
241
  return class CompanyInteractiveMode extends Base {
@@ -191,6 +243,8 @@ export function companyMode(Base, { summary, whatsNew, components }) {
191
243
  async handleBugCommand() { say(this, 'bug'); }
192
244
  suggestBugReport() {}
193
245
  crashReportInstructions() { return 'Lần sau mở lại: piagent studio, rồi /resume. Gửi admin của team giờ xảy ra lỗi.'; }
246
+ // Pi's start-up warnings point at /bug (off here) or name a model.
247
+ showWarning(message) { super.showWarning(companyWarning(message)); }
194
248
  async handleLoginCommand() { say(this, 'login', 'info'); }
195
249
  async showOAuthSelector() { say(this, 'logout', 'info'); }
196
250
  async handleModelCommand() { say(this, 'model', 'info'); }
@@ -235,7 +289,7 @@ export function companyTerminalExtension({ managed, packageRoot, sdkRoot, bindin
235
289
  pi.on('session_start', async (_event, ctx) => showPermission(ctx));
236
290
  pi.registerCommand('bypass', {
237
291
  description: 'Bật/tắt Bypass cho cuộc trò chuyện công ty này (on, off, hoặc để trống để chọn)',
238
- getArgumentCompletions: (prefix) => ['on', 'off', 'status'].filter((v) => v.startsWith(prefix)).map((value) => ({ value, label: value })),
292
+ getArgumentCompletions: argumentCompletions(['on', 'off', 'status']),
239
293
  handler: async (args, ctx) => {
240
294
  const session = managed();
241
295
  if (typeof session?.session?.managedSetPermission !== 'function') { ctx.ui.notify('Cuộc trò chuyện này chưa sẵn sàng.', 'error'); return; }
@@ -266,7 +320,7 @@ export function companyTerminalExtension({ managed, packageRoot, sdkRoot, bindin
266
320
  pi.on('session_start', async (_event, ctx) => showFast(ctx));
267
321
  pi.registerCommand('fast', {
268
322
  description: 'Bật/tắt Fast cho cuộc trò chuyện công ty này (on, off, status); chỉ có khi Harness của team cho phép',
269
- getArgumentCompletions: (prefix) => ['on', 'off', 'status'].filter((v) => v.startsWith(prefix)).map((value) => ({ value, label: value })),
323
+ getArgumentCompletions: argumentCompletions(['on', 'off', 'status']),
270
324
  handler: async (args, ctx) => {
271
325
  const session = managed()?.session, state = session?.managedFastState?.();
272
326
  if (typeof session?.managedSetFast !== 'function') { ctx.ui.notify('Cuộc trò chuyện này chưa sẵn sàng.', 'error'); return; }
@@ -294,7 +348,7 @@ export function companyTerminalExtension({ managed, packageRoot, sdkRoot, bindin
294
348
  let last = null;
295
349
  const summary = () => {
296
350
  const grant = managed()?.grant;
297
- if (grant?.model_id) last = { model_id: grant.model_id, effort: grant.effort };
351
+ if (grant?.effort) last = { effort: grant.effort };
298
352
  return harnessSummary(managed(), { last, version: installed });
299
353
  };
300
354
  pi.on('session_start', async (_event, ctx) => {
@@ -318,7 +372,7 @@ export function companyTerminalExtension({ managed, packageRoot, sdkRoot, bindin
318
372
  });
319
373
  pi.on('tool_execution_end', async (event, ctx) => {
320
374
  if (event?.toolName !== 'ask_user') return;
321
- ctx.ui?.setWorkingMessage?.(); ctx.ui?.setWorkingIndicator?.();
375
+ ctx.ui?.setWorkingMessage?.(WORKING); ctx.ui?.setWorkingIndicator?.();
322
376
  });
323
377
 
324
378
  // The Harness's notes carry English for the agent; the member reads them in Vietnamese.
@@ -334,11 +388,16 @@ export function companyTerminalExtension({ managed, packageRoot, sdkRoot, bindin
334
388
  const session = managed()?.session;
335
389
  if (!session?.subscribe || heard === session) return;
336
390
  heard = session;
391
+ const showBranch = async () => { const branch = await branchOf(managed()?.cwd ?? ctx.cwd); ctx.ui?.setStatus?.('agent-watch-branch', branch ? `⎇ ${branch}` : undefined); };
392
+ ctx.ui?.setWorkingMessage?.(WORKING);
393
+ void showBranch();
337
394
  session.subscribe((event) => {
395
+ if (event?.type === 'managed_helpers') ctx.ui?.setStatus?.('agent-watch-helpers', event.active ? `Subagent ${event.active}/${event.maximum}` : undefined);
338
396
  const end = event?.type === 'managed_turn_end' ? turnEndOf(event.end) : null;
339
397
  if (!end) return;
340
- const copy = turnEndCopy(end, LOCALE);
341
- ctx.ui?.notify?.(`${copy.title}. ${copy.text.replace('Bấm Tiếp tục', 'Gửi "tiếp tục"').replace('bấm Tiếp tục', 'gửi "tiếp tục"')}`, copy.tone === 'success' ? 'info' : copy.tone === 'error' ? 'error' : copy.tone === 'warning' ? 'warning' : 'info');
398
+ const copy = turnEndCopy(end, LOCALE), stats = turnStats(session.messages);
399
+ ctx.ui?.notify?.(`${copy.title}. ${copy.text.replace('Bấm Tiếp tục', 'Gửi "tiếp tục"').replace('bấm Tiếp tục', 'gửi "tiếp tục"')}${stats ? `\n${stats}` : ''}`, copy.tone === 'success' ? 'info' : copy.tone === 'error' ? 'error' : copy.tone === 'warning' ? 'warning' : 'info');
400
+ void showBranch();
342
401
  });
343
402
  });
344
403
  pi.registerMessageRenderer('agent-watch-review-status', (_message, _options, theme) =>
@@ -23,6 +23,8 @@ for (let i = 0; i < args.length; i++) {
23
23
  }
24
24
  if (flags.has('--json') && !flags.has('--prompt')) throw Error('managed-launch-arguments');
25
25
  const home = os.homedir();
26
+ // Joining Agent Watch takes a few seconds; keys typed before the Terminal draws are lost, so say so.
27
+ if (!['--check', '--prompt', '--serve', '--doctor', '--web'].some((f) => flags.has(f)) && process.stderr.isTTY) process.stderr.write('Piagent công ty: đang kết nối Agent Watch, chờ ô nhập hiện ra rồi gõ…\n');
26
28
  // Do this before importing the SDK: it must not discover personal keys, Node
27
29
  // preload hooks, proxy overrides or provider endpoints from an ambient shell.
28
30
  const clean = { PATH: '/usr/bin:/bin', HOME: home, LANG: 'en_US.UTF-8', TERM: process.env.TERM || 'xterm-256color', ...interopEnvironment() };
@@ -81,7 +83,9 @@ try {
81
83
  }
82
84
  const agentDir = path.join(root, store);
83
85
  const cwd = serve ? path.join(agentDir, 'probe') : fs.realpathSync(flags.get('--project') || process.cwd());
84
- if (configPath.startsWith(cwd + path.sep)) throw Error('managed-launch-binding-changed');
86
+ // A project folder that holds the company configuration (the home folder,
87
+ // ~/.pi) would put it in reach of the project's tools: refuse, and say so.
88
+ if (configPath.startsWith(cwd + path.sep)) throw Error('managed-project-contains-config');
85
89
  fs.mkdirSync(agentDir, { recursive: true, mode: 0o700 }); fs.chmodSync(agentDir, 0o700);
86
90
  if (serve) { fs.mkdirSync(cwd, { recursive: true, mode: 0o700 }); fs.chmodSync(cwd, 0o700); }
87
91
  await import('./register-typescript-loader.mjs');