@piagent/platform 1.36.0 → 1.37.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.
- package/CHANGELOG.md +35 -0
- package/README.md +1 -1
- package/README.vi.md +1 -1
- package/docs/command-reference-vietnamese.md +2 -2
- package/docs/distribution-standard.md +6 -6
- package/docs/languages.json +2 -2
- package/docs/operator-manual-vietnamese.md +7 -7
- package/docs/publishing-for-teams.md +2 -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/benchmark.html +1 -1
- package/docs-site/commands.html +1 -1
- package/docs-site/content/en/index.html +4 -4
- package/docs-site/content/en/whats-new.html +12 -0
- package/docs-site/content/index.html +4 -4
- package/docs-site/content/whats-new.html +12 -0
- 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 +5 -5
- 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/whats-new.html +13 -1
- package/docs-site/en/windows.html +1 -1
- package/docs-site/en/workflows.html +1 -1
- package/docs-site/index.html +5 -5
- 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/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/whats-new.html +13 -1
- package/docs-site/windows.html +1 -1
- package/docs-site/workflows.html +1 -1
- package/evals/long-task-soak-v1/README.md +96 -0
- package/evals/long-task-soak-v1/lib/judge.mjs +67 -0
- package/evals/long-task-soak-v1/lib/persona.mjs +63 -0
- package/evals/long-task-soak-v1/lib/report.mjs +35 -0
- package/evals/long-task-soak-v1/lib/session-loop.mjs +165 -0
- package/evals/long-task-soak-v1/lib/workspace.mjs +115 -0
- package/evals/long-task-soak-v1/runner.mjs +89 -0
- package/evals/long-task-soak-v1/scenarios/autonomous-node-inventory.json +15 -0
- package/evals/long-task-soak-v1/scenarios/business-python-billing.json +34 -0
- package/evals/long-task-soak-v1/scenarios/framework-go-docker.json +40 -0
- package/evals/long-task-soak-v1/scenarios/framework-playwright-e2e.json +15 -0
- package/evals/long-task-soak-v1/scenarios/harness-guardrails-secrets.json +23 -0
- package/evals/long-task-soak-v1/scenarios/long-horizon-polyglot-rename.json +30 -0
- package/evals/long-task-soak-v1/scenarios/long-node-inventory-platform.json +64 -0
- package/evals/long-task-soak-v1/scenarios/long-polyglot-platform.json +288 -0
- package/evals/long-task-soak-v1/scenarios/long-postgres-booking-platform.json +210 -0
- package/evals/long-task-soak-v1/scenarios/long-python-billing-platform.json +84 -0
- package/evals/long-task-soak-v1/scenarios/mixed-requirement-change.json +20 -0
- package/evals/long-task-soak-v1/scenarios/stability-flaky-slow-suite.json +22 -0
- package/evals/long-task-soak-v1/scenarios/thirdparty-postgres-integration.json +30 -0
- package/evals/long-task-soak-v1/surfaces/terminal-rpc.mjs +150 -0
- package/evals/long-task-soak-v1/surfaces/webui-gateway.mjs +214 -0
- package/evals/private-holdout-v1/public-exposure.v2.json +2 -2
- package/package.json +2 -1
- package/packages/piagent-core/managed/session.mjs +3 -3
- package/packages/piagent-core/managed/studio-link.mjs +34 -4
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/runtime/managed-failure.mjs +5 -0
- package/scripts/company-terminal.mjs +2 -1
- package/scripts/piagent-studio.mjs +13 -3
|
@@ -15,6 +15,7 @@ import path from 'node:path';
|
|
|
15
15
|
import { randomUUID } from 'node:crypto';
|
|
16
16
|
import { setTimeout as sleep } from 'node:timers/promises';
|
|
17
17
|
import { CLIENT_AGENT } from './client-agent.mjs';
|
|
18
|
+
import { failureCode, failureText } from '../runtime/managed-failure.mjs';
|
|
18
19
|
|
|
19
20
|
// Codes whose cause is Studio or the way to it: Studio, its gateway or its
|
|
20
21
|
// connector did not answer (stopped, restarting, an update), or Agent Watch
|
|
@@ -34,6 +35,13 @@ const LOG_BYTES = 2 * 1024 * 1024, QUEUE = 50, BATCH = 20, SEND_TRIES = 5;
|
|
|
34
35
|
const MIN_PAUSE_MS = 2_000, MAX_PAUSE_MS = 30_000, MAX_STREAK_PAUSE_MS = 5 * 60_000, BEAT_TIMEOUT_MS = 5_000;
|
|
35
36
|
// After Studio was away, Agent Watch gets this long to reach it again.
|
|
36
37
|
export const BROKER_GRACE_MS = 10 * 60_000;
|
|
38
|
+
// Agent Watch could not reach Studio while Studio answers here: its way to
|
|
39
|
+
// Studio failed for a moment (a gateway's 5xx, a dropped connection, a slow
|
|
40
|
+
// answer). Found on 2026-10-22: one such failure renewing the grant stopped a
|
|
41
|
+
// 25-minute task at once. It is asked again, pausing longer each time, until
|
|
42
|
+
// it has failed for this long without a request going through; then it fails
|
|
43
|
+
// as before (Agent Watch itself is broken, not the way to Studio).
|
|
44
|
+
export const BROKER_RETRY_MS = 3 * 60_000;
|
|
37
45
|
|
|
38
46
|
const header = (headers, name) => {
|
|
39
47
|
if (!headers) return null;
|
|
@@ -74,7 +82,7 @@ export class StudioLink {
|
|
|
74
82
|
if (seen.boot) this.last = { boot: seen.boot, version: seen.version ?? this.last.version };
|
|
75
83
|
}
|
|
76
84
|
// A request went through: the next wait starts with a short pause again.
|
|
77
|
-
ok() { this.streak = 0; }
|
|
85
|
+
ok() { this.streak = 0; this.brokerSince = null; }
|
|
78
86
|
waits(code) { return STUDIO_WAIT_CODES.has(code); }
|
|
79
87
|
// Studio or the way to it, whatever Studio's heartbeat says now.
|
|
80
88
|
always(code) { return this.waits(code) && !conditional(code); }
|
|
@@ -107,7 +115,7 @@ export class StudioLink {
|
|
|
107
115
|
if (signal?.aborted) throw signal.reason ?? Error('aborted');
|
|
108
116
|
if (!this.waits(code)) return null;
|
|
109
117
|
const first = conditional(code) ? await this.beat(signal) : null;
|
|
110
|
-
if (first?.state === 'ready' && !this.justBack()) return null;
|
|
118
|
+
if (first?.state === 'ready' && !this.justBack() && !this.brokerRetry(code)) return null;
|
|
111
119
|
const controller = new AbortController(), stop = () => controller.abort(signal?.reason);
|
|
112
120
|
signal?.addEventListener('abort', stop, { once: true });
|
|
113
121
|
this.waiting.add(controller);
|
|
@@ -115,7 +123,8 @@ export class StudioLink {
|
|
|
115
123
|
const seen = new Set(first ? [first.state] : []);
|
|
116
124
|
this.log({ kind: 'studio_wait', state: 'start', id: event.id, phase, role, code, version: event.before.version, conversation });
|
|
117
125
|
this.notify({ type: 'managed_studio_wait', id: event.id, role, phase, code, state: 'start', waitedMs: 0 });
|
|
118
|
-
this.status(`
|
|
126
|
+
this.status(first?.state === 'ready' ? `Agent Watch could not reach Studio (${code}). Asking again; the work goes on by itself. Esc stops.`
|
|
127
|
+
: `Studio is not answering (${code}). Waiting; the work goes on by itself when Studio is back. Esc stops.`);
|
|
119
128
|
// Failing again right after a wait (Studio answers, the request does
|
|
120
129
|
// not): each such wait starts with a longer pause, up to five minutes.
|
|
121
130
|
let pause = Math.min(MAX_STREAK_PAUSE_MS, this.minPauseMs * 2 ** Math.min(this.streak, 8));
|
|
@@ -147,7 +156,15 @@ export class StudioLink {
|
|
|
147
156
|
async needed(code, signal) {
|
|
148
157
|
if (signal?.aborted) throw signal.reason ?? Error('aborted');
|
|
149
158
|
if (!this.waits(code)) return false;
|
|
150
|
-
return !(conditional(code) && (await this.beat(signal)).state === 'ready' && !this.justBack());
|
|
159
|
+
return !(conditional(code) && (await this.beat(signal)).state === 'ready' && !this.justBack() && !this.brokerRetry(code, false));
|
|
160
|
+
}
|
|
161
|
+
// An Agent Watch failure on a Studio that answers: asked again within
|
|
162
|
+
// BROKER_RETRY_MS of the first one since a request last went through.
|
|
163
|
+
brokerRetry(code, start = true) {
|
|
164
|
+
if (!BROKER.test(code)) return false;
|
|
165
|
+
const since = this.brokerSince ?? this.now();
|
|
166
|
+
if (start) this.brokerSince = since;
|
|
167
|
+
return this.now() - since < (this.brokerRetryMs ?? BROKER_RETRY_MS);
|
|
151
168
|
}
|
|
152
169
|
justBack() { return !!this.recoveredAt && this.now() - this.recoveredAt < (this.brokerGraceMs ?? BROKER_GRACE_MS); }
|
|
153
170
|
|
|
@@ -239,3 +256,16 @@ export function resumeMessage(wait, code) {
|
|
|
239
256
|
content: `Harness: your last step was cut off because ${why} [${code}]; Studio answers again after about ${minutes} min. Nothing you asked for is lost: the files and the checklist are as you left them. Check where you were (the checklist, git status, the last command you ran) and continue the member's task from there; do not redo work that is already done.`,
|
|
240
257
|
details: { phase: 'resume', cause: wait.cause, waitedMs: wait.waited_ms, code } };
|
|
241
258
|
}
|
|
259
|
+
|
|
260
|
+
// A broker call (closing the round's run) when Agent Watch's way to Studio
|
|
261
|
+
// fails for a moment: asked again while the link waits, so a passing gateway
|
|
262
|
+
// error does not fail a turn whose work is done.
|
|
263
|
+
export async function viaLink(link, ask, { conversation = null, runId = null } = {}) {
|
|
264
|
+
for (;;) {
|
|
265
|
+
try { return await ask(); }
|
|
266
|
+
catch (error) {
|
|
267
|
+
const code = failureCode(failureText(error));
|
|
268
|
+
if (!(await link?.waitUntilBack({ phase: 'broker', code, conversation, runId }).catch(() => null))) throw error;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
@@ -166,6 +166,11 @@ export function failureCode(raw) {
|
|
|
166
166
|
// The provider library's words for an answer stream that ended empty: Studio
|
|
167
167
|
// admitted the request and nothing came back. It is not asked again.
|
|
168
168
|
if (/stream ended without a (terminal event|stop reason)|response has no body/i.test(text)) return 'upstream_incomplete';
|
|
169
|
+
// Studio's own words when its upstream cut an answer already streaming: its
|
|
170
|
+
// error event carries the code, which the provider libraries drop (seen
|
|
171
|
+
// 2026-10-21: "Request interrupted" stopped a long task with no ask again).
|
|
172
|
+
if (/^Request interrupted\b/i.test(text)) return 'upstream_interrupted';
|
|
173
|
+
if (/^The answer ran past its output limit\b/i.test(text)) return 'output_limit_exceeded';
|
|
169
174
|
if (NETWORK.test(text)) return 'studio-unreachable';
|
|
170
175
|
return 'managed-request-failed';
|
|
171
176
|
}
|
|
@@ -388,7 +388,8 @@ export function companyTerminalExtension({ managed, packageRoot, sdkRoot, bindin
|
|
|
388
388
|
const session = managed()?.session;
|
|
389
389
|
if (!session?.subscribe || heard === session) return;
|
|
390
390
|
heard = session;
|
|
391
|
-
|
|
391
|
+
// The conversation can close while git answers (a client ends right after a turn): its ctx is then stale.
|
|
392
|
+
const showBranch = async () => { try { const branch = await branchOf(managed()?.cwd ?? ctx.cwd); ctx.ui?.setStatus?.('agent-watch-branch', branch ? `⎇ ${branch}` : undefined); } catch { /* closed meanwhile */ } };
|
|
392
393
|
ctx.ui?.setWorkingMessage?.(WORKING);
|
|
393
394
|
void showBranch();
|
|
394
395
|
session.subscribe((event) => {
|
|
@@ -15,16 +15,17 @@ const digest = file => createHash('sha256').update(fs.readFileSync(file)).digest
|
|
|
15
15
|
process.removeAllListeners('warning');
|
|
16
16
|
process.on('warning', warning => { if (warning.name !== 'ExperimentalWarning') process.stderr.write(`${warning.name}: ${warning.message}\n`); });
|
|
17
17
|
const args = process.argv.slice(2), flags = new Map();
|
|
18
|
-
if (args.includes('--help')) { console.log('piagent studio [--web [--no-open] | --serve | --check | --doctor | --prompt <text> [--json]] [--config <Agent Watch configuration>] [--project <folder>]\nDefaults: the configuration imported into ~/.pi/agent and the current project. Personal sessions remain under pi / piagent dashboard.\n--json with --prompt writes the event stream of `pi --mode json` (JSONL on stdout; failures on stderr, exit 1).'); process.exit(0); }
|
|
18
|
+
if (args.includes('--help')) { console.log('piagent studio [--web [--no-open] | --serve | --check | --doctor | --prompt <text> [--json] | --rpc] [--config <Agent Watch configuration>] [--project <folder>]\nDefaults: the configuration imported into ~/.pi/agent and the current project. Personal sessions remain under pi / piagent dashboard.\n--json with --prompt writes the event stream of `pi --mode json` (JSONL on stdout; failures on stderr, exit 1).\n--rpc speaks the protocol of `pi --mode rpc` on stdin/stdout for one company conversation (several messages, questions as extension UI requests).'); process.exit(0); }
|
|
19
19
|
for (let i = 0; i < args.length; i++) {
|
|
20
|
-
if (['--check', '--doctor', '--web', '--serve', '--no-open', '--json'].includes(args[i])) { flags.set(args[i], true); continue; }
|
|
20
|
+
if (['--check', '--doctor', '--web', '--serve', '--no-open', '--json', '--rpc'].includes(args[i])) { flags.set(args[i], true); continue; }
|
|
21
21
|
if (!['--config', '--project', '--prompt'].includes(args[i]) || flags.has(args[i]) || !args[i + 1]) throw Error('managed-launch-arguments');
|
|
22
22
|
flags.set(args[i], args[++i]);
|
|
23
23
|
}
|
|
24
24
|
if (flags.has('--json') && !flags.has('--prompt')) throw Error('managed-launch-arguments');
|
|
25
|
+
if (flags.has('--rpc') && ['--check', '--doctor', '--web', '--serve', '--prompt', '--json'].some((f) => flags.has(f))) throw Error('managed-launch-arguments');
|
|
25
26
|
const home = os.homedir();
|
|
26
27
|
// Joining Agent Watch takes a few seconds; keys typed before the Terminal draws are lost, so say so.
|
|
27
|
-
if (!['--check', '--prompt', '--serve', '--doctor', '--web'].some((f) => flags.has(f)) && process.stderr.isTTY) process.stderr.write('Piagent công ty: đang kết nối Agent Watch, chờ ô nhập hiện ra rồi gõ…\n');
|
|
28
|
+
if (!['--check', '--prompt', '--serve', '--doctor', '--web', '--rpc'].some((f) => flags.has(f)) && process.stderr.isTTY) process.stderr.write('Piagent công ty: đang kết nối Agent Watch, chờ ô nhập hiện ra rồi gõ…\n');
|
|
28
29
|
// Do this before importing the SDK: it must not discover personal keys, Node
|
|
29
30
|
// preload hooks, proxy overrides or provider endpoints from an ambient shell.
|
|
30
31
|
const clean = { PATH: '/usr/bin:/bin', HOME: home, LANG: 'en_US.UTF-8', TERM: process.env.TERM || 'xterm-256color', ...interopEnvironment() };
|
|
@@ -182,6 +183,15 @@ try {
|
|
|
182
183
|
const { failureLines } = await import('./company-terminal.mjs');
|
|
183
184
|
process.stderr.write(`${failureLines(last.errorMessage) ?? String(last.errorMessage??'Agent Watch: the request failed [managed-request-failed]')}\n`);process.exitCode = 1;
|
|
184
185
|
}
|
|
186
|
+
} else if (flags.has('--rpc')) {
|
|
187
|
+
// One company conversation driven by a client (a script, a test that
|
|
188
|
+
// plays the member) over Pi's RPC protocol. Pi exits the process when the
|
|
189
|
+
// client closes stdin, so the conversation, its broker and its lease are
|
|
190
|
+
// released inside Pi's own dispose instead of the finally below.
|
|
191
|
+
process.env.PI_OFFLINE = '1'; process.env.PI_SKIP_VERSION_CHECK = '1';
|
|
192
|
+
const dispose = host.dispose.bind(host);
|
|
193
|
+
host.dispose = async () => { try { await dispose(); } finally { await current?.dispose(); current = null; hold?.release(); } };
|
|
194
|
+
await api.runRpcMode(host);
|
|
185
195
|
} else {
|
|
186
196
|
// The company terminal runs the pinned Pi release: no "Run pi update" notice
|
|
187
197
|
// (updating Pi breaks the Agent Watch import), no package-update, catalog or
|