@piagent/platform 1.38.2 → 1.40.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 +29 -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/private-holdout-v1/public-exposure.v2.json +2 -2
- package/package.json +1 -1
- package/packages/piagent-core/managed/request-stream.mjs +3 -0
- package/packages/piagent-core/managed/workspace-projects.mjs +61 -0
- package/packages/piagent-core/managed/workspace-report.mjs +72 -0
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-DtEwqme0.js → MarkdownMessage-BpoF2k2-.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/{TerminalPanel-C3vkzjQ6.js → TerminalPanel-Dg3eghy4.js} +2 -2
- package/packages/piagent-webui/dist/client/assets/{TerminalView-TNjhBGfa.js → TerminalView-Dw9xqakv.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/{WorkspaceCodeEditor-BAhYwQNM.js → WorkspaceCodeEditor-DtwXZAgK.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/{WorkspaceExplorer-Coh_GWCO.js → WorkspaceExplorer-WS-yLlnb.js} +3 -3
- package/packages/piagent-webui/dist/client/assets/{WorkspaceQuickOpen-Bn4miZq4.js → WorkspaceQuickOpen-CYCY-s44.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/{index-a-perpOX.js → index-BgJgso6j.js} +18 -18
- package/packages/piagent-webui/dist/client/assets/{tab-close-nAxrK7Lq.js → tab-close-CqPDs-w5.js} +1 -1
- package/packages/piagent-webui/dist/client/index.html +1 -1
- package/packages/piagent-webui/gateway/gateway-service.ts +3 -0
- package/packages/piagent-webui/gateway/managed-projection.ts +7 -1
- package/packages/piagent-webui/gateway/session-catalog.ts +3 -2
- package/packages/piagent-webui/gateway/ui-preferences-store.ts +45 -0
- package/packages/piagent-webui/server/loopback-server.ts +2 -1
- package/packages/piagent-webui/server/member-routes.ts +20 -0
- package/packages/piagent-webui/shared/project-warning-copy.ts +21 -0
- package/schemas/piagent-webui/session-catalog-v1.schema.json +1 -1
- package/scripts/check-runtime-advisories.mjs +28 -3
- package/scripts/company-terminal.mjs +2 -0
- package/scripts/runtime-advisory-exceptions.json +26 -1
package/packages/piagent-webui/dist/client/assets/{tab-close-nAxrK7Lq.js → tab-close-CqPDs-w5.js}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{D as e}from"./ui-preferences-BO3AV7QE.js";import{m as t}from"./Alert-DGWhP2MM.js";import{E as n}from"./index-
|
|
1
|
+
import{D as e}from"./ui-preferences-BO3AV7QE.js";import{m as t}from"./Alert-DGWhP2MM.js";import{E as n}from"./index-BgJgso6j.js";var r=e(),i=t((0,r.jsx)(`path`,{d:`M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z`}),`Close`);function a(e,t,a){let o=e=>{e.stopPropagation(),e.preventDefault(),a()};return(0,r.jsxs)(n,{component:`span`,sx:{display:`inline-flex`,alignItems:`center`,gap:.75},children:[(0,r.jsx)(`span`,{children:e}),(0,r.jsx)(n,{component:`span`,role:`button`,tabIndex:0,"aria-label":t,title:t,onClick:o,onMouseDown:e=>e.stopPropagation(),onKeyDown:e=>{(e.key===`Enter`||e.key===` `)&&o(e)},sx:{display:`inline-grid`,placeItems:`center`,width:18,height:18,borderRadius:`2px`,opacity:.7,"&:hover":{opacity:1,bgcolor:`action.hover`}},children:(0,r.jsx)(i,{sx:{fontSize:14}})})]})}export{i as n,a as t};
|
|
@@ -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-BgJgso6j.js"></script>
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-hePW80VL.js">
|
|
12
12
|
<link rel="modulepreload" crossorigin href="/assets/ui-preferences-BO3AV7QE.js">
|
|
13
13
|
<link rel="modulepreload" crossorigin href="/assets/useId-DEefKUAT.js">
|
|
@@ -54,6 +54,7 @@ import { ProviderAuthBroker } from "./provider-auth-broker.ts";
|
|
|
54
54
|
import { McpAuthBroker } from "./mcp-auth-broker.ts";
|
|
55
55
|
import { AgentStatusFiles } from "./agent-status-files.ts";
|
|
56
56
|
import type { ScopedBrokerRouter, RuntimeFactory } from "./session-runtime-factory.ts";
|
|
57
|
+
import { UiPreferencesStore } from "./ui-preferences-store.ts";
|
|
57
58
|
|
|
58
59
|
function unavailable(reasonCode: string) {
|
|
59
60
|
return { status: "unavailable" as const, version: null, reasonCode };
|
|
@@ -292,7 +293,9 @@ export async function startPiagentGateway(options: {
|
|
|
292
293
|
const sourceReverts = options.managed ? new ManagedSourceRevertSessions({ root: state.root, key, gatewayInstanceRef, runtimes,
|
|
293
294
|
managedSource: options.managed.managedSource, changed: sessionRef => events.publish("session.changed", { sessionRef }) }) : new SourceRevertSessions({ root: state.root, key, runtimes, managed: !!options.managed,
|
|
294
295
|
provider: sessionRef => inspections.provider(sessionRef), changed: sessionRef => events.publish("session.changed", { sessionRef }) });
|
|
296
|
+
const uiPreferences = new UiPreferencesStore(state.root);
|
|
295
297
|
loopback = await startLoopbackServer({
|
|
298
|
+
uiPreferences: { read: () => uiPreferences.read(), write: value => uiPreferences.write(value) },
|
|
296
299
|
checkpoints: {
|
|
297
300
|
state: sessionRef => company(sessionRef) ? relay!.json("GET", `/api/v1/sessions/${encodeURIComponent(sessionRef)}/checkpoints`) : checkpoints.state(sessionRef),
|
|
298
301
|
preview: (sessionRef, target) => company(sessionRef) ? relay!.json("GET", `/api/v1/sessions/${encodeURIComponent(sessionRef)}/checkpoints/${encodeURIComponent(target)}`) : checkpoints.preview(sessionRef, target),
|
|
@@ -36,7 +36,13 @@ export function managedProjection(context: any, entries: any[]) {
|
|
|
36
36
|
const permission = chosen === 'bypass' ? (bypassAllowed ? 'bypass' : 'trusted-full-access') : chosen === 'trusted-full-access' ? 'trusted-full-access' : 'workspace-write';
|
|
37
37
|
// Fast mode, only where the conversation's Harness allows it (fast-mode.mjs).
|
|
38
38
|
const fast = last('agent-watch-fast');
|
|
39
|
+
// Projects Studio says do not belong to the key's team (workspace-report.mjs);
|
|
40
|
+
// a local project carries its folder name.
|
|
41
|
+
const project = last('agent-watch-project'), kinds = ['other_team', 'unlisted', 'unregistered', 'no_repository'];
|
|
42
|
+
const projectWarnings = Array.isArray(project?.warnings) ? project.warnings.filter((w: any) => kinds.includes(w?.kind) && typeof w.repo === 'string' && w.repo.trim())
|
|
43
|
+
.slice(0, 6).map((w: any) => ({ repo: w.repo.trim().slice(0, 200), kind: w.kind, ...(typeof w.name === 'string' && w.name.trim() ? { name: w.name.trim().slice(0, 80) } : {}) })) : [];
|
|
44
|
+
const projectWarning = projectWarnings.length ? { warnings: projectWarnings, ...(typeof project.team === 'string' && project.team.trim() ? { team: project.team.trim().slice(0, 80) } : {}) } : null;
|
|
39
45
|
return {modelLabel:'agent-watch-auto', managedPermission: permission, ...(bypassAllowed ? {managedBypass: true as const} : {}), ...(fast?.allowed === true ? {managedFast: fast.enabled === true ? 'on' as const : 'off' as const} : {}), managedHelpers:{active:Number.isInteger(counter?.active) && counter.active>=0 && counter.active<=maximum ? counter.active : 0, maximum},
|
|
40
46
|
...(Array.isArray(capabilities)?{managedThinkingLevels:levels.filter(level=>capabilities.includes(level))}:{}),
|
|
41
|
-
...(steps ? {managedPlan:{steps}} : {}), ...(process ? {managedProcess:process} : {}), ...(turnEnd ? {managedTurnEnd:turnEnd} : {}), ...(review ? {managedReview:review} : {})};
|
|
47
|
+
...(steps ? {managedPlan:{steps}} : {}), ...(process ? {managedProcess:process} : {}), ...(turnEnd ? {managedTurnEnd:turnEnd} : {}), ...(review ? {managedReview:review} : {}), ...(projectWarning ? {managedProjectWarning:projectWarning} : {})};
|
|
42
48
|
}
|
|
@@ -49,7 +49,7 @@ function catalogRef(key: Buffer, namespace: string, value: string): string {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
export type SessionOwnerProjection = Pick<SessionRow, "state" | "liveState" | "composerAvailable" | "needsAttention" | "owner" | "reasonCode">;
|
|
52
|
-
export type SessionOptionProjection = Pick<SessionRow, "modelLabel" | "thinkingLevel" | "managedHelpers" | "managedThinkingLevels" | "managedPermission" | "managedBypass" | "managedFast" | "managedPlan" | "managedProcess" | "managedTurnEnd" | "managedReview">;
|
|
52
|
+
export type SessionOptionProjection = Pick<SessionRow, "modelLabel" | "thinkingLevel" | "managedHelpers" | "managedThinkingLevels" | "managedPermission" | "managedBypass" | "managedFast" | "managedPlan" | "managedProcess" | "managedTurnEnd" | "managedReview" | "managedProjectWarning">;
|
|
53
53
|
|
|
54
54
|
function display(value: unknown, maximum: number, fallback: string): string {
|
|
55
55
|
const clean = redactSensitiveText(String(value ?? "")).text
|
|
@@ -141,6 +141,7 @@ function row(key: Buffer, info: PiSessionInfo, metadata: SessionMetadata | undef
|
|
|
141
141
|
...(sessionOptions?.managedProcess ? {managedProcess:sessionOptions.managedProcess} : {}),
|
|
142
142
|
...(sessionOptions?.managedTurnEnd ? {managedTurnEnd:sessionOptions.managedTurnEnd} : {}),
|
|
143
143
|
...(sessionOptions?.managedReview ? {managedReview:sessionOptions.managedReview} : {}),
|
|
144
|
+
...(sessionOptions?.managedProjectWarning ? {managedProjectWarning:sessionOptions.managedProjectWarning} : {}),
|
|
144
145
|
modelLabel: sessionOptions?.modelLabel ?? null,
|
|
145
146
|
thinkingLevel: sessionOptions?.thinkingLevel ?? "unknown",
|
|
146
147
|
contextUsage: { usedTokens: null, contextWindow: null, ratio: null, state: "unknown" },
|
|
@@ -149,7 +150,7 @@ function row(key: Buffer, info: PiSessionInfo, metadata: SessionMetadata | undef
|
|
|
149
150
|
? { kind: "none", ownerEpoch: null, gatewayInstanceRef: null, runtimeInstanceRef: null, continuity: "released" }
|
|
150
151
|
: ownership?.owner ?? { kind: "none", ownerEpoch: null, gatewayInstanceRef: null, runtimeInstanceRef: null, continuity: "unknown" },
|
|
151
152
|
sessionRevision: revision(key, [info.path, info.modified.toISOString(), info.messageCount, info.name ?? null, metadata?.revision ?? null,
|
|
152
|
-
sessionOptions?.modelLabel ?? null, sessionOptions?.thinkingLevel ?? "unknown", sessionOptions?.managedHelpers, sessionOptions?.managedThinkingLevels, sessionOptions?.managedPermission ?? null, sessionOptions?.managedBypass ?? null, sessionOptions?.managedFast ?? null, sessionOptions?.managedPlan, sessionOptions?.managedProcess, sessionOptions?.managedTurnEnd, sessionOptions?.managedReview,
|
|
153
|
+
sessionOptions?.modelLabel ?? null, sessionOptions?.thinkingLevel ?? "unknown", sessionOptions?.managedHelpers, sessionOptions?.managedThinkingLevels, sessionOptions?.managedPermission ?? null, sessionOptions?.managedBypass ?? null, sessionOptions?.managedFast ?? null, sessionOptions?.managedPlan, sessionOptions?.managedProcess, sessionOptions?.managedTurnEnd, sessionOptions?.managedReview, sessionOptions?.managedProjectWarning,
|
|
153
154
|
gitBranch, gitRepositories,
|
|
154
155
|
metadata?.archived ? "archived" : ownership ? [ownership.state, ownership.liveState, ownership.owner.kind,
|
|
155
156
|
ownership.owner.ownerEpoch, ownership.owner.runtimeInstanceRef, ownership.reasonCode] : "offline"]),
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { randomBytes } from "node:crypto";
|
|
4
|
+
|
|
5
|
+
// The dashboard's language and colour theme, kept by the Gateway so they
|
|
6
|
+
// survive a restart: the browser's own storage belongs to one origin, and the
|
|
7
|
+
// dashboard's port changes each time it starts.
|
|
8
|
+
export type UiPreferences = { locale?: "vi" | "en"; themeId?: string };
|
|
9
|
+
const MAX_BYTES = 4096, THEME = /^[a-z0-9][a-z0-9-]{0,39}$/;
|
|
10
|
+
|
|
11
|
+
function valid(value: unknown): UiPreferences | null {
|
|
12
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
13
|
+
const { locale, themeId, ...rest } = value as Record<string, unknown>;
|
|
14
|
+
if (Object.keys(rest).length) return null;
|
|
15
|
+
if (locale !== undefined && locale !== "vi" && locale !== "en") return null;
|
|
16
|
+
if (themeId !== undefined && (typeof themeId !== "string" || !THEME.test(themeId))) return null;
|
|
17
|
+
return { ...(locale ? { locale: locale as "vi" | "en" } : {}), ...(themeId ? { themeId: themeId as string } : {}) };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class UiPreferencesStore {
|
|
21
|
+
readonly file: string;
|
|
22
|
+
#root: string;
|
|
23
|
+
constructor(root: string) { this.#root = root; this.file = path.join(root, "ui-preferences.json"); }
|
|
24
|
+
|
|
25
|
+
read(): UiPreferences {
|
|
26
|
+
try {
|
|
27
|
+
const stat = fs.lstatSync(this.file);
|
|
28
|
+
if (!stat.isFile() || stat.size > MAX_BYTES) return {};
|
|
29
|
+
return valid(JSON.parse(fs.readFileSync(this.file, "utf8"))) ?? {};
|
|
30
|
+
} catch { return {}; }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Merges what the browser sent; anything else is refused unchanged.
|
|
34
|
+
write(value: unknown): UiPreferences {
|
|
35
|
+
const change = valid(value);
|
|
36
|
+
if (!change) throw new Error("preferences-invalid");
|
|
37
|
+
const next = { ...this.read(), ...change };
|
|
38
|
+
const temporary = path.join(this.#root, `.ui-preferences-${process.pid}-${randomBytes(8).toString("hex")}.tmp`);
|
|
39
|
+
const descriptor = fs.openSync(temporary, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL, 0o600);
|
|
40
|
+
try { fs.writeFileSync(descriptor, `${JSON.stringify(next)}\n`); fs.fsyncSync(descriptor); }
|
|
41
|
+
finally { fs.closeSync(descriptor); }
|
|
42
|
+
fs.renameSync(temporary, this.file);
|
|
43
|
+
return next;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -3,7 +3,7 @@ import http, { type ServerResponse } from "node:http";
|
|
|
3
3
|
import { randomBytes } from "node:crypto";
|
|
4
4
|
|
|
5
5
|
import { errorResponse, applySecurityHeaders, jsonResponse, requestBody } from "./http-security.ts";
|
|
6
|
-
import { routeMemberRequest, type UpdateRoutes } from "./member-routes.ts";
|
|
6
|
+
import { routeMemberRequest, type UiPreferenceRoutes, type UpdateRoutes } from "./member-routes.ts";
|
|
7
7
|
import { routeWorkspaceWrite, type WorkspaceWriteRoutes } from "./workspace-write-routes.ts";
|
|
8
8
|
import { routeCheckpoint, type CheckpointRoutes } from "./checkpoint-routes.ts";
|
|
9
9
|
import { ReadModelNotFound, type WebUiReadModelProvider } from "./read-model-provider.ts";
|
|
@@ -81,6 +81,7 @@ export async function startLoopbackServer(options: {
|
|
|
81
81
|
listBranches?: (projectRef: string, repository: string | null) => unknown | Promise<unknown>;
|
|
82
82
|
switchBranch?: (projectRef: string, request: unknown) => unknown | Promise<unknown>;
|
|
83
83
|
updates?: UpdateRoutes;
|
|
84
|
+
uiPreferences?: UiPreferenceRoutes;
|
|
84
85
|
bootstrapTtlMs?: number;
|
|
85
86
|
sessionTtlMs?: number;
|
|
86
87
|
// Requests a minute from one address before it has a browser session (the
|
|
@@ -23,6 +23,9 @@ export type UpdateRoutes = {
|
|
|
23
23
|
apply(request: unknown): unknown | Promise<unknown>;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
+
// The dashboard's language and theme, kept across restarts (the Gateway only).
|
|
27
|
+
export type UiPreferenceRoutes = { read(): unknown | Promise<unknown>; write(value: unknown): unknown | Promise<unknown> };
|
|
28
|
+
|
|
26
29
|
type Context = {
|
|
27
30
|
request: IncomingMessage; response: ServerResponse; url: URL; origin: string; requestOrigin: string | undefined;
|
|
28
31
|
auth: SessionAuthority; now: number; consumeControl(sessionId: string, now: number): boolean;
|
|
@@ -40,6 +43,7 @@ type Options = {
|
|
|
40
43
|
listBranches?: (projectRef: string, repository: string | null) => unknown | Promise<unknown>;
|
|
41
44
|
switchBranch?: (projectRef: string, request: unknown) => unknown | Promise<unknown>;
|
|
42
45
|
updates?: UpdateRoutes;
|
|
46
|
+
uiPreferences?: UiPreferenceRoutes;
|
|
43
47
|
};
|
|
44
48
|
|
|
45
49
|
// The checks every change shares; a refusal is answered here and false returned.
|
|
@@ -137,6 +141,22 @@ export async function routeMemberRequest(context: Context, options: Options): Pr
|
|
|
137
141
|
return true;
|
|
138
142
|
}
|
|
139
143
|
|
|
144
|
+
if (options.uiPreferences && url.pathname === "/api/v1/preferences") {
|
|
145
|
+
const store = options.uiPreferences;
|
|
146
|
+
if (request.method === "GET") {
|
|
147
|
+
if (!auth.authenticate(request)) { errorResponse(response, 401, "authentication-required"); return true; }
|
|
148
|
+
try { jsonResponse(response, 200, await store.read()); } catch { errorResponse(response, 503, "preferences-unavailable"); }
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
if (request.method !== "POST") { errorResponse(response, 405, "method-not-allowed"); return true; }
|
|
152
|
+
if (!authorizeChange(context)) return true;
|
|
153
|
+
const body = await jsonBody(context, "preferences-invalid");
|
|
154
|
+
if (!body.ok) return true;
|
|
155
|
+
try { jsonResponse(response, 200, await store.write(body.value)); }
|
|
156
|
+
catch (error) { const invalid = error instanceof Error && error.message === "preferences-invalid"; errorResponse(response, invalid ? 400 : 503, invalid ? "preferences-invalid" : "preferences-unavailable"); }
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
|
|
140
160
|
if (!options.updates || !url.pathname.startsWith("/api/v1/updates")) return false;
|
|
141
161
|
const updates = options.updates;
|
|
142
162
|
if (request.method === "GET" && url.pathname === "/api/v1/updates") {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// The line a member reads when Studio says their company conversation works
|
|
2
|
+
// in a project that does not belong to the key's team ("agent-watch-project"),
|
|
3
|
+
// in the dashboard and in the company Terminal. A local project (no readable
|
|
4
|
+
// remote) is named by its folder.
|
|
5
|
+
import type { CopyLocale } from "./company-copy.ts";
|
|
6
|
+
|
|
7
|
+
export type ProjectWarningItem = { repo: string; kind: "other_team" | "unlisted" | "unregistered" | "no_repository"; name?: string };
|
|
8
|
+
|
|
9
|
+
const label = (item: ProjectWarningItem) => item.name ?? item.repo;
|
|
10
|
+
|
|
11
|
+
export function projectWarningText(warnings: readonly ProjectWarningItem[], team: string | null | undefined, locale: CopyLocale): string {
|
|
12
|
+
const vi = locale === "vi", name = team ? ` ${team}` : "", told = vi ? " Admin Studio đã được báo." : " Studio administrators were told.";
|
|
13
|
+
const of = (kind: ProjectWarningItem["kind"]) => warnings.filter(item => item.kind === kind).map(label).join(", ");
|
|
14
|
+
const parts: string[] = [];
|
|
15
|
+
const other = of("other_team"), unlisted = of("unlisted"), unregistered = of("unregistered"), none = of("no_repository");
|
|
16
|
+
if (other) parts.push(vi ? `${other} thuộc team khác, không phải team${name} của key này.` : `${other} belongs to another team, not this key's team${name}.`);
|
|
17
|
+
if (unlisted) parts.push(vi ? `${unlisted} chưa có trong danh sách project của team${name}.` : `${unlisted} is not in the project list of team${name}.`);
|
|
18
|
+
if (unregistered) parts.push(vi ? `${unregistered} chưa được đăng ký cho team${name}.` : `${unregistered} is not registered to team${name}.`);
|
|
19
|
+
if (none) parts.push(vi ? `Không xác định được project của thư mục ${none}.` : `The project of folder ${none} could not be identified.`);
|
|
20
|
+
return parts.join(" ") + told;
|
|
21
|
+
}
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"composerAvailable": { "type": "boolean" },
|
|
93
93
|
"needsAttention": { "type": "boolean" },
|
|
94
94
|
"managedHelpers": { "type": "object", "additionalProperties": false, "required": ["active", "maximum"], "properties": {"active": {"type": ["integer", "null"], "minimum": 0, "maximum": 4}, "maximum": {"type": "integer", "minimum": 0, "maximum": 4}} },
|
|
95
|
-
"managedThinkingLevels": {"type": "array", "uniqueItems": true, "maxItems": 7, "items": {"enum": ["off","minimal","low","medium","high","xhigh","max"]}}, "managedPermission": {"enum": ["workspace-write","trusted-full-access","bypass"]}, "managedBypass": {"const": true}, "managedFast": {"enum": ["off","on"]},
|
|
95
|
+
"managedThinkingLevels": {"type": "array", "uniqueItems": true, "maxItems": 7, "items": {"enum": ["off","minimal","low","medium","high","xhigh","max"]}}, "managedPermission": {"enum": ["workspace-write","trusted-full-access","bypass"]}, "managedBypass": {"const": true}, "managedProjectWarning": {"type": "object", "additionalProperties": false, "required": ["warnings"], "properties": {"team": {"type": "string", "minLength": 1, "maxLength": 80}, "warnings": {"type": "array", "minItems": 1, "maxItems": 6, "items": {"type": "object", "additionalProperties": false, "required": ["repo", "kind"], "properties": {"repo": {"type": "string", "minLength": 1, "maxLength": 200}, "name": {"type": "string", "minLength": 1, "maxLength": 80}, "kind": {"enum": ["other_team", "unlisted", "unregistered", "no_repository"]}}}}}}, "managedFast": {"enum": ["off","on"]},
|
|
96
96
|
"managedPlan": { "type": "object", "additionalProperties": false, "required": ["steps"], "properties": {
|
|
97
97
|
"steps": { "type": "array", "minItems": 1, "maxItems": 30, "items": { "type": "object", "additionalProperties": false, "required": ["step", "status"], "properties": {
|
|
98
98
|
"step": { "type": "string", "minLength": 1, "maxLength": 200 }, "status": { "enum": ["pending", "in_progress", "completed", "deferred"] } } } } } },
|
|
@@ -7,7 +7,11 @@
|
|
|
7
7
|
// The only exceptions are listed in runtime-advisory-exceptions.json, each an
|
|
8
8
|
// owner decision for one advisory at one package path in the tree, with a day
|
|
9
9
|
// it stops applying. The same advisory anywhere else, any other advisory, and
|
|
10
|
-
// an exception past its day all block. Its tests check every entry.
|
|
10
|
+
// an exception past its day all block. Its tests check every entry. A package
|
|
11
|
+
// flagged only because it depends on flagged packages (no advisory of its
|
|
12
|
+
// own, `via` names packages) is carried: it passes when every package it
|
|
13
|
+
// names is in the report with all of its advisories accepted, and blocks
|
|
14
|
+
// otherwise.
|
|
11
15
|
import fs from "node:fs";
|
|
12
16
|
|
|
13
17
|
function readStdin() {
|
|
@@ -50,12 +54,14 @@ const { exceptions } = JSON.parse(fs.readFileSync(process.env.PIAGENT_ADVISORY_E
|
|
|
50
54
|
// A test seam for the day exceptions are judged on; the gate uses today (UTC).
|
|
51
55
|
const today = process.env.PIAGENT_ADVISORY_TODAY ?? new Date().toISOString().slice(0, 10);
|
|
52
56
|
|
|
53
|
-
const blocking = [], accepted = [];
|
|
57
|
+
const blocking = [], accepted = [], pending = [], carried = [];
|
|
54
58
|
for (const [name, vulnerability] of Object.entries(report.vulnerabilities ?? {})) {
|
|
55
59
|
if (!["moderate", "high", "critical"].includes(vulnerability.severity)) continue;
|
|
56
60
|
const ids = advisoryIdsFor(vulnerability);
|
|
57
61
|
if (ids.length === 0) {
|
|
58
|
-
|
|
62
|
+
const via = vulnerability.via ?? [];
|
|
63
|
+
if (via.length > 0 && via.every((item) => typeof item === "string")) pending.push({ name, severity: vulnerability.severity, via });
|
|
64
|
+
else blocking.push({ name, severity: vulnerability.severity, id: "(no advisory id)" });
|
|
59
65
|
continue;
|
|
60
66
|
}
|
|
61
67
|
const nodes = Array.isArray(vulnerability.nodes) ? vulnerability.nodes : [];
|
|
@@ -67,10 +73,29 @@ for (const [name, vulnerability] of Object.entries(report.vulnerabilities ?? {})
|
|
|
67
73
|
}
|
|
68
74
|
}
|
|
69
75
|
|
|
76
|
+
// Carried packages may chain (a via b via c): settle them until nothing changes;
|
|
77
|
+
// whatever is left names a package that is not accepted.
|
|
78
|
+
const cleared = new Set(accepted.map((entry) => entry.name).filter((name) => !blocking.some((entry) => entry.name === name)));
|
|
79
|
+
for (let changed = true; changed;) {
|
|
80
|
+
changed = false;
|
|
81
|
+
for (const entry of [...pending]) {
|
|
82
|
+
if (!entry.via.every((name) => cleared.has(name))) continue;
|
|
83
|
+
pending.splice(pending.indexOf(entry), 1);
|
|
84
|
+
carried.push(entry);
|
|
85
|
+
cleared.add(entry.name);
|
|
86
|
+
changed = true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
for (const entry of pending) blocking.push({ name: entry.name, severity: entry.severity, id: `(no advisory id, through ${entry.via.join(", ")})` });
|
|
90
|
+
|
|
70
91
|
for (const entry of accepted) {
|
|
71
92
|
console.log(`ACCEPTED: ${entry.severity} advisory ${entry.id} in ${entry.name} at ${entry.exception.node} until ${entry.exception.until} (owner decision ${entry.exception.decided})`);
|
|
72
93
|
}
|
|
73
94
|
|
|
95
|
+
for (const entry of carried) {
|
|
96
|
+
console.log(`CARRIED: ${entry.severity} in ${entry.name} only through accepted advisories of ${entry.via.join(", ")}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
74
99
|
const failures = blocking.map(
|
|
75
100
|
(entry) => `${entry.severity} advisory ${entry.id} in ${entry.name} blocks the supported runtime`
|
|
76
101
|
+ (entry.expired ? ` (its exception ended ${entry.expired})` : "")
|
|
@@ -16,6 +16,7 @@ import { memoryOn } from '../packages/piagent-core/managed/memory.mjs';
|
|
|
16
16
|
import { companyFailureText } from '../packages/piagent-webui/shared/company-copy.ts';
|
|
17
17
|
import { headlineText, outcomeText, processItems } from '../packages/piagent-webui/shared/process-copy.ts';
|
|
18
18
|
import { turnEndCopy, turnEndOf } from '../packages/piagent-webui/shared/turn-end.ts';
|
|
19
|
+
import { projectWarningText } from '../packages/piagent-webui/shared/project-warning-copy.ts';
|
|
19
20
|
|
|
20
21
|
const LOCALE = 'vi';
|
|
21
22
|
|
|
@@ -399,6 +400,7 @@ export function companyTerminalExtension({ managed, packageRoot, sdkRoot, bindin
|
|
|
399
400
|
void showBranch();
|
|
400
401
|
session.subscribe((event) => {
|
|
401
402
|
if (event?.type === 'managed_helpers') ctx.ui?.setStatus?.('agent-watch-helpers', event.active ? `Subagent ${event.active}/${event.maximum}` : undefined);
|
|
403
|
+
if (event?.type === 'managed_project_warning') ctx.ui?.notify?.(projectWarningText(event.warnings, event.team, LOCALE), 'warning');
|
|
402
404
|
const end = event?.type === 'managed_turn_end' ? turnEndOf(event.end) : null;
|
|
403
405
|
if (!end) return;
|
|
404
406
|
const copy = turnEndCopy(end, LOCALE), stats = turnStats(session.messages);
|
|
@@ -1,4 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
|
-
"exceptions": [
|
|
3
|
+
"exceptions": [
|
|
4
|
+
{
|
|
5
|
+
"id": "GHSA-xjj6-f33q-929c",
|
|
6
|
+
"package": "undici",
|
|
7
|
+
"node": "node_modules/undici",
|
|
8
|
+
"decided": "2026-10-24",
|
|
9
|
+
"until": "2026-11-07",
|
|
10
|
+
"reason": "Denial of service (an unhandled error crashes the process) when a response lists too many content-encodings; no data exposure. undici 8.10.2 is an exact dependency of every published Pi host (1.3.0 and 1.4.0), installed globally, so no Piagent change can update it; 8.12.2 fixes it. Remove when a Pi host carries undici >= 8.12.2."
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"id": "GHSA-4qv8-2p9j-2685",
|
|
14
|
+
"package": "undici",
|
|
15
|
+
"node": "node_modules/undici",
|
|
16
|
+
"decided": "2026-10-24",
|
|
17
|
+
"until": "2026-11-07",
|
|
18
|
+
"reason": "Denial of service (unbounded memory) in undici's EventSource parser fed by a hostile server; no data exposure. Pinned by the Pi host as above; fixed in undici 8.12.2."
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "GHSA-f72v-6w45-q472",
|
|
22
|
+
"package": "undici",
|
|
23
|
+
"node": "node_modules/undici",
|
|
24
|
+
"decided": "2026-10-24",
|
|
25
|
+
"until": "2026-11-07",
|
|
26
|
+
"reason": "Subresource Integrity check bypassed with a non-lowercase algorithm token; Pi does not fetch with integrity metadata. Pinned by the Pi host as above; fixed in undici 8.12.2."
|
|
27
|
+
}
|
|
28
|
+
]
|
|
4
29
|
}
|