@piagent/platform 1.16.3 → 1.17.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 (63) hide show
  1. package/CHANGELOG.md +8 -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/package.json +1 -1
  58. package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-BDdLJYnZ.js → MarkdownMessage-CeQ79h30.js} +1 -1
  59. package/packages/piagent-webui/dist/client/assets/{index-DJGByyfL.js → index-BNQK7rHr.js} +11 -11
  60. package/packages/piagent-webui/dist/client/index.html +1 -1
  61. package/packages/piagent-webui/gateway/git-head.ts +52 -0
  62. package/packages/piagent-webui/gateway/session-catalog.ts +8 -2
  63. package/schemas/piagent-webui/session-catalog-v1.schema.json +3 -0
@@ -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-DJGByyfL.js"></script>
10
+ <script type="module" crossorigin src="/assets/index-BNQK7rHr.js"></script>
11
11
  <link rel="stylesheet" crossorigin href="/assets/index-Z91z6SlN.css">
12
12
  </head>
13
13
  <body>
@@ -0,0 +1,52 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ export type GitHead = { name: string; detached: boolean };
5
+
6
+ const MAX_DEPTH = 40;
7
+
8
+ // The branch a project folder stands on, read from the repository's HEAD file
9
+ // rather than by running git: the catalog asks for every project each time the
10
+ // dashboard refreshes, and a file read costs nothing. A linked worktree's
11
+ // `.git` is a file naming its own git directory, which holds its own HEAD.
12
+ // Null when the folder is not inside a repository or HEAD cannot be read.
13
+ export function readGitHead(cwd: string): GitHead | null {
14
+ if (!cwd) return null;
15
+ let folder = path.resolve(cwd);
16
+ for (let depth = 0; depth < MAX_DEPTH; depth += 1) {
17
+ const dotGit = path.join(folder, ".git");
18
+ let stat: fs.Stats | null = null;
19
+ try { stat = fs.statSync(dotGit); } catch { /* keep walking up */ }
20
+ if (stat) return headAt(stat.isDirectory() ? dotGit : linkedGitDir(dotGit, folder));
21
+ const parent = path.dirname(folder);
22
+ if (parent === folder) return null;
23
+ folder = parent;
24
+ }
25
+ return null;
26
+ }
27
+
28
+ function linkedGitDir(dotGitFile: string, folder: string): string | null {
29
+ try {
30
+ const match = /^gitdir:\s*(.+?)\s*$/m.exec(fs.readFileSync(dotGitFile, "utf8").slice(0, 4096));
31
+ return match ? path.resolve(folder, match[1]) : null;
32
+ } catch { return null; }
33
+ }
34
+
35
+ function headAt(gitDir: string | null): GitHead | null {
36
+ if (!gitDir) return null;
37
+ let head: string;
38
+ try { head = fs.readFileSync(path.join(gitDir, "HEAD"), "utf8").slice(0, 1024).trim(); } catch { return null; }
39
+ const ref = /^ref:\s*refs\/heads\/(.+)$/.exec(head);
40
+ if (ref) return { name: ref[1], detached: false };
41
+ if (/^[0-9a-f]{40,64}$/i.test(head)) return { name: head.slice(0, 7), detached: true };
42
+ return null;
43
+ }
44
+
45
+ // One read per project folder while a catalog is built.
46
+ export function gitHeadReader(): (cwd: string) => GitHead | null {
47
+ const seen = new Map<string, GitHead | null>();
48
+ return (cwd) => {
49
+ if (!seen.has(cwd)) seen.set(cwd, readGitHead(cwd));
50
+ return seen.get(cwd)!;
51
+ };
52
+ }
@@ -6,6 +6,7 @@ import { workflowCommandPattern } from "../../piagent-core/runtime/workflows/web
6
6
  import type { Catalog, SessionRow } from "../contracts/generated/session-catalog-v1.ts";
7
7
  import type { MetadataSnapshot, SessionMetadata } from "./session-metadata-store.ts";
8
8
  import { projectRefForCwd, sessionRefForPath } from "../ownership/session-refs.ts";
9
+ import { gitHeadReader, type GitHead } from "./git-head.ts";
9
10
 
10
11
  export { projectRefForCwd, sessionRefForPath } from "../ownership/session-refs.ts";
11
12
 
@@ -105,7 +106,8 @@ function revision(key: Buffer, value: unknown): string {
105
106
  }
106
107
 
107
108
  function row(key: Buffer, info: PiSessionInfo, metadata: SessionMetadata | undefined, metadataReason: string | null,
108
- ownership?: SessionOwnerProjection, sessionOptions?: SessionOptionProjection): SessionRow {
109
+ ownership?: SessionOwnerProjection, sessionOptions?: SessionOptionProjection, gitHead?: GitHead | null): SessionRow {
110
+ const gitBranch = gitHead ? { name: display(gitHead.name, 200, "HEAD"), detached: gitHead.detached } : null;
109
111
  const projectLabel = display(info.cwd ? path.basename(info.cwd) : "", 120, "Unknown project");
110
112
  const projected = projectedSessionTitle(info);
111
113
  return {
@@ -123,6 +125,7 @@ function row(key: Buffer, info: PiSessionInfo, metadata: SessionMetadata | undef
123
125
  unread: metadata?.unread ?? false,
124
126
  composerAvailable: metadata?.archived ? false : ownership?.composerAvailable ?? false,
125
127
  needsAttention: metadata?.archived ? false : ownership?.needsAttention ?? false,
128
+ ...(gitBranch ? { gitBranch } : {}),
126
129
  ...(sessionOptions?.managedHelpers ? {managedHelpers:{...sessionOptions.managedHelpers,
127
130
  active: ownership?.liveState === 'idle' ? 0 : ownership?.liveState === 'running' || ownership?.liveState === 'waiting-approval' ? sessionOptions.managedHelpers.active : null}} : {}),
128
131
  ...(sessionOptions?.managedThinkingLevels ? {managedThinkingLevels:sessionOptions.managedThinkingLevels} : {}),
@@ -138,6 +141,7 @@ function row(key: Buffer, info: PiSessionInfo, metadata: SessionMetadata | undef
138
141
  : ownership?.owner ?? { kind: "none", ownerEpoch: null, gatewayInstanceRef: null, runtimeInstanceRef: null, continuity: "unknown" },
139
142
  sessionRevision: revision(key, [info.path, info.modified.toISOString(), info.messageCount, info.name ?? null, metadata?.revision ?? null,
140
143
  sessionOptions?.modelLabel ?? null, sessionOptions?.thinkingLevel ?? "unknown", sessionOptions?.managedHelpers, sessionOptions?.managedThinkingLevels, sessionOptions?.managedPermission ?? null, sessionOptions?.managedPlan, sessionOptions?.managedProcess,
144
+ gitBranch,
141
145
  metadata?.archived ? "archived" : ownership ? [ownership.state, ownership.liveState, ownership.owner.kind,
142
146
  ownership.owner.ownerEpoch, ownership.owner.runtimeInstanceRef, ownership.reasonCode] : "offline"]),
143
147
  reasonCode: metadata?.archived ? metadataReason : ownership?.reasonCode ?? metadataReason
@@ -151,16 +155,18 @@ export async function buildSessionCatalog(options: {
151
155
  readMetadata?(): MetadataSnapshot;
152
156
  readOwnership?(sessionRef: string): SessionOwnerProjection;
153
157
  readSessionOptions?(info: PiSessionInfo): SessionOptionProjection;
158
+ readGitHead?(cwd: string): GitHead | null;
154
159
  limit?: number;
155
160
  }): Promise<Catalog> {
156
161
  const limit = Math.max(1, Math.min(200, options.limit ?? 200));
157
162
  try {
158
163
  const found = (await options.listSessions()).filter(isUserConversationSession);
164
+ const readHead = options.readGitHead ?? gitHeadReader();
159
165
  const metadata = options.readMetadata?.() ?? { state: "ready", revision: null, sessions: new Map(), reasonCode: null };
160
166
  const sessions = found.map((info) => {
161
167
  const sessionRef = sessionRefForPath(options.key, info.path);
162
168
  return row(options.key, info, metadata.sessions.get(sessionRef), metadata.reasonCode, options.readOwnership?.(sessionRef),
163
- options.readSessionOptions?.(info));
169
+ options.readSessionOptions?.(info), readHead(info.cwd));
164
170
  }).sort((left, right) => Number(right.pinned) - Number(left.pinned)
165
171
  || Date.parse(right.updatedAt) - Date.parse(left.updatedAt)).slice(0, limit);
166
172
  return {
@@ -99,6 +99,9 @@
99
99
  "managedProcess": { "type": "object", "additionalProperties": false, "required": ["outcome", "verified", "reviewed", "blockingOpen"], "properties": {
100
100
  "outcome": { "enum": ["no_change", "interrupted", "disputed", "blocking_open", "unverified", "review_unavailable", "unreviewed", "clean"] },
101
101
  "verified": { "type": "boolean" }, "reviewed": { "type": "boolean" }, "blockingOpen": { "type": "integer", "minimum": 0, "maximum": 1000 }, "planSkipped": { "const": true } } },
102
+ "gitBranch": { "type": "object", "additionalProperties": false, "required": ["name", "detached"], "properties": {
103
+ "name": { "type": "string", "minLength": 1, "maxLength": 200, "pattern": "^[^\\u0000-\\u001F\\u007F]*$" },
104
+ "detached": { "type": "boolean" } } },
102
105
  "modelLabel": { "$ref": "common-v1.schema.json#/$defs/nullableDisplayText" },
103
106
  "thinkingLevel": { "enum": ["off", "minimal", "low", "medium", "high", "xhigh", "max", "unknown"] },
104
107
  "contextUsage": { "$ref": "#/$defs/contextUsage" },