@piagent/platform 1.38.2 → 1.39.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.
Files changed (77) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +1 -1
  3. package/README.vi.md +1 -1
  4. package/docs/command-reference-vietnamese.md +2 -2
  5. package/docs/distribution-standard.md +6 -6
  6. package/docs/languages.json +2 -2
  7. package/docs/operator-manual-vietnamese.md +7 -7
  8. package/docs/publishing-for-teams.md +1 -0
  9. package/docs/release-install-policy.md +2 -2
  10. package/docs/team-onboarding.md +2 -2
  11. package/docs-site/architecture.html +1 -1
  12. package/docs-site/benchmark.html +1 -1
  13. package/docs-site/commands.html +1 -1
  14. package/docs-site/content/en/index.html +4 -4
  15. package/docs-site/content/en/whats-new.html +6 -0
  16. package/docs-site/content/index.html +4 -4
  17. package/docs-site/content/whats-new.html +6 -0
  18. package/docs-site/context-engine.html +1 -1
  19. package/docs-site/ecosystem.html +1 -1
  20. package/docs-site/en/architecture.html +1 -1
  21. package/docs-site/en/benchmark.html +1 -1
  22. package/docs-site/en/commands.html +1 -1
  23. package/docs-site/en/context-engine.html +1 -1
  24. package/docs-site/en/ecosystem.html +1 -1
  25. package/docs-site/en/index.html +5 -5
  26. package/docs-site/en/mcp-approval.html +1 -1
  27. package/docs-site/en/mcp-auth.html +1 -1
  28. package/docs-site/en/mcp-servers.html +1 -1
  29. package/docs-site/en/mcp.html +1 -1
  30. package/docs-site/en/permissions.html +1 -1
  31. package/docs-site/en/profiles.html +1 -1
  32. package/docs-site/en/quickstart.html +1 -1
  33. package/docs-site/en/runtime.html +1 -1
  34. package/docs-site/en/scope.html +1 -1
  35. package/docs-site/en/security.html +1 -1
  36. package/docs-site/en/team.html +1 -1
  37. package/docs-site/en/whats-new.html +7 -1
  38. package/docs-site/en/windows.html +1 -1
  39. package/docs-site/en/workflows.html +1 -1
  40. package/docs-site/index.html +5 -5
  41. package/docs-site/mcp-approval.html +1 -1
  42. package/docs-site/mcp-auth.html +1 -1
  43. package/docs-site/mcp-servers.html +1 -1
  44. package/docs-site/mcp.html +1 -1
  45. package/docs-site/permissions.html +1 -1
  46. package/docs-site/profiles.html +1 -1
  47. package/docs-site/quickstart.html +1 -1
  48. package/docs-site/runtime.html +1 -1
  49. package/docs-site/scope.html +1 -1
  50. package/docs-site/security.html +1 -1
  51. package/docs-site/team.html +1 -1
  52. package/docs-site/whats-new.html +7 -1
  53. package/docs-site/windows.html +1 -1
  54. package/docs-site/workflows.html +1 -1
  55. package/evals/private-holdout-v1/public-exposure.v2.json +2 -2
  56. package/package.json +1 -1
  57. package/packages/piagent-core/managed/request-stream.mjs +3 -0
  58. package/packages/piagent-core/managed/workspace-report.mjs +51 -0
  59. package/packages/piagent-core/package.json +1 -1
  60. package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-DtEwqme0.js → MarkdownMessage-CMZESBjK.js} +1 -1
  61. package/packages/piagent-webui/dist/client/assets/{TerminalPanel-C3vkzjQ6.js → TerminalPanel-DlcfsRaR.js} +2 -2
  62. package/packages/piagent-webui/dist/client/assets/{TerminalView-TNjhBGfa.js → TerminalView-B83HGKgY.js} +1 -1
  63. package/packages/piagent-webui/dist/client/assets/{WorkspaceCodeEditor-BAhYwQNM.js → WorkspaceCodeEditor-Ctf0VMQh.js} +1 -1
  64. package/packages/piagent-webui/dist/client/assets/{WorkspaceExplorer-Coh_GWCO.js → WorkspaceExplorer-fO2uXHZg.js} +3 -3
  65. package/packages/piagent-webui/dist/client/assets/{WorkspaceQuickOpen-Bn4miZq4.js → WorkspaceQuickOpen-ClaXnKl1.js} +1 -1
  66. package/packages/piagent-webui/dist/client/assets/{index-a-perpOX.js → index-BeBiJUDV.js} +18 -18
  67. package/packages/piagent-webui/dist/client/assets/{tab-close-nAxrK7Lq.js → tab-close-ggR0dHlZ.js} +1 -1
  68. package/packages/piagent-webui/dist/client/index.html +1 -1
  69. package/packages/piagent-webui/gateway/gateway-service.ts +3 -0
  70. package/packages/piagent-webui/gateway/managed-projection.ts +6 -1
  71. package/packages/piagent-webui/gateway/session-catalog.ts +3 -2
  72. package/packages/piagent-webui/gateway/ui-preferences-store.ts +45 -0
  73. package/packages/piagent-webui/server/loopback-server.ts +2 -1
  74. package/packages/piagent-webui/server/member-routes.ts +20 -0
  75. package/packages/piagent-webui/shared/project-warning-copy.ts +16 -0
  76. package/schemas/piagent-webui/session-catalog-v1.schema.json +1 -1
  77. package/scripts/company-terminal.mjs +2 -0
@@ -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-a-perpOX.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};
1
+ import{D as e}from"./ui-preferences-BO3AV7QE.js";import{m as t}from"./Alert-DGWhP2MM.js";import{E as n}from"./index-BeBiJUDV.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-a-perpOX.js"></script>
10
+ <script type="module" crossorigin src="/assets/index-BeBiJUDV.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,12 @@ 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
+ // Repositories Studio says do not belong to the key's team (workspace-report.mjs).
40
+ const project = last('agent-watch-project'), kinds = ['other_team', 'unlisted'];
41
+ const projectWarnings = Array.isArray(project?.warnings) ? project.warnings.filter((w: any) => kinds.includes(w?.kind) && typeof w.repo === 'string' && w.repo.trim())
42
+ .slice(0, 6).map((w: any) => ({ repo: w.repo.trim().slice(0, 200), kind: w.kind })) : [];
43
+ const projectWarning = projectWarnings.length ? { warnings: projectWarnings, ...(typeof project.team === 'string' && project.team.trim() ? { team: project.team.trim().slice(0, 80) } : {}) } : null;
39
44
  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
45
  ...(Array.isArray(capabilities)?{managedThinkingLevels:levels.filter(level=>capabilities.includes(level))}:{}),
41
- ...(steps ? {managedPlan:{steps}} : {}), ...(process ? {managedProcess:process} : {}), ...(turnEnd ? {managedTurnEnd:turnEnd} : {}), ...(review ? {managedReview:review} : {})};
46
+ ...(steps ? {managedPlan:{steps}} : {}), ...(process ? {managedProcess:process} : {}), ...(turnEnd ? {managedTurnEnd:turnEnd} : {}), ...(review ? {managedReview:review} : {}), ...(projectWarning ? {managedProjectWarning:projectWarning} : {})};
42
47
  }
@@ -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,16 @@
1
+ // The line a member reads when Studio says their company conversation works
2
+ // in a repository that does not belong to the key's team
3
+ // ("agent-watch-project"), in the dashboard and in the company Terminal.
4
+ import type { CopyLocale } from "./company-copy.ts";
5
+
6
+ export type ProjectWarningItem = { repo: string; kind: "other_team" | "unlisted" };
7
+
8
+ export function projectWarningText(warnings: readonly ProjectWarningItem[], team: string | null | undefined, locale: CopyLocale): string {
9
+ const repos = warnings.map(item => item.repo).join(", "), name = team ? ` ${team}` : "";
10
+ if (warnings.some(item => item.kind === "other_team")) return locale === "vi"
11
+ ? `${repos} thuộc team khác, không phải team${name} của key này. Admin Studio đã được báo.`
12
+ : `${repos} belongs to another team, not this key's team${name}. Studio administrators were told.`;
13
+ return locale === "vi"
14
+ ? `${repos} chưa có trong danh sách repository của team${name}. Admin Studio đã được báo.`
15
+ : `${repos} is not in the repository list of team${name}. Studio administrators were told.`;
16
+ }
@@ -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}, "kind": {"enum": ["other_team", "unlisted"]}}}}}}, "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"] } } } } } },
@@ -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);