@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.
- package/CHANGELOG.md +40 -0
- package/README.md +1 -1
- package/README.vi.md +1 -1
- package/catalog/capabilities.json +2 -2
- package/docs/command-reference-vietnamese.md +2 -2
- package/docs/distribution-standard.md +7 -7
- package/docs/languages.json +2 -2
- package/docs/operator-manual-vietnamese.md +7 -7
- package/docs/publishing-for-teams.md +7 -0
- package/docs/release-install-policy.md +6 -6
- package/docs/security-threat-model.md +1 -1
- 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/context-engine.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/index.html +1 -1
- 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 +1 -1
- package/docs-site/en/workflows.html +1 -1
- package/docs-site/index.html +1 -1
- 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 +1 -1
- package/docs-site/workflows.html +1 -1
- package/package.json +1 -1
- package/packages/piagent-core/extensions/guard-types.ts +1 -0
- package/packages/piagent-core/extensions/piagent-guard.ts +17 -2
- package/packages/piagent-core/extensions/policy-core.js +1 -1
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/policies/base-policy.json +1 -0
- package/packages/piagent-core/runtime/inspection/activity-event-adapter.ts +5 -1
- package/packages/piagent-core/runtime/inspection/approval-broker.ts +29 -5
- package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-BnvXTpou.js → MarkdownMessage-rD9WJKxs.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/{index-DZg979Ht.js → index-BZRFw1u6.js} +3 -3
- package/packages/piagent-webui/dist/client/index.html +1 -1
- package/packages/piagent-webui/gateway/gateway-session-stream.ts +10 -0
- package/packages/piagent-webui/gateway/rpc-ui-context.ts +28 -0
- package/packages/piagent-webui/gateway/session-operation-start.ts +34 -0
- package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +17 -33
- 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-
|
|
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
|
-
|
|
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
|
|
311
|
-
|
|
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
|
|
215
|
+
await waitForGatewayStopped(previous);
|
|
204
216
|
const launchUrl = await ensureStarted();
|
|
205
217
|
output(json ? { state: "running", launchUrl } : launchUrl);
|
|
206
218
|
openBrowser(launchUrl);
|