@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.
- package/CHANGELOG.md +13 -0
- package/docs/command-reference-vietnamese.md +2 -2
- package/docs/distribution-standard.md +6 -6
- package/docs/harness-conformance.md +14 -0
- package/docs/operator-manual-vietnamese.md +7 -7
- package/docs/publishing-for-teams.md +1 -0
- package/docs/release-install-policy.md +2 -2
- package/docs/team-onboarding.md +2 -2
- package/docs-site/architecture.html +1 -1
- package/docs-site/assets/search-en.json +1 -1
- package/docs-site/assets/search-vi.json +1 -1
- package/docs-site/benchmark.html +1 -1
- package/docs-site/commands.html +1 -1
- package/docs-site/context-engine.html +1 -1
- package/docs-site/ecosystem.html +1 -1
- package/docs-site/en/architecture.html +1 -1
- package/docs-site/en/benchmark.html +1 -1
- package/docs-site/en/commands.html +1 -1
- package/docs-site/en/context-engine.html +1 -1
- package/docs-site/en/ecosystem.html +1 -1
- package/docs-site/en/index.html +9 -9
- package/docs-site/en/mcp-approval.html +1 -1
- package/docs-site/en/mcp-auth.html +1 -1
- package/docs-site/en/mcp-servers.html +1 -1
- package/docs-site/en/mcp.html +1 -1
- package/docs-site/en/permissions.html +1 -1
- package/docs-site/en/profiles.html +1 -1
- package/docs-site/en/quickstart.html +1 -1
- package/docs-site/en/runtime.html +1 -1
- package/docs-site/en/scope.html +1 -1
- package/docs-site/en/security.html +1 -1
- package/docs-site/en/team.html +1 -1
- package/docs-site/en/update.html +3 -3
- package/docs-site/en/whats-new.html +27 -17
- package/docs-site/en/windows.html +1 -1
- package/docs-site/en/workflows.html +1 -1
- package/docs-site/index.html +9 -9
- package/docs-site/mcp-approval.html +1 -1
- package/docs-site/mcp-auth.html +1 -1
- package/docs-site/mcp-servers.html +1 -1
- package/docs-site/mcp.html +1 -1
- package/docs-site/permissions.html +1 -1
- package/docs-site/profiles.html +1 -1
- package/docs-site/quickstart.html +1 -1
- package/docs-site/releases.json +31 -0
- package/docs-site/runtime.html +1 -1
- package/docs-site/scope.html +1 -1
- package/docs-site/security.html +1 -1
- package/docs-site/team.html +1 -1
- package/docs-site/update.html +3 -3
- package/docs-site/whats-new.html +27 -17
- package/docs-site/windows.html +1 -1
- package/docs-site/workflows.html +1 -1
- package/evals/private-holdout-v1/public-exposure.v2.json +1 -1
- package/package.json +1 -1
- package/packages/piagent-core/managed/helper-roles.mjs +24 -6
- package/packages/piagent-core/managed/risk-lanes.mjs +38 -0
- package/packages/piagent-core/managed/session.mjs +4 -4
- package/packages/piagent-core/managed/workflow.mjs +24 -4
- 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
|
-
//
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
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,
|
|
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 }
|
|
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
|