@piagent/platform 1.9.3 → 1.10.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 (83) hide show
  1. package/CHANGELOG.md +31 -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/en/architecture.md +1 -1
  7. package/docs/languages.json +4 -4
  8. package/docs/managed-local-acceptance.md +35 -0
  9. package/docs/operator-manual-vietnamese.md +7 -7
  10. package/docs/publishing-for-teams.md +1 -0
  11. package/docs/release-install-policy.md +2 -2
  12. package/docs/team-onboarding.md +2 -2
  13. package/docs/vi/architecture.md +1 -1
  14. package/docs-site/architecture.html +1 -1
  15. package/docs-site/benchmark.html +1 -1
  16. package/docs-site/commands.html +1 -1
  17. package/docs-site/content/en/index.html +4 -4
  18. package/docs-site/content/en/whats-new.html +11 -0
  19. package/docs-site/content/index.html +4 -4
  20. package/docs-site/content/whats-new.html +11 -0
  21. package/docs-site/context-engine.html +1 -1
  22. package/docs-site/ecosystem.html +1 -1
  23. package/docs-site/en/architecture.html +1 -1
  24. package/docs-site/en/benchmark.html +1 -1
  25. package/docs-site/en/commands.html +1 -1
  26. package/docs-site/en/context-engine.html +1 -1
  27. package/docs-site/en/ecosystem.html +1 -1
  28. package/docs-site/en/index.html +5 -5
  29. package/docs-site/en/mcp-approval.html +1 -1
  30. package/docs-site/en/mcp-auth.html +1 -1
  31. package/docs-site/en/mcp-servers.html +1 -1
  32. package/docs-site/en/mcp.html +1 -1
  33. package/docs-site/en/permissions.html +1 -1
  34. package/docs-site/en/profiles.html +1 -1
  35. package/docs-site/en/quickstart.html +1 -1
  36. package/docs-site/en/runtime.html +1 -1
  37. package/docs-site/en/scope.html +1 -1
  38. package/docs-site/en/security.html +1 -1
  39. package/docs-site/en/team.html +1 -1
  40. package/docs-site/en/whats-new.html +12 -1
  41. package/docs-site/en/workflows.html +1 -1
  42. package/docs-site/index.html +5 -5
  43. package/docs-site/mcp-approval.html +1 -1
  44. package/docs-site/mcp-auth.html +1 -1
  45. package/docs-site/mcp-servers.html +1 -1
  46. package/docs-site/mcp.html +1 -1
  47. package/docs-site/permissions.html +1 -1
  48. package/docs-site/profiles.html +1 -1
  49. package/docs-site/quickstart.html +1 -1
  50. package/docs-site/runtime.html +1 -1
  51. package/docs-site/scope.html +1 -1
  52. package/docs-site/security.html +1 -1
  53. package/docs-site/team.html +1 -1
  54. package/docs-site/whats-new.html +12 -1
  55. package/docs-site/workflows.html +1 -1
  56. package/evals/private-holdout-v1/public-exposure.v2.json +2 -2
  57. package/package.json +1 -1
  58. package/packages/piagent-core/managed/grant-share.mjs +18 -0
  59. package/packages/piagent-core/managed/helper-roles.mjs +8 -4
  60. package/packages/piagent-core/managed/helper-run.mjs +116 -0
  61. package/packages/piagent-core/managed/member-questions.d.mts +15 -0
  62. package/packages/piagent-core/managed/member-questions.mjs +178 -0
  63. package/packages/piagent-core/managed/objections.mjs +73 -0
  64. package/packages/piagent-core/managed/patch-snapshot.mjs +108 -22
  65. package/packages/piagent-core/managed/request-stream.mjs +54 -7
  66. package/packages/piagent-core/managed/review-snapshot-worker.mjs +31 -13
  67. package/packages/piagent-core/managed/session.mjs +33 -85
  68. package/packages/piagent-core/managed/web-search.mjs +23 -2
  69. package/packages/piagent-core/managed/workflow.mjs +137 -51
  70. package/packages/piagent-core/package.json +1 -1
  71. package/packages/piagent-core/runtime/managed-failure.mjs +6 -2
  72. package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-IaGBsZNX.js → MarkdownMessage-CnSRvuah.js} +1 -1
  73. package/packages/piagent-webui/dist/client/assets/{index-DgSmcnP8.css → index-Z91z6SlN.css} +1 -1
  74. package/packages/piagent-webui/dist/client/assets/{index-Cv5u0MN8.js → index-yg9s8sA_.js} +14 -14
  75. package/packages/piagent-webui/dist/client/index.html +2 -2
  76. package/packages/piagent-webui/gateway/gateway-service.ts +7 -0
  77. package/packages/piagent-webui/gateway/managed-projection.ts +1 -1
  78. package/packages/piagent-webui/gateway/session-questions.ts +15 -0
  79. package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +7 -2
  80. package/packages/piagent-webui/server/loopback-server.ts +29 -0
  81. package/packages/piagent-webui/server/transcript-projection.ts +17 -4
  82. package/schemas/piagent-webui/session-catalog-v1.schema.json +1 -1
  83. package/schemas/piagent-webui/transcript-v1.schema.json +29 -2
@@ -7,8 +7,8 @@
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-Cv5u0MN8.js"></script>
11
- <link rel="stylesheet" crossorigin href="/assets/index-DgSmcnP8.css">
10
+ <script type="module" crossorigin src="/assets/index-yg9s8sA_.js"></script>
11
+ <link rel="stylesheet" crossorigin href="/assets/index-Z91z6SlN.css">
12
12
  </head>
13
13
  <body>
14
14
  <div id="root"></div>
@@ -276,6 +276,13 @@ export async function startPiagentGateway(options: {
276
276
  ? relayPost("/api/v1/runtime-commands", command) : runtimeCommands.execute(command),
277
277
  executeSessionAttachment: (sessionRef, command) => company(sessionRef)
278
278
  ? relayPost(`/api/v1/sessions/${encodeURIComponent(sessionRef)}/attachments`, command) : attachments.execute(sessionRef, command),
279
+ // The main agent's questions to the member, read and answered where the
280
+ // conversation runs (the company Gateway for a company conversation).
281
+ readSessionQuestions: (sessionRef) => company(sessionRef)
282
+ ? relay!.json("GET", `/api/v1/sessions/${encodeURIComponent(sessionRef)}/questions`) : runtimes!.questions(sessionRef),
283
+ answerSessionQuestion: (sessionRef, questionRef, answer) => company(sessionRef)
284
+ ? relayPost(`/api/v1/sessions/${encodeURIComponent(sessionRef)}/questions/${encodeURIComponent(questionRef)}/answer`, answer)
285
+ : runtimes!.answerQuestion(sessionRef, questionRef, answer),
279
286
  // Approval refs carry no session; a company approval is not pending here.
280
287
  executeApproval: async (approvalRef, decision) => {
281
288
  try { return await runtimes!.decideApproval(approvalRef, decision); }
@@ -1,6 +1,6 @@
1
1
  // Public managed state contains no provider/model route or credential.
2
2
  const STATUS = ['pending', 'in_progress', 'completed'];
3
- const OUTCOMES = ['no_change', 'interrupted', 'blocking_open', 'unverified', 'review_unavailable', 'unreviewed', 'clean'];
3
+ const OUTCOMES = ['no_change', 'interrupted', 'disputed', 'blocking_open', 'unverified', 'review_unavailable', 'unreviewed', 'clean'];
4
4
  export function managedProjection(context: any, entries: any[]) {
5
5
  if (context?.model?.provider !== 'agent_watch_managed') return {};
6
6
  const last = (type: string) => entries.filter(e => e.type === 'custom' && e.customType === type).at(-1)?.data;
@@ -0,0 +1,15 @@
1
+ import { memberQuestions } from "../../piagent-core/managed/member-questions.mjs";
2
+
3
+ // The main agent's questions to the member of a conversation this Gateway
4
+ // runs: the conversation's runtime is where they are answered, and a question
5
+ // waiting for its answer keeps the running turn alive (`progress`).
6
+ export function bindSessionQuestions(sessionId: string, progress: () => void): () => void {
7
+ return memberQuestions.bindSurface(sessionId, progress);
8
+ }
9
+ export function sessionQuestions(sessionId: string | null): { questions: ReturnType<typeof memberQuestions.pending> } {
10
+ return { questions: sessionId ? memberQuestions.pending(sessionId) : [] };
11
+ }
12
+ export function answerSessionQuestion(sessionId: string | null, questionRef: string, answer: unknown): unknown {
13
+ if (!sessionId) throw new Error("question-not-pending");
14
+ return memberQuestions.answer(sessionId, questionRef, answer);
15
+ }
@@ -6,6 +6,7 @@ import { activeSessionTask } from "../../piagent-core/extensions/task-state.js";
6
6
  import { createSessionOperationSettlement } from "./session-operation-settlement.ts";
7
7
  import { inspectTaskControlState } from "../../piagent-core/runtime/inspection/task-control-journal.ts";
8
8
  import { piApprovalBroker, type ApprovalBrokerEvent } from "../../piagent-core/runtime/inspection/approval-broker.ts";
9
+ import { answerSessionQuestion, bindSessionQuestions, sessionQuestions } from "./session-questions.ts";
9
10
  import { isUserConversationSession, projectRefForCwd, sessionRefForPath, type SessionOwnerProjection, type PiSessionInfo } from "./session-catalog.ts";
10
11
  import { SessionLeaseStore, type SessionLeaseSnapshot } from "./session-lease-store.ts";
11
12
  import { GatewayEventStore } from "./gateway-events.ts";
@@ -311,8 +312,10 @@ export class SessionRuntimeSupervisor {
311
312
  }
312
313
  #bindApproval(sessionRef: string, active: ActiveRuntime): void {
313
314
  const authority = () => sessionApprovalAuthority(this.#key, sessionRef, active);
314
- active.unbindApproval = piApprovalBroker.bind({ cwd: active.info.cwd, rawSessionId: active.info.id,
315
- runtimeInstanceId: active.lease.runtimeInstanceRef!, authority });
315
+ // The main agent's questions to the member are answered here too (session-questions.ts).
316
+ const unbindQuestions = bindSessionQuestions(active.info.id, () => active.watchdog?.progress()), unbindApproval = piApprovalBroker.bind({
317
+ cwd: active.info.cwd, rawSessionId: active.info.id, runtimeInstanceId: active.lease.runtimeInstanceRef!, authority });
318
+ active.unbindApproval = () => { unbindQuestions(); unbindApproval(); };
316
319
  active.unsubscribeApproval = piApprovalBroker.subscribe(active.info.cwd, active.info.id, (event: ApprovalBrokerEvent) => {
317
320
  active.watchdog?.progress();
318
321
  const projection = piApprovalBroker.projection(active.info.cwd, active.info.id);
@@ -388,6 +391,8 @@ export class SessionRuntimeSupervisor {
388
391
  const active = this.#active.get(sessionRef);
389
392
  return active ? piApprovalBroker.detail(active.info.cwd, active.info.id, approvalRef) : null;
390
393
  }
394
+ questions(sessionRef: string) { return sessionQuestions(this.#active.get(sessionRef)?.info.id ?? null); }
395
+ answerQuestion(sessionRef: string, questionRef: string, answer: unknown): unknown { return answerSessionQuestion(this.#active.get(sessionRef)?.info.id ?? null, questionRef, answer); }
391
396
  async decideApproval(approvalRef: string, decision: unknown): Promise<unknown> {
392
397
  for (const active of this.#active.values()) {
393
398
  if (piApprovalBroker.detail(active.info.cwd, active.info.id, approvalRef)) {
@@ -81,6 +81,8 @@ export async function startLoopbackServer(options: {
81
81
  executeControl?: (command: unknown) => unknown | Promise<unknown>;
82
82
  executeAttachment?: (command: unknown) => unknown | Promise<unknown>;
83
83
  executeApproval?: (approvalRef: string, decision: unknown) => unknown | Promise<unknown>;
84
+ readSessionQuestions?: (sessionRef: string) => unknown | Promise<unknown>;
85
+ answerSessionQuestion?: (sessionRef: string, questionRef: string, answer: unknown) => unknown | Promise<unknown>;
84
86
  bootstrapTtlMs?: number;
85
87
  sessionTtlMs?: number;
86
88
  }): Promise<LoopbackServer> {
@@ -302,6 +304,33 @@ export async function startLoopbackServer(options: {
302
304
  catch { return errorResponse(response, 503, "attachment-runtime-unavailable"); }
303
305
  }
304
306
 
307
+ // The main agent's questions to the member of a conversation, and the answer.
308
+ const questionPath = /^\/api\/v1\/sessions\/([^/]+)\/questions(?:\/([^/]+)\/answer)?$/.exec(url.pathname);
309
+ if (request.method === "GET" && questionPath && !questionPath[2] && options.readSessionQuestions) {
310
+ if (!auth.authenticate(request)) return errorResponse(response, 401, "authentication-required");
311
+ const sessionRef = decodeURIComponent(questionPath[1]);
312
+ if (!CURSOR.test(sessionRef)) return errorResponse(response, 400, "invalid-session-ref");
313
+ try { return jsonResponse(response, 200, await options.readSessionQuestions(sessionRef)); }
314
+ catch { return errorResponse(response, 503, "questions-unavailable"); }
315
+ }
316
+ if (request.method === "POST" && questionPath && questionPath[2] && options.answerSessionQuestion) {
317
+ if (requestOrigin !== origin) return errorResponse(response, 403, "origin-required");
318
+ const mutationSession = auth.authorizeMutation(request);
319
+ if (!mutationSession) return errorResponse(response, 403, "mutation-authority-rejected");
320
+ if (!consumeControl(mutationSession.id, now)) return errorResponse(response, 429, "control-rate-limit");
321
+ if (!String(request.headers["content-type"] ?? "").toLowerCase().startsWith("application/json")) return errorResponse(response, 415, "content-type");
322
+ const sessionRef = decodeURIComponent(questionPath[1]), questionRef = decodeURIComponent(questionPath[2]);
323
+ if (!CURSOR.test(sessionRef) || !/^question\.[0-9a-f-]{36}$/.test(questionRef)) return errorResponse(response, 400, "invalid-question-ref");
324
+ let answer: unknown;
325
+ try { answer = JSON.parse((await requestBody(request, MAX_CONTROL_BODY_BYTES)).toString("utf8")); }
326
+ catch (error) { return errorResponse(response, (error as Error).message === "body-limit" ? 413 : 400, "invalid-question-answer"); }
327
+ try { return jsonResponse(response, 200, await options.answerSessionQuestion(sessionRef, questionRef, answer)); }
328
+ catch (error) {
329
+ const code = error instanceof Error ? error.message : "questions-unavailable";
330
+ return errorResponse(response, code === "question-not-pending" ? 409 : code === "question-answer-invalid" ? 400 : 503, code);
331
+ }
332
+ }
333
+
305
334
  if (request.method === "POST" && url.pathname.startsWith("/api/v1/approvals/") && url.pathname.endsWith("/decision") && options.executeApproval) {
306
335
  if (requestOrigin !== origin) return errorResponse(response, 403, "origin-required");
307
336
  const mutationSession = auth.authorizeMutation(request);
@@ -35,8 +35,10 @@ type TranscriptItem = { messageRef: string; parentMessageRef: string | null; rol
35
35
  toolCalls: Array<{ toolCallRef: string; toolName: string; state: "requested" | "completed" | "failed" | "unknown";
36
36
  summary?: ToolSummary; change?: ToolChange; result?: ToolResult }>; usage?: TurnUsage; model?: string; process?: HarnessProcess };
37
37
  type HarnessFinding = { severity: "blocking" | "major" | "minor"; file: string; line: number | null; issue: string };
38
- type HarnessProcess = { phase: "plan" | "verify" | "review" | "final"; outcome?: string; loop?: number; maxLoops?: number; verified?: boolean; reviewed?: boolean;
39
- blockingOpen?: number; planOpen?: number; planSkipped?: true; verifyPolicy?: string; reviewPolicy?: string; reviewUnavailable?: string; findings?: HarnessFinding[] };
38
+ type HarnessIssue = { kind: "wrong_premise" | "conflicts_with_request" | "ambiguous" | "out_of_scope"; detail: string };
39
+ type HarnessProcess = { phase: "plan" | "verify" | "review" | "final" | "objection" | "answer" | "claims" | "rejudge" | "dispute"; outcome?: string; loop?: number; maxLoops?: number;
40
+ verified?: boolean; reviewed?: boolean; blockingOpen?: number; planOpen?: number; planSkipped?: true; verifyPolicy?: string; reviewPolicy?: string; reviewUnavailable?: string;
41
+ findings?: HarnessFinding[]; role?: "scout" | "research" | "verify" | "review"; by?: "main" | "harness"; disputes?: number; unchanged?: true; issues?: HarnessIssue[]; claims?: { claim: string }[] };
40
42
  export type TranscriptDocument = { schemaVersion: 1; version: "piagent-webui-transcript-v1"; generatedAt: string; identity: TranscriptIdentity;
41
43
  revision: TranscriptRevision; eventCursor: string; state: "ready" | "unavailable"; items: TranscriptItem[];
42
44
  page: { beforeCursor: string | null; nextBeforeCursor: string | null; hasOlder: boolean; limit: number; truncated: boolean }; reasonCode: string | null };
@@ -163,10 +165,13 @@ function toolCalls(message: any, sessionRef: string, role: TranscriptItem["role"
163
165
  }
164
166
  // The harness's process notes ("agent-watch-process"): what it asked of the
165
167
  // agent before a code-changing turn ended, and how that turn ended.
166
- const PROCESS_OUTCOMES = ["no_change", "interrupted", "blocking_open", "unverified", "review_unavailable", "unreviewed", "clean"];
168
+ const PROCESS_OUTCOMES = ["no_change", "interrupted", "disputed", "blocking_open", "unverified", "review_unavailable", "unreviewed", "clean"];
169
+ // A helper's objection to its brief, a failed claim, a disagreement handed to the member.
170
+ const PROCESS_PHASES = ["plan", "verify", "review", "final", "objection", "answer", "claims", "rejudge", "dispute"];
171
+ const ISSUE_KINDS = ["wrong_premise", "conflicts_with_request", "ambiguous", "out_of_scope"], HELPER_ROLES = ["scout", "research", "verify", "review"];
167
172
  const POLICY_MODES = ["off", "suggest", "require"];
168
173
  function harnessProcess(details: any): HarnessProcess | null {
169
- if (!["plan", "verify", "review", "final"].includes(details?.phase)) return null;
174
+ if (!PROCESS_PHASES.includes(details?.phase)) return null;
170
175
  const loop = (n: unknown): n is number => Number.isInteger(n) && (n as number) >= 0 && (n as number) <= 3;
171
176
  const out: HarnessProcess = { phase: details.phase };
172
177
  if (PROCESS_OUTCOMES.includes(details.outcome)) out.outcome = details.outcome;
@@ -184,6 +189,14 @@ function harnessProcess(details: any): HarnessProcess | null {
184
189
  .filter((f: any) => ["blocking", "major", "minor"].includes(f?.severity) && typeof f.issue === "string" && f.issue.trim()).slice(0, 10)
185
190
  .map((f: any) => ({ severity: f.severity, file: safeText(String(f.file ?? "")).full.slice(0, 300), line: Number.isSafeInteger(f.line) && f.line > 0 ? f.line : null,
186
191
  issue: safeText(f.issue).full.slice(0, 500) }));
192
+ if (HELPER_ROLES.includes(details.role)) out.role = details.role;
193
+ if (["main", "harness"].includes(details.by)) out.by = details.by;
194
+ if (Number.isInteger(details.disputes) && details.disputes >= 1) out.disputes = Math.min(details.disputes, 1000);
195
+ if (details.unchanged === true) out.unchanged = true;
196
+ if (Array.isArray(details.issues)) out.issues = details.issues.filter((i: any) => ISSUE_KINDS.includes(i?.kind) && typeof i.detail === "string" && i.detail.trim()).slice(0, 10)
197
+ .map((i: any) => ({ kind: i.kind, detail: safeText(i.detail).full.slice(0, 400) }));
198
+ if (Array.isArray(details.claims)) out.claims = details.claims.filter((c: any) => typeof c?.claim === "string" && c.claim.trim()).slice(0, 10)
199
+ .map((c: any) => ({ claim: safeText(c.claim).full.slice(0, 500) }));
187
200
  return out;
188
201
  }
189
202
  function role(message: any): TranscriptItem["role"] | null {
@@ -97,7 +97,7 @@
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"] } } } } } },
99
99
  "managedProcess": { "type": "object", "additionalProperties": false, "required": ["outcome", "verified", "reviewed", "blockingOpen"], "properties": {
100
- "outcome": { "enum": ["no_change", "interrupted", "blocking_open", "unverified", "review_unavailable", "unreviewed", "clean"] },
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
102
  "modelLabel": { "$ref": "common-v1.schema.json#/$defs/nullableDisplayText" },
103
103
  "thinkingLevel": { "enum": ["off", "minimal", "low", "medium", "high", "xhigh", "max", "unknown"] },
@@ -157,8 +157,35 @@
157
157
  "additionalProperties": false,
158
158
  "required": ["phase"],
159
159
  "properties": {
160
- "phase": { "enum": ["plan", "verify", "review", "final"] },
161
- "outcome": { "enum": ["no_change", "interrupted", "blocking_open", "unverified", "review_unavailable", "unreviewed", "clean"] },
160
+ "phase": { "enum": ["plan", "verify", "review", "final", "objection", "answer", "claims", "rejudge", "dispute"] },
161
+ "outcome": { "enum": ["no_change", "interrupted", "disputed", "blocking_open", "unverified", "review_unavailable", "unreviewed", "clean"] },
162
+ "role": { "enum": ["scout", "research", "verify", "review"] },
163
+ "by": { "enum": ["main", "harness"] },
164
+ "disputes": { "type": "integer", "minimum": 1, "maximum": 1000 },
165
+ "unchanged": { "const": true },
166
+ "issues": {
167
+ "type": "array",
168
+ "maxItems": 10,
169
+ "items": {
170
+ "type": "object",
171
+ "additionalProperties": false,
172
+ "required": ["kind", "detail"],
173
+ "properties": {
174
+ "kind": { "enum": ["wrong_premise", "conflicts_with_request", "ambiguous", "out_of_scope"] },
175
+ "detail": { "type": "string", "maxLength": 400 }
176
+ }
177
+ }
178
+ },
179
+ "claims": {
180
+ "type": "array",
181
+ "maxItems": 10,
182
+ "items": {
183
+ "type": "object",
184
+ "additionalProperties": false,
185
+ "required": ["claim"],
186
+ "properties": { "claim": { "type": "string", "maxLength": 500 } }
187
+ }
188
+ },
162
189
  "loop": { "type": "integer", "minimum": 0, "maximum": 3 },
163
190
  "maxLoops": { "type": "integer", "minimum": 0, "maximum": 3 },
164
191
  "verified": { "type": "boolean" },