@piagent/platform 1.48.2 → 1.48.3

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 (100) hide show
  1. package/CHANGELOG.md +18 -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 +28 -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 +41 -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 +28 -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/extensions/workspace-inspection.ts +4 -1
  56. package/packages/piagent-core/managed/dist/workspace/manifest.json +1 -1
  57. package/packages/piagent-core/managed/dist/workspace/worker.mjs +7 -1
  58. package/packages/piagent-core/managed/patch-snapshot.mjs +28 -13
  59. package/packages/piagent-core/managed/review-snapshot-request.mjs +1 -1
  60. package/packages/piagent-core/managed/review-snapshot-worker.mjs +52 -11
  61. package/packages/piagent-core/managed/session.mjs +7 -7
  62. package/packages/piagent-core/managed/workflow.mjs +4 -3
  63. package/packages/piagent-core/package.json +1 -1
  64. package/packages/piagent-webui/dist/client/assets/{ActivityPanel-DxQ3_dyT.js → ActivityPanel-Bv-jc1s3.js} +1 -1
  65. package/packages/piagent-webui/dist/client/assets/App-DqN7B1JR.js +5 -0
  66. package/packages/piagent-webui/dist/client/assets/AttachmentImage-eZKqLYZ2.js +1 -0
  67. package/packages/piagent-webui/dist/client/assets/GatewayApp-B3--WjSd.js +63 -0
  68. package/packages/piagent-webui/dist/client/assets/{InspectionApp-CEilR0FG.js → InspectionApp-BKQiA0Ce.js} +1 -1
  69. package/packages/piagent-webui/dist/client/assets/{LineReview-C_0ZpgCH.js → LineReview-CB3mQe7m.js} +1 -1
  70. package/packages/piagent-webui/dist/client/assets/MarkdownMessage-zw5RxDmZ.js +29 -0
  71. package/packages/piagent-webui/dist/client/assets/{SessionAgentWorkspace-yh0py16t.js → SessionAgentWorkspace-ZAdRbxVA.js} +1 -1
  72. package/packages/piagent-webui/dist/client/assets/SessionInspectorDrawer-B2e4-1FG.js +2 -0
  73. package/packages/piagent-webui/dist/client/assets/SettingsNavigation-C3MC0X4P.js +3 -0
  74. package/packages/piagent-webui/dist/client/assets/SettingsPage-D4os5vHj.js +1 -0
  75. package/packages/piagent-webui/dist/client/assets/{TerminalPanel-B2-YAz2x.js → TerminalPanel-CVYUiDkn.js} +2 -2
  76. package/packages/piagent-webui/dist/client/assets/{TerminalView-yaIg6RCs.js → TerminalView-C3upxbKw.js} +1 -1
  77. package/packages/piagent-webui/dist/client/assets/{WorkspaceCodeEditor-DAdMuw6x.js → WorkspaceCodeEditor-E0Zopcke.js} +1 -1
  78. package/packages/piagent-webui/dist/client/assets/WorkspaceExplorer-DldLoJyf.js +5 -0
  79. package/packages/piagent-webui/dist/client/assets/WorkspaceFilePreview-CvIBYige.js +1 -0
  80. package/packages/piagent-webui/dist/client/assets/{WorkspaceQuickOpen-CJmMBmK8.js → WorkspaceQuickOpen-Bi-kTEEU.js} +1 -1
  81. package/packages/piagent-webui/dist/client/assets/index-DcrpSyBK.js +37 -0
  82. package/packages/piagent-webui/dist/client/assets/{tab-close-ITV4Cf4X.js → tab-close-BwkkhMH7.js} +1 -1
  83. package/packages/piagent-webui/dist/client/assets/transcript-view-model-acWtHGyE.js +3 -0
  84. package/packages/piagent-webui/dist/client/assets/{ui-preferences-0waeSO-S.js → ui-preferences-OfS30-ZV.js} +1 -1
  85. package/packages/piagent-webui/dist/client/assets/{vendor-CSDKB7mU.js → vendor-DNZbaWhT.js} +62 -26
  86. package/packages/piagent-webui/dist/client/assets/{workspace-api-D8sJkiZB.js → workspace-api-CFBR73lU.js} +1 -1
  87. package/packages/piagent-webui/dist/client/assets/workspace-navigation-DW8zhH6U.js +1 -0
  88. package/packages/piagent-webui/dist/client/index.html +3 -3
  89. package/packages/piagent-webui/shared/process-copy.ts +2 -1
  90. package/schemas/piagent-webui/workspace-media-v1.schema.json +6 -0
  91. package/packages/piagent-webui/dist/client/assets/App-Dg6Firq1.js +0 -5
  92. package/packages/piagent-webui/dist/client/assets/GatewayApp-CCczgphI.js +0 -99
  93. package/packages/piagent-webui/dist/client/assets/MarkdownMessage-DGt5B-6D.js +0 -29
  94. package/packages/piagent-webui/dist/client/assets/SessionInspectorDrawer-C-18TcGH.js +0 -2
  95. package/packages/piagent-webui/dist/client/assets/SettingsNavigation-BFfqMcuL.js +0 -3
  96. package/packages/piagent-webui/dist/client/assets/SettingsPage-CxpZ7cdM.js +0 -1
  97. package/packages/piagent-webui/dist/client/assets/WorkspaceExplorer-97MLNtEa.js +0 -5
  98. package/packages/piagent-webui/dist/client/assets/index-B8tL7HFz.js +0 -37
  99. package/packages/piagent-webui/dist/client/assets/transcript-view-model-DqO4xO12.js +0 -3
  100. package/packages/piagent-webui/dist/client/assets/workspace-navigation-DkVobhwg.js +0 -1
@@ -3,7 +3,7 @@
3
3
  "files": [
4
4
  {
5
5
  "file": "worker.mjs",
6
- "sha256": "71d88643e57135d306045901e0c81ad48d58e6c18c3cb028859cb330a0b1da83"
6
+ "sha256": "e338340fd4ffe968fcde52dca317280621a9134f732d755d6c3e156e9b8a47de"
7
7
  },
8
8
  {
9
9
  "file": "workspace-search-index.mjs",
@@ -526,7 +526,13 @@ var IMAGE_MIMES = {
526
526
  webp: "image/webp",
527
527
  bmp: "image/bmp",
528
528
  ico: "image/x-icon",
529
- avif: "image/avif"
529
+ avif: "image/avif",
530
+ svg: "image/svg+xml",
531
+ pdf: "application/pdf",
532
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
533
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
534
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
535
+ zip: "application/zip"
530
536
  };
531
537
  var MAX_MEDIA_BYTES = 8 << 20;
532
538
  function inspectWorkspaceMedia(root, relative, options) {
@@ -11,16 +11,20 @@ const MAX_SNAPSHOT = 16 * 1024 * 1024;
11
11
  // `base`: the commit the patch starts from (the turn's starting HEAD), so a
12
12
  // commit made during the turn stays part of its change; none: HEAD.
13
13
  const BASE = /^[0-9a-f]{40}([0-9a-f]{24})?$/;
14
- async function worker(boundary, mode, scope = null, base = null) {
15
- let scopeName = null;
16
- if (scope) {
17
- scopeName = `scope-${randomUUID()}.json`;
18
- fs.writeFileSync(path.join(boundary.temporary, scopeName), JSON.stringify([...scope]), { mode: 0o600, flag: 'wx' });
19
- }
14
+ // `bases`: in a folder of several repositories, where each started the turn.
15
+ async function worker(boundary, mode, scope = null, base = null, bases = null) {
16
+ const written = [];
17
+ const write = (prefix, value) => {
18
+ const name = `${prefix}-${randomUUID()}.json`;
19
+ fs.writeFileSync(path.join(boundary.temporary, name), JSON.stringify(value), { mode: 0o600, flag: 'wx' });
20
+ written.push(name);
21
+ };
22
+ if (scope) write('scope', [...scope]);
23
+ if (bases && Object.keys(bases).length) write('bases', bases);
20
24
  let result;
21
- const extra = [...(scopeName ? [scopeName] : []), ...(typeof base === 'string' && BASE.test(base) ? [base] : [])];
25
+ const extra = [...written, ...(typeof base === 'string' && BASE.test(base) ? [base] : [])];
22
26
  try { result = await invokeReviewSnapshot(boundary, mode, extra); }
23
- finally { if (scopeName) fs.rmSync(path.join(boundary.temporary, scopeName), { force: true }); }
27
+ finally { for (const name of written) fs.rmSync(path.join(boundary.temporary, name), { force: true }); }
24
28
  const text = textContent(result).trim();
25
29
  if (result.details?.truncation?.truncated || text.includes('\n')) throw Error(/review-(patch|file|untracked)-limit/.exec(text)?.[0] ?? 'managed-review-patch-unreadable');
26
30
  const [digest, bytes, name] = text.split(' ');
@@ -30,7 +34,17 @@ async function worker(boundary, mode, scope = null, base = null) {
30
34
 
31
35
  // The digest of the patch a reviewer would see (diff against HEAD plus
32
36
  // untracked files): whether a review is still current. Nothing else moves.
33
- export async function readPatchDigest(boundary, scope = null, base = null) { return (await worker(boundary, 'digest', scope, base)).digest; }
37
+ export async function readPatchDigest(boundary, scope = null, base = null, bases = null) { return (await worker(boundary, 'digest', scope, base, bases)).digest; }
38
+
39
+ // Where each repository below a folder that is none itself stands now:
40
+ // { repository: commit }; empty inside one repository.
41
+ export async function readRepositoryHeads(boundary) {
42
+ try {
43
+ const { digest, bytes, name } = await worker(boundary, 'heads');
44
+ if (!/^heads-[0-9a-f-]{36}\.json$/.test(name ?? '')) return null;
45
+ return JSON.parse(readOnce(boundary, name, bytes, digest).toString('utf8'));
46
+ } catch { return null; }
47
+ }
34
48
 
35
49
  // The commit HEAD names now, or null (no repository, no commit yet).
36
50
  export async function readHead(boundary) {
@@ -69,8 +83,8 @@ export function emptySnapshot(snapshot) {
69
83
  // it to the boundary's temporary directory and names it, with its digest, on
70
84
  // its output; the file is read once, must match that digest (a file changed
71
85
  // meanwhile never passes) and is removed.
72
- export async function readPatchSnapshot(boundary, scope = null, base = null) {
73
- const { digest, bytes, name } = await worker(boundary, 'file', scope, base);
86
+ export async function readPatchSnapshot(boundary, scope = null, base = null, bases = null) {
87
+ const { digest, bytes, name } = await worker(boundary, 'file', scope, base, bases);
74
88
  if (!/^review-[0-9a-f-]{36}\.json$/.test(name ?? '')) throw Error('managed-review-patch-unreadable');
75
89
  return { patch: readOnce(boundary, name, bytes, digest).toString('utf8'), digest };
76
90
  }
@@ -79,8 +93,8 @@ export async function readPatchSnapshot(boundary, scope = null, base = null) {
79
93
  // Compared before and after a command, it names the files that command
80
94
  // changed, so a conversation answers for its own changes, not for another
81
95
  // conversation's in the same folder.
82
- export async function readChangedFiles(boundary, base = null) {
83
- const { digest, bytes, name } = await worker(boundary, 'files', null, base);
96
+ export async function readChangedFiles(boundary, base = null, bases = null) {
97
+ const { digest, bytes, name } = await worker(boundary, 'files', null, base, bases);
84
98
  if (!/^files-[0-9a-f-]{36}\.json$/.test(name ?? '')) throw Error('managed-review-patch-unreadable');
85
99
  return new Map(Object.entries(JSON.parse(readOnce(boundary, name, bytes, digest).toString('utf8'))));
86
100
  }
@@ -106,6 +120,7 @@ export function reviewUnavailableReason(message) {
106
120
  if (/review-(patch|file|untracked)-limit|managed-review-patch-too-large/.test(text)) return 'too_large';
107
121
  if (/not a git repository|Could not access 'HEAD'|managed-review-patch-unreadable/i.test(text)) return 'not_git';
108
122
  if (/managed-helper-limit|helper_run_limit_reached/.test(text)) return 'limit';
123
+ if (/managed-runtime-changed/.test(text)) return 'runtime_changed';
109
124
  return 'failed';
110
125
  }
111
126
 
@@ -7,7 +7,7 @@ const quote = value => "'" + value.replaceAll("'", "'\"'\"'") + "'";
7
7
  // Only fixed runtime snapshot reads preserve provenance. JSON tool arguments
8
8
  // cannot forge this identity; freezing prevents substitution before dispatch.
9
9
  export function invokeReviewSnapshot(boundary, mode, extra = []) {
10
- if (!['head', 'since', 'branch-base', 'digest', 'file', 'files'].includes(mode)
10
+ if (!['head', 'heads', 'since', 'branch-base', 'digest', 'file', 'files'].includes(mode)
11
11
  || !Array.isArray(extra) || extra.some(value => typeof value !== 'string')) throw Error('review-request-invalid');
12
12
  const args = Object.freeze({ command: [process.execPath, worker, mode, ...extra].map(quote).join(' '), timeout: 60 });
13
13
  requests.add(args);
@@ -27,12 +27,40 @@ function repositoryOf(file) {
27
27
  for (let dir = path.posix.dirname(file); dir !== '.'; dir = path.posix.dirname(dir)) if (fs.existsSync(path.join(dir, '.git'))) return dir;
28
28
  return null;
29
29
  }
30
+ // The repositories below a folder that is none itself (clones side by side),
31
+ // relative to the folder. A repository's own folders are not searched.
32
+ const SEARCH_DEPTH = 3, MAX_REPOSITORIES = 64, NOT_SEARCHED = new Set(['node_modules', 'vendor', 'dist', 'build', 'target']);
33
+ function childRepositories() {
34
+ const found = [];
35
+ const search = (dir, depth) => {
36
+ let entries; try { entries = fs.readdirSync(dir || '.', { withFileTypes: true }); } catch { return; }
37
+ for (const entry of entries.sort((a, b) => a.name < b.name ? -1 : 1)) {
38
+ if (!entry.isDirectory() || entry.name.startsWith('.') || NOT_SEARCHED.has(entry.name) || found.length >= MAX_REPOSITORIES) continue;
39
+ const child = dir ? `${dir}/${entry.name}` : entry.name;
40
+ if (fs.existsSync(path.join(child, '.git'))) found.push(child);
41
+ else if (depth < SEARCH_DEPTH) search(child, depth + 1);
42
+ }
43
+ };
44
+ search('', 1);
45
+ return found;
46
+ }
47
+ const repoGit = (repository, args) => git(['-C', repository, ...args]);
48
+ const repoHead = repository => { try { return repoGit(repository, ['rev-parse', '--verify', '-q', 'HEAD^{commit}']).trim(); } catch { return ''; } };
30
49
  const mode = process.argv[2];
31
50
  // The arguments after the mode: a scope file, then the commit the patch starts
32
51
  // from (the turn's starting HEAD; commits made since are part of the change).
33
52
  // While HEAD has not moved, the patch is the plain one against HEAD.
34
53
  const head = () => { try { return git(['rev-parse', '--verify', '-q', 'HEAD^{commit}']).trim(); } catch { return ''; } };
35
54
  if (mode === 'head') { process.stdout.write(head() || 'none'); process.exit(0); }
55
+ // "heads": where each repository below the folder stands, so a commit made
56
+ // during the turn stays part of its change, as `base` does in one repository.
57
+ if (mode === 'heads') {
58
+ const heads = insideWorkTree() ? {} : Object.fromEntries(childRepositories().map(r => [r, repoHead(r)]).filter(([, sha]) => sha));
59
+ const name = `heads-${randomUUID()}.json`, data = Buffer.from(JSON.stringify(heads));
60
+ fs.writeFileSync(path.join(os.tmpdir(), name), data, { mode: 0o600, flag: 'wx' });
61
+ process.stdout.write(`${sha256(data)} ${data.length} ${name}`);
62
+ process.exit(0);
63
+ }
36
64
  // "since <ref>": the commit a reference names, when HEAD descends from it (a
37
65
  // review of the commits made since); "branch-base": where this branch left
38
66
  // the default branch (or, on it, its upstream), when commits came since.
@@ -53,19 +81,29 @@ if (mode === 'branch-base') {
53
81
  }
54
82
  const extra = process.argv.slice(3), given = extra.find(a => /^[0-9a-f]{40}([0-9a-f]{24})?$/.test(a));
55
83
  const base = given && given !== head() ? given : 'HEAD';
84
+ // `bases-….json` (from "heads"): the commit each repository below the folder
85
+ // started the turn on; a repository not named is compared with its HEAD.
86
+ const basesName = extra.find(a => a.startsWith('bases-'));
87
+ if (basesName !== undefined && !/^bases-[0-9a-f-]{36}\.json$/.test(basesName)) throw Error('review-scope-invalid');
88
+ const bases = basesName ? JSON.parse(fs.readFileSync(path.join(os.tmpdir(), basesName), 'utf8')) : {};
89
+ const baseOf = repository => /^[0-9a-f]{40}([0-9a-f]{24})?$/.test(bases[repository] ?? '') ? bases[repository] : 'HEAD';
56
90
  // "files": what each changed or new file holds now (a hash, "deleted"), so
57
91
  // the runtime can tell which files one conversation's command changed.
58
92
  if (mode === 'files') {
59
- const changed = git(['diff', '--name-only', '--no-renames', '--relative', '-z', base, '--', '.']).split('\0').filter(Boolean);
60
- const fresh = git(['ls-files', '--others', '--exclude-standard', '-z']).split('\0').filter(Boolean);
61
- if (changed.length + fresh.length > MAX_UNTRACKED * 4) throw Error('review-untracked-limit');
93
+ // In a folder of several repositories each one answers for its own files,
94
+ // named from the folder. Without this a command's changes there have no
95
+ // owner and a review of them cannot run (seen 2026-10-30: `not_git`).
96
+ const repositories = insideWorkTree() ? [] : childRepositories();
97
+ const listed = (run, from, prefix) => [...run(['diff', '--name-only', '--no-renames', '--relative', '-z', from, '--', '.']).split('\0'), ...run(['ls-files', '--others', '--exclude-standard', '-z']).split('\0')].filter(Boolean).map(name => prefix + name);
98
+ const all = repositories.length ? repositories.flatMap(r => { try { return listed(args => repoGit(r, args), baseOf(r), `${r}/`); } catch { return []; } }) : listed(git, base, '');
99
+ if (all.length > MAX_UNTRACKED * 4) throw Error('review-untracked-limit');
62
100
  const print = name => {
63
101
  let stat; try { stat = fs.lstatSync(name); } catch { return 'deleted'; }
64
102
  if (stat.isSymbolicLink()) return 'link:' + sha256(fs.readlinkSync(name));
65
103
  if (stat.isDirectory()) return 'dir:' + sha256(fs.readdirSync(name).sort().join('\0'));
66
104
  return stat.size > HASHED_FILE ? `size:${stat.size}:${stat.mtimeMs}` : sha256(fs.readFileSync(name)) + ':' + (stat.mode & 0o777);
67
105
  };
68
- const files = Object.fromEntries([...new Set([...changed, ...fresh])].sort().map(name => [name, print(name)]));
106
+ const files = Object.fromEntries([...new Set(all)].sort().map(name => [name, print(name)]));
69
107
  const name = `files-${randomUUID()}.json`, data = Buffer.from(JSON.stringify(files));
70
108
  fs.writeFileSync(path.join(os.tmpdir(), name), data, { mode: 0o600, flag: 'wx' });
71
109
  process.stdout.write(`${sha256(data)} ${data.length} ${name}`);
@@ -73,20 +111,23 @@ if (mode === 'files') {
73
111
  }
74
112
  // A scope (a file the runtime wrote in this boundary's temporary directory):
75
113
  // only these paths, the ones this conversation changed, make the patch.
76
- const scopeName = extra.find(a => a !== given);
114
+ const scopeName = extra.find(a => a !== given && a !== basesName);
77
115
  if (scopeName !== undefined && !/^scope-[0-9a-f-]{36}\.json$/.test(scopeName)) throw Error('review-scope-invalid');
78
116
  const scope = scopeName ? new Set(JSON.parse(fs.readFileSync(path.join(os.tmpdir(), scopeName), 'utf8'))) : null;
79
117
  if (scope && [...scope].some(p => typeof p !== 'string' || !p || path.isAbsolute(p) || p.split('/').includes('..'))) throw Error('review-scope-invalid');
80
118
  // A folder that is not a repository but holds several (clones side by side):
81
119
  // each changed path is diffed in the repository that holds it, against that
82
120
  // repository's HEAD, with its paths kept relative to the folder. A path no
83
- // repository holds is named, not read. With no scope there is nothing to
84
- // tell which repositories to read: the folder is not a Git repository.
121
+ // repository holds is named, not read. With no scope, every repository's
122
+ // changes make the patch; a conversation with no change yet has an empty one.
85
123
  const repositories = [], outside = [];
86
124
  let patch = '', paths;
87
- if (scope && scope.size && !insideWorkTree()) {
125
+ const several = insideWorkTree() ? [] : childRepositories();
126
+ if (scope && !scope.size && several.length) paths = [];
127
+ else if ((scope ? scope.size : several.length) && !insideWorkTree()) {
88
128
  const groups = new Map();
89
- for (const file of [...scope].sort()) {
129
+ if (!scope) for (const repository of several) groups.set(repository, ['.']);
130
+ for (const file of [...(scope ?? [])].sort()) {
90
131
  const repository = repositoryOf(file);
91
132
  if (repository === null) { outside.push(file); continue; }
92
133
  if (!groups.has(repository)) groups.set(repository, []);
@@ -95,9 +136,9 @@ if (scope && scope.size && !insideWorkTree()) {
95
136
  if (!groups.size) throw Error('managed-review-patch-unreadable: not a git repository');
96
137
  paths = [];
97
138
  for (const [repository, files] of groups) {
98
- const spec = files.map(p => ':(literal)' + p), run = args => git(['-C', repository, ...args]);
139
+ const spec = files.map(p => p === '.' ? '.' : ':(literal)' + p), run = args => repoGit(repository, args);
99
140
  repositories.push(repository);
100
- patch += run(['diff', '--binary', '--no-ext-diff', '--no-textconv', '--no-renames', `--src-prefix=a/${repository}/`, `--dst-prefix=b/${repository}/`, 'HEAD', '--', ...spec]);
141
+ patch += run(['diff', '--binary', '--no-ext-diff', '--no-textconv', '--no-renames', `--src-prefix=a/${repository}/`, `--dst-prefix=b/${repository}/`, baseOf(repository), '--', ...spec]);
101
142
  paths.push(...run(['ls-files', '--others', '--exclude-standard', '-z', '--', ...spec]).split('\0').filter(Boolean).map(p => `${repository}/${p}`));
102
143
  }
103
144
  paths.sort();
@@ -20,7 +20,7 @@ import { runHelper } from './helper-run.mjs';
20
20
  import { askTool } from './member-questions.mjs';
21
21
  import { setTimeout as sleep } from 'node:timers/promises';
22
22
  import { stageCompaction, compactEarly, backOffFailedCompaction, trackCompaction, boundRequestImages } from './compaction.mjs';
23
- import { readPatchDigest, readPatchSnapshot, readChangedFiles, readHead } from './patch-snapshot.mjs';
23
+ import { readPatchDigest, readPatchSnapshot, readChangedFiles, readHead, readRepositoryHeads } from './patch-snapshot.mjs';
24
24
  import { memberTurn } from './continuation.mjs';
25
25
  import { workflowPolicy, repositoryChecks, sessionPlanTool, currentPlan, workflowPrompt, RunProcess, completionGate, pendingBaseline, pendingPaths, pendingHead, processEditTools } from './workflow.mjs';
26
26
  import { workflowContextTool, workflowContextExtension } from './workflow-context.mjs';
@@ -35,7 +35,7 @@ const PROVIDER = 'agent_watch_managed';
35
35
  export const HELPER_BEAT_MS = 60_000;
36
36
  // Who the agent is: the model account may put another product's name in an
37
37
  // earlier system line; the member is talking to Piagent.
38
- const BASE_PROMPT = 'You are Piagent, the company coding assistant. If an earlier system line gives you another product name, that line belongs to the model account: when asked who you are, say you are Piagent, the company coding assistant. Work directly on the user request. Do not require task contracts or workflow commands. ' + UNTRUSTED_RULE + ' Never claim a stale review covers changed code. When the request leaves a decision open that changes the result and you cannot settle it from the code or a subagent, ask the member with ask_user before you act on a guess. To hand a file back to the member, keep it in the project and link it with its project path, such as [report.pdf](out/report.pdf) or [app.ts:42](src/app.ts:42): the web view opens that file (code at the line, images and documents as a preview).';
38
+ const BASE_PROMPT = 'You are Piagent, the company coding assistant. If an earlier system line gives you another product name, that line belongs to the model account: when asked who you are, say you are Piagent, the company coding assistant. Work directly on the user request. Do not require task contracts or workflow commands. ' + UNTRUSTED_RULE + ' Never claim a stale review covers changed code. When the request leaves a decision open that changes the result and you cannot settle it from the code or a subagent, ask the member with ask_user before you act on a guess. To send the member a picture or a file in the chat, keep it in the project and write ![name](its/project/path) in your answer, such as ![Logo](design/logo.svg) or ![report.pdf](out/report.pdf): the web view draws a picture in place and shows any other file as a card to open or download; say the path too, for the Terminal. To point at code, link it, such as [app.ts:42](src/app.ts:42): the web view opens it at the line.';
39
39
  // Folders outside the project the member points to (an @ mention, a folder
40
40
  // they name) are readable for reference; the sandbox keeps them read-only.
41
41
  const referencePrompt = home => ` The member may point you to folders outside the project for reference (an @path mention or a folder they name): read them with read, ls, grep and find. They are read-only: changes go in the project only. In those tools ~ is the member's home folder (${home}); in bash ~ is a private empty folder, so use absolute paths there.`;
@@ -264,7 +264,7 @@ export class ManagedSession {
264
264
  // during the turn is still its change.
265
265
  const pending = pendingBaseline(manager);
266
266
  self.ownPaths = new Set(pending !== null ? pendingPaths(manager) : []);
267
- self.base = (pending !== null ? pendingHead(manager) : null) ?? await readHead(self.boundary);
267
+ self.base = (pending !== null ? pendingHead(manager) : null) ?? await readHead(self.boundary); self.repositoryBases = await readRepositoryHeads(self.boundary);
268
268
  self.run.startDigest = pending ?? await self.digest(); await openRound(self, manager, text); // sends what waited; a new conversation gets the team brief
269
269
  } catch (error) { self.preflight = describeFailure('main', failureText(error)); }
270
270
  self.activePrompt = true;
@@ -430,14 +430,14 @@ export class ManagedSession {
430
430
  // Whether this conversation changed code, and whether a check ran on it,
431
431
  // look only at the files it changed: another conversation (or the member)
432
432
  // changing other files in the same folder is not this run's change.
433
- async changeDigest() { return readPatchDigest(this.boundary, this.ownPaths, this.base); }
433
+ async changeDigest() { return readPatchDigest(this.boundary, this.ownPaths, this.base, this.repositoryBases); }
434
434
  async folderDigest() { try { return await readPatchDigest(this.boundary); } catch { return null; } }
435
435
  // What a reviewer reads: this conversation's files; before it changed any,
436
436
  // the whole folder (the member's own changes, asked to be reviewed).
437
437
  reviewScope() { return this.ownPaths.size ? this.ownPaths : null; }
438
- async patchSnapshot() { return readPatchSnapshot(this.boundary, this.reviewScope(), this.base); }
439
- async patchDigest() { return readPatchDigest(this.boundary, this.reviewScope(), this.base); }
440
- async changedFiles() { try { return await readChangedFiles(this.boundary, this.base); } catch { return null; } }
438
+ async patchSnapshot() { return readPatchSnapshot(this.boundary, this.reviewScope(), this.base, this.repositoryBases); }
439
+ async patchDigest() { return readPatchDigest(this.boundary, this.reviewScope(), this.base, this.repositoryBases); }
440
+ async changedFiles() { try { return await readChangedFiles(this.boundary, this.base, this.repositoryBases); } catch { return null; } }
441
441
  // A write or edit names its file; a command, the files it changed.
442
442
  claimPath(raw) {
443
443
  if (typeof raw !== 'string' || !raw) return;
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { reviewUnavailableReason } from './patch-snapshot.mjs';
4
- import { failureCode } from '../runtime/managed-failure.mjs';
4
+ import { failureCode, unnamedFailureText } from '../runtime/managed-failure.mjs';
5
5
  import { TsDiagnostics } from './ts-diagnostics.mjs';
6
6
  import { checkpointsOf } from './checkpoints.mjs';
7
7
  import { discoverDeliveryContext } from './delivery-context.mjs';
@@ -367,8 +367,9 @@ export async function completionGate(managed, run, signal) {
367
367
  try { await managed.delegate({ role: 'review', task: brief }, signal, { harness: true }); }
368
368
  catch (error) {
369
369
  run.reviewUnavailable = !stopped(); run.reviewUnavailableReason = reviewUnavailableReason(error?.message); interrupted = stopped();
370
- // Why the review could not run, as a code only (the report says "failed").
371
- if (run.reviewUnavailable) try { session.sessionManager.appendCustomEntry('agent-watch-failure-detail', { role: 'review', code: failureCode(error?.message) }); } catch { /* the report still says it */ }
370
+ // Why the review could not run: its code, and the redacted words of a
371
+ // failure no code names (the report says only "failed").
372
+ if (run.reviewUnavailable) try { session.sessionManager.appendCustomEntry('agent-watch-failure-detail', { role: 'review', code: failureCode(error?.message), ...(unnamedFailureText(error?.message) ? { detail: unnamedFailureText(error?.message) } : {}) }); } catch { /* the report still says it */ }
372
373
  break;
373
374
  } finally { managed.reviewFocus = null; }
374
375
  continue;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piagent/core",
3
- "version": "1.48.2",
3
+ "version": "1.48.3",
4
4
  "type": "module",
5
5
  "private": true,
6
6
  "license": "MIT",
@@ -1 +1 @@
1
- import{n as e,r as t}from"./rolldown-runtime-hePW80VL.js";import{Dn as n,Fn as r}from"./vendor-CSDKB7mU.js";import{i,n as a}from"./ui-preferences-0waeSO-S.js";import{B as o,x as s}from"./api-BQS4Dtrr.js";import{c}from"./view-model-CWNiS4JN.js";var l=t(r(),1);function u(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.toolLabel} ${r}`,preview: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 d(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 f(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=>d(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=>u(e,r));return{rows:[...c,...s,...t],terminalCount:s.length,runningCount:c.length}}function p(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 m(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`}var h=e({ActivityPanel:()=>v}),g=n();function _({activity:e,preview:t,onPreview:n,locale:r}){return(0,g.jsxs)(`article`,{className:`activity-row`,children:[(0,g.jsx)(`span`,{className:`activity-state tone-${c(e.state)}`,"aria-label":e.state}),(0,g.jsxs)(`div`,{className:`activity-copy`,children:[(0,g.jsxs)(`div`,{children:[(0,g.jsx)(`strong`,{children:e.label}),(0,g.jsx)(`span`,{children:e.kind})]}),(0,g.jsx)(`p`,{children:e.preview||a(r,`Không có mô tả ngắn`,`No short description`)}),t&&(0,g.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,g.jsx)(`small`,{children:a(r,`Preview đã được rút gọn`,`Preview truncated`)})]})]}),(0,g.jsxs)(`div`,{className:`activity-result`,children:[(0,g.jsx)(`strong`,{children:p(e,r)}),(0,g.jsx)(`span`,{children:m(e,r)}),e.logRef&&n&&(0,g.jsx)(`button`,{type:`button`,onClick:n,"aria-expanded":!!t,children:t?a(r,`Ẩn log`,`Hide log`):a(r,`Xem log`,`View log`)})]})]})}function v({snapshot:e,sessionRef:t,terminalActivities:n=[],liveActivities:r}){let{locale:c}=i(),[u,d]=(0,l.useState)({}),p=e?.activity.running??[],m=e?.activity.recent??[],{rows:h,terminalCount:v,runningCount:y}=f(p,m,n,c,r),b=e=>{if(u[e.activityRef]){d(t=>({...t,[e.activityRef]:void 0}));return}d(t=>({...t,[e.activityRef]:{state:`loading`}})),(t?o(t,e.activityRef):s(e.activityRef)).then(t=>d(n=>({...n,[e.activityRef]:{state:`ready`,value:t}}))).catch(()=>d(t=>({...t,[e.activityRef]:{state:`error`}})))};return(0,g.jsxs)(`section`,{className:`activity-panel surface`,"aria-labelledby":`activity-title`,children:[(0,g.jsxs)(`header`,{className:`panel-heading`,children:[(0,g.jsxs)(`div`,{children:[(0,g.jsx)(`p`,{className:`section-kicker`,children:`Activity`}),(0,g.jsx)(`h2`,{id:`activity-title`,children:a(c,`Tool, command và kết quả lượt chạy`,`Tools, commands, and operation outcomes`)})]}),(0,g.jsxs)(`div`,{children:[(0,g.jsxs)(`span`,{className:`running-count`,children:[y,` `,a(c,`đang chạy`,`running`)]}),(0,g.jsxs)(`span`,{children:[m.length+v,` `,a(c,`gần đây`,`recent`)]})]})]}),(0,g.jsxs)(`div`,{className:`activity-list`,children:[h.map(e=>(0,g.jsx)(_,{activity:e,preview:u[e.activityRef],onPreview:e.logRef?()=>b(e):void 0,locale:c},e.activityRef)),h.length===0&&(0,g.jsx)(`div`,{className:`activity-empty`,children:a(c,`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,g.jsx)(`footer`,{className:`panel-footnote`,children:a(c,`Timeline đã được rút gọn theo giới hạn local.`,`The timeline was truncated to local limits.`)})]})}export{h as n,v 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,x as s}from"./api-BQS4Dtrr.js";import{c}from"./view-model-CWNiS4JN.js";var l=t(r(),1);function u(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.toolLabel} ${r}`,preview: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 d(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 f(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=>d(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=>u(e,r));return{rows:[...c,...s,...t],terminalCount:s.length,runningCount:c.length}}function p(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 m(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`}var h=e({ActivityPanel:()=>v}),g=n();function _({activity:e,preview:t,onPreview:n,locale:r}){return(0,g.jsxs)(`article`,{className:`activity-row`,children:[(0,g.jsx)(`span`,{className:`activity-state tone-${c(e.state)}`,"aria-label":e.state}),(0,g.jsxs)(`div`,{className:`activity-copy`,children:[(0,g.jsxs)(`div`,{children:[(0,g.jsx)(`strong`,{children:e.label}),(0,g.jsx)(`span`,{children:e.kind})]}),(0,g.jsx)(`p`,{children:e.preview||a(r,`Không có mô tả ngắn`,`No short description`)}),t&&(0,g.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,g.jsx)(`small`,{children:a(r,`Preview đã được rút gọn`,`Preview truncated`)})]})]}),(0,g.jsxs)(`div`,{className:`activity-result`,children:[(0,g.jsx)(`strong`,{children:p(e,r)}),(0,g.jsx)(`span`,{children:m(e,r)}),e.logRef&&n&&(0,g.jsx)(`button`,{type:`button`,onClick:n,"aria-expanded":!!t,children:t?a(r,`Ẩn log`,`Hide log`):a(r,`Xem log`,`View log`)})]})]})}function v({snapshot:e,sessionRef:t,terminalActivities:n=[],liveActivities:r}){let{locale:c}=i(),[u,d]=(0,l.useState)({}),p=e?.activity.running??[],m=e?.activity.recent??[],{rows:h,terminalCount:v,runningCount:y}=f(p,m,n,c,r),b=e=>{if(u[e.activityRef]){d(t=>({...t,[e.activityRef]:void 0}));return}d(t=>({...t,[e.activityRef]:{state:`loading`}})),(t?o(t,e.activityRef):s(e.activityRef)).then(t=>d(n=>({...n,[e.activityRef]:{state:`ready`,value:t}}))).catch(()=>d(t=>({...t,[e.activityRef]:{state:`error`}})))};return(0,g.jsxs)(`section`,{className:`activity-panel surface`,"aria-labelledby":`activity-title`,children:[(0,g.jsxs)(`header`,{className:`panel-heading`,children:[(0,g.jsxs)(`div`,{children:[(0,g.jsx)(`p`,{className:`section-kicker`,children:`Activity`}),(0,g.jsx)(`h2`,{id:`activity-title`,children:a(c,`Tool, command và kết quả lượt chạy`,`Tools, commands, and operation outcomes`)})]}),(0,g.jsxs)(`div`,{children:[(0,g.jsxs)(`span`,{className:`running-count`,children:[y,` `,a(c,`đang chạy`,`running`)]}),(0,g.jsxs)(`span`,{children:[m.length+v,` `,a(c,`gần đây`,`recent`)]})]})]}),(0,g.jsxs)(`div`,{className:`activity-list`,children:[h.map(e=>(0,g.jsx)(_,{activity:e,preview:u[e.activityRef],onPreview:e.logRef?()=>b(e):void 0,locale:c},e.activityRef)),h.length===0&&(0,g.jsx)(`div`,{className:`activity-empty`,children:a(c,`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,g.jsx)(`footer`,{className:`panel-footnote`,children:a(c,`Timeline đã được rút gọn theo giới hạn local.`,`The timeline was truncated to local limits.`)})]})}export{h as n,v as t};