@piagent/platform 1.22.0 → 1.22.1
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 +11 -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 +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/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 +7 -0
- package/docs-site/content/index.html +4 -4
- package/docs-site/content/whats-new.html +7 -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 +8 -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 +8 -1
- package/docs-site/windows.html +1 -1
- package/docs-site/workflows.html +1 -1
- package/evals/private-holdout-v1/public-exposure.v2.json +2 -2
- package/package.json +1 -1
- package/packages/piagent-core/extensions/task-state.js +10 -10
- package/packages/piagent-core/extensions/workspace-evidence-roots.js +21 -1
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/runtime/inspection/webui-snapshot.ts +2 -0
- package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-BYa-Jveq.js → MarkdownMessage-EmQ_ohQe.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/{index-coozlgfe.js → index-Posch2XH.js} +3 -3
- package/packages/piagent-webui/dist/client/index.html +1 -1
- package/packages/piagent-webui/gateway/company-relay.ts +56 -7
- package/packages/piagent-webui/shared/company-copy.ts +2 -1
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<meta name="csp-nonce" content="__PIAGENT_CSP_NONCE__" />
|
|
8
8
|
<meta name="piagent-webui-mode" content="__PIAGENT_WEBUI_MODE__" />
|
|
9
9
|
<title>Piagent WebUI</title>
|
|
10
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
+
<script type="module" crossorigin src="/assets/index-Posch2XH.js"></script>
|
|
11
11
|
<link rel="stylesheet" crossorigin href="/assets/index-ZLemRWyJ.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
|
@@ -31,6 +31,13 @@ const KINDS = new Set<GatewayEventKind>(["catalog.changed", "session.changed", "
|
|
|
31
31
|
"message.completed", "operation.settled", "tool.started", "tool.completed"]);
|
|
32
32
|
const LOOPBACK = /^http:\/\/127\.0\.0\.1:\d{1,5}$/;
|
|
33
33
|
const CATALOG_REUSE_MS = 5_000;
|
|
34
|
+
// A company Gateway that takes the connection and never answers (found on
|
|
35
|
+
// Windows/WSL) kept every list of the dashboard loading: the browser waits for
|
|
36
|
+
// the catalog without a limit, and the catalog waited for the company rows.
|
|
37
|
+
// A read is given this long, a change as long as a command; a Gateway that
|
|
38
|
+
// missed one is left alone for a while, not asked again by every read.
|
|
39
|
+
const READ_TIMEOUT_MS = 15_000, ANSWER_TIMEOUT_MS = 5_000, CHANGE_TIMEOUT_MS = 120_000, STALL_RETRY_MS = 60_000;
|
|
40
|
+
const timedOut = (error: unknown) => error instanceof Error && (error.name === "TimeoutError" || error.name === "AbortError");
|
|
34
41
|
const failure = (error: unknown, fallback: string) => error instanceof Error && /^[a-z][a-z0-9:._-]{2,95}$/.test(error.message) ? error.message : fallback;
|
|
35
42
|
|
|
36
43
|
export class CompanyRelay {
|
|
@@ -54,11 +61,15 @@ export class CompanyRelay {
|
|
|
54
61
|
#retry: NodeJS.Timeout | null = null;
|
|
55
62
|
#closed = false;
|
|
56
63
|
#lastAttach = 0;
|
|
64
|
+
#stalledAt = 0;
|
|
65
|
+
readonly #readTimeoutMs: number;
|
|
66
|
+
readonly #answerTimeoutMs: number;
|
|
57
67
|
|
|
58
68
|
constructor(options: { connector: CompanyConnector; events: GatewayEventStore; hubProject(cwd: string): string;
|
|
59
|
-
folder(projectRef: string): Promise<string | null
|
|
69
|
+
folder(projectRef: string): Promise<string | null>; readTimeoutMs?: number; answerTimeoutMs?: number }) {
|
|
60
70
|
this.#connector = options.connector; this.#events = options.events;
|
|
61
71
|
this.#hubProject = options.hubProject; this.#folder = options.folder;
|
|
72
|
+
this.#readTimeoutMs = options.readTimeoutMs ?? READ_TIMEOUT_MS; this.#answerTimeoutMs = options.answerTimeoutMs ?? ANSWER_TIMEOUT_MS;
|
|
62
73
|
}
|
|
63
74
|
|
|
64
75
|
status(): CompanyStatus {
|
|
@@ -91,9 +102,12 @@ export class CompanyRelay {
|
|
|
91
102
|
if (!socket) throw new Error("company-gateway-stopped");
|
|
92
103
|
this.#link = await this.#bootstrap(socket);
|
|
93
104
|
await this.#open(this.#link);
|
|
105
|
+
// Ready only once a read is answered: the reads of the dashboard wait on it.
|
|
106
|
+
await this.#answers(this.#link);
|
|
94
107
|
this.#state = "ready"; this.#fresh = false;
|
|
95
108
|
} catch (error) {
|
|
96
109
|
const reason = failure(error, "company-connect-failed");
|
|
110
|
+
if (reason === "company-gateway-timeout") this.#drop();
|
|
97
111
|
// Agent Watch re-imports after an update on its own, so a changed
|
|
98
112
|
// binding is not kept once a later attach finds nothing running.
|
|
99
113
|
this.#state = "unavailable";
|
|
@@ -128,7 +142,7 @@ export class CompanyRelay {
|
|
|
128
142
|
if (typeof launch !== "string") throw new Error("company-gateway-starting");
|
|
129
143
|
const url = new URL(launch), capability = new URLSearchParams(url.hash.slice(1)).get("bootstrap");
|
|
130
144
|
if (!LOOPBACK.test(url.origin) || !capability) throw new Error("company-gateway-invalid");
|
|
131
|
-
const response = await fetch(`${url.origin}/api/v1/bootstrap`, { method: "POST", redirect: "error",
|
|
145
|
+
const response = await fetch(`${url.origin}/api/v1/bootstrap`, { method: "POST", redirect: "error", signal: AbortSignal.timeout(this.#readTimeoutMs),
|
|
132
146
|
headers: { Origin: url.origin, "Content-Type": "application/json", Accept: "application/json" }, body: JSON.stringify({ capability }) });
|
|
133
147
|
const cookie = response.headers.get("set-cookie")?.split(";", 1)[0], body = await response.json().catch(() => null) as { csrfToken?: unknown } | null;
|
|
134
148
|
if (!response.ok || !cookie || typeof body?.csrfToken !== "string") throw new Error("company-gateway-auth-failed");
|
|
@@ -177,6 +191,33 @@ export class CompanyRelay {
|
|
|
177
191
|
});
|
|
178
192
|
}
|
|
179
193
|
|
|
194
|
+
// Lets go of a company Gateway that does not answer, without waiting for its socket to close.
|
|
195
|
+
#drop(): void {
|
|
196
|
+
this.#stalledAt = Date.now();
|
|
197
|
+
const ws = this.#ws; this.#ws = null; this.#link = null; ws?.terminate();
|
|
198
|
+
for (const [, pending] of this.#pending) { clearTimeout(pending.timer); pending.reject(new Error("company-gateway-timeout")); }
|
|
199
|
+
this.#pending.clear();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async #answers(link: Link): Promise<void> {
|
|
203
|
+
try {
|
|
204
|
+
const response = await fetch(`${link.origin}/api/v1/session-live-state`, { redirect: "error", signal: AbortSignal.timeout(this.#answerTimeoutMs),
|
|
205
|
+
headers: { Accept: "application/json", Origin: link.origin, Cookie: link.cookie } });
|
|
206
|
+
await response.arrayBuffer();
|
|
207
|
+
} catch (error) { throw timedOut(error) ? new Error("company-gateway-timeout") : error; }
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// A read that was not answered in time: the company rows are left out and
|
|
211
|
+
// the Gateway is asked again later, so the dashboard's own lists keep loading.
|
|
212
|
+
#stalled(): void {
|
|
213
|
+
this.#drop();
|
|
214
|
+
this.#state = "unavailable"; this.#reason = "company-gateway-timeout";
|
|
215
|
+
this.#events.publish("catalog.changed", { reasonCode: "company-unavailable" });
|
|
216
|
+
if (this.#closed) return;
|
|
217
|
+
if (this.#retry) clearTimeout(this.#retry);
|
|
218
|
+
this.#retry = setTimeout(() => void this.connect(false), STALL_RETRY_MS); this.#retry.unref();
|
|
219
|
+
}
|
|
220
|
+
|
|
180
221
|
// Reads only attach; starting the company runtime (and possibly asking for
|
|
181
222
|
// Keychain access) is left to commands and the explicit connect route.
|
|
182
223
|
async #ready(launch = false): Promise<Link> {
|
|
@@ -190,10 +231,18 @@ export class CompanyRelay {
|
|
|
190
231
|
async http(method: "GET" | "POST", path: string, body?: unknown): Promise<{ status: number; value: unknown }> {
|
|
191
232
|
for (let attempt = 0; ; attempt += 1) {
|
|
192
233
|
const link = await this.#ready();
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
234
|
+
let response: Response;
|
|
235
|
+
try {
|
|
236
|
+
response = await fetch(`${link.origin}${path}`, { method, redirect: "error",
|
|
237
|
+
signal: AbortSignal.timeout(method === "GET" ? this.#readTimeoutMs : CHANGE_TIMEOUT_MS),
|
|
238
|
+
headers: { Accept: "application/json", Origin: link.origin, Cookie: link.cookie,
|
|
239
|
+
...(method === "POST" ? { "Content-Type": "application/json", "X-Piagent-CSRF": link.csrf } : {}) },
|
|
240
|
+
body: method === "POST" ? JSON.stringify(body) : undefined });
|
|
241
|
+
} catch (error) {
|
|
242
|
+
if (!timedOut(error)) throw error;
|
|
243
|
+
if (method === "GET" && this.#link === link) this.#stalled();
|
|
244
|
+
throw new Error("company-gateway-timeout");
|
|
245
|
+
}
|
|
197
246
|
if (response.status === 401 && attempt === 0) { this.#link = await this.#bootstrap(link.socket); continue; }
|
|
198
247
|
// The browser's own session is not the one refused here.
|
|
199
248
|
if (response.status === 401 || response.status === 403) return { status: 503, value: { error: { code: "company-auth-failed" } } };
|
|
@@ -232,7 +281,7 @@ export class CompanyRelay {
|
|
|
232
281
|
#attachSoon(): void {
|
|
233
282
|
if (this.#closed || this.#connecting || this.#state === "ready" || !this.#connector.configured()) return;
|
|
234
283
|
const now = Date.now();
|
|
235
|
-
if (now - this.#lastAttach < 10_000) return;
|
|
284
|
+
if (now - this.#lastAttach < 10_000 || now - this.#stalledAt < STALL_RETRY_MS) return;
|
|
236
285
|
this.#lastAttach = now; void this.connect(false);
|
|
237
286
|
}
|
|
238
287
|
|
|
@@ -14,7 +14,8 @@ export const LAUNCH_REASONS: Record<string, [string, string]> = {
|
|
|
14
14
|
"managed-launch-binding-changed": ["Agent Watch hoặc Piagent vừa cập nhật. Nhập lại cấu hình cho Piagent trong Agent Watch (trên Windows: Kết nối Piagent trong WSL).", "Agent Watch or Piagent changed. Import the Piagent configuration again in Agent Watch (on Windows: Connect Piagent in WSL)."],
|
|
15
15
|
"managed-launch-version-mismatch": ["Agent Watch đang dùng một bản Piagent khác với dashboard này. Mở Agent Watch → Studio, bấm Áp dụng cho Piagent rồi kết nối lại.", "Agent Watch points at another Piagent install than this dashboard. Open Agent Watch → Studio, apply for Piagent, then connect again."],
|
|
16
16
|
"managed-launch-stale-runtime": ["Phiên công ty của bản Piagent cũ chưa dừng được. Thử kết nối lại sau ít giây.", "The company runtime from the previous Piagent did not stop yet. Connect again in a few seconds."],
|
|
17
|
-
"managed-launch-timeout": ["Phiên công ty khởi động quá lâu. Kết nối lại.", "The company runtime took too long to start. Connect again."]
|
|
17
|
+
"managed-launch-timeout": ["Phiên công ty khởi động quá lâu. Kết nối lại.", "The company runtime took too long to start. Connect again."],
|
|
18
|
+
"company-gateway-timeout": ["Phiên công ty đang chạy nhưng không trả lời. Đóng Piagent, chạy `pkill -f piagent-studio.mjs` trong Terminal (Windows: trong Ubuntu), rồi mở lại.", "The company runtime is running but does not answer. Close Piagent, run `pkill -f piagent-studio.mjs` in a terminal (Windows: in Ubuntu), then open it again."]
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
export type Copy = { title: [string, string]; text: [string, string] };
|