@piagent/platform 1.50.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 (116) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/catalog/capabilities.json +2 -2
  3. package/docs/command-reference-vietnamese.md +5 -3
  4. package/docs/company-dev-commands.md +1 -1
  5. package/docs/distribution-standard.md +6 -6
  6. package/docs/herdr-workflow.md +30 -0
  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/security-threat-model.md +2 -0
  11. package/docs/team-onboarding.md +2 -2
  12. package/docs-site/architecture.html +1 -1
  13. package/docs-site/assets/search-en.json +1 -1
  14. package/docs-site/assets/search-vi.json +1 -1
  15. package/docs-site/benchmark.html +1 -1
  16. package/docs-site/commands.html +1 -1
  17. package/docs-site/content/en/workflows.html +6 -0
  18. package/docs-site/content/workflows.html +6 -0
  19. package/docs-site/context-engine.html +1 -1
  20. package/docs-site/ecosystem.html +1 -1
  21. package/docs-site/en/architecture.html +1 -1
  22. package/docs-site/en/benchmark.html +1 -1
  23. package/docs-site/en/commands.html +1 -1
  24. package/docs-site/en/context-engine.html +1 -1
  25. package/docs-site/en/ecosystem.html +1 -1
  26. package/docs-site/en/index.html +15 -15
  27. package/docs-site/en/mcp-approval.html +1 -1
  28. package/docs-site/en/mcp-auth.html +1 -1
  29. package/docs-site/en/mcp-servers.html +1 -1
  30. package/docs-site/en/mcp.html +1 -1
  31. package/docs-site/en/permissions.html +1 -1
  32. package/docs-site/en/profiles.html +1 -1
  33. package/docs-site/en/quickstart.html +1 -1
  34. package/docs-site/en/runtime.html +1 -1
  35. package/docs-site/en/scope.html +1 -1
  36. package/docs-site/en/security.html +1 -1
  37. package/docs-site/en/team.html +1 -1
  38. package/docs-site/en/update.html +3 -3
  39. package/docs-site/en/whats-new.html +54 -30
  40. package/docs-site/en/windows.html +1 -1
  41. package/docs-site/en/workflows.html +7 -1
  42. package/docs-site/index.html +15 -15
  43. package/docs-site/mcp-approval.html +1 -1
  44. package/docs-site/mcp-auth.html +1 -1
  45. package/docs-site/mcp-servers.html +1 -1
  46. package/docs-site/mcp.html +1 -1
  47. package/docs-site/permissions.html +1 -1
  48. package/docs-site/profiles.html +1 -1
  49. package/docs-site/quickstart.html +1 -1
  50. package/docs-site/releases.json +72 -0
  51. package/docs-site/runtime.html +1 -1
  52. package/docs-site/scope.html +1 -1
  53. package/docs-site/security.html +1 -1
  54. package/docs-site/team.html +1 -1
  55. package/docs-site/update.html +3 -3
  56. package/docs-site/whats-new.html +54 -30
  57. package/docs-site/windows.html +1 -1
  58. package/docs-site/workflows.html +7 -1
  59. package/evals/private-holdout-v1/public-exposure.v2.json +2 -2
  60. package/package.json +2 -1
  61. package/packages/piagent-core/extensions/fallback-base-policy.ts +1 -1
  62. package/packages/piagent-core/extensions/guard-types.ts +4 -1
  63. package/packages/piagent-core/{managed/herdr-reporter.mjs → extensions/herdr-reporter.js} +29 -13
  64. package/packages/piagent-core/extensions/piagent-guard.ts +20 -9
  65. package/packages/piagent-core/extensions/session-status.d.ts +1 -0
  66. package/packages/piagent-core/extensions/session-status.js +217 -0
  67. package/packages/piagent-core/managed/check-questions.mjs +24 -14
  68. package/packages/piagent-core/managed/command-tools.mjs +46 -7
  69. package/packages/piagent-core/managed/dist/workspace/assets/packages/piagent-core/policies/base-policy.json +2 -1
  70. package/packages/piagent-core/managed/dist/workspace/manifest.json +1 -1
  71. package/packages/piagent-core/managed/fetch-guard.mjs +2 -1
  72. package/packages/piagent-core/managed/helper-run.mjs +4 -2
  73. package/packages/piagent-core/managed/host-command.mjs +8 -4
  74. package/packages/piagent-core/managed/member-mcp.mjs +2 -0
  75. package/packages/piagent-core/managed/permission.mjs +11 -3
  76. package/packages/piagent-core/managed/session.mjs +5 -5
  77. package/packages/piagent-core/managed/tool-boundary.mjs +4 -4
  78. package/packages/piagent-core/package.json +1 -1
  79. package/packages/piagent-core/policies/base-policy.json +2 -1
  80. package/packages/piagent-core/runtime/hooks/session-start-hook.ts +3 -0
  81. package/packages/piagent-core/runtime/policy/critical-removal.mjs +122 -0
  82. package/packages/piagent-core/runtime/policy/handoff-policy.mjs +23 -4
  83. package/packages/piagent-core/runtime/registration/permission-commands.ts +14 -6
  84. package/packages/piagent-core/runtime/registration/task-evidence-tools.ts +1 -1
  85. package/packages/piagent-webui/dist/client/assets/{ActivityPanel-CmjxPmNT.js → ActivityPanel-DeeJLOYx.js} +1 -1
  86. package/packages/piagent-webui/dist/client/assets/App-C9tlGPMb.js +5 -0
  87. package/packages/piagent-webui/dist/client/assets/{GatewayApp-BmkqeXih.js → GatewayApp-Dvo96qoX.js} +17 -17
  88. package/packages/piagent-webui/dist/client/assets/{InspectionApp-9jBsgkVJ.js → InspectionApp-CGgHsOyC.js} +1 -1
  89. package/packages/piagent-webui/dist/client/assets/{SessionAgentWorkspace-RQ0N65d9.js → SessionAgentWorkspace-n8UMMcrP.js} +1 -1
  90. package/packages/piagent-webui/dist/client/assets/{SessionInspectorDrawer-BWyEN4bf.js → SessionInspectorDrawer-B62kkr8J.js} +2 -2
  91. package/packages/piagent-webui/dist/client/assets/SettingsNavigation-BZijmuPt.js +3 -0
  92. package/packages/piagent-webui/dist/client/assets/SettingsPage-DKg0SfZQ.js +1 -0
  93. package/packages/piagent-webui/dist/client/assets/{TerminalPanel-DzHdSjpN.js → TerminalPanel-BPTVsNjP.js} +1 -1
  94. package/packages/piagent-webui/dist/client/assets/{WorkspaceCodeEditor-CEuPTTBd.js → WorkspaceCodeEditor-BHGFFBc2.js} +1 -1
  95. package/packages/piagent-webui/dist/client/assets/{WorkspaceExplorer-B_NwYf4S.js → WorkspaceExplorer-nd4kghwK.js} +3 -3
  96. package/packages/piagent-webui/dist/client/assets/{index-fUpioGMZ.js → index-BiLVX8S-.js} +2 -2
  97. package/packages/piagent-webui/dist/client/assets/{transcript-view-model-acWtHGyE.js → transcript-view-model-BB1niiXd.js} +1 -1
  98. package/packages/piagent-webui/dist/client/assets/{view-model-CWNiS4JN.js → view-model-BrdSO09E.js} +1 -1
  99. package/packages/piagent-webui/dist/client/index.html +1 -1
  100. package/packages/piagent-webui/gateway/managed-projection.ts +1 -1
  101. package/packages/piagent-webui/gateway/runtime-session-controls.ts +1 -1
  102. package/packages/piagent-webui/gateway/session-command-controller.ts +4 -4
  103. package/packages/piagent-webui/gateway/session-inspection-registry.ts +1 -1
  104. package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +1 -1
  105. package/schemas/piagent-webui/session-catalog-v1.schema.json +1 -1
  106. package/schemas/piagent-webui/session-command-v1.schema.json +1 -1
  107. package/schemas/piagent-webui/snapshot-v1.schema.json +2 -1
  108. package/schemas/project-profile.schema.json +2 -1
  109. package/scripts/company-terminal.mjs +8 -7
  110. package/scripts/pi-auto.sh +5 -1
  111. package/scripts/piagent-cli.mjs +3 -1
  112. package/scripts/piagent-sessions.mjs +97 -0
  113. package/scripts/piagent-studio.mjs +5 -3
  114. package/packages/piagent-webui/dist/client/assets/App-JrRb7jgn.js +0 -5
  115. package/packages/piagent-webui/dist/client/assets/SettingsNavigation-Wx-q5QQk.js +0 -3
  116. package/packages/piagent-webui/dist/client/assets/SettingsPage-xAt6kSyZ.js +0 -1
@@ -21,7 +21,7 @@ import { proxyEnvironment } from './package-proxy.mjs';
21
21
  import { startBoundaryPackageProxy } from './boundary-package-proxy.mjs';
22
22
  import { SourceRevertRequests } from './source-revert-requests.mjs';
23
23
  import { BWRAP, bubblewrapArgs, credentialFiles, referenceFolders } from './linux-sandbox.mjs';
24
- import { agentWatchDataDirectory } from './store.mjs';
24
+ import { agentWatchDataDirectory } from './store.mjs'; import { spawnOnHost } from './host-command.mjs';
25
25
  const TOOL_NAMES = Object.freeze(['read', 'bash', 'edit', 'write', 'grep', 'find', 'ls']);
26
26
  const MAX_WIRE_BYTES = 12 * 1024 * 1024;
27
27
  const workerPath = fileURLToPath(new URL('./tool-worker.mjs', import.meta.url));
@@ -369,9 +369,9 @@ export class ManagedToolBoundary {
369
369
  if (Buffer.byteLength(message) > MAX_WIRE_BYTES) throw new Error('managed-tool-input-too-large');
370
370
  return await new Promise((resolve, reject) => {
371
371
  guard?.();
372
- const [sandbox, sandboxArgs] = this.linux ? [BWRAP, this.bubblewrap(network, docker)]
372
+ const [sandbox, sandboxArgs] = this.unsandboxed?.() ? [null, []] : this.linux ? [BWRAP, this.bubblewrap(network, docker)]
373
373
  : ['/usr/bin/sandbox-exec', ['-p', docker ? this.dockerProfile : network ? this.networkProfile : isolated ? this.isolatedProfile : this.profile]];
374
- const child = spawn(sandbox, [...sandboxArgs, this.node, this.workerPath, this.sdkPath, this.cwd], {
374
+ const child = spawn(sandbox ?? this.node, [...sandboxArgs, ...(sandbox ? [this.node] : []), this.workerPath, this.sdkPath, this.cwd], {
375
375
  cwd: this.cwd, detached: true, stdio: ['pipe', 'pipe', 'pipe'],
376
376
  env: docker ? this.environment.docker : network ? this.environment.network : this.environment.offline,
377
377
  });
@@ -455,7 +455,7 @@ export class ManagedToolBoundary {
455
455
  // A process the agent keeps running across turns (background-process.mjs):
456
456
  // the plain command sandbox and environment, marked as this conversation's
457
457
  // background process instead of the turn's, so stopStrays leaves it.
458
- async spawnBackground(command, key) { return this.spawnSandboxed(['/bin/bash', '-c', command], key); }
458
+ async spawnBackground(command, key) { return this.unsandboxed?.() ? { child: spawnOnHost(command, this.cwd, this.userHome), isolated: false } : this.spawnSandboxed(['/bin/bash', '-c', command], key); }
459
459
  // `argv` in the plain command sandbox; stdin 'pipe' for a process that
460
460
  // answers requests (ts-diagnostics.mjs).
461
461
  async spawnSandboxed(argv, key, { stdin = 'ignore' } = {}) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piagent/core",
3
- "version": "1.50.0",
3
+ "version": "1.51.1",
4
4
  "type": "module",
5
5
  "private": true,
6
6
  "license": "MIT",
@@ -94,7 +94,8 @@
94
94
  "read-only",
95
95
  "workspace-write",
96
96
  "trusted-full-access",
97
- "bypass"
97
+ "bypass",
98
+ "always-bypass"
98
99
  ]
99
100
  },
100
101
  "execPolicy": {
@@ -319,6 +319,9 @@ export function registerSessionStartHook(pi: ExtensionAPI, dependencies: Session
319
319
  if (permissionProfile.mode === "trusted-full-access") {
320
320
  ctx.ui.notify("Piagent permission profile trusted-full-access is active; protected paths, secret redaction, and destructive/external confirmations remain enforced.", "warning");
321
321
  }
322
+ if (permissionProfile.mode === "always-bypass") {
323
+ ctx.ui.notify("Warning: Piagent always bypass is active, by your decision. Only committing and pushing code and removing a critical path (rm -rf ~) are asked; deploys, MCP, network and credentials run. The project's autoDeny rules, output redaction and /undo checkpoints remain.", "warning");
324
+ }
322
325
  if (permissionProfile.mode === "bypass") {
323
326
  ctx.ui.notify("Piagent bypass is active: the work runs without confirmation, protected-path or policy gates in this personal session. The handoff (push, PR, publish, deploy, remote tools, MCP writes) is still asked; output redaction and /undo checkpoints remain.", "warning");
324
327
  }
@@ -0,0 +1,122 @@
1
+ // What Always bypass asks about besides commit and push: an rm or rmdir whose
2
+ // target is a critical path, by the rules of Claude's bypassPermissions
3
+ // (permission-modes, "Critical paths" and "The mounted drive prompt"). A text
4
+ // check, not a boundary: other commands and scripts can delete these paths too.
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { evaluateExecPolicyCore } from '../../extensions/policy-core.js';
8
+ import { astCommands } from '../../extensions/shell-ast.js';
9
+ import { executable } from './handoff-policy.mjs';
10
+
11
+ const TOP_LEVEL = new Set(['bin', 'boot', 'dev', 'etc', 'home', 'lib', 'lib64', 'media', 'mnt', 'opt', 'proc', 'root', 'run', 'sbin', 'srv', 'sys', 'tmp', 'usr', 'var', 'Applications', 'Library', 'System', 'Users', 'Volumes', 'private']);
12
+ const DIRECTORY_PRINTER = /(?:^|[\s;&|(])([A-Za-z_]\w*)=["']?(?:\$\(|`)\s*(?:pwd|git\s+rev-parse\s+--show-toplevel|dirname\b|realpath\b)/g;
13
+ const MOUNTED = [/^\/Volumes\/[^/]+(\/[^/]+)?$/, /^\/System\/Volumes(\/[^/]+){0,2}$/, /^\/media\/[^/]+(\/[^/]+){0,2}$/, /^\/run\/media(\/[^/]+){0,3}$/, /^\/mnt\/[^/]+$/, /^\/mnt\/[A-Za-z]\/[^/]+$/];
14
+ const DYNAMIC = /\$(?:\(…\)|[A-Za-z_@*#?!\d]\w*)/;
15
+ const onlyDynamic = /^\$(?:\(…\)|[A-Za-z_@*#?!\d]\w*)$/;
16
+
17
+ // On Windows the checks below see one spelling of a path: `D:\\a\\B`, `d:/a/b` and
18
+ // Git Bash's `/d/a/b` are all `/d:/a/b`.
19
+ function comparable(value) {
20
+ if (process.platform !== 'win32') return value;
21
+ return value.replace(/\\/g, '/').toLowerCase().replace(/^\/?([a-z]):?(?=\/|$)/, (_, drive) => `/${drive}:`);
22
+ }
23
+
24
+ // `{/,}` is `/` by the time it runs.
25
+ function expanded(word) {
26
+ const brace = /^(.*?)\{([^{}]*,[^{}]*)\}(.*)$/.exec(word);
27
+ return brace ? brace[2].split(',').flatMap(part => expanded(brace[1] + part + brace[3])) : [word];
28
+ }
29
+ function parseTargets(args) {
30
+ const targets = []; let recursive = false, options = true;
31
+ for (const arg of args) {
32
+ if (options && arg === '--') options = false;
33
+ else if (options && arg.startsWith('-') && arg !== '-') recursive ||= arg === '--recursive' || (!arg.startsWith('--') && /[rR]/.test(arg));
34
+ else targets.push(arg);
35
+ }
36
+ return { targets, recursive };
37
+ }
38
+
39
+ function isCritical(target, cwd, home) {
40
+ if (target === '/' || path.posix.dirname(target) === '/') return true;
41
+ return target === home || target === cwd || cwd.startsWith(`${target}/`);
42
+ }
43
+
44
+ // Why this one target needs the member, or null.
45
+ function targetReason(raw, { cwd, project, home, recursive, guarded, printers, singleQuotedScript, afterCd }) {
46
+ const DANGER = text => `Dangerous rm operation on critical path: ${text}`;
47
+ if (/^\\+$/.test(raw)) return DANGER(raw);
48
+ let word = raw.replace(/^~(?=\/|$)/, home).replace(/\$\{?HOME\}?(?=\/|$)/, home).replace(/\$\{?PWD\}?(?=\/|$)/, cwd);
49
+ if (/^~/.test(word)) return DANGER(raw);
50
+ for (const name of printers) if (new RegExp(`\\$${name}(?:\\b|/|$)`).test(word)) return `Dangerous rm operation: ${raw} comes from a directory-printing substitution`;
51
+ const dynamic = DYNAMIC.exec(word);
52
+ if (dynamic) {
53
+ const name = dynamic[0].slice(1), positional = /^[@*\d#]/.test(name);
54
+ if (onlyDynamic.test(word) && word.includes('(…)') && recursive) return `Dangerous rm operation: ${raw} is only the output of a command`;
55
+ const under = /^\$[^/]*\/(\*(\/\*)*\/?)?$/.test(word), glob = under && !(guarded.has(name) || (positional && singleQuotedScript));
56
+ if (glob) return `Dangerous rm operation: ${raw} removes from the filesystem root when the variable is empty`;
57
+ if (/^\$[^/]+\/[^/]+$/.test(word) && TOP_LEVEL.has(word.split('/')[1]) && !guarded.has(name)) return DANGER(`${raw} (/${word.split('/')[1]} when the variable is empty)`);
58
+ const before = word.slice(0, dynamic.index);
59
+ // A trailing substitution: the path that remains when it expands empty.
60
+ if (word.slice(dynamic.index) === dynamic[0] && before.startsWith('/') && isCritical(path.posix.resolve(comparable(before)), cwd, home)) return DANGER(raw);
61
+ return null;
62
+ }
63
+ const glob = /\/?\*\/?$/.test(word) || word === '*';
64
+ if (glob && (/\*\/(\*\/?)?$/.test(word) || (afterCd && !word.startsWith('/')))) return `Dangerous rm operation: ${raw} cannot be checked before the command runs`;
65
+ const literal = word.replace(/\/?\*$/, '') || (word.startsWith('/') ? '/' : '.');
66
+ const resolved = path.posix.resolve(cwd, comparable(literal));
67
+ if (isCritical(resolved, cwd, home) || isCritical(resolved, project, home)) return DANGER(raw);
68
+ if ((recursive || glob) && MOUNTED.some(rule => rule.test(resolved))) return `Dangerous rm operation on mounted drive path: ${raw}`;
69
+ return null;
70
+ }
71
+
72
+ // Why this command must be asked about in Always bypass, or null.
73
+ export function criticalRemovalReason(command, options) {
74
+ const cwd = comparable(options.cwd), home = comparable(options.home);
75
+ const text = String(command), commands = astCommands(text);
76
+ if (commands === null) return /\b(rm|rmdir)\b/.test(text) ? 'Dangerous rm operation not ruled out: the shell may read this command differently than it was parsed' : null;
77
+ const guarded = new Set([...text.matchAll(/\$\{([A-Za-z_]\w*):\?/g)].map(match => match[1]));
78
+ const printers = new Set([...text.matchAll(DIRECTORY_PRINTER)].map(match => match[1]));
79
+ const singleQuotedScript = /\s-[a-z]*c\s+'/.test(text);
80
+ let base = cwd, afterCd = false;
81
+ for (const { words } of commands) {
82
+ const [name, ...args] = executable(words), program = String(name ?? '').split('/').pop();
83
+ if (program === 'cd') {
84
+ afterCd = true;
85
+ if (args[0] && !DYNAMIC.test(args[0])) base = path.posix.resolve(base, comparable(args[0].replace(/^~(?=\/|$)/, home)));
86
+ continue;
87
+ }
88
+ if (program !== 'rm' && program !== 'rmdir') continue;
89
+ const { targets, recursive } = parseTargets(args);
90
+ for (const target of targets.flatMap(expanded)) {
91
+ const reason = targetReason(target, { cwd: base, project: cwd, home, recursive, guarded, printers, singleQuotedScript, afterCd });
92
+ if (reason) return reason;
93
+ }
94
+ }
95
+ return null;
96
+ }
97
+
98
+ // A project's own refusals (profile `autoDeny`) in a company session, as a deny
99
+ // rule holds in every mode: the reason, or null.
100
+ export function projectDenyReason(command, cwd) {
101
+ let profile;
102
+ try { profile = JSON.parse(fs.readFileSync(path.join(cwd, '.pi', 'piagent-profile.json'), 'utf8')); } catch { return null; }
103
+ const rules = (Array.isArray(profile?.autoDeny) ? profile.autoDeny : []).filter(rule => typeof rule?.pattern === 'string' && rule.pattern.trim())
104
+ .map((rule, index) => ({ id: `auto-deny-${index + 1}`, action: 'forbid', match: rule.match ?? 'contains', value: rule.pattern, reason: String(rule.reason ?? "Refused by this project's autoDeny rule") }));
105
+ if (!rules.length) return null;
106
+ const refused = evaluateExecPolicyCore(String(command), { policy: { execPolicy: { defaultMode: 'enforce', bannedPrefixSuggestions: [], rules } }, mode: 'enforce' });
107
+ return refused.decision === 'forbid' ? refused.reasons[0] ?? 'Refused by this project' : null;
108
+ }
109
+
110
+ // Claude's time limit: two minutes, then a denial that lets the work go on; after
111
+ // three unanswered prompts, denied at once until the member sends a message.
112
+ export const REMOVAL_TTL_MS = 120_000;
113
+ export const REMOVAL_EXPIRED = 'the member did not answer within two minutes, so this removal did not run. Carry on with the rest of the work: name the exact paths to remove instead, or leave the removal to the member and say so.';
114
+ const unanswered = new Map();
115
+ const memberMessages = manager => (manager?.getEntries?.() ?? []).filter(entry => entry.type === 'message' && entry.message?.role === 'user').length;
116
+ const current = (id, manager) => {
117
+ const turn = memberMessages(manager), held = unanswered.get(id);
118
+ if (held?.turn === turn) return held;
119
+ const fresh = { turn, count: 0 }; unanswered.set(id, fresh); return fresh;
120
+ };
121
+ export const removalsHeld = (id, manager) => current(id, manager).count >= 3;
122
+ export const noteRemovalExpired = (id, manager) => { current(id, manager).count += 1; };
@@ -1,9 +1,9 @@
1
- // What every permission mode still asks about, Bypass included (owner,
2
- // 2026-10-29: "Bypass bypasses the work and stops at the handoff").
1
+ // What every permission mode but Always bypass still asks about, Bypass
2
+ // included (owner, 2026-10-29: "Bypass bypasses the work and stops at the handoff").
3
3
  //
4
4
  // handoffReason: the work leaves this machine. A push, a pull request or a
5
5
  // merge, a release, a package publish, a deploy, a registry push or login, a
6
- // remote shell, a cloud or cluster tool, an upload. Asked in every mode, in
6
+ // remote shell, a cloud or cluster tool, an upload. Asked in these modes, in
7
7
  // personal and company sessions alike.
8
8
  // mustConfirm: the handoff plus what is destructive or irreversible here:
9
9
  // database migrations, deletes whose target is only known at run time,
@@ -50,7 +50,7 @@ function treeFindings(command) {
50
50
  // `bash -c`, substitutions and compound statements included).
51
51
  export const commandSegments = command => [...parse(command).segments, ...(astCommands(command) ?? []).map(({ words }) => ({ command: words.join(' '), words }))];
52
52
 
53
- function executable(words) {
53
+ export function executable(words) {
54
54
  let current = stripWrapper(words);
55
55
  for (;;) {
56
56
  if (RUNNERS.has(current[0]) || (['pnpm', 'yarn'].includes(current[0]) && current[1] === 'dlx')) {
@@ -91,6 +91,25 @@ function segmentHandoff(words) {
91
91
  return null;
92
92
  }
93
93
 
94
+ // What Always bypass still asks (owner, 2026-11-02): committing and pushing
95
+ // code, and a merge, which pushes it on the server. The reason, or null.
96
+ function segmentCode(words) {
97
+ const [name, ...args] = executable(words), sub = positional(args);
98
+ if (name === 'gh') return sub[0] === 'pr' && sub[1] === 'merge' ? 'gh pr merge pushes code' : null;
99
+ if (name !== 'git') return null;
100
+ if (words.some((word, index) => (word === '-c' && /^alias\./i.test(words[index + 1] ?? '')) || /^-calias\./i.test(word))) return 'git runs an alias defined in the command';
101
+ if (sub[0] === 'commit') return 'git commit records code';
102
+ if (sub[0] === 'push' || (['subtree', 'lfs'].includes(sub[0]) && sub[1] === 'push')) return 'git push sends code to a remote';
103
+ return null;
104
+ }
105
+ export function codeHandoffReason(command) {
106
+ for (const segment of [...parse(command).segments, ...treeFindings(command)]) {
107
+ const reason = segmentCode(segment.words);
108
+ if (reason) return reason;
109
+ }
110
+ return null;
111
+ }
112
+
94
113
  // Why this command hands the work off (asked in every mode), or null.
95
114
  export async function handoffReason(command) {
96
115
  const result = parse(command);
@@ -49,7 +49,7 @@ export function registerPermissionCommands(pi: ExtensionAPI, deps: Record<string
49
49
  function emitCurrentPermissionStatus(ctx: ExtensionContext): void {
50
50
  const profile = loadProfileFromContext(ctx);
51
51
  const permissionProfile = resolvePermissionProfile(profile, policy, permissionOverrideFromContext(ctx));
52
- ctx.ui.notify(`Piagent permission profile: ${permissionProfile.mode}`, ["trusted-full-access", "bypass"].includes(permissionProfile.mode) ? "warning" : "info");
52
+ ctx.ui.notify(`Piagent permission profile: ${permissionProfile.mode}`, ["trusted-full-access", "bypass", "always-bypass"].includes(permissionProfile.mode) ? "warning" : "info");
53
53
  emitPermissionStatus(ctx, permissionProfile);
54
54
  }
55
55
 
@@ -58,7 +58,7 @@ export function registerPermissionCommands(pi: ExtensionAPI, deps: Record<string
58
58
  const profile = loadProfileFromContext(ctx);
59
59
  const permissionProfile = resolvePermissionProfile(profile, policy, permissionOverrideFromContext(ctx));
60
60
  const isActive = permissionProfile.mode === mode && !permissionProfile.warning;
61
- const level = !isActive || mode === "trusted-full-access" || mode === "bypass" ? "warning" : "info";
61
+ const level = !isActive || mode === "trusted-full-access" || mode === "bypass" || mode === "always-bypass" ? "warning" : "info";
62
62
  const message = isActive
63
63
  ? `Piagent permission profile set to ${mode} for this session.`
64
64
  : `Requested ${mode}, but active profile is ${permissionProfile.mode}.`;
@@ -67,6 +67,9 @@ export function registerPermissionCommands(pi: ExtensionAPI, deps: Record<string
67
67
  if (permissionProfile.mode === "trusted-full-access") {
68
68
  ctx.ui.notify("Trusted full access is active; protected paths, secret redaction, and destructive and handoff confirmations remain enforced.", "warning");
69
69
  }
70
+ if (permissionProfile.mode === "always-bypass") {
71
+ ctx.ui.notify("Warning: Always bypass is active, by your decision. Network, MCP, files, deploys and credentials run without asking; only committing and pushing code and removing a critical path (rm -rf ~) are asked. The project's autoDeny rules, output redaction and /undo remain.", "warning");
72
+ }
70
73
  if (permissionProfile.mode === "bypass") {
71
74
  ctx.ui.notify("Bypass is active: the work is not asked about or blocked in this personal session (deletes and protected paths included). The handoff (push, PR, publish, deploy, remote tools, MCP writes) is still asked; output redaction and /undo checkpoints remain. Company sessions keep their own rules.", "warning");
72
75
  }
@@ -78,6 +81,7 @@ export function registerPermissionCommands(pi: ExtensionAPI, deps: Record<string
78
81
  if (["read", "readonly", "read-only", "ro"].includes(action)) return "read-only";
79
82
  if (["write", "workspace", "workspace-write", "ww"].includes(action)) return "workspace-write";
80
83
  if (["full", "full-access", "trusted", "trusted-full-access"].includes(action)) return "trusted-full-access";
84
+ if (["always-bypass", "always"].includes(action)) return "always-bypass";
81
85
  if (["bypass", "yolo", "dangerously-bypass"].includes(action)) return "bypass";
82
86
  return undefined;
83
87
  }
@@ -90,7 +94,8 @@ export function registerPermissionCommands(pi: ExtensionAPI, deps: Record<string
90
94
  { value: "read-only", label: "Read-only", description: "Scout/review without writes" },
91
95
  { value: "workspace-write", label: "Workspace write", description: "Normal governed implementation mode" },
92
96
  { value: "full-access", label: "Full access", description: "Trusted local full access; guardrails still apply" },
93
- { value: "bypass", label: "Bypass", description: "Nothing asked or blocked (personal sessions only); redaction and /undo remain" },
97
+ { value: "bypass", label: "Bypass", description: "The work is not asked or blocked (personal sessions only); the handoff is still asked" },
98
+ { value: "always-bypass", label: "Always bypass", description: "Warning: nothing asked except commit, push and rm on a critical path" },
94
99
  { value: "help", label: "Help", description: "Show typed forms" }
95
100
  ], "status");
96
101
  if (chosen && chosen !== "help") {
@@ -104,7 +109,8 @@ export function registerPermissionCommands(pi: ExtensionAPI, deps: Record<string
104
109
  "read: /permission read-only",
105
110
  "write: /permission workspace-write",
106
111
  "full: /permission full-access",
107
- "bypass: /permission bypass (personal; nothing asked or blocked)",
112
+ "bypass: /permission bypass (personal; the handoff is still asked)",
113
+ "always: /permission always-bypass (only commit, push and rm on a critical path are asked)",
108
114
  "legacy: /piagent-permission"
109
115
  ].join("\n"));
110
116
  return;
@@ -120,7 +126,8 @@ export function registerPermissionCommands(pi: ExtensionAPI, deps: Record<string
120
126
  "read: /permission read-only",
121
127
  "write: /permission workspace-write",
122
128
  "full: /permission full-access",
123
- "bypass: /permission bypass (personal; nothing asked or blocked)",
129
+ "bypass: /permission bypass (personal; the handoff is still asked)",
130
+ "always: /permission always-bypass (only commit, push and rm on a critical path are asked)",
124
131
  "legacy: /piagent-permission"
125
132
  ].join("\n"));
126
133
  return;
@@ -181,6 +188,7 @@ export function registerPermissionCommands(pi: ExtensionAPI, deps: Record<string
181
188
  registerPermissionProfileCommand("workspace-write", "workspace-write", "Switch this session to workspace-write permission profile");
182
189
  registerPermissionProfileCommand("full-access", "trusted-full-access", "Switch this session to trusted full-access permission profile");
183
190
  registerPermissionProfileCommand("trusted-full-access", "trusted-full-access", "Alias for /full-access");
184
- registerPermissionProfileCommand("bypass", "bypass", "Switch this personal session to bypass: nothing asked or blocked");
191
+ registerPermissionProfileCommand("bypass", "bypass", "Switch this personal session to bypass: the handoff is still asked");
192
+ registerPermissionProfileCommand("always-bypass", "always-bypass", "Switch this session to always bypass: only commit, push and rm on a critical path are asked");
185
193
 
186
194
  }
@@ -208,7 +208,7 @@ export function registerTaskEvidenceTools(pi: ExtensionAPI, deps: Record<string,
208
208
  if (
209
209
  projectRelative
210
210
  && capabilityState.filesystemRead
211
- && !["trusted-full-access", "bypass"].includes(permissionProfile.mode)
211
+ && !["trusted-full-access", "bypass", "always-bypass"].includes(permissionProfile.mode)
212
212
  && !matchesAnyPath(projectRelative, capabilityState.filesystemRead)
213
213
  ) {
214
214
  return {
@@ -1 +1 @@
1
- import{n as e,r as t}from"./rolldown-runtime-hePW80VL.js";import{An as n,Rn as r}from"./vendor-DNZbaWhT.js";import{i,n as a}from"./ui-preferences-OfS30-ZV.js";import{B as o,K as s,x as c}from"./api-BQS4Dtrr.js";import{c as l}from"./view-model-CWNiS4JN.js";var u=t(r(),1);function d(e,t=`vi`){let n=e.state===`running`?`running`:e.state===`failed`?`failed`:`passed`,r=n===`running`?t===`vi`?`đang chạy`:`running`:n===`failed`?t===`vi`?`thất bại`:`failed`:t===`vi`?`hoàn tất`:`completed`,i=e.startedAt??e.finishedAt??``;return{activityRef:`live.${e.toolCallRef}`,kind:[`bash`,`shell`,`exec`].includes(e.toolLabel)?`command`:`tool`,state:n,label:`${e.agent?`${e.agent} · `:``}${e.toolLabel} ${r}`,preview:e.detail??e.fileLabel??(t===`vi`?`Cập nhật trực tiếp từ Gateway`:`Live update from the Gateway`),toolCallId:e.toolCallRef,toolName:e.toolLabel,commandDigest:null,logRef:null,exitCode:null,exitCodeExact:!1,startedAt:i,finishedAt:n===`running`?null:e.finishedAt??i}}function f(e,t=`vi`){let n=e.state===`failed`?t===`vi`?`Lượt chạy gặp lỗi`:`Operation failed`:e.state===`blocked`?t===`vi`?`Lượt chạy bị chặn`:`Operation blocked`:e.state===`aborted`?t===`vi`?`Lượt chạy đã dừng`:`Operation stopped`:t===`vi`?`Chưa xác định kết quả lượt chạy`:`Operation outcome unknown`;return{activityRef:e.activityRef,kind:`system`,state:e.state,label:n,preview:e.reasonCode,toolCallId:null,toolName:null,commandDigest:null,logRef:null,exitCode:null,exitCodeExact:!1,startedAt:e.settledAt,finishedAt:e.settledAt}}function p(e,t,n,r=`vi`,i){let a=[...e,...t],o=new Set(a.map(e=>e.activityRef)),s=n.filter(e=>!o.has(e.activityRef)).map(e=>f(e,r));if(i===void 0)return{rows:[...e,...s,...t],terminalCount:s.length,runningCount:e.length};let c=i.filter(e=>e.state===`running`).map(e=>d(e,r));return{rows:[...c,...s,...t],terminalCount:s.length,runningCount:c.length}}function m(e,t=`vi`){return e.state===`passed`&&/ recovered$/i.test(e.label)?t===`vi`?`Đã khôi phục`:`Recovered`:e.state===`passed`&&/ warning$/i.test(e.label)?t===`vi`?`Đã xử lý cảnh báo`:`Handled warning`:e.state===`passed`&&e.exitCodeExact?`Pass · exit ${e.exitCode??0}`:e.state===`failed`&&e.exitCodeExact?`Fail · exit ${e.exitCode??`?`}`:e.state===`blocked`?`Blocked`:e.state===`aborted`?t===`vi`?`Đã dừng`:`Stopped`:e.state===`unknown`?t===`vi`?`Chưa xác định`:`Unknown`:e.state===`failed`?t===`vi`?`Thất bại`:`Failed`:e.state===`running`?t===`vi`?`Đang chạy`:`Running`:e.state.replaceAll(`-`,` `)}function h(e,t=`vi`){let n=Date.parse(e.startedAt),r=e.finishedAt?Date.parse(e.finishedAt):Date.now();if(!Number.isFinite(n)||!Number.isFinite(r)||r<n)return t===`vi`?`Thời gian chưa xác định`:`Unknown duration`;let i=Math.round((r-n)/1e3);return i<60?`${i}s`:`${Math.floor(i/60)}m ${i%60}s`}function g(e,t=[]){let n=new Map([[`main`,{agent:`main`,steps:[],total:0}]]),r=e=>{let t=n.get(e);if(t)return t;let r={agent:e,steps:[],total:0};return n.set(e,r),r};for(let t of e){if(t.helperRun){let e=r(t.helperRun.agent);e.total+=t.helperRun.total,e.steps.push(...t.helperRun.steps.map((e,n)=>({key:`${t.messageRef}:${n}`,toolName:e.toolName,target:e.target,state:`completed`,at:e.at})));continue}if(t.role!==`assistant`)continue;let e=r(`main`);for(let n of t.toolCalls)e.total+=1,e.steps.push({key:`${t.messageRef}:${n.toolCallRef}`,toolName:n.toolName,target:n.summary?.target??n.summary?.detail??null,state:n.state===`failed`||n.result?.isError?`failed`:`completed`,at:t.recordedAt})}for(let e of t){if(!e.agent||e.state===`running`)continue;let t=r(e.agent);t.total+=1,t.steps.push({key:e.toolCallRef,toolName:e.toolLabel,target:e.detail??null,state:e.state,at:e.startedAt??``})}for(let e of n.values())e.steps.sort((e,t)=>t.at.localeCompare(e.at));return[...n.values()].filter(e=>e.steps.length>0)}var _=n();function v(e,t){return e===`main`?a(t,`Main agent`,`Main agent`):`Subagent ${e}`}function y(e,t){let n=new Date(e);return Number.isFinite(n.getTime())?n.toLocaleTimeString(t===`vi`?`vi-VN`:`en-US`,{hour:`2-digit`,minute:`2-digit`,second:`2-digit`}):``}var b=5e3;function x({sessionRef:e,running:t,live:n,locale:r,empty:i}){let[o,c]=(0,u.useState)([]);(0,u.useEffect)(()=>{let n=new AbortController,r=()=>void s(e,null,200,n.signal).then(e=>c(e.items)).catch(()=>void 0);r();let i=t?window.setInterval(r,b):void 0;return()=>{n.abort(),i&&window.clearInterval(i)}},[e,t]);let d=(0,u.useMemo)(()=>g(o,n),[o,n]);return d.length?(0,_.jsx)(`div`,{className:`agent-activity-log`,children:d.map(e=>(0,_.jsxs)(`details`,{open:e.agent===`main`||t,className:`agent-activity-group`,children:[(0,_.jsxs)(`summary`,{children:[(0,_.jsx)(`strong`,{children:v(e.agent,r)}),` `,(0,_.jsxs)(`span`,{children:[e.total,` `,a(r,`lần gọi`,`calls`)]}),e.total>e.steps.length&&(0,_.jsxs)(`span`,{children:[` · `,a(r,`hiện ${e.steps.length} gần nhất`,`showing the latest ${e.steps.length}`)]})]}),e.steps.map(e=>(0,_.jsxs)(`article`,{className:`activity-row`,children:[(0,_.jsx)(`span`,{className:`activity-state tone-${l(e.state)}`,"aria-label":e.state}),(0,_.jsxs)(`div`,{className:`activity-copy`,children:[(0,_.jsx)(`div`,{children:(0,_.jsx)(`strong`,{children:e.toolName})}),e.target&&(0,_.jsx)(`p`,{children:e.target})]}),(0,_.jsx)(`div`,{className:`activity-result`,children:(0,_.jsx)(`span`,{children:y(e.at,r)})})]},e.key))]},e.agent))}):i?(0,_.jsx)(`div`,{className:`activity-empty`,children:i}):null}var S=e({ActivityPanel:()=>w});function C({activity:e,preview:t,onPreview:n,locale:r}){return(0,_.jsxs)(`article`,{className:`activity-row`,children:[(0,_.jsx)(`span`,{className:`activity-state tone-${l(e.state)}`,"aria-label":e.state}),(0,_.jsxs)(`div`,{className:`activity-copy`,children:[(0,_.jsxs)(`div`,{children:[(0,_.jsx)(`strong`,{children:e.label}),(0,_.jsx)(`span`,{children:e.kind})]}),(0,_.jsx)(`p`,{children:e.preview||a(r,`Không có mô tả ngắn`,`No short description`)}),t&&(0,_.jsxs)(`div`,{className:`log-preview ${t.state===`error`?`error-state`:``}`,children:[t.state===`loading`?a(r,`Đang tải log ngắn…`,`Loading log preview…`):t.state===`error`?a(r,`Log preview không còn sẵn sàng.`,`Log preview is no longer available.`):t.value?.preview??`${a(r,`Không có nội dung log`,`No log content`)} · ${t.value?.reasonCode??`unavailable`}`,t.value?.truncated&&(0,_.jsx)(`small`,{children:a(r,`Preview đã được rút gọn`,`Preview truncated`)})]})]}),(0,_.jsxs)(`div`,{className:`activity-result`,children:[(0,_.jsx)(`strong`,{children:m(e,r)}),(0,_.jsx)(`span`,{children:h(e,r)}),e.logRef&&n&&(0,_.jsx)(`button`,{type:`button`,onClick:n,"aria-expanded":!!t,children:t?a(r,`Ẩn log`,`Hide log`):a(r,`Xem log`,`View log`)})]})]})}function w({snapshot:e,sessionRef:t,terminalActivities:n=[],liveActivities:r}){let{locale:s}=i(),[l,d]=(0,u.useState)({}),f=e?.activity.running??[],m=e?.activity.recent??[],{rows:h,terminalCount:g,runningCount:v}=p(f,m,n,s,r),y=e=>{if(l[e.activityRef]){d(t=>({...t,[e.activityRef]:void 0}));return}d(t=>({...t,[e.activityRef]:{state:`loading`}})),(t?o(t,e.activityRef):c(e.activityRef)).then(t=>d(n=>({...n,[e.activityRef]:{state:`ready`,value:t}}))).catch(()=>d(t=>({...t,[e.activityRef]:{state:`error`}})))};return(0,_.jsxs)(`section`,{className:`activity-panel surface`,"aria-labelledby":`activity-title`,children:[(0,_.jsxs)(`header`,{className:`panel-heading`,children:[(0,_.jsxs)(`div`,{children:[(0,_.jsx)(`p`,{className:`section-kicker`,children:`Activity`}),(0,_.jsx)(`h2`,{id:`activity-title`,children:a(s,`Tool, command và kết quả lượt chạy`,`Tools, commands, and operation outcomes`)})]}),(0,_.jsxs)(`div`,{children:[(0,_.jsxs)(`span`,{className:`running-count`,children:[v,` `,a(s,`đang chạy`,`running`)]}),(0,_.jsxs)(`span`,{children:[m.length+g,` `,a(s,`gần đây`,`recent`)]})]})]}),(0,_.jsxs)(`div`,{className:`activity-list`,children:[h.map(e=>(0,_.jsx)(C,{activity:e,preview:l[e.activityRef],onPreview:e.logRef?()=>y(e):void 0,locale:s},e.activityRef)),t&&(0,_.jsx)(x,{sessionRef:t,running:!!r,live:r,locale:s,empty:h.length===0?a(s,`Chưa có tool, command hoặc kết quả lượt chạy nào trong timeline hiện tại.`,`No tools, commands, or operation outcomes in the current timeline.`):void 0}),h.length===0&&!t&&(0,_.jsx)(`div`,{className:`activity-empty`,children:a(s,`Chưa có tool, command hoặc kết quả lượt chạy nào trong timeline hiện tại.`,`No tools, commands, or operation outcomes in the current timeline.`)})]}),e?.activity.page.truncated&&(0,_.jsx)(`footer`,{className:`panel-footnote`,children:a(s,`Timeline đã được rút gọn theo giới hạn local.`,`The timeline was truncated to local limits.`)})]})}export{S as n,w as t};
1
+ import{n as e,r as t}from"./rolldown-runtime-hePW80VL.js";import{An as n,Rn as r}from"./vendor-DNZbaWhT.js";import{i,n as a}from"./ui-preferences-OfS30-ZV.js";import{B as o,K as s,x as c}from"./api-BQS4Dtrr.js";import{u as l}from"./view-model-BrdSO09E.js";var u=t(r(),1);function d(e,t=`vi`){let n=e.state===`running`?`running`:e.state===`failed`?`failed`:`passed`,r=n===`running`?t===`vi`?`đang chạy`:`running`:n===`failed`?t===`vi`?`thất bại`:`failed`:t===`vi`?`hoàn tất`:`completed`,i=e.startedAt??e.finishedAt??``;return{activityRef:`live.${e.toolCallRef}`,kind:[`bash`,`shell`,`exec`].includes(e.toolLabel)?`command`:`tool`,state:n,label:`${e.agent?`${e.agent} · `:``}${e.toolLabel} ${r}`,preview:e.detail??e.fileLabel??(t===`vi`?`Cập nhật trực tiếp từ Gateway`:`Live update from the Gateway`),toolCallId:e.toolCallRef,toolName:e.toolLabel,commandDigest:null,logRef:null,exitCode:null,exitCodeExact:!1,startedAt:i,finishedAt:n===`running`?null:e.finishedAt??i}}function f(e,t=`vi`){let n=e.state===`failed`?t===`vi`?`Lượt chạy gặp lỗi`:`Operation failed`:e.state===`blocked`?t===`vi`?`Lượt chạy bị chặn`:`Operation blocked`:e.state===`aborted`?t===`vi`?`Lượt chạy đã dừng`:`Operation stopped`:t===`vi`?`Chưa xác định kết quả lượt chạy`:`Operation outcome unknown`;return{activityRef:e.activityRef,kind:`system`,state:e.state,label:n,preview:e.reasonCode,toolCallId:null,toolName:null,commandDigest:null,logRef:null,exitCode:null,exitCodeExact:!1,startedAt:e.settledAt,finishedAt:e.settledAt}}function p(e,t,n,r=`vi`,i){let a=[...e,...t],o=new Set(a.map(e=>e.activityRef)),s=n.filter(e=>!o.has(e.activityRef)).map(e=>f(e,r));if(i===void 0)return{rows:[...e,...s,...t],terminalCount:s.length,runningCount:e.length};let c=i.filter(e=>e.state===`running`).map(e=>d(e,r));return{rows:[...c,...s,...t],terminalCount:s.length,runningCount:c.length}}function m(e,t=`vi`){return e.state===`passed`&&/ recovered$/i.test(e.label)?t===`vi`?`Đã khôi phục`:`Recovered`:e.state===`passed`&&/ warning$/i.test(e.label)?t===`vi`?`Đã xử lý cảnh báo`:`Handled warning`:e.state===`passed`&&e.exitCodeExact?`Pass · exit ${e.exitCode??0}`:e.state===`failed`&&e.exitCodeExact?`Fail · exit ${e.exitCode??`?`}`:e.state===`blocked`?`Blocked`:e.state===`aborted`?t===`vi`?`Đã dừng`:`Stopped`:e.state===`unknown`?t===`vi`?`Chưa xác định`:`Unknown`:e.state===`failed`?t===`vi`?`Thất bại`:`Failed`:e.state===`running`?t===`vi`?`Đang chạy`:`Running`:e.state.replaceAll(`-`,` `)}function h(e,t=`vi`){let n=Date.parse(e.startedAt),r=e.finishedAt?Date.parse(e.finishedAt):Date.now();if(!Number.isFinite(n)||!Number.isFinite(r)||r<n)return t===`vi`?`Thời gian chưa xác định`:`Unknown duration`;let i=Math.round((r-n)/1e3);return i<60?`${i}s`:`${Math.floor(i/60)}m ${i%60}s`}function g(e,t=[]){let n=new Map([[`main`,{agent:`main`,steps:[],total:0}]]),r=e=>{let t=n.get(e);if(t)return t;let r={agent:e,steps:[],total:0};return n.set(e,r),r};for(let t of e){if(t.helperRun){let e=r(t.helperRun.agent);e.total+=t.helperRun.total,e.steps.push(...t.helperRun.steps.map((e,n)=>({key:`${t.messageRef}:${n}`,toolName:e.toolName,target:e.target,state:`completed`,at:e.at})));continue}if(t.role!==`assistant`)continue;let e=r(`main`);for(let n of t.toolCalls)e.total+=1,e.steps.push({key:`${t.messageRef}:${n.toolCallRef}`,toolName:n.toolName,target:n.summary?.target??n.summary?.detail??null,state:n.state===`failed`||n.result?.isError?`failed`:`completed`,at:t.recordedAt})}for(let e of t){if(!e.agent||e.state===`running`)continue;let t=r(e.agent);t.total+=1,t.steps.push({key:e.toolCallRef,toolName:e.toolLabel,target:e.detail??null,state:e.state,at:e.startedAt??``})}for(let e of n.values())e.steps.sort((e,t)=>t.at.localeCompare(e.at));return[...n.values()].filter(e=>e.steps.length>0)}var _=n();function v(e,t){return e===`main`?a(t,`Main agent`,`Main agent`):`Subagent ${e}`}function y(e,t){let n=new Date(e);return Number.isFinite(n.getTime())?n.toLocaleTimeString(t===`vi`?`vi-VN`:`en-US`,{hour:`2-digit`,minute:`2-digit`,second:`2-digit`}):``}var b=5e3;function x({sessionRef:e,running:t,live:n,locale:r,empty:i}){let[o,c]=(0,u.useState)([]);(0,u.useEffect)(()=>{let n=new AbortController,r=()=>void s(e,null,200,n.signal).then(e=>c(e.items)).catch(()=>void 0);r();let i=t?window.setInterval(r,b):void 0;return()=>{n.abort(),i&&window.clearInterval(i)}},[e,t]);let d=(0,u.useMemo)(()=>g(o,n),[o,n]);return d.length?(0,_.jsx)(`div`,{className:`agent-activity-log`,children:d.map(e=>(0,_.jsxs)(`details`,{open:e.agent===`main`||t,className:`agent-activity-group`,children:[(0,_.jsxs)(`summary`,{children:[(0,_.jsx)(`strong`,{children:v(e.agent,r)}),` `,(0,_.jsxs)(`span`,{children:[e.total,` `,a(r,`lần gọi`,`calls`)]}),e.total>e.steps.length&&(0,_.jsxs)(`span`,{children:[` · `,a(r,`hiện ${e.steps.length} gần nhất`,`showing the latest ${e.steps.length}`)]})]}),e.steps.map(e=>(0,_.jsxs)(`article`,{className:`activity-row`,children:[(0,_.jsx)(`span`,{className:`activity-state tone-${l(e.state)}`,"aria-label":e.state}),(0,_.jsxs)(`div`,{className:`activity-copy`,children:[(0,_.jsx)(`div`,{children:(0,_.jsx)(`strong`,{children:e.toolName})}),e.target&&(0,_.jsx)(`p`,{children:e.target})]}),(0,_.jsx)(`div`,{className:`activity-result`,children:(0,_.jsx)(`span`,{children:y(e.at,r)})})]},e.key))]},e.agent))}):i?(0,_.jsx)(`div`,{className:`activity-empty`,children:i}):null}var S=e({ActivityPanel:()=>w});function C({activity:e,preview:t,onPreview:n,locale:r}){return(0,_.jsxs)(`article`,{className:`activity-row`,children:[(0,_.jsx)(`span`,{className:`activity-state tone-${l(e.state)}`,"aria-label":e.state}),(0,_.jsxs)(`div`,{className:`activity-copy`,children:[(0,_.jsxs)(`div`,{children:[(0,_.jsx)(`strong`,{children:e.label}),(0,_.jsx)(`span`,{children:e.kind})]}),(0,_.jsx)(`p`,{children:e.preview||a(r,`Không có mô tả ngắn`,`No short description`)}),t&&(0,_.jsxs)(`div`,{className:`log-preview ${t.state===`error`?`error-state`:``}`,children:[t.state===`loading`?a(r,`Đang tải log ngắn…`,`Loading log preview…`):t.state===`error`?a(r,`Log preview không còn sẵn sàng.`,`Log preview is no longer available.`):t.value?.preview??`${a(r,`Không có nội dung log`,`No log content`)} · ${t.value?.reasonCode??`unavailable`}`,t.value?.truncated&&(0,_.jsx)(`small`,{children:a(r,`Preview đã được rút gọn`,`Preview truncated`)})]})]}),(0,_.jsxs)(`div`,{className:`activity-result`,children:[(0,_.jsx)(`strong`,{children:m(e,r)}),(0,_.jsx)(`span`,{children:h(e,r)}),e.logRef&&n&&(0,_.jsx)(`button`,{type:`button`,onClick:n,"aria-expanded":!!t,children:t?a(r,`Ẩn log`,`Hide log`):a(r,`Xem log`,`View log`)})]})]})}function w({snapshot:e,sessionRef:t,terminalActivities:n=[],liveActivities:r}){let{locale:s}=i(),[l,d]=(0,u.useState)({}),f=e?.activity.running??[],m=e?.activity.recent??[],{rows:h,terminalCount:g,runningCount:v}=p(f,m,n,s,r),y=e=>{if(l[e.activityRef]){d(t=>({...t,[e.activityRef]:void 0}));return}d(t=>({...t,[e.activityRef]:{state:`loading`}})),(t?o(t,e.activityRef):c(e.activityRef)).then(t=>d(n=>({...n,[e.activityRef]:{state:`ready`,value:t}}))).catch(()=>d(t=>({...t,[e.activityRef]:{state:`error`}})))};return(0,_.jsxs)(`section`,{className:`activity-panel surface`,"aria-labelledby":`activity-title`,children:[(0,_.jsxs)(`header`,{className:`panel-heading`,children:[(0,_.jsxs)(`div`,{children:[(0,_.jsx)(`p`,{className:`section-kicker`,children:`Activity`}),(0,_.jsx)(`h2`,{id:`activity-title`,children:a(s,`Tool, command và kết quả lượt chạy`,`Tools, commands, and operation outcomes`)})]}),(0,_.jsxs)(`div`,{children:[(0,_.jsxs)(`span`,{className:`running-count`,children:[v,` `,a(s,`đang chạy`,`running`)]}),(0,_.jsxs)(`span`,{children:[m.length+g,` `,a(s,`gần đây`,`recent`)]})]})]}),(0,_.jsxs)(`div`,{className:`activity-list`,children:[h.map(e=>(0,_.jsx)(C,{activity:e,preview:l[e.activityRef],onPreview:e.logRef?()=>y(e):void 0,locale:s},e.activityRef)),t&&(0,_.jsx)(x,{sessionRef:t,running:!!r,live:r,locale:s,empty:h.length===0?a(s,`Chưa có tool, command hoặc kết quả lượt chạy nào trong timeline hiện tại.`,`No tools, commands, or operation outcomes in the current timeline.`):void 0}),h.length===0&&!t&&(0,_.jsx)(`div`,{className:`activity-empty`,children:a(s,`Chưa có tool, command hoặc kết quả lượt chạy nào trong timeline hiện tại.`,`No tools, commands, or operation outcomes in the current timeline.`)})]}),e?.activity.page.truncated&&(0,_.jsx)(`footer`,{className:`panel-footnote`,children:a(s,`Timeline đã được rút gọn theo giới hạn local.`,`The timeline was truncated to local limits.`)})]})}export{S as n,w as t};