@piagent/platform 1.51.1 → 1.52.0

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 (90) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/docs/command-reference-vietnamese.md +2 -2
  3. package/docs/distribution-standard.md +6 -6
  4. package/docs/harness-conformance.md +13 -13
  5. package/docs/operator-manual-vietnamese.md +7 -7
  6. package/docs/publishing-for-teams.md +2 -0
  7. package/docs/release-install-policy.md +2 -2
  8. package/docs/team-onboarding.md +2 -2
  9. package/docs-site/architecture.html +1 -1
  10. package/docs-site/assets/search-en.json +1 -1
  11. package/docs-site/assets/search-vi.json +1 -1
  12. package/docs-site/benchmark.html +1 -1
  13. package/docs-site/commands.html +1 -1
  14. package/docs-site/context-engine.html +1 -1
  15. package/docs-site/ecosystem.html +1 -1
  16. package/docs-site/en/architecture.html +1 -1
  17. package/docs-site/en/benchmark.html +1 -1
  18. package/docs-site/en/commands.html +1 -1
  19. package/docs-site/en/context-engine.html +1 -1
  20. package/docs-site/en/ecosystem.html +1 -1
  21. package/docs-site/en/index.html +15 -15
  22. package/docs-site/en/mcp-approval.html +1 -1
  23. package/docs-site/en/mcp-auth.html +1 -1
  24. package/docs-site/en/mcp-servers.html +1 -1
  25. package/docs-site/en/mcp.html +1 -1
  26. package/docs-site/en/permissions.html +1 -1
  27. package/docs-site/en/profiles.html +1 -1
  28. package/docs-site/en/quickstart.html +1 -1
  29. package/docs-site/en/runtime.html +1 -1
  30. package/docs-site/en/scope.html +1 -1
  31. package/docs-site/en/security.html +1 -1
  32. package/docs-site/en/team.html +1 -1
  33. package/docs-site/en/update.html +3 -3
  34. package/docs-site/en/whats-new.html +54 -31
  35. package/docs-site/en/windows.html +1 -1
  36. package/docs-site/en/workflows.html +1 -1
  37. package/docs-site/index.html +15 -15
  38. package/docs-site/mcp-approval.html +1 -1
  39. package/docs-site/mcp-auth.html +1 -1
  40. package/docs-site/mcp-servers.html +1 -1
  41. package/docs-site/mcp.html +1 -1
  42. package/docs-site/permissions.html +1 -1
  43. package/docs-site/profiles.html +1 -1
  44. package/docs-site/quickstart.html +1 -1
  45. package/docs-site/releases.json +65 -0
  46. package/docs-site/runtime.html +1 -1
  47. package/docs-site/scope.html +1 -1
  48. package/docs-site/security.html +1 -1
  49. package/docs-site/team.html +1 -1
  50. package/docs-site/update.html +3 -3
  51. package/docs-site/whats-new.html +54 -31
  52. package/docs-site/windows.html +1 -1
  53. package/docs-site/workflows.html +1 -1
  54. package/evals/private-holdout-v1/public-exposure.v2.json +2 -2
  55. package/package.json +1 -1
  56. package/packages/piagent-core/managed/check-questions.mjs +2 -46
  57. package/packages/piagent-core/managed/copies.mjs +10 -10
  58. package/packages/piagent-core/managed/general-clone.mjs +111 -0
  59. package/packages/piagent-core/managed/helper-roles.mjs +33 -42
  60. package/packages/piagent-core/managed/helper-run.mjs +21 -15
  61. package/packages/piagent-core/managed/helper-settlement.mjs +6 -10
  62. package/packages/piagent-core/managed/language-environment.mjs +6 -2
  63. package/packages/piagent-core/managed/ledger.mjs +11 -10
  64. package/packages/piagent-core/managed/objections.mjs +8 -25
  65. package/packages/piagent-core/managed/role-names.mjs +6 -0
  66. package/packages/piagent-core/managed/session.mjs +13 -13
  67. package/packages/piagent-core/managed/test-scope.mjs +170 -0
  68. package/packages/piagent-core/managed/tool-boundary.mjs +4 -4
  69. package/packages/piagent-core/managed/workflow.mjs +2 -39
  70. package/packages/piagent-core/package.json +1 -1
  71. package/packages/piagent-core/runtime/managed-failure.d.mts +1 -1
  72. package/packages/piagent-core/runtime/managed-failure.mjs +3 -3
  73. package/packages/piagent-webui/dist/client/assets/{App-C9tlGPMb.js → App-DQwj6l-F.js} +1 -1
  74. package/packages/piagent-webui/dist/client/assets/{GatewayApp-Dvo96qoX.js → GatewayApp-CP8GpZsK.js} +3 -3
  75. package/packages/piagent-webui/dist/client/assets/{InspectionApp-CGgHsOyC.js → InspectionApp-BZqu1pjw.js} +1 -1
  76. package/packages/piagent-webui/dist/client/assets/{SessionAgentWorkspace-n8UMMcrP.js → SessionAgentWorkspace-4nJSbzE9.js} +1 -1
  77. package/packages/piagent-webui/dist/client/assets/{SessionInspectorDrawer-B62kkr8J.js → SessionInspectorDrawer-DEb_TBE8.js} +2 -2
  78. package/packages/piagent-webui/dist/client/assets/SettingsNavigation-BF4X5xkR.js +3 -0
  79. package/packages/piagent-webui/dist/client/assets/{SettingsPage-DKg0SfZQ.js → SettingsPage-CS8LsdhS.js} +1 -1
  80. package/packages/piagent-webui/dist/client/assets/{TerminalPanel-BPTVsNjP.js → TerminalPanel-CiF2_woF.js} +1 -1
  81. package/packages/piagent-webui/dist/client/assets/{WorkspaceCodeEditor-BHGFFBc2.js → WorkspaceCodeEditor-OFxSEbBs.js} +1 -1
  82. package/packages/piagent-webui/dist/client/assets/{WorkspaceExplorer-nd4kghwK.js → WorkspaceExplorer-ZYIi-FWr.js} +3 -3
  83. package/packages/piagent-webui/dist/client/assets/{index-BiLVX8S-.js → index-CfJC4Unf.js} +2 -2
  84. package/packages/piagent-webui/dist/client/index.html +1 -1
  85. package/packages/piagent-webui/server/transcript-projection.ts +3 -3
  86. package/packages/piagent-webui/shared/company-copy.ts +3 -1
  87. package/packages/piagent-webui/shared/turn-end.ts +1 -1
  88. package/schemas/piagent-webui/transcript-v1.schema.json +3 -3
  89. package/scripts/runtime-advisory-exceptions.json +27 -27
  90. package/packages/piagent-webui/dist/client/assets/SettingsNavigation-BZijmuPt.js +0 -3
@@ -0,0 +1,170 @@
1
+ // How much of the suite a test command covers: 'partial' (a filter, one file, one
2
+ // package or module), 'suite' (the whole test command) or null when it runs no tests.
3
+ // A member who chose "only the related part" is stuck when a part reads as the
4
+ // whole suite, so each tool says how its own commands narrow: the flags that pick
5
+ // tests, the flags that take a value, and the words that still run everything
6
+ // (a Maven goal, `vitest run`). Any other word on the line names a part.
7
+
8
+ // What a command runs, without the text it merely quotes (a commit message, an
9
+ // echo): a quoted word stays one word with nothing in it that splits a command.
10
+ export const unquoted = text => text.replace(/"[^"]*"|'[^']*'/g, quoted => quoted.startsWith('"$') ? '$' : quoted.length > 2 ? quoted.slice(1, -1).replace(/[\s;&|<>()]/g, '_') : '""');
11
+ export const partsOf = bare => bare.split(/\s*(?:\|\|?|&&|;)\s*/).map(part => part.replace(/\s\d*[<>].*$/, '').replace(/[\s)}]+$/, '').trim());
12
+
13
+ const VERSION_ONLY = /(?:^|\s)(?:--version|-V|--help)(?:\s|$)/;
14
+ // Listing the tests runs none of them.
15
+ const LIST_ONLY = /(?:^|\s)(?:--collect-only|--co|--listTests|--list-tests|--list|-list|--dry-run|--dryRun|-showTestPlans)(?=[\s=]|$)/;
16
+ const NOT_A_RUN = /^(echo|printf|grep|rg|cat|sed|awk|head|tail|less|git|ls|find)\b/;
17
+ const NOT_TESTS = /\b(lint|typecheck|type-check|tsc|eslint|biome|ruff|mypy|pyright|vet|clippy|format|build)\b/;
18
+ // What starts a command before the tool itself: a function body, a group, a loop body, env, a runner of binaries.
19
+ const LAUNCHER = /^(?:[\w-]+\s*\(\)\s*\{?\s*|\{\s+|\(\s*|(?:do|then|else)\s+|\w+=\S*\s+|env\s+|time\s+|nohup\s+|timeout\s+\S+\s+|npx\s+(?:-\S+\s+)*|bunx\s+|bun\s+x\s+|pnpm\s+(?:exec|dlx)\s+|(?:yarn|pnpm|bun)\s+(?=(?:jest|vitest|mocha|ava|playwright|cypress|nx|turbo|lerna)\b)|uv\s+run\s+|poetry\s+run\s+|pipenv\s+run\s+|hatch\s+run\s+|bundle\s+exec\s+|php\s+(?=\S*(?:phpunit|pest)\b)|node_modules\/\.bin\/|(?:vendor\/)?bin\/|\.\/)+/;
20
+
21
+ const NONE = /$^/;
22
+ // A word that names everything there is: the test folder, every package, a glob, a count.
23
+ const ALL_OF_IT = /^(?:\.\/)?(?:tests?|specs?|__tests__|e2e|src)\/?$|^(?:\.\/)?\.\.\.$|\*|^\d+[smh]?$|^auto$/;
24
+ // Does this command pick part of the tests? `narrow` flags pick tests (by name before
25
+ // `=`), `value` flags take the next word, `whole` words still run everything; any
26
+ // other word names a part, unless the tool takes none (`words: false`).
27
+ function narrows(args, { narrow = NONE, value = NONE, whole = NONE, words = true }) {
28
+ for (let i = 0; i < args.length; i++) {
29
+ const a = args[i];
30
+ // A variable: a function's arguments, a loop's file. The caller chose them, so this is not the whole command.
31
+ if (a.startsWith('$')) return true;
32
+ if (a === '--') continue;
33
+ if (a.startsWith('-')) {
34
+ const name = a.split('=')[0];
35
+ if (narrow.test(name)) return true;
36
+ if (!a.includes('=') && value.test(name)) i++;
37
+ continue;
38
+ }
39
+ if (words && !whole.test(a) && !ALL_OF_IT.test(a)) return true;
40
+ }
41
+ return false;
42
+ }
43
+
44
+ const JS = {
45
+ narrow: /^(?:-t|--testNamePattern|--test-name-pattern|-g|--grep|--fgrep|--testPathPattern|--testPathPatterns|--filter|--spec|--project|--selectProjects|--findRelatedTests|--onlyChanged|-o|--changed|--changedSince|--lastCommit|--only|--test-only|--match|-m)$/,
46
+ value: /^(?:-w|--maxWorkers|--workers|-j|--jobs|--timeout|--reporter|-r|--require|--import|--loader|--config|-c|--rootDir|--root|--outputFile|--coverageDirectory|--test-concurrency|--test-reporter|--test-reporter-destination|--retries|--repeat-each|--env|--browser|--shard|--seed|--slow|--file|--mode|--environment|--pool|--dir)$/,
47
+ whole: /^(?:run|watch|dev|test)$/,
48
+ };
49
+ const PYTEST = { narrow: /^(?:-k|-m|--lf|--last-failed|--sw|--stepwise|--deselect|--pyargs)$/, value: /^(?:-n|--numprocesses|-c|-p|--maxfail|--rootdir|--durations|-o|--junitxml|--cov|--timeout|--dist|-W|--basetemp|--log-level|--confcutdir|--ignore|--ignore-glob|--tb|-r|--reruns)$/ };
50
+ const tool = (start, spec, test = () => true) => ({ start, scope: args => test(args) ? (narrows(args, spec) ? 'partial' : 'suite') : null });
51
+
52
+ // npm/pnpm/yarn/bun: the workspace or filter before the script, the script, then its own arguments (Jest, Vitest, …).
53
+ function packageScript(args) {
54
+ let picked = false;
55
+ for (let i = 0; i < args.length; i++) {
56
+ const a = args[i];
57
+ if (/^(?:-w|--workspace|--filter|-F)$/.test(a)) { picked = true; i++; continue; }
58
+ if (/^(?:--workspace|--filter)=/.test(a)) { picked = true; continue; }
59
+ if (a.startsWith('-')) continue;
60
+ if (a === 'workspace') { picked = true; i++; continue; }
61
+ // `npm t` is `npm test`; `npm run t` is a script of that name.
62
+ const run = /^(?:run|run-script)$/.test(a), script = run ? args[++i] : a;
63
+ if (!(run ? /^test(?::\S+)?$/ : /^(?:test|t|tst)(?::\S+)?$/).test(script ?? '')) return null;
64
+ // Before `--`, `-w web` is still npm's workspace; a number is Jest's worker count.
65
+ const rest = args.slice(i + 1), end = rest.includes('--') ? rest.indexOf('--') : rest.length;
66
+ picked ||= rest.slice(0, end).some((a, j) => /^--workspace=/.test(a) || (/^(?:-w|--workspace)$/.test(a) && !/^\d+$/.test(rest[j + 1] ?? '0')));
67
+ return picked || narrows(rest, JS) ? 'partial' : 'suite';
68
+ }
69
+ return null;
70
+ }
71
+ // Maven: every word is a goal; it tests when a goal reaches `test`. The flags pick classes, groups or modules.
72
+ function maven(args) {
73
+ if (!args.some(a => /^(?:[\w.-]+:)*(?:test|verify|integration-test)$/.test(a))) return null;
74
+ if (args.some(a => /^-D(?:skipTests|maven\.test\.skip)(?:=true)?$/.test(a))) return null;
75
+ const file = args.findIndex(a => a === '-f' || a === '--file');
76
+ if (file >= 0 && args[file + 1]?.includes('/')) return 'partial';
77
+ return narrows(args, { narrow: /^(?:-Dtest|-Dit\.test|-Dgroups|-Dcucumber\.filter\.tags|-Dcucumber\.options|-pl|--projects|-rf|--resume-from)$/, value: /^(?:-T|--threads|-s|--settings|-gs|--global-settings|-P|--activate-profiles|-l|--log-file|-t|--toolchains|-b|--builder|-f|--file)$/, words: false }) ? 'partial' : 'suite';
78
+ }
79
+ // Gradle: the words are tasks; `:cart:test` is one module's.
80
+ function gradle(args) {
81
+ const value = /^(?:-x|--exclude-task|--max-workers|-I|--init-script|-c|--settings-file|--console|-g|--gradle-user-home)$/;
82
+ const tasks = args.filter((a, i) => !a.startsWith('-') && !value.test(args[i - 1] ?? ''));
83
+ const testTasks = tasks.filter(t => /^(?:test|check|[a-zA-Z]*Test)$/.test(t.split(':').pop()));
84
+ if (!testTasks.length) return null;
85
+ if (testTasks.some(t => /^:?[\w-]+(?::[\w-]+)*:\w+$/.test(t) && t.replace(/^:/, '').includes(':'))) return 'partial';
86
+ return narrows(args, { narrow: /^(?:--tests|-p|--project-dir|-b|--build-file)$/, value, words: false }) ? 'partial' : 'suite';
87
+ }
88
+ // Go: the words are packages; none is the current one, `./...` is all of them.
89
+ function goTest(args) {
90
+ const end = args.indexOf('-args');
91
+ const own = end >= 0 ? args.slice(0, end) : args;
92
+ const spec = { narrow: /^-{1,2}run$/, value: /^-{1,2}(?:p|count|timeout|parallel|cpu|tags|exec|covermode|coverpkg|coverprofile|o|bench|benchtime|shuffle|vet|C|fuzz|fuzztime|skip|json)$/ };
93
+ const packages = own.filter((a, i) => !a.startsWith('-') && !(spec.value.test(own[i - 1] ?? '') && !own[i - 1].includes('=')));
94
+ return !packages.length || narrows(own, spec) ? 'partial' : 'suite';
95
+ }
96
+ // make: a test target; a variable naming tests narrows it (`make test TEST=./cart`).
97
+ function make(args) {
98
+ if (!args.some(a => /^(?:test|check)(?:[-_:]\w+)*$/.test(a))) return null;
99
+ return args.some(a => /^\w*(?:TEST|RUN|FILTER|PKG|SPEC|ONLY|PATTERN|FILE)\w*=/i.test(a) || a.startsWith('$')) ? 'partial' : 'suite';
100
+ }
101
+ // Nx, Turborepo, Lerna run a target across projects; naming projects or `affected` narrows.
102
+ function monorepo(name, args) {
103
+ const words = args.filter(a => !a.startsWith('-'));
104
+ const target = args.find((a, i) => /^(?:-t|--targets?)$/.test(args[i - 1] ?? '')) ?? args.find(a => /^--targets?=/.test(a))?.split('=')[1];
105
+ if (name === 'nx') {
106
+ const [command, project] = words;
107
+ if (command === 'run-many') return /test|e2e/.test(target ?? '') ? (args.some(a => /^(?:-p|--projects)(?:=|$)/.test(a)) ? 'partial' : 'suite') : null;
108
+ if (command === 'affected') return /test|e2e/.test(target ?? words[1] ?? '') ? 'partial' : null;
109
+ if (command === 'run') return /:(?:\w*test|e2e)\w*$/.test(project ?? '') ? 'partial' : null;
110
+ return /test|e2e/.test(command ?? '') ? (project ? 'partial' : 'suite') : null;
111
+ }
112
+ const tasks = words[0] === 'run' ? words.slice(1) : words;
113
+ if (!tasks.some(t => /test|e2e/.test(t))) return null;
114
+ return args.some(a => /^(?:--filter|-F|--scope|--since|--affected)(?:=|$)/.test(a)) ? 'partial' : 'suite';
115
+ }
116
+
117
+ const TOOLS = [
118
+ { start: /^(?:npm|pnpm|yarn|bun)(?=\s|$)/, scope: packageScript },
119
+ tool(/^(?:jest|vitest|mocha|ava|tap|uvu)(?=\s|$)/, JS),
120
+ tool(/^node(?=\s)/, JS, args => args.includes('--test')),
121
+ tool(/^playwright\s+test(?=\s|$)/, JS),
122
+ tool(/^cypress\s+run(?=\s|$)/, JS),
123
+ tool(/^(?:pytest|py\.test|python[\d.]*\s+-m\s+pytest)(?=\s|$)/, PYTEST),
124
+ tool(/^python[\d.]*\s+-m\s+unittest(?=\s|$)/, { narrow: /^(?:-k|-p|--pattern)$/, value: /^(?:-s|--start-directory|-t|--top-level-directory)$/, whole: /^discover$/ }),
125
+ tool(/^python[\d.]*\s+manage\.py\s+test(?=\s|$)/, { narrow: /^(?:-k|--tag)$/, value: /^(?:--parallel|--settings|-v|--verbosity|--exclude-tag|--testrunner)$/ }),
126
+ tool(/^tox(?=\s|$)/, { value: /^(?:-e|-c|--conf|-p|--parallel|-x|--override)$/ }),
127
+ { start: /^go\s+test(?=\s|$)/, scope: goTest },
128
+ tool(/^cargo\s+test(?=\s|$)/, { narrow: /^(?:-p|--package|--test|--lib|--bin|--bins|--example|--examples|--doc|--bench|--manifest-path|--ignored|--exact)$/, value: /^(?:-j|--jobs|--features|-F|--target|--profile|--test-threads|--color|-Z|--target-dir|--message-format|--config|--exclude)$/ }),
129
+ tool(/^cargo\s+nextest\s+run(?=\s|$)/, { narrow: /^(?:-E|--filterset|--filter-expr|-p|--package|--test|--lib|--bin)$/, value: /^(?:-j|--jobs|--profile|--test-threads|--retries|--partition|--features|-F)$/ }),
130
+ { start: /^mvnw?(?=\s|$)/, scope: maven },
131
+ { start: /^gradlew?(?=\s|$)/, scope: gradle },
132
+ tool(/^dotnet\s+test(?=\s|$)/, { narrow: /^--filter$/, value: /^(?:-c|--configuration|-f|--framework|-r|--runtime|-l|--logger|-s|--settings|-v|--verbosity|--results-directory|-a|--test-adapter-path|--collect|-d|--diag|-o|--output|--arch|--os|--blame-hang-timeout|--blame-crash-dump-type|-e|--environment)$/, whole: /\.slnx?$/ }),
133
+ tool(/^swift\s+test(?=\s|$)/, { narrow: /^(?:--filter|--package-path)$/, value: /^(?:-c|--configuration|-j|--jobs|--num-workers|--scratch-path|--skip)$/ }),
134
+ tool(/^xcodebuild(?=\s)/, { narrow: /^-only-testing(?::|$)/, value: /^-(?:scheme|destination|project|workspace|sdk|configuration|derivedDataPath|resultBundlePath|testPlan|skip-testing|parallel-testing-worker-count|xctestrun|target|arch|toolchain|destination-timeout|maximum-parallel-testing-workers|test-iterations|testLanguage|testRegion|only-test-configuration|skip-test-configuration)$/, words: false }, args => args.some(a => /^test(?:-without-building)?$/.test(a))),
135
+ tool(/^rspec(?=\s|$)/, { narrow: /^(?:-t|--tag|-e|--example|-E|--example-matches|--only-failures|--next-failure|-P|--pattern)$/, value: /^(?:-f|--format|-o|--out|--seed|--order|-r|--require|-I|--default-path|-O|--options)$/ }),
136
+ tool(/^(?:rails|rake)\s+test(?::\w+)?(?=\s|$)/, { narrow: /^(?:-n|--name)$/, value: /^(?:--seed|-s|-e|--environment|-j)$/ }),
137
+ tool(/^(?:phpunit|pest|php\s+artisan\s+test)(?=\s|$)/, { narrow: /^(?:--filter|--group|--testsuite)$/, value: /^(?:-c|--configuration|--bootstrap|--log-junit|--exclude-group|--coverage-html|--coverage-clover|--order-by|--cache-directory|--processes|-d)$/ }),
138
+ tool(/^mix\s+test(?=\s|$)/, { narrow: /^(?:--only|--failed|--stale)$/, value: /^(?:--seed|--max-failures|--exclude|--include|--formatter|--partitions|--max-cases)$/ }),
139
+ tool(/^(?:flutter|dart)\s+test(?=\s|$)/, { narrow: /^(?:--name|-n|--plain-name|-N|--tags|-t)$/, value: /^(?:-j|--concurrency|-r|--reporter|-p|--platform|-x|--exclude-tags|--file-reporter|--coverage-path|--flavor|--total-shards|--shard-index)$/ }),
140
+ tool(/^deno\s+test(?=\s|$)/, { narrow: /^--filter$/, value: /^(?:-c|--config|--import-map|--reporter|--seed)$/ }),
141
+ { start: /^make(?=\s)/, scope: make },
142
+ { start: /^(?:nx|turbo|lerna)(?=\s)/, scope: (args, name) => monorepo(name, args) },
143
+ ];
144
+ function testRun(part) {
145
+ const own = part.replace(LAUNCHER, '');
146
+ for (const { start, scope } of TOOLS) {
147
+ const hit = own.match(start);
148
+ if (!hit) continue;
149
+ const result = scope(own.slice(hit[0].length).split(' ').filter(Boolean), hit[0]);
150
+ return result ? { own, result } : null;
151
+ }
152
+ return null;
153
+ }
154
+
155
+ export function checkScope(command, declared = []) {
156
+ const text = String(command ?? '').replace(/\s+/g, ' ').trim();
157
+ if (!text || NOT_A_RUN.test(text) || VERSION_ONLY.test(text) || LIST_ONLY.test(text)) return null;
158
+ const bare = unquoted(text), runs = partsOf(bare).map(testRun).filter(Boolean);
159
+ // A command the repository declares is its whole check, however it is written;
160
+ // a line that also runs a whole suite is a whole run.
161
+ if (runs.length) return declared.includes(text) || runs.some(run => run.result === 'suite') ? 'suite' : 'partial';
162
+ // A script the repository declares: it cannot be narrowed from here.
163
+ const named = declared.find(d => partsOf(bare).some(part => part.startsWith(unquoted(d))));
164
+ return named && !NOT_TESTS.test(named) ? 'suite' : null;
165
+ }
166
+ // The test step of a command, without what surrounds it: the same step costs the same.
167
+ export function suitePart(command) {
168
+ const runs = partsOf(unquoted(String(command ?? '').replace(/\s+/g, ' ').trim())).map(testRun).filter(Boolean);
169
+ return (runs.find(run => run.result === 'suite') ?? runs[0])?.own ?? String(command ?? '').trim();
170
+ }
@@ -4,9 +4,7 @@ import { BoundaryTerminals } from './terminal-boundary.mjs';
4
4
  import { ToolFreshness } from './tool-freshness.mjs';
5
5
  import { BoundaryQueue } from './boundary-queue.mjs';
6
6
  import { workspaceWorkerPins, verifyWorkspaceWorker } from './workspace-worker-pins.mjs'; import { snapshotRuntime } from './runtime-snapshot.mjs';
7
- import fs from 'node:fs';
8
- import os from 'node:os';
9
- import path from 'node:path';
7
+ import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path';
10
8
  import { execFileSync, spawn } from 'node:child_process';
11
9
  import net from 'node:net';
12
10
  import { StringDecoder } from 'node:string_decoder';
@@ -208,7 +206,7 @@ export class ManagedToolBoundary {
208
206
  // readOnly: the project cannot be written. commands: bash is offered all
209
207
  // the same (a helper that runs checks); it writes only to its temporary
210
208
  // directory, and has no network.
211
- constructor({ cwd, sdkRoot, protectedRoots = [], readOnly = false, commands = false, node = process.execPath, identity, userHome: home, searchTools = true, skills: skillFolders = [], proxyPorts }) {
209
+ constructor({ cwd, sdkRoot, protectedRoots = [], readOnly = false, commands = false, node = process.execPath, identity, userHome: home, searchTools = true, skills: skillFolders = [], readRoots = [], proxyPorts }) {
212
210
  // macOS: Seatbelt. Linux, and Windows through WSL2: bubblewrap (linux-sandbox.mjs).
213
211
  this.linux = process.platform === 'linux';
214
212
  if (process.platform !== 'darwin' && !(this.linux && BWRAP)) throw new Error('managed-sandbox-unavailable');
@@ -249,6 +247,8 @@ export class ManagedToolBoundary {
249
247
  const skills = [...new Set(skillFolders.flatMap(dir => { try { return [fs.realpathSync(dir)]; } catch { return []; } }))]
250
248
  .filter(dir => dir !== '/' && !/[\x00-\x1f\x7f]/.test(dir) && !narrowed.some(root => inside(root, dir)) && !inside(userHome, dir)
251
249
  && (!inside(dir, userHome) || agentFolders.some(root => inside(dir, root)) || !path.relative(userHome, dir).startsWith('.')));
250
+ // readRoots: folders the runtime opens for reading only (the general helper's borrowed git objects).
251
+ skills.push(...readRoots.map(dir => fs.realpathSync(dir)).filter(dir => dir !== '/' && !narrowed.some(root => inside(root, dir))));
252
252
  this.userHome = userHome;
253
253
  // Only for commands the user approved (run_with_network); credentials stay
254
254
  // denied, and so do reference folders: what a command with network can
@@ -119,22 +119,6 @@ export function reviewFindings(text) {
119
119
  }
120
120
  return null;
121
121
  }
122
- // The verify helper re-checks a result on its own: claims against the code and
123
- // primary sources, the repository's checks on the current code.
124
- export const VERIFY_PROMPT = `You verify a result for the company coding assistant, independently of whoever produced it. For each claim or requirement in the brief: read the code it is about, run the repository checks that cover it with bash, and confirm external facts against primary sources (official documentation, the project's own repository) with web_search and web_fetch. Commands run offline and the project is read-only for them: a check that must write into the project or reach the network fails for that reason. Report such a claim as unverifiable and say why; do not work around the sandbox. Never mark a claim pass without evidence you saw. ${BRIEF_CHECK} End your answer with one JSON object in a \`\`\`json block: {"verdicts":[{"claim":"what was checked","status":"pass|fail|unverifiable","evidence":"file:line, the command and its result, or the source URL"}],${BRIEF_ISSUES_JSON},"summary":"one sentence"}. Use an empty brief_issues list when the brief is sound. You cannot change files or delegate.`;
125
- // The verifier's verdicts from its answer; null when it gave no valid JSON.
126
- export function verifyVerdicts(text) {
127
- const blocks = [...String(text).matchAll(/```json\s*([\s\S]*?)```/g)].map(m => m[1]);
128
- for (const raw of blocks.reverse()) {
129
- try {
130
- const value = JSON.parse(raw);
131
- if (!Array.isArray(value?.verdicts)) continue;
132
- return value.verdicts.filter(v => ['pass', 'fail', 'unverifiable'].includes(v?.status) && typeof v.claim === 'string' && v.claim.trim()).slice(0, 40)
133
- .map(v => ({ status: v.status, claim: clip(v.claim, 500), evidence: clip(v.evidence, 500) }));
134
- } catch { /* not this block */ }
135
- }
136
- return null;
137
- }
138
122
  const where = f => f.file ? ` ${f.file}${f.line ? ':' + f.line : ''}` : '';
139
123
  export const findingText = f => `[${f.severity}]${where(f)} — ${f.issue}${f.evidence ? `\n Evidence: ${f.evidence}` : ''}`;
140
124
 
@@ -170,10 +154,10 @@ export class RunProcess {
170
154
  reviews: 0, blocking: 0, blockingOpen: 0, fixLoops: 0, planUpdated: false, planRefusals: new Set(), planSkipped: false, unplannedChange: false,
171
155
  unknownTools: 0, changed: false, verified: false, docsOnly: false, approvalDeclined: false, reviewed: false, reviewUnavailable: false, outcome: null, legacyOutcome: null,
172
156
  // The objection ledger (ledger.mjs), disagreements handed to the
173
- // member, the claims verify marked failed, the reviews the main agent
157
+ // member, the reviews the main agent
174
158
  // asked for, and where the harness last sent the agent back (to read
175
159
  // its answer and see whether the code moved).
176
- ledger: [], disputes: [], verifyFails: [], claimsAsked: false, claimsAt: null, claimsRechecked: false, claimsDone: false, mainReviews: 0, minorFindings: 0,
160
+ ledger: [], disputes: [], mainReviews: 0, minorFindings: 0,
177
161
  sentAt: null, sentDigest: null, rejudged: false, reviewSettled: false,
178
162
  // How much of the suite the member wants run (check-questions.mjs).
179
163
  scopeChoice: null, scopeWords: null, fullAsked: asksFullChecks(request), pushAllowedOn: undefined,
@@ -356,27 +340,6 @@ export async function completionGate(managed, run, signal) {
356
340
  }
357
341
  }
358
342
  }
359
- // Claims the verify subagent marked failed, under a required verify: back
360
- // to the agent once, then checked again with its answer; still failing,
361
- // the member decides.
362
- if (policy.verify === 'require' && run.verifyFails.length && !run.claimsDone) {
363
- const fails = run.verifyFails, list = fails.map((v, i) => `${i + 1}. ${v.claim}${v.evidence ? `\n Evidence: ${v.evidence}` : ''}`).join('\n'), claims = fails.slice(0, 10).map(v => ({ claim: v.claim }));
364
- if (!run.claimsAsked) {
365
- run.claimsAsked = true; run.claimsAt = session.messages.length;
366
- await send(`Harness: the verify subagent marked ${fails.length} claim(s) as failed:\n${list}\nFix the cause, or reject a verdict with resolve_objection (what you checked and why). The verify subagent then checks these claims again with your answer.`, { phase: 'claims', claims });
367
- continue;
368
- }
369
- if (!run.claimsRechecked) {
370
- run.claimsRechecked = true;
371
- await note("Harness: the verify subagent checks the failed claims again with the main agent's answer.", { phase: 'rejudge', role: 'verify' });
372
- try { await managed.delegate({ role: 'verify', task: `Check these claims again on the current code; they were marked failed before:\n${list}\n\nThe main agent's answer:\n${answerSince(run.claimsAt) || '(no answer)'}${decisionsText(run, 'verify')}\n\nA claim passes only on evidence you see yourself. It stays failed when it does not hold and the result the member asked for depends on it. When the answer shows the claim is not part of what the member asked and the result does not depend on it, mark it unverifiable and say why.` }, signal, { harness: true }); }
373
- catch { if (stopped()) { interrupted = run.changed; break; } }
374
- continue;
375
- }
376
- run.claimsDone = true; run.disputes.push({ source: 'verify' });
377
- for (const item of ledgerOf(run)) if (item.source === 'verify' && ['open', 'accepted', 'rejected'].includes(item.status)) setStatus(item, 'disputed');
378
- await note(`Harness: the main agent and the verify subagent still disagree after two rounds; the member decides.\n${list}`, { phase: 'dispute', role: 'verify', claims });
379
- }
380
343
  // Plan "require": a checklist left with open steps is the next round's
381
344
  // (continuation.mjs), after this run's checks and review.
382
345
  // A rejected premise objection goes back to its helper; then what the
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piagent/core",
3
- "version": "1.51.1",
3
+ "version": "1.52.0",
4
4
  "type": "module",
5
5
  "private": true,
6
6
  "license": "MIT",
@@ -1,4 +1,4 @@
1
- export type ManagedFailure = { role: "main" | "scout" | "research" | "verify" | "review"; code: string; kind: string; requestId: string | null; local: boolean };
1
+ export type ManagedFailure = { role: "main" | "scout" | "research" | "advisor" | "general" | "review"; code: string; kind: string; requestId: string | null; local: boolean };
2
2
  export function failureKind(code: string): string;
3
3
  export function failureIsLocal(code: string): boolean;
4
4
  export function failureCode(raw: unknown): string;
@@ -113,7 +113,7 @@ const CODES = {
113
113
  // Studio is stopped or restarting behind it, or the way to it failed.
114
114
  'studio-gateway-unavailable': ['unreachable', "Studio's gateway answered that Studio is not reachable"],
115
115
  };
116
- const ROLES = { main: 'main agent', scout: 'scout subagent', research: 'research subagent', verify: 'verify subagent', review: 'review subagent' };
116
+ const ROLES = { main: 'main agent', scout: 'scout subagent', research: 'research subagent', advisor: 'advisor subagent', general: 'general subagent', review: 'review subagent' };
117
117
  const REQUEST = /\b([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\b/;
118
118
  const PROVIDER_SERVER_ERROR = /^(?:Error Code )?(?:server_error|api_error|internal_error|internal_server_error|overloaded_error|undefined):|An error occurred while processing your request|"type"\s*:\s*"(?:api_error|overloaded_error)"|\bOverloaded\b/i;
119
119
  const NETWORK = /fetch failed|econnrefused|econnreset|enotfound|ehostunreach|etimedout|socket hang up|network|connection error|terminated|timed? ?out|aborted due to timeout/i;
@@ -203,8 +203,8 @@ export function parseFailure(raw) {
203
203
  const text = String(raw ?? '');
204
204
  if (!text.trim()) return null;
205
205
  const code = failureCode(text);
206
- const role = /^Agent Watch (main agent|(?:scout|research|verify|review) subagent): /.exec(text)?.[1]?.split(' ')[0]
207
- ?? /(scout|research|verify|review) subagent/.exec(text)?.[1] ?? 'main';
206
+ const role = /^Agent Watch (main agent|(?:scout|research|advisor|general|review) subagent): /.exec(text)?.[1]?.split(' ')[0]
207
+ ?? /(scout|research|advisor|general|review) subagent/.exec(text)?.[1] ?? 'main';
208
208
  const request = failureIsLocal(code) ? null : REQUEST.exec(text)?.[1] ?? null;
209
209
  return { role, code, kind: failureKind(code), requestId: request, local: failureIsLocal(code) };
210
210
  }