@piagent/platform 1.51.0 → 1.51.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 (56) hide show
  1. package/CHANGELOG.md +8 -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 +27 -18
  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 +21 -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 +27 -18
  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/managed/check-questions.mjs +21 -11
  56. package/packages/piagent-core/package.json +1 -1
@@ -8,6 +8,10 @@ import { digestsOnce, knownFast, markCheckStart, missingSuites, pushQuestion, pu
8
8
  // suite and before a push, on the dashboard card or in the Terminal. Bypass skips
9
9
  // the questions about the work (it runs the tests that cover the change) but not
10
10
  // the one before a push. With no screen nobody can answer: nothing is asked.
11
+ // A question nobody answers in UNANSWERED_MS (the member left the task running
12
+ // overnight) is taken back and the cheap answer goes on: only the tests that
13
+ // cover the change, and no push. Before this a turn waited until morning.
14
+ export const UNANSWERED_MS = 10 * 60_000;
11
15
 
12
16
  // How much of the suite a test command covers: 'partial' (a filter, one file, one
13
17
  // package), 'suite' (the whole test command) or null when it runs no tests.
@@ -82,14 +86,20 @@ function surfaceOf(self, ctx) {
82
86
  const runner = self.session?.extensionRunner, ui = ctx ? ctx.hasUI && ctx.ui : runner?.hasUI?.() && runner.getUIContext?.();
83
87
  return ui?.select && !ui[GATEWAY_UI] ? { ui } : null;
84
88
  }
85
- // The label picked, { other } for the member's own words, undefined for no answer (Esc, stopped).
86
- async function ask(surface, question, labels, signal) {
87
- if (surface.ui) return surface.ui.select(question, labels, { signal });
88
- const questions = [{ header: 'Test', question, options: labels.map(label => ({ label, description: '' })), multiSelect: false }];
89
- const answer = await memberQuestions.ask({ sessionId: surface.sessionId, toolCallId: `check-scope-${randomUUID()}`, questions, signal }).catch(() => null);
90
- if (!answer || answer.skipped) return undefined;
91
- const { selected, other } = answer.answers[0];
92
- return other ? { other } : labels[selected[0]];
89
+ // The label picked, { other } for the member's own words, undefined for no
90
+ // answer (Esc, stopped, or nobody answered in time).
91
+ async function ask(surface, question, labels, signal, waitMs = UNANSWERED_MS) {
92
+ // A plain timer, not AbortSignal.timeout: the wait itself keeps the process up.
93
+ const expired = new AbortController(), timer = setTimeout(() => expired.abort(), waitMs);
94
+ const until = signal ? AbortSignal.any([signal, expired.signal]) : expired.signal;
95
+ try {
96
+ if (surface.ui) return await surface.ui.select(question, labels, { signal: until });
97
+ const questions = [{ header: 'Test', question, options: labels.map(label => ({ label, description: '' })), multiSelect: false }];
98
+ const answer = await memberQuestions.ask({ sessionId: surface.sessionId, toolCallId: `check-scope-${randomUUID()}`, questions, signal: until }).catch(() => null);
99
+ if (!answer || answer.skipped) return undefined;
100
+ const { selected, other } = answer.answers[0];
101
+ return other ? { other } : labels[selected[0]];
102
+ } finally { clearTimeout(timer); }
93
103
  }
94
104
 
95
105
  // At the end of a change: { choice: 'partial' | 'full' | 'skip', words? }, or null
@@ -99,7 +109,7 @@ export async function askCheckScope(self, { partPassed = false, signal } = {}) {
99
109
  if (!surface) return null;
100
110
  if (['bypass', 'always-bypass'].includes(effectivePermission(self))) return { choice: 'partial' };
101
111
  const part = partPassed ? KEEP : PART;
102
- 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);
112
+ 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, self.unansweredMs);
103
113
  if (signal?.aborted) return null;
104
114
  if (picked?.other) return { choice: 'partial', words: picked.other };
105
115
  return { choice: picked === FULL ? 'full' : picked === NONE ? 'skip' : 'partial' };
@@ -133,7 +143,7 @@ async function holdOrLet(self, run, command, ctx, signal) {
133
143
  const now = await digestOf('.'), repositories = pushRepositories(self, command), single = !repositories.length || repositories[0] === '.';
134
144
  const missing = await missingSuites(digestOf, run, repositories.length ? repositories : ['.']);
135
145
  if ((single ? run.suitePassedOn(now) : false) || !missing.length || (run.pushAllowedOn !== undefined && run.pushAllowedOn === now)) return;
136
- const picked = await ask(surface, await pushQuestion(self, digestOf, run, repositories, missing), [PUSH_FULL, PUSH_NOW], signal);
146
+ const picked = await ask(surface, await pushQuestion(self, digestOf, run, repositories, missing), [PUSH_FULL, PUSH_NOW], signal, self.unansweredMs);
137
147
  if (picked === PUSH_NOW) { run.pushAllowedOn = now; return; }
138
148
  if (picked?.other) throw Error(WORDS_REASON(picked.other));
139
149
  if (picked !== PUSH_FULL) throw Error(STOPPED_REASON);
@@ -144,7 +154,7 @@ async function holdOrLet(self, run, command, ctx, signal) {
144
154
  const held = run.scopeHeld ??= new Set(), again = held.has(command); held.add(command);
145
155
  if (effectivePermission(self) === 'bypass') { if (again || knownFast(self, command, suitePart(command))) return; throw Error(BYPASS_REASON); }
146
156
  if (run.scopeChoice === 'partial' && !again) throw Error(PARTIAL_REASON);
147
- const picked = await ask(surface, `Agent muốn chạy toàn bộ test: ${String(command).slice(0, 160)}`, [PART, FULL], signal);
157
+ const picked = await ask(surface, `Agent muốn chạy toàn bộ test: ${String(command).slice(0, 160)}`, [PART, FULL], signal, self.unansweredMs);
148
158
  if (picked === FULL) { run.scopeChoice = 'full'; return; }
149
159
  run.scopeChoice = 'partial';
150
160
  throw Error(picked?.other ? WORDS_REASON(picked.other) : PARTIAL_REASON);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piagent/core",
3
- "version": "1.51.0",
3
+ "version": "1.51.1",
4
4
  "type": "module",
5
5
  "private": true,
6
6
  "license": "MIT",