@piagent/platform 1.44.1 → 1.44.2

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 (60) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/docs/command-reference-vietnamese.md +2 -2
  3. package/docs/distribution-standard.md +6 -6
  4. package/docs/harness-conformance.md +14 -0
  5. package/docs/operator-manual-vietnamese.md +7 -7
  6. package/docs/publishing-for-teams.md +1 -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 +9 -9
  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 +27 -17
  35. package/docs-site/en/windows.html +1 -1
  36. package/docs-site/en/workflows.html +1 -1
  37. package/docs-site/index.html +9 -9
  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 +31 -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 +27 -17
  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 +1 -1
  55. package/package.json +1 -1
  56. package/packages/piagent-core/managed/helper-roles.mjs +24 -6
  57. package/packages/piagent-core/managed/risk-lanes.mjs +38 -0
  58. package/packages/piagent-core/managed/session.mjs +4 -4
  59. package/packages/piagent-core/managed/workflow.mjs +24 -4
  60. package/packages/piagent-core/package.json +1 -1
@@ -12,12 +12,30 @@ export const HELPER_ROLES = Object.freeze(['scout', 'research', 'verify', 'revie
12
12
  export const helperRoles = manifest => HELPER_ROLES.filter(role => manifest?.harness?.configuration?.[role]);
13
13
  const WIDE_READING = 'when understanding the task needs more reading than a few batches of files, such as a flow across many modules of an unfamiliar codebase';
14
14
  const NETWORK_COMMANDS = 'run_with_network only when a command itself needs the internet or starts a local server or browser, such as end-to-end tests (the user approves each one)';
15
- // How the main agent reaches the web. With a research helper it has no web
16
- // tools: searching and reading pages happen on the helper's model.
17
- export function webPrompt(roles) {
18
- return roles.includes('research')
19
- ? ` Shell commands run without network and you have no web tools: hand anything on the web (current information, news, documentation, releases, error messages) to the research helper, and use ${NETWORK_COMMANDS}.`
20
- : ` Shell commands run without network: use web_search for current information or documentation, web_fetch to read a public https page, and ${NETWORK_COMMANDS}.`;
15
+ // With a research helper the main agent may still look one thing up itself:
16
+ // the Harness's workflow.main_web_lookups (0 to 5, default 0) web_search and
17
+ // web_fetch calls per member message. Anything wider is the helper's work.
18
+ export const MAIN_WEB_LOOKUPS_MAX = 5;
19
+ export function mainWebLookups(manifest) {
20
+ const n = manifest?.harness?.configuration?.workflow?.main_web_lookups;
21
+ return Number.isInteger(n) && n >= 0 && n <= MAIN_WEB_LOOKUPS_MAX ? n : 0;
22
+ }
23
+ // The main agent's web tools, counted against the current run's lookups.
24
+ export function limitedWebTools(tools, currentRun, limit) {
25
+ return tools.map(tool => ({ ...tool, execute: async (...args) => {
26
+ const run = currentRun();
27
+ if (run && ++run.webLookups > limit) throw Error(`managed-web-lookups-spent: you used your ${limit} quick web lookup(s) for this message. Hand the rest of the web work to the research helper (delegate role "research").`);
28
+ return tool.execute(...args);
29
+ } }));
30
+ }
31
+ // How the main agent reaches the web. With a research helper, searching and
32
+ // reading pages happen on the helper's model, beyond a few quick lookups.
33
+ export function webPrompt(roles, lookups = 0) {
34
+ if (!roles.includes('research'))
35
+ return ` Shell commands run without network: use web_search for current information or documentation, web_fetch to read a public https page, and ${NETWORK_COMMANDS}.`;
36
+ return lookups > 0
37
+ ? ` Shell commands run without network. For one quick fact (a version, a flag, one documentation page) use web_search or web_fetch yourself, at most ${lookups} call(s) per member message; hand anything wider on the web (several sources, comparisons, news, research) to the research helper, and use ${NETWORK_COMMANDS}.`
38
+ : ` Shell commands run without network and you have no web tools: hand anything on the web (current information, news, documentation, releases, error messages) to the research helper, and use ${NETWORK_COMMANDS}.`;
21
39
  }
22
40
  export function helperPrompt(roles) {
23
41
  const lines = [];
@@ -0,0 +1,38 @@
1
+ // Risk lanes (the Harness's workflow.lanes, on unless Studio sets it false):
2
+ // the configured policy is the standard lane, sized on the patch the run made.
3
+ // A tiny change (few files and lines, nothing sensitive) ends after its
4
+ // checks, without a review; a risky one (a complex request, a sensitive file,
5
+ // a wide patch) has a suggested check and review required. "off" stays off.
6
+ export const LANE_LIMITS = Object.freeze({ tinyFiles: 2, tinyLines: 40, riskyFiles: 12, riskyLines: 400 });
7
+ const SENSITIVE = /(^|\/)(migrations?|auth|security|secrets?|permissions?|\.github|infra|terraform|helm|k8s)(\/|$)|(^|\/)(Dockerfile|docker-compose[^/]*|\.gitlab-ci\.yml|package\.json|package-lock\.json|pnpm-lock\.yaml|yarn\.lock|go\.mod|go\.sum|Cargo\.toml|Cargo\.lock|pyproject\.toml|uv\.lock|AGENTS\.md|\.env[^/]*)$|\.(sql|tf|pem|key)$/i;
8
+ export const sensitivePath = file => SENSITIVE.test(String(file ?? ''));
9
+ // Files and changed lines of a patch snapshot (readPatchSnapshot); null when
10
+ // it cannot be read. A new text file counts its lines, a binary one as one.
11
+ export function patchStats(snapshot) {
12
+ let value;
13
+ try { value = JSON.parse(snapshot?.patch); } catch { return null; }
14
+ if (!value || typeof value !== 'object') return null;
15
+ const paths = new Set(); let lines = 0;
16
+ for (const line of String(value.patch ?? '').split('\n')) {
17
+ if (line.startsWith('diff --git ')) paths.add(line.match(/^diff --git a\/(.+) b\/\1$/)?.[1] ?? line.slice(11));
18
+ else if (/^[+-]/.test(line) && !/^(\+\+\+|---) /.test(line)) lines += 1;
19
+ }
20
+ for (const file of Array.isArray(value.untracked) ? value.untracked : []) {
21
+ if (typeof file?.path !== 'string') continue;
22
+ paths.add(file.path); lines += file.encoding === 'utf8' ? String(file.contents ?? '').split('\n').filter((l, i, all) => l || i < all.length - 1).length : 1;
23
+ }
24
+ return { files: paths.size, lines, paths: [...paths] };
25
+ }
26
+ // An unreadable patch is standard: the configured policy applies.
27
+ export function changeLane(stats, { complex = false } = {}) {
28
+ if (!stats) return 'standard';
29
+ const L = LANE_LIMITS;
30
+ if (complex || stats.paths.some(sensitivePath) || stats.files > L.riskyFiles || stats.lines > L.riskyLines) return 'risky';
31
+ return stats.files <= L.tinyFiles && stats.lines <= L.tinyLines ? 'tiny' : 'standard';
32
+ }
33
+ export function lanePolicy(policy, lane) {
34
+ if (!policy.lanes || lane === 'standard') return policy;
35
+ if (lane === 'tiny') return { ...policy, review: 'off' };
36
+ const up = mode => mode === 'suggest' ? 'require' : mode;
37
+ return { ...policy, verify: up(policy.verify), review: up(policy.review) };
38
+ }
@@ -25,7 +25,7 @@ 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';
27
27
  import { beforeDelegate } from './objections.mjs';
28
- import { HELPER_CALLS, HELPER_ROLES, helperRoles, helperPrompt, webPrompt, delegateDescription } from './helper-roles.mjs'; import { delegateParameters, delegateArgs, SPEC_PROMPT } from './helper-settlement.mjs';
28
+ import { HELPER_CALLS, HELPER_ROLES, helperRoles, helperPrompt, webPrompt, delegateDescription, mainWebLookups, limitedWebTools } from './helper-roles.mjs'; import { delegateParameters, delegateArgs, SPEC_PROMPT } from './helper-settlement.mjs';
29
29
  import { loadAgentResources } from './agent-skills.mjs';
30
30
  import { loadPiagentThemes } from '../runtime/terminal/terminal-style.mjs';
31
31
  import { restorePermission, recordBypass, permissionSetter } from './permission.mjs';
@@ -114,9 +114,9 @@ export class ManagedSession {
114
114
  self.wrapStreams(self.modelRuntime, 'main');
115
115
  const customTools = processEditTools(self, self.boundary.tools(api));
116
116
  customTools.push(fetchOriginTool(self));
117
- // With a research helper the web is its work: search results and pages
118
- // are read on its cheaper model, not in the main agent's context.
117
+ // With a research helper the web is its work (pages read on its cheaper model), beyond the Harness's few quick lookups.
119
118
  if (!helperRoles(manifest).includes('research')) customTools.push(...self.webTools(self.modelRuntime, 'main'));
119
+ else if (mainWebLookups(manifest)) customTools.push(...limitedWebTools(self.webTools(self.modelRuntime, 'main'), () => self.run, mainWebLookups(manifest)));
120
120
  // Commands with network, and with Docker when this machine has an engine.
121
121
  customTools.push(...commandTools(self));
122
122
  // Every helper role passes the tool's schema: Pi fixes a tool's schema
@@ -135,7 +135,7 @@ export class ManagedSession {
135
135
  const agentsFiles = projectInstructions(self.cwd, self.boundary.repositoryTop);
136
136
  self.checks = repositoryChecks(agentsFiles, self.cwd, self.boundary.repositoryTop);
137
137
  // The Harness workflow (possibly changed on a later enrollment) adds its process to the prompt.
138
- const loader = managedResourceLoader(api, { systemPrompt: () => BASE_PROMPT + referencePrompt(self.boundary.userHome) + TEST_PROMPT + servicesPrompt(self.boundary) + webPrompt(helperRoles(manifest)) + helperPrompt(helperRoles(self.manifest)) + workflowPrompt(workflowPolicy(self.manifest), self.checks), agentsFiles,
138
+ const loader = managedResourceLoader(api, { systemPrompt: () => BASE_PROMPT + referencePrompt(self.boundary.userHome) + TEST_PROMPT + servicesPrompt(self.boundary) + webPrompt(helperRoles(manifest), mainWebLookups(manifest)) + helperPrompt(helperRoles(self.manifest)) + workflowPrompt(workflowPolicy(self.manifest), self.checks), agentsFiles,
139
139
  skills: self.resources.skills, prompts: self.resources.prompts, themes: terminalExtensions.length ? await loadPiagentThemes(sdk) : [], ...await inlineExtensions(api, sdk, self.cwd,
140
140
  [...terminalExtensions, workflowContextExtension(self.cwd, () => self.session.sessionManager)]) });
141
141
  const settings = api.SettingsManager.inMemory({ retry: { enabled: false, provider: { maxRetries: 0 } }, cacheWarming: 'off', enableInstallTelemetry: false, enableAnalytics: false, enableSkillCommands: true });
@@ -5,6 +5,7 @@ import { failureCode } 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';
8
+ import { LANE_LIMITS, patchStats, changeLane, lanePolicy } from './risk-lanes.mjs';
8
9
 
9
10
  // The process layer of the company harness: a plan for multi-step work, the
10
11
  // repository's checks after a code change, an independent review of the patch.
@@ -17,7 +18,7 @@ export function workflowPolicy(manifest) {
17
18
  const mode = value => MODES.includes(value) ? value : 'suggest';
18
19
  const loops = Number.isInteger(w.max_fix_loops) && w.max_fix_loops >= 0 && w.max_fix_loops <= 3 ? w.max_fix_loops : 2;
19
20
  // A review needs a review helper; without one in the Harness it is off.
20
- return { plan: mode(w.plan), verify: mode(w.verify), review: config.review ? mode(w.review) : 'off', maxFixLoops: loops };
21
+ return { plan: mode(w.plan), verify: mode(w.verify), review: config.review ? mode(w.review) : 'off', maxFixLoops: loops, lanes: w.lanes !== false };
21
22
  }
22
23
 
23
24
  // Checks come from the repository: a "## Checks" section in AGENTS.md (code
@@ -150,6 +151,10 @@ export function workflowPrompt(policy, checks) {
150
151
  }
151
152
  if (policy.review !== 'off') lines.push(policy.review === 'require' ? '- Review: the harness reviews the final patch with an independent reviewer; blocking findings come back to you to fix, or to answer when a finding is wrong: the reviewer then judges your answer, and a disagreement that remains goes to the member.'
152
153
  : '- Review: before you finish a code change, ask the review helper (delegate role "review") to review the patch, then fix blocking findings.');
154
+ if (policy.lanes && (policy.verify !== 'off' || policy.review !== 'off')) {
155
+ const L = LANE_LIMITS;
156
+ lines.push(`- Size: the process follows the size of your change. A small change (at most ${L.tinyFiles} files and ${L.tinyLines} changed lines, no migration, auth, security, CI, dependency or AGENTS.md file) ends after its checks, without a review: do not ask for one. A risky change (a complex request, one of those files, more than ${L.riskyFiles} files or ${L.riskyLines} lines) must pass the checks${policy.review !== 'off' ? ' and the harness review' : ''} even where they are only suggested.`);
157
+ }
153
158
  return lines.length ? `\n\nProcess for code changes (company Harness):\n${lines.join('\n')}\n${process.platform === 'darwin' ? 'End-to-end and UI checks run in bash with the project\'s Playwright (@playwright/test; add it with npm when the project lacks it) and the browsers installed on this Mac, against a server the test or start_process starts; while a local proxy listens they need run_with_network. When Playwright\'s browsers are missing, ask with run_on_host to run `npx playwright install chromium`.' : 'End-to-end tests that start a local server and a browser run through run_with_network, which the user approves.'} If a check cannot run because the machine lacks something (a runtime such as a JDK, Docker, a database or another service, the network), do not stop there: request the approval that makes it run (run_with_network, run_with_docker, start_docker, or run_on_host to install what is missing) or ask the member with ask_user; never install or start anything without that approval.\nA command stops after 10 minutes and anything left running in the background stops when the turn ends: run the checks that cover your change first and a long suite in parts, not in the background.${process.platform === 'darwin' ? ' A dev server, watcher or local service you need across turns runs with start_process (read it with process_output, stop it with stop_process).' : ''}` : '';
154
159
  }
155
160
 
@@ -241,7 +246,9 @@ export class RunProcess {
241
246
  // claims verify marked failed, and where the harness last sent the
242
247
  // agent back (to read its answer and see whether the code moved).
243
248
  objections: [], disputes: [], verifyFails: [], claimsAsked: false, claimsAt: null, claimsRechecked: false, claimsDone: false,
244
- sentAt: null, sentDigest: null, rejudged: false, reviewSettled: false, objectionsAsked: false });
249
+ sentAt: null, sentDigest: null, rejudged: false, reviewSettled: false, objectionsAsked: false,
250
+ // The patch's risk lane (workflow.lanes) and the main agent's own web lookups.
251
+ lane: null, laneStats: null, laneDigest: null, webLookups: 0 });
245
252
  }
246
253
  // Plan "require" on a complex task that has no plan yet.
247
254
  planMissing(plan) {
@@ -299,7 +306,18 @@ export class RunProcess {
299
306
  // A finding, a failed claim or an objection to a brief is the agent's to fix
300
307
  // or answer; the same disagreement a second time goes to the member.
301
308
  export async function completionGate(managed, run, signal) {
302
- const session = managed.session, policy = run.policy;
309
+ const session = managed.session, configured = run.policy;
310
+ let policy = configured;
311
+ // The lane of the patch as it is now, read again only when it changed.
312
+ const laneOf = async digest => {
313
+ if (!configured.lanes) return configured;
314
+ if (run.laneDigest === null || run.laneDigest !== digest) {
315
+ let stats = null;
316
+ try { stats = patchStats(await managed.patchSnapshot?.()); } catch { /* unreadable: standard */ }
317
+ run.lane = changeLane(stats, run); run.laneStats = stats ? { files: stats.files, lines: stats.lines } : null; run.laneDigest = digest;
318
+ }
319
+ return lanePolicy(configured, run.lane);
320
+ };
303
321
  const digestOf = async () => { try { return await (managed.changeDigest ?? managed.patchDigest).call(managed); } catch { return null; } };
304
322
  const reviewDigestOf = async () => { try { return await managed.patchDigest(); } catch { return null; } };
305
323
  const stopped = () => signal?.aborted || ['aborted', 'error'].includes(session.messages.findLast(m => m.role === 'assistant')?.stopReason);
@@ -316,6 +334,7 @@ export async function completionGate(managed, run, signal) {
316
334
  // A check that passed still counts when the turn was cut off after it.
317
335
  if (run.changed) run.verified = run.verifiedOn(digest);
318
336
  run.docsOnly = run.changed && onlyNonCode(managed.ownPaths);
337
+ policy = run.changed ? await laneOf(digest) : configured;
319
338
  if (stopped()) { interrupted = run.changed; break; }
320
339
  if (run.changed) {
321
340
  const reviewDigest = managed.review ? await reviewDigestOf() : null;
@@ -424,7 +443,8 @@ export async function completionGate(managed, run, signal) {
424
443
  run.planSkipped ? 'code was changed before the required plan existed' : null, planOpen ? `plan: ${planOpen} step(s) not completed` : null] : []),
425
444
  run.disputes.length ? `${run.disputes.length} disagreement(s) between the main agent and a subagent go to the member` : null].filter(Boolean);
426
445
  await session.sendCustomMessage({ customType: 'agent-watch-process', content: `Process status for this turn: ${run.changed ? 'code changed' : 'no code changed'}; ${words.join('; ') || 'no process step configured'}.`, display: true,
427
- details: { phase: 'final', outcome: run.outcome, ...status, ...(run.reviewUnavailable ? { reviewUnavailable: run.reviewUnavailableReason } : {}), policy: { verify: policy.verify, review: policy.review } } }, { triggerTurn: false });
446
+ details: { phase: 'final', outcome: run.outcome, ...status, ...(run.reviewUnavailable ? { reviewUnavailable: run.reviewUnavailableReason } : {}), policy: { verify: policy.verify, review: policy.review },
447
+ ...(configured.lanes && run.lane ? { lane: run.lane, ...(run.laneStats ? { size: run.laneStats } : {}) } : {}) } }, { triggerTurn: false });
428
448
  }
429
449
 
430
450
  // The run's process sees each edit and command: plan guard before, check
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piagent/core",
3
- "version": "1.44.1",
3
+ "version": "1.44.2",
4
4
  "type": "module",
5
5
  "private": true,
6
6
  "license": "MIT",