@piagent/platform 1.5.3 → 1.5.5

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 (64) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +1 -1
  3. package/README.vi.md +1 -1
  4. package/catalog/capabilities.json +2 -2
  5. package/docs/command-reference-vietnamese.md +2 -2
  6. package/docs/distribution-standard.md +7 -7
  7. package/docs/languages.json +2 -2
  8. package/docs/operator-manual-vietnamese.md +7 -7
  9. package/docs/publishing-for-teams.md +7 -0
  10. package/docs/release-install-policy.md +6 -6
  11. package/docs/security-threat-model.md +1 -1
  12. package/docs/team-onboarding.md +2 -2
  13. package/docs-site/architecture.html +1 -1
  14. package/docs-site/benchmark.html +1 -1
  15. package/docs-site/commands.html +1 -1
  16. package/docs-site/context-engine.html +1 -1
  17. package/docs-site/en/architecture.html +1 -1
  18. package/docs-site/en/benchmark.html +1 -1
  19. package/docs-site/en/commands.html +1 -1
  20. package/docs-site/en/context-engine.html +1 -1
  21. package/docs-site/en/index.html +1 -1
  22. package/docs-site/en/mcp-approval.html +1 -1
  23. package/docs-site/en/mcp-auth.html +1 -1
  24. package/docs-site/en/mcp-servers.html +1 -1
  25. package/docs-site/en/mcp.html +1 -1
  26. package/docs-site/en/permissions.html +1 -1
  27. package/docs-site/en/profiles.html +1 -1
  28. package/docs-site/en/quickstart.html +1 -1
  29. package/docs-site/en/runtime.html +1 -1
  30. package/docs-site/en/scope.html +1 -1
  31. package/docs-site/en/security.html +1 -1
  32. package/docs-site/en/team.html +1 -1
  33. package/docs-site/en/whats-new.html +1 -1
  34. package/docs-site/en/workflows.html +1 -1
  35. package/docs-site/index.html +1 -1
  36. package/docs-site/mcp-approval.html +1 -1
  37. package/docs-site/mcp-auth.html +1 -1
  38. package/docs-site/mcp-servers.html +1 -1
  39. package/docs-site/mcp.html +1 -1
  40. package/docs-site/permissions.html +1 -1
  41. package/docs-site/profiles.html +1 -1
  42. package/docs-site/quickstart.html +1 -1
  43. package/docs-site/runtime.html +1 -1
  44. package/docs-site/scope.html +1 -1
  45. package/docs-site/security.html +1 -1
  46. package/docs-site/team.html +1 -1
  47. package/docs-site/whats-new.html +1 -1
  48. package/docs-site/workflows.html +1 -1
  49. package/package.json +1 -1
  50. package/packages/piagent-core/extensions/guard-types.ts +1 -0
  51. package/packages/piagent-core/extensions/piagent-guard.ts +17 -2
  52. package/packages/piagent-core/extensions/policy-core.js +1 -1
  53. package/packages/piagent-core/package.json +1 -1
  54. package/packages/piagent-core/policies/base-policy.json +1 -0
  55. package/packages/piagent-core/runtime/inspection/activity-event-adapter.ts +5 -1
  56. package/packages/piagent-core/runtime/inspection/approval-broker.ts +29 -5
  57. package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-BnvXTpou.js → MarkdownMessage-rD9WJKxs.js} +1 -1
  58. package/packages/piagent-webui/dist/client/assets/{index-DZg979Ht.js → index-BZRFw1u6.js} +3 -3
  59. package/packages/piagent-webui/dist/client/index.html +1 -1
  60. package/packages/piagent-webui/gateway/gateway-session-stream.ts +10 -0
  61. package/packages/piagent-webui/gateway/rpc-ui-context.ts +28 -0
  62. package/packages/piagent-webui/gateway/session-operation-start.ts +34 -0
  63. package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +17 -33
  64. package/scripts/piagent-dashboard.mjs +13 -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-DZg979Ht.js"></script>
10
+ <script type="module" crossorigin src="/assets/index-BZRFw1u6.js"></script>
11
11
  <link rel="stylesheet" crossorigin href="/assets/index-BV-_03yl.css">
12
12
  </head>
13
13
  <body>
@@ -31,6 +31,8 @@ export class GatewaySessionStream {
31
31
  #truncated = false;
32
32
  #started = false;
33
33
  #startListeners: Array<() => void> = [];
34
+ #settled = false;
35
+ #settleListeners: Array<() => void> = [];
34
36
  readonly #toolRefs = new Map<string, string>();
35
37
 
36
38
  constructor(options: { sessionRef: string; operationRef: string; events: GatewayEventStore }) {
@@ -42,10 +44,18 @@ export class GatewaySessionStream {
42
44
  return new Promise((resolve) => this.#startListeners.push(resolve));
43
45
  }
44
46
 
47
+ settled(): Promise<void> {
48
+ if (this.#settled) return Promise.resolve();
49
+ return new Promise((resolve) => this.#settleListeners.push(resolve));
50
+ }
51
+
45
52
  observe(event: any): void {
46
53
  if (event?.type === "agent_start") {
47
54
  this.#started = true; for (const resolve of this.#startListeners.splice(0)) resolve(); return;
48
55
  }
56
+ if (event?.type === "agent_settled") {
57
+ this.#settled = true; for (const resolve of this.#settleListeners.splice(0)) resolve(); return;
58
+ }
49
59
  if (event?.type === "message_start" && event.message?.role === "assistant") {
50
60
  this.#messageRef = ref("message"); this.#buffer = ""; return;
51
61
  }
@@ -0,0 +1,28 @@
1
+ import { GATEWAY_RUNTIME_UI_MARKER } from "../ownership/gateway-runtime-context.ts";
2
+
3
+ export function rpcUiContext(): object {
4
+ const plain = (text: unknown): string => String(text ?? "");
5
+ const theme = Object.freeze({
6
+ fg: (_color: unknown, text: unknown) => plain(text),
7
+ bg: (_color: unknown, text: unknown) => plain(text),
8
+ bold: plain,
9
+ italic: plain,
10
+ underline: plain,
11
+ inverse: plain,
12
+ strikethrough: plain,
13
+ getFgAnsi: () => "",
14
+ getBgAnsi: () => "",
15
+ getColorMode: () => "truecolor",
16
+ getThinkingBorderColor: () => plain,
17
+ getBashModeBorderColor: () => plain
18
+ });
19
+ return new Proxy({}, {
20
+ get(_target, property) {
21
+ if (property === GATEWAY_RUNTIME_UI_MARKER) return true;
22
+ if (property === "theme") return theme;
23
+ if (property === "confirm") return () => new Promise<boolean>(() => undefined);
24
+ if (property === "select" || property === "input") return async () => undefined;
25
+ return () => undefined;
26
+ }
27
+ });
28
+ }
@@ -0,0 +1,34 @@
1
+ import { GatewaySessionStream } from "./gateway-session-stream.ts";
2
+
3
+ const DEFERRED_OPERATION_START_GRACE_MS = 5_000;
4
+
5
+ export async function waitForOperationStart(
6
+ stream: GatewaySessionStream,
7
+ prompt: Promise<void>
8
+ ): Promise<"prompt-owned" | "deferred"> {
9
+ const started = stream.started();
10
+ const first = await Promise.race([
11
+ started.then(() => ({ state: "started" as const })),
12
+ prompt.then(() => ({ state: "prompt-resolved" as const }),
13
+ (error: unknown) => ({ state: "prompt-rejected" as const, error }))
14
+ ]);
15
+ if (first.state === "started") return "prompt-owned";
16
+ if (first.state === "prompt-rejected") throw first.error;
17
+
18
+ // Extension commands such as `/workflow` launch their agent turn through
19
+ // `pi.sendUserMessage()`. The outer prompt resolves before that deferred turn
20
+ // emits `agent_start`, so retain ownership until the real operation appears.
21
+ let timer: ReturnType<typeof setTimeout> | undefined;
22
+ try {
23
+ await Promise.race([
24
+ started,
25
+ new Promise<never>((_resolve, reject) => {
26
+ timer = setTimeout(() => reject(new Error("session-operation-start-unobserved")),
27
+ DEFERRED_OPERATION_START_GRACE_MS);
28
+ })
29
+ ]);
30
+ return "deferred";
31
+ } finally {
32
+ if (timer) clearTimeout(timer);
33
+ }
34
+ }
@@ -11,9 +11,10 @@ import { isUserConversationSession, projectRefForCwd, sessionRefForPath } from "
11
11
  import { SessionLeaseStore, type SessionLeaseSnapshot } from "./session-lease-store.ts";
12
12
  import { GatewayEventStore } from "./gateway-events.ts";
13
13
  import { GatewaySessionStream } from "./gateway-session-stream.ts";
14
- import { GATEWAY_RUNTIME_UI_MARKER } from "../ownership/gateway-runtime-context.ts";
15
14
  import { preferAuthoritativePiagentGuard } from "./extension-authority.ts";
16
15
  import { executePermissionCommand, executeRuntimeCommand } from "./runtime-session-controls.ts";
16
+ import { rpcUiContext } from "./rpc-ui-context.ts";
17
+ import { waitForOperationStart } from "./session-operation-start.ts";
17
18
 
18
19
  const MAX_WARM_RUNTIMES = 10;
19
20
 
@@ -25,33 +26,6 @@ type ActiveRuntime = { runtime: RuntimeHandle; lease: SessionLeaseSnapshot; info
25
26
  type Projection = { sessionRevision: string; liveState: "offline" | "idle" | "running" | "paused" | "waiting-approval" | "uncertain" };
26
27
  type RuntimeCommandResult = Awaited<ReturnType<typeof executeRuntimeCommand>>;
27
28
 
28
- function rpcUiContext(): object {
29
- const plain = (text: unknown): string => String(text ?? "");
30
- const theme = Object.freeze({
31
- fg: (_color: unknown, text: unknown) => plain(text),
32
- bg: (_color: unknown, text: unknown) => plain(text),
33
- bold: plain,
34
- italic: plain,
35
- underline: plain,
36
- inverse: plain,
37
- strikethrough: plain,
38
- getFgAnsi: () => "",
39
- getBgAnsi: () => "",
40
- getColorMode: () => "truecolor",
41
- getThinkingBorderColor: () => plain,
42
- getBashModeBorderColor: () => plain
43
- });
44
- return new Proxy({}, {
45
- get(_target, property) {
46
- if (property === GATEWAY_RUNTIME_UI_MARKER) return true;
47
- if (property === "theme") return theme;
48
- if (property === "confirm") return () => new Promise<boolean>(() => undefined);
49
- if (property === "select" || property === "input") return async () => undefined;
50
- return () => undefined;
51
- }
52
- });
53
- }
54
-
55
29
  function productionRuntimeFactory(options: { host: any; agentDir: string; packageRoot: string; modelRuntime?: any }): RuntimeFactory {
56
30
  return async (info, _runtimeInstanceRef, sessionManager) => {
57
31
  const guard = path.join(options.packageRoot, "packages", "piagent-core", "extensions", "piagent-guard.ts");
@@ -305,12 +279,18 @@ export class SessionRuntimeSupervisor {
305
279
  let prompt: Promise<void>;
306
280
  try { prompt = session.prompt(payload.message, payload.images?.length ? { images: payload.images } : undefined); }
307
281
  catch (error) { await this.#finishOperation(sessionRef, operationRef, stream); throw error; }
308
- active.completion = prompt.then(() => this.#finishOperation(sessionRef, operationRef, stream),
282
+ const started = waitForOperationStart(stream, prompt);
283
+ // A normal prompt resolves after the agent settles. A workflow extension
284
+ // command resolves before its nested agent turn starts. In both cases the
285
+ // canonical completion boundary is `agent_settled`, not the outer promise.
286
+ const lifecycle = started.then(async (mode) => {
287
+ if (mode === "deferred") await stream.settled();
288
+ else await Promise.race([stream.settled(), prompt]);
289
+ });
290
+ active.completion = lifecycle.then(() => this.#finishOperation(sessionRef, operationRef, stream),
309
291
  () => this.#finishOperation(sessionRef, operationRef, stream));
310
- await Promise.race([
311
- stream.started(),
312
- prompt.then(() => { throw new Error("session-operation-start-unobserved"); }, (error: unknown) => { throw error; })
313
- ]);
292
+ try { await started; }
293
+ catch (error) { await active.completion; throw error; }
314
294
  return { resultCode: "started", operationRef };
315
295
  }
316
296
 
@@ -318,6 +298,10 @@ export class SessionRuntimeSupervisor {
318
298
  const active = this.#active.get(sessionRef), session = active?.runtime.session;
319
299
  if (!active || !session || active.operationRef !== operationRef) throw new Error("session-operation-conflict");
320
300
  const completion = active.completion;
301
+ // Extension hooks do not receive the Agent abort signal while awaiting a
302
+ // human approval. Resolve the exact operation's broker promise first so a
303
+ // stop request can reach the host instead of waiting for approval expiry.
304
+ piApprovalBroker.cancelForOperation(active.info.cwd, active.info.id, operationRef);
321
305
  await session.abort();
322
306
  if (clearQueued) session.clearQueue();
323
307
  await completion;
@@ -122,6 +122,17 @@ async function currentLaunchUrl() {
122
122
  return null;
123
123
  }
124
124
 
125
+ async function waitForGatewayStopped(previous, timeoutMs = 8_000) {
126
+ const deadline = Date.now() + timeoutMs;
127
+ while (Date.now() < deadline) {
128
+ const previousAlive = Boolean(previous && processAlive(previous.pid));
129
+ const liveUrl = await currentLaunchUrl();
130
+ if (!previousAlive && !liveUrl) return;
131
+ await new Promise((resolve) => setTimeout(resolve, 80));
132
+ }
133
+ throw new Error("gateway-restart-stop-timeout\nThe previous Gateway did not stop; restart refused to reuse the old process.");
134
+ }
135
+
125
136
  async function ensureStarted() {
126
137
  const existing = await currentLaunchUrl();
127
138
  if (existing) return existing;
@@ -199,8 +210,9 @@ async function main() {
199
210
  return;
200
211
  }
201
212
  if (command === "restart") {
213
+ const previous = readGatewayDescriptor(gatewayProfileState(agentDir));
202
214
  try { await control("stop"); } catch { /* already stopped */ }
203
- await new Promise((resolve) => setTimeout(resolve, 150));
215
+ await waitForGatewayStopped(previous);
204
216
  const launchUrl = await ensureStarted();
205
217
  output(json ? { state: "running", launchUrl } : launchUrl);
206
218
  openBrowser(launchUrl);