@piagent/platform 1.48.3 → 1.48.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/docs/command-reference-vietnamese.md +2 -2
  3. package/docs/distribution-standard.md +6 -6
  4. package/docs/operator-manual-vietnamese.md +7 -7
  5. package/docs/publishing-for-teams.md +1 -0
  6. package/docs/release-install-policy.md +2 -2
  7. package/docs/team-onboarding.md +2 -2
  8. package/docs-site/architecture.html +1 -1
  9. package/docs-site/assets/search-en.json +1 -1
  10. package/docs-site/assets/search-vi.json +1 -1
  11. package/docs-site/benchmark.html +1 -1
  12. package/docs-site/commands.html +1 -1
  13. package/docs-site/context-engine.html +1 -1
  14. package/docs-site/ecosystem.html +1 -1
  15. package/docs-site/en/architecture.html +1 -1
  16. package/docs-site/en/benchmark.html +1 -1
  17. package/docs-site/en/commands.html +1 -1
  18. package/docs-site/en/context-engine.html +1 -1
  19. package/docs-site/en/ecosystem.html +1 -1
  20. package/docs-site/en/index.html +9 -9
  21. package/docs-site/en/mcp-approval.html +1 -1
  22. package/docs-site/en/mcp-auth.html +1 -1
  23. package/docs-site/en/mcp-servers.html +1 -1
  24. package/docs-site/en/mcp.html +1 -1
  25. package/docs-site/en/permissions.html +1 -1
  26. package/docs-site/en/profiles.html +1 -1
  27. package/docs-site/en/quickstart.html +1 -1
  28. package/docs-site/en/runtime.html +1 -1
  29. package/docs-site/en/scope.html +1 -1
  30. package/docs-site/en/security.html +1 -1
  31. package/docs-site/en/team.html +1 -1
  32. package/docs-site/en/update.html +3 -3
  33. package/docs-site/en/whats-new.html +26 -15
  34. package/docs-site/en/windows.html +1 -1
  35. package/docs-site/en/workflows.html +1 -1
  36. package/docs-site/index.html +9 -9
  37. package/docs-site/mcp-approval.html +1 -1
  38. package/docs-site/mcp-auth.html +1 -1
  39. package/docs-site/mcp-servers.html +1 -1
  40. package/docs-site/mcp.html +1 -1
  41. package/docs-site/permissions.html +1 -1
  42. package/docs-site/profiles.html +1 -1
  43. package/docs-site/quickstart.html +1 -1
  44. package/docs-site/releases.json +31 -0
  45. package/docs-site/runtime.html +1 -1
  46. package/docs-site/scope.html +1 -1
  47. package/docs-site/security.html +1 -1
  48. package/docs-site/team.html +1 -1
  49. package/docs-site/update.html +3 -3
  50. package/docs-site/whats-new.html +26 -15
  51. package/docs-site/windows.html +1 -1
  52. package/docs-site/workflows.html +1 -1
  53. package/evals/private-holdout-v1/public-exposure.v2.json +1 -1
  54. package/package.json +1 -1
  55. package/packages/piagent-core/package.json +1 -1
  56. package/packages/piagent-core/runtime/update/terminal-reload.mjs +23 -13
  57. package/scripts/company-terminal.mjs +10 -5
@@ -2,9 +2,9 @@
2
2
  //
3
3
  // `piagent reload` (and `piagent-update` once it installed a release) writes
4
4
  // one request file. Each Terminal started before that request takes it when it
5
- // is idle: no run going, nothing queued, nothing typed in the editor. 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
5
+ // is idle: no run going, nothing queued, nothing typed in the editor, and quiet
6
+ // for a moment. A run in progress is never cut: the footer says the reload
7
+ // waits, and it happens once the run has settled. How a Terminal reloads is its own: the personal Terminal
8
8
  // runs Pi's /reload (Piagent's extensions are read again from disk), the
9
9
  // company Terminal starts again in place on the same conversation, since its
10
10
  // company code is loaded once per process.
@@ -14,7 +14,15 @@ import path from "node:path";
14
14
 
15
15
  const REQUEST_FILE = "piagent-reload.json";
16
16
  const POLL_MS = 2_000;
17
+ // A member who stops a run with Esc usually types the next message right away:
18
+ // a Terminal that starts again in that moment loses what they type.
19
+ const QUIET_MS = 10_000;
17
20
  const STATUS_KEY = "piagent-reload";
21
+ // Per process, not per extension runtime: /reload runs this module again, and a
22
+ // request sent before it (the company Terminal's /reload loads no company code)
23
+ // must still be taken, while one already taken must not reload twice.
24
+ const APPLIED_AT = "PIAGENT_RELOAD_APPLIED_AT";
25
+ const processStartedAt = () => Date.now() - process.uptime() * 1000;
18
26
 
19
27
  const requestPath = (home) => path.join(home ?? os.homedir(), ".pi", REQUEST_FILE);
20
28
 
@@ -47,8 +55,8 @@ function idle(ctx) {
47
55
 
48
56
  // `apply(ctx, request)` reloads this Terminal; it runs at most once per
49
57
  // process. Only Terminals with a UI take requests: scripted runs keep theirs.
50
- export function watchTerminalReload(pi, { apply, home, startedAt = Date.now(), pollMs = POLL_MS }) {
51
- let ctx = null, applied = false, waiting = false, timer = null;
58
+ export function watchTerminalReload(pi, { apply, home, startedAt = processStartedAt(), pollMs = POLL_MS, quietMs = QUIET_MS }) {
59
+ let ctx = null, applied = false, waiting = false, timer = null, busyAt = 0;
52
60
  // A ctx kept from an earlier session (replaced, reloaded) throws on any use:
53
61
  // a check that meets one does nothing, and the next session_start brings a new one.
54
62
  const check = async () => {
@@ -56,17 +64,19 @@ export function watchTerminalReload(pi, { apply, home, startedAt = Date.now(), p
56
64
  try {
57
65
  if (applied || !ctx?.hasUI) return;
58
66
  request = readReloadRequest(home);
59
- if (!request || request.at <= startedAt) return;
60
- 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`); }
67
+ if (!request || request.at <= Math.max(startedAt, Number(process.env[APPLIED_AT]) || 0)) return;
68
+ const quiet = idle(ctx);
69
+ if (!quiet) busyAt = Date.now();
70
+ if (!quiet || Date.now() - busyAt < quietMs) {
71
+ if (!waiting) { waiting = true; ctx.ui?.setStatus?.(STATUS_KEY, `⟳ Nạp Piagent${request.version ? ` ${request.version}` : " mới"} khi rảnh`); }
62
72
  return;
63
73
  }
64
- applied = true;
74
+ applied = true; process.env[APPLIED_AT] = String(request.at);
65
75
  ctx.ui?.setStatus?.(STATUS_KEY, undefined);
66
76
  } catch { return; }
67
77
  try { await apply(ctx, request); }
68
78
  catch (error) {
69
- applied = false;
79
+ applied = false; delete process.env[APPLIED_AT];
70
80
  try { ctx.ui?.notify?.(`Chưa nạp được Piagent mới: ${error instanceof Error ? error.message : String(error)}`, "warning"); } catch { /* the session is gone */ }
71
81
  }
72
82
  };
@@ -75,9 +85,9 @@ export function watchTerminalReload(pi, { apply, home, startedAt = Date.now(), p
75
85
  keep(event, next);
76
86
  if (!timer) { timer = setInterval(() => { void check(); }, pollMs); timer.unref?.(); }
77
87
  });
78
- pi.on("agent_start", keep);
79
- // The 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?.(); });
88
+ pi.on("agent_start", (event, next) => { keep(event, next); busyAt = Date.now(); });
89
+ // The next check after the quiet time takes a request that waits.
90
+ pi.on("agent_settled", (event, next) => { keep(event, next); busyAt = Date.now(); });
81
91
  pi.on("session_shutdown", () => { if (timer) clearInterval(timer); timer = null; ctx = null; });
82
92
  return { check };
83
93
  }
@@ -301,6 +301,7 @@ export function companyTerminalExtension({ managed, packageRoot, sdkRoot, bindin
301
301
  let tui;
302
302
  const components = async () => (tui ??= await import(pathToFileURL(require.resolve('@earendil-works/pi-tui')).href));
303
303
  const installed = (() => { try { return JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8')).version; } catch { return undefined; } })();
304
+ let stopHearing = null;
304
305
 
305
306
  return async (pi) => {
306
307
  const { Box, Text } = await components();
@@ -426,11 +427,12 @@ export function companyTerminalExtension({ managed, packageRoot, sdkRoot, bindin
426
427
  });
427
428
  // How the member's message ended (finished, stopped with steps open,
428
429
  // failed, stopped), as the dashboard says it under the last answer.
429
- let heard = null;
430
430
  pi.on('session_start', async (_event, ctx) => {
431
431
  const session = managed()?.session;
432
- if (!session?.subscribe || heard === session) return;
433
- heard = session;
432
+ if (!session?.subscribe) return;
433
+ // /reload runs this extension again on the same session: the listener of the
434
+ // runtime it replaces goes, or each event reaches a stale ctx and fails the run.
435
+ stopHearing?.();
434
436
  // The conversation can close while git answers (a client ends right after a turn): its ctx is then stale.
435
437
  const showBranch = async () => { try { const branch = await branchOf(managed()?.cwd ?? ctx.cwd); ctx.ui?.setStatus?.('agent-watch-branch', branch ? `⎇ ${branch}` : undefined); } catch { /* closed meanwhile */ } };
436
438
  ctx.ui?.setWorkingMessage?.(WORKING);
@@ -438,7 +440,7 @@ export function companyTerminalExtension({ managed, packageRoot, sdkRoot, bindin
438
440
  // The checklist and the subagents working now stay above the input (setWidget).
439
441
  const showPlan = () => ctx.ui?.setWidget?.('agent-watch-plan', planWidget(currentPlan(session.sessionManager)));
440
442
  showPlan();
441
- session.subscribe((event) => {
443
+ const hear = (event) => {
442
444
  if (event?.type === 'tool_execution_end' && event.toolName === 'update_plan' || event?.type === 'agent_end') showPlan();
443
445
  if (event?.type === 'managed_helper_state') ctx.ui?.setWidget?.('agent-watch-helpers', helperLines(event.helpers));
444
446
  if (event?.type === 'managed_helpers') ctx.ui?.setStatus?.('agent-watch-helpers', event.active ? `Subagent ${event.active}/${event.maximum}` : undefined);
@@ -448,7 +450,10 @@ export function companyTerminalExtension({ managed, packageRoot, sdkRoot, bindin
448
450
  const copy = turnEndCopy(end, LOCALE), stats = turnStats(session.messages);
449
451
  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');
450
452
  void showBranch();
451
- });
453
+ };
454
+ // A session closing (a restart in place, /reload) can still report while its ctx is already stale.
455
+ const unsubscribe = session.subscribe((event) => { try { hear(event); } catch { /* the Terminal has moved on */ } });
456
+ stopHearing = unsubscribe;
452
457
  });
453
458
  pi.registerMessageRenderer('agent-watch-review-status', (_message, _options, theme) =>
454
459
  box('Review đã cũ: code đổi sau khi review. Chỉ cần review lại khi đã sửa lỗi blocking.', theme, 'warning'));