@piagent/platform 1.4.1 → 1.5.2
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 +69 -0
- package/README.md +5 -1
- package/README.vi.md +21 -1
- 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 +9 -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/evals/fixtures/piagent-webui/document-workspace-v1.invalid.json +17 -0
- package/evals/fixtures/piagent-webui/document-workspace-v1.valid.json +42 -0
- package/evals/fixtures/piagent-webui/session-command-v1.valid.json +2 -1
- package/package.json +1 -1
- package/packages/piagent-core/extensions/document-intake.ts +6 -3
- package/packages/piagent-core/extensions/piagent-guard.ts +2 -2
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/runtime/input/attachment-document-command.ts +39 -0
- package/packages/piagent-core/runtime/input/attachment-reservation.ts +36 -0
- package/packages/{piagent-webui/extension → piagent-core/runtime/input}/attachment-store.ts +221 -44
- package/packages/piagent-core/runtime/inspection/document-workspace-projection.ts +276 -0
- package/packages/piagent-core/runtime/inspection/project-runtime-inspection.ts +20 -5
- package/packages/piagent-core/runtime/inspection/session-identity.ts +20 -0
- package/packages/piagent-core/runtime/registration/workflow-commands.ts +5 -4
- package/packages/piagent-core/runtime/workflows/input-routing.ts +13 -2
- package/packages/piagent-webui/dist/client/assets/MarkdownMessage-CbUK4gg2.js +29 -0
- package/packages/piagent-webui/dist/client/assets/index-BV-_03yl.css +1 -0
- package/packages/piagent-webui/dist/client/assets/index-CNbYWd-R.js +174 -0
- package/packages/piagent-webui/dist/client/index.html +2 -2
- package/packages/piagent-webui/extension/piagent-webui.ts +15 -2
- package/packages/piagent-webui/extension/same-session-bridge.ts +9 -9
- package/packages/piagent-webui/gateway/ai-capability-inspection.ts +53 -0
- package/packages/piagent-webui/gateway/gateway-service.ts +23 -3
- package/packages/piagent-webui/gateway/native-project-picker.ts +63 -7
- package/packages/piagent-webui/gateway/session-attachment-registry.ts +165 -0
- package/packages/piagent-webui/gateway/session-catalog.ts +62 -9
- package/packages/piagent-webui/gateway/session-command-controller.ts +47 -14
- package/packages/piagent-webui/gateway/session-inspection-registry.ts +30 -10
- package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +19 -10
- package/packages/piagent-webui/package.json +3 -1
- package/packages/piagent-webui/server/core-inspection-provider.ts +48 -11
- package/packages/piagent-webui/server/ipc-read-model-client.ts +2 -0
- package/packages/piagent-webui/server/loopback-server.ts +63 -25
- package/packages/piagent-webui/server/read-model-provider.ts +2 -0
- package/packages/piagent-webui/server/read-only-router.ts +6 -0
- package/packages/piagent-webui/server/transcript-projection.ts +43 -2
- package/schemas/piagent-webui/attachment-v1.schema.json +24 -4
- package/schemas/piagent-webui/capabilities-v1.schema.json +2 -2
- package/schemas/piagent-webui/catalog-v1.json +9 -0
- package/schemas/piagent-webui/document-workspace-v1.schema.json +155 -0
- package/schemas/piagent-webui/session-command-v1.schema.json +14 -3
- package/schemas/piagent-webui/transcript-v1.schema.json +27 -0
- package/scripts/verify-local.sh +16 -1
- package/packages/piagent-webui/dist/client/assets/index-DwKCWbwd.css +0 -1
- package/packages/piagent-webui/dist/client/assets/index-bu7XpD9Q.js +0 -172
|
@@ -35,7 +35,10 @@ function nullableRef(value: unknown): value is string | null { return value ===
|
|
|
35
35
|
|
|
36
36
|
function validPayload(action: SessionAction, payload: Record<string, unknown>): boolean {
|
|
37
37
|
if (["session.archive", "session.unarchive", "session.acquire", "session.release"].includes(action)) return exactKeys(payload, []);
|
|
38
|
-
if (action === "session.send") return exactKeys(payload, ["delivery", "message", "messageRequestId", "expectedOperationRef"])
|
|
38
|
+
if (action === "session.send") return exactKeys(payload, ["delivery", "message", "messageRequestId", "expectedOperationRef", "attachmentRefs"])
|
|
39
|
+
&& Array.isArray(payload.attachmentRefs) && payload.attachmentRefs.length <= 4
|
|
40
|
+
&& new Set(payload.attachmentRefs as unknown[]).size === payload.attachmentRefs.length
|
|
41
|
+
&& (payload.attachmentRefs as unknown[]).every((item) => typeof item === "string" && REF.test(item))
|
|
39
42
|
&& ["new-operation", "follow-up", "steer"].includes(String(payload.delivery)) && typeof payload.message === "string"
|
|
40
43
|
&& payload.message.length >= 1 && payload.message.length <= 32_768 && !payload.message.includes("\0") && REF.test(String(payload.messageRequestId))
|
|
41
44
|
&& nullableRef(payload.expectedOperationRef) && (payload.delivery === "new-operation" ? payload.expectedOperationRef === null : payload.expectedOperationRef !== null);
|
|
@@ -52,11 +55,12 @@ function validPayload(action: SessionAction, payload: Record<string, unknown>):
|
|
|
52
55
|
if (action === "session.fork") return exactKeys(payload, ["entryRef", "title"]) && nullableRef(payload.entryRef)
|
|
53
56
|
&& (payload.title === null || typeof payload.title === "string" && payload.title.length >= 1 && payload.title.length <= 500
|
|
54
57
|
&& !/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/.test(payload.title));
|
|
55
|
-
|
|
58
|
+
const createKeys = ["projectRef", "placeRef", "modelRef", "thinkingLevel", "message", "messageRequestId"];
|
|
59
|
+
return (exactKeys(payload, createKeys) || exactKeys(payload, [...createKeys, "deferInitialMessage"]))
|
|
56
60
|
&& REF.test(String(payload.projectRef)) && REF.test(String(payload.placeRef)) && nullableRef(payload.modelRef)
|
|
57
61
|
&& ["off", "minimal", "low", "medium", "high", "xhigh", "max"].includes(String(payload.thinkingLevel))
|
|
58
62
|
&& typeof payload.message === "string" && payload.message.length >= 1 && payload.message.length <= 32_768 && !payload.message.includes("\0")
|
|
59
|
-
&& REF.test(String(payload.messageRequestId));
|
|
63
|
+
&& REF.test(String(payload.messageRequestId)) && (payload.deferInitialMessage === undefined || typeof payload.deferInitialMessage === "boolean");
|
|
60
64
|
}
|
|
61
65
|
|
|
62
66
|
function parseCommand(value: unknown): SessionCommand | null {
|
|
@@ -86,10 +90,15 @@ export class SessionCommandController {
|
|
|
86
90
|
readonly #metadata: SessionMetadataStore | null;
|
|
87
91
|
readonly #now: () => Date;
|
|
88
92
|
readonly #chains = new Map<string, Promise<unknown>>();
|
|
93
|
+
readonly #prepareAttachments: ((sessionRef: string, refs: string[], messageRequestId: string, text: string) => Promise<{
|
|
94
|
+
text: string; images: unknown[]; commit(): void; release(): void }>) | undefined;
|
|
89
95
|
|
|
90
96
|
constructor(options: { catalog(): Promise<Catalog>; runtimes: SessionRuntimeSupervisor; store: SessionCommandStore; events: GatewayEventStore;
|
|
91
97
|
metadata?: SessionMetadataStore;
|
|
98
|
+
prepareAttachments?(sessionRef: string, refs: string[], messageRequestId: string, text: string): Promise<{
|
|
99
|
+
text: string; images: unknown[]; commit(): void; release(): void }>;
|
|
92
100
|
now?: () => Date }) {
|
|
101
|
+
this.#prepareAttachments = options.prepareAttachments;
|
|
93
102
|
this.#catalog = options.catalog; this.#runtimes = options.runtimes; this.#store = options.store; this.#events = options.events;
|
|
94
103
|
this.#metadata = options.metadata ?? null;
|
|
95
104
|
this.#now = options.now ?? (() => new Date());
|
|
@@ -129,8 +138,10 @@ export class SessionCommandController {
|
|
|
129
138
|
try { this.#store.admit(command, this.#now()); }
|
|
130
139
|
catch { return this.#rejected(command, current, row, "unavailable", "session-command-admission-failed"); }
|
|
131
140
|
let targetSessionRef = command.sessionRef;
|
|
141
|
+
let attachmentReservation: { commit(): void; release(): void } | null = null;
|
|
142
|
+
let sendAttempted = false;
|
|
132
143
|
try {
|
|
133
|
-
let resultCode: "acquired" | "released" | "started" | "queued" | "steered" | "aborted" | "model-changed" | "thinking-changed" | "permission-changed"
|
|
144
|
+
let resultCode: "created" | "acquired" | "released" | "started" | "queued" | "steered" | "aborted" | "model-changed" | "thinking-changed" | "permission-changed"
|
|
134
145
|
| "renamed" | "pinned" | "unpinned" | "archived" | "unarchived" | "forked" | "no-change",
|
|
135
146
|
operationRef: string | null = null;
|
|
136
147
|
if (command.action === "session.create") {
|
|
@@ -138,18 +149,32 @@ export class SessionCommandController {
|
|
|
138
149
|
command.payload.modelRef === null ? null : String(command.payload.modelRef), String(command.payload.thinkingLevel));
|
|
139
150
|
const created = await this.#readyCatalog(), createdRow = created.sessions.find((item) => item.sessionRef === targetSessionRef);
|
|
140
151
|
if (!createdRow) throw new Error("session-catalog-refresh-failed");
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
152
|
+
if (command.payload.deferInitialMessage === true) resultCode = "created";
|
|
153
|
+
else {
|
|
154
|
+
const sent = await this.#runtimes.send(targetSessionRef, { delivery: "new-operation", message: String(command.payload.message),
|
|
155
|
+
expectedOperationRef: null }, createdRow.sessionRevision);
|
|
156
|
+
resultCode = sent.resultCode === "started" ? "started" : sent.resultCode; operationRef = sent.operationRef;
|
|
157
|
+
}
|
|
144
158
|
} else if (command.action === "session.acquire") { await this.#runtimes.acquire(command.sessionRef!); resultCode = "acquired"; }
|
|
145
159
|
else if (command.action === "session.release") { await this.#runtimes.release(command.sessionRef!); resultCode = "released"; }
|
|
146
160
|
else if (command.action === "session.send") {
|
|
147
161
|
await this.#runtimes.acquire(command.sessionRef!);
|
|
148
162
|
const acquired = await this.#readyCatalog(), acquiredRow = acquired.sessions.find((item) => item.sessionRef === command.sessionRef);
|
|
149
163
|
if (!acquiredRow) throw new Error("session-catalog-refresh-failed");
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
164
|
+
const payload = command.payload as { delivery: "new-operation" | "follow-up" | "steer"; message: string;
|
|
165
|
+
expectedOperationRef: string | null; messageRequestId: string; attachmentRefs: string[] };
|
|
166
|
+
// Staged refs are claimed once, here, before the runtime is asked to
|
|
167
|
+
// dispatch. Documents were turned into text at staging so they join the
|
|
168
|
+
// message; images travel on the host's own channel.
|
|
169
|
+
const prepared = this.#prepareAttachments
|
|
170
|
+
? await this.#prepareAttachments(command.sessionRef!, payload.attachmentRefs, payload.messageRequestId, payload.message)
|
|
171
|
+
: { text: payload.message, images: [] as unknown[], commit() {}, release() {} };
|
|
172
|
+
attachmentReservation = prepared;
|
|
173
|
+
if (!this.#prepareAttachments && payload.attachmentRefs.length > 0) throw new Error("session-attachment-unavailable");
|
|
174
|
+
sendAttempted = true;
|
|
175
|
+
const sent = await this.#runtimes.send(command.sessionRef!, { ...payload, message: prepared.text, images: prepared.images },
|
|
176
|
+
acquiredRow.sessionRevision);
|
|
177
|
+
prepared.commit();
|
|
153
178
|
resultCode = sent.resultCode; operationRef = sent.operationRef;
|
|
154
179
|
} else if (command.action === "session.abort") {
|
|
155
180
|
operationRef = String(command.payload.operationRef);
|
|
@@ -195,10 +220,18 @@ export class SessionCommandController {
|
|
|
195
220
|
return receipt;
|
|
196
221
|
} catch (cause) {
|
|
197
222
|
const after = await this.#readyCatalog(), afterRow = after.sessions.find((item) => item.sessionRef === targetSessionRef) ?? row;
|
|
198
|
-
const
|
|
199
|
-
|
|
223
|
+
const message = cause instanceof Error ? cause.message : "session-command-failed";
|
|
224
|
+
const noDispatchEffect = command.action === "session.send" && (!sendAttempted
|
|
225
|
+
|| /^(session-runtime-unavailable|session-operation-conflict|session-owner-conflict|session-runtime-busy)$/.test(message));
|
|
226
|
+
if (attachmentReservation) {
|
|
227
|
+
try { if (noDispatchEffect) attachmentReservation.release(); else attachmentReservation.commit(); }
|
|
228
|
+
catch { /* an attempted dispatch with an unprovable reservation remains uncertain */ }
|
|
229
|
+
}
|
|
230
|
+
const code = /recovery/.test(message) ? "recovery-required"
|
|
231
|
+
: /(owner-conflict|operation-conflict|runtime-busy)/.test(message) ? "owner-conflict"
|
|
232
|
+
: noDispatchEffect || command.action === "session.send" && !sendAttempted ? "unavailable" : "effect-unknown";
|
|
200
233
|
const receipt = code === "effect-unknown" ? this.#uncertain(command, after, afterRow, "session-command-effect-unknown")
|
|
201
|
-
: this.#rejected(command, after, afterRow, code,
|
|
234
|
+
: this.#rejected(command, after, afterRow, code, message);
|
|
202
235
|
try { this.#store.settle(command, receipt, this.#now()); } catch { /* intent remains uncertain */ }
|
|
203
236
|
return receipt;
|
|
204
237
|
}
|
|
@@ -219,7 +252,7 @@ export class SessionCommandController {
|
|
|
219
252
|
sessionRevisionAfter: row?.sessionRevision ?? null, deduplicated: false };
|
|
220
253
|
}
|
|
221
254
|
#settled(command: SessionCommand, catalog: Catalog, row: SessionRow,
|
|
222
|
-
resultCode: "acquired" | "released" | "started" | "queued" | "steered" | "aborted" | "model-changed" | "thinking-changed" | "permission-changed"
|
|
255
|
+
resultCode: "created" | "acquired" | "released" | "started" | "queued" | "steered" | "aborted" | "model-changed" | "thinking-changed" | "permission-changed"
|
|
223
256
|
| "renamed" | "pinned" | "unpinned" | "archived" | "unarchived" | "forked" | "no-change",
|
|
224
257
|
operationRef: string | null): Receipt {
|
|
225
258
|
return { ...this.#base(command, catalog, row), operationRef, phase: "settled", resultCode, settledAt: this.#now().toISOString(),
|
|
@@ -3,16 +3,18 @@ import path from "node:path";
|
|
|
3
3
|
|
|
4
4
|
import { readContextTelemetry } from "../../piagent-core/extensions/context-engine.js";
|
|
5
5
|
import { activeSessionTask } from "../../piagent-core/extensions/task-state.js";
|
|
6
|
-
import { runtimeConnectionDefinitions, runtimeProtectedPaths } from "../../piagent-core/runtime/inspection/project-runtime-inspection.ts";
|
|
6
|
+
import { runtimeConnectionDefinitions, runtimeDocumentReadRoots, runtimeProtectedPaths } from "../../piagent-core/runtime/inspection/project-runtime-inspection.ts";
|
|
7
7
|
import { setRuntimeMcpEnabled } from "../../piagent-core/runtime/inspection/mcp-control.ts";
|
|
8
8
|
import { webUiModelRef } from "../../piagent-core/runtime/inspection/webui-snapshot.ts";
|
|
9
9
|
import { redactSensitiveText } from "../../piagent-core/security/sensitive-data.js";
|
|
10
10
|
import { piApprovalBroker } from "../../piagent-core/runtime/inspection/approval-broker.ts";
|
|
11
|
+
import { attachmentCapability } from "../../piagent-core/runtime/input/attachment-store.ts";
|
|
11
12
|
import { CoreInspectionProvider } from "../server/core-inspection-provider.ts";
|
|
12
13
|
import { ReadModelNotFound, type WebUiReadModelProvider } from "../server/read-model-provider.ts";
|
|
13
|
-
import { projectRefForCwd, sessionRefForPath, type PiSessionInfo } from "./session-catalog.ts";
|
|
14
|
+
import { isUserConversationSession, projectRefForCwd, sessionRefForPath, type PiSessionInfo } from "./session-catalog.ts";
|
|
14
15
|
import { nativeProjectPickerAvailable } from "./native-project-picker.ts";
|
|
15
16
|
import type { McpAuthBroker } from "./mcp-auth-broker.ts";
|
|
17
|
+
import { inspectWebSearchCapability } from "./ai-capability-inspection.ts";
|
|
16
18
|
|
|
17
19
|
type PiHost = {
|
|
18
20
|
SessionManager: {
|
|
@@ -95,19 +97,26 @@ function persistedContextUsage(host: PiHost, entries: unknown[], messages: unkno
|
|
|
95
97
|
export class SessionInspectionRegistry {
|
|
96
98
|
readonly #gatewayInstanceRef: string;
|
|
97
99
|
readonly #host: PiHost;
|
|
100
|
+
readonly #listSessions: () => Promise<PiSessionInfo[]>;
|
|
101
|
+
readonly #openLiveSession: ((sessionRef: string) => ReturnType<PiHost["SessionManager"]["open"]> | null) | null;
|
|
98
102
|
readonly #key: Buffer;
|
|
99
103
|
readonly #packageRoot: string;
|
|
104
|
+
readonly #agentDir?: string;
|
|
100
105
|
readonly #models?: ModelRuntime;
|
|
101
106
|
readonly #projects?: ProjectSource;
|
|
102
107
|
readonly #mcpAuth?: McpAuthBroker;
|
|
103
108
|
readonly #providers = new Map<string, { modifiedAt: string; provider: CoreInspectionProvider }>();
|
|
104
109
|
|
|
105
|
-
constructor(options: { gatewayInstanceRef: string; host: PiHost; key: Buffer; packageRoot: string; models?: ModelRuntime; projects?: ProjectSource;
|
|
106
|
-
mcpAuth?: McpAuthBroker
|
|
110
|
+
constructor(options: { gatewayInstanceRef: string; host: PiHost; key: Buffer; packageRoot: string; agentDir?: string; models?: ModelRuntime; projects?: ProjectSource;
|
|
111
|
+
mcpAuth?: McpAuthBroker; listSessions?: () => Promise<PiSessionInfo[]>;
|
|
112
|
+
openLiveSession?: (sessionRef: string) => ReturnType<PiHost["SessionManager"]["open"]> | null }) {
|
|
107
113
|
this.#gatewayInstanceRef = options.gatewayInstanceRef;
|
|
108
114
|
this.#host = options.host;
|
|
115
|
+
this.#listSessions = options.listSessions ?? (() => options.host.SessionManager.listAll());
|
|
116
|
+
this.#openLiveSession = options.openLiveSession ?? null;
|
|
109
117
|
this.#key = options.key;
|
|
110
118
|
this.#packageRoot = options.packageRoot;
|
|
119
|
+
this.#agentDir = options.agentDir;
|
|
111
120
|
this.#models = options.models;
|
|
112
121
|
this.#projects = options.projects;
|
|
113
122
|
this.#mcpAuth = options.mcpAuth;
|
|
@@ -118,7 +127,7 @@ export class SessionInspectionRegistry {
|
|
|
118
127
|
}
|
|
119
128
|
|
|
120
129
|
async #sessionInfo(sessionRef: string): Promise<PiSessionInfo> {
|
|
121
|
-
const sessions = await this.#
|
|
130
|
+
const sessions = (await this.#listSessions()).filter(isUserConversationSession);
|
|
122
131
|
const found = sessions.filter((candidate) => sessionRefForPath(this.#key, candidate.path) === sessionRef);
|
|
123
132
|
if (found.length !== 1) throw new ReadModelNotFound();
|
|
124
133
|
return found[0]!;
|
|
@@ -168,7 +177,7 @@ export class SessionInspectionRegistry {
|
|
|
168
177
|
}
|
|
169
178
|
|
|
170
179
|
async creationOptions(): Promise<unknown> {
|
|
171
|
-
const sessions = await this.#
|
|
180
|
+
const sessions = (await this.#listSessions()).filter(isUserConversationSession);
|
|
172
181
|
const projects = new Map<string, { projectRef: string; placeRef: string; label: string }>();
|
|
173
182
|
for (const info of sessions) {
|
|
174
183
|
if (!info.cwd) continue;
|
|
@@ -179,7 +188,8 @@ export class SessionInspectionRegistry {
|
|
|
179
188
|
for (const project of safeRead(() => this.#projects?.list() ?? [], [])) {
|
|
180
189
|
if (!projects.has(project.projectRef)) projects.set(project.projectRef, project);
|
|
181
190
|
}
|
|
182
|
-
const
|
|
191
|
+
const availableModels = (this.#models?.getAvailableSnapshot() ?? []).slice(0, 300);
|
|
192
|
+
const models = availableModels.flatMap((value) => {
|
|
183
193
|
const provider = safeModelId(value.provider);
|
|
184
194
|
const modelId = safeModelId(value.id);
|
|
185
195
|
if (!provider || !modelId || typeof value.reasoning !== "boolean") return [];
|
|
@@ -187,11 +197,12 @@ export class SessionInspectionRegistry {
|
|
|
187
197
|
? value.thinkingLevelMap as Record<string, unknown> : {};
|
|
188
198
|
const thinkingLevels = value.reasoning ? ["off", "minimal", "low", "medium", "high", "xhigh", "max"]
|
|
189
199
|
.filter((level) => mapping[level] !== null && !(["xhigh", "max"].includes(level) && mapping[level] === undefined)) : ["off"];
|
|
200
|
+
const inputs = Array.isArray(value.input) ? value.input.filter((item): item is string => typeof item === "string") : null;
|
|
190
201
|
return [{ modelRef: webUiModelRef(provider, modelId), provider, modelId, displayName: safeName(value.name ?? modelId),
|
|
191
|
-
reasoning: value.reasoning, thinkingLevels }];
|
|
202
|
+
reasoning: value.reasoning, imageInput: inputs ? inputs.includes("image") : null, thinkingLevels }];
|
|
192
203
|
});
|
|
193
204
|
return { schemaVersion: 1, version: "piagent-session-creation-options-v1", generatedAt: new Date().toISOString(),
|
|
194
|
-
projects: [...projects.values()].slice(0, 200), models,
|
|
205
|
+
projects: [...projects.values()].slice(0, 200), models, webSearch: inspectWebSearchCapability({ agentDir: this.#agentDir, models: availableModels }),
|
|
195
206
|
projectImport: nativeProjectPickerAvailable() ? { status: "available", reasonCode: null }
|
|
196
207
|
: { status: "unavailable", reasonCode: "native-project-picker-unavailable" },
|
|
197
208
|
reasonCode: projects.size ? null : "no-known-project" };
|
|
@@ -204,7 +215,7 @@ export class SessionInspectionRegistry {
|
|
|
204
215
|
if (cached?.modifiedAt === modifiedAt) return cached.provider;
|
|
205
216
|
|
|
206
217
|
let manager: ReturnType<PiHost["SessionManager"]["open"]>;
|
|
207
|
-
try { manager = this.#host.SessionManager.open(info.path); }
|
|
218
|
+
try { manager = this.#openLiveSession?.(sessionRef) ?? this.#host.SessionManager.open(info.path); }
|
|
208
219
|
catch { throw new ReadModelNotFound(); }
|
|
209
220
|
const context = safeRead(() => manager.buildSessionContext(), { model: null, thinkingLevel: "unknown", messages: [] });
|
|
210
221
|
const model = context.model ? this.#models?.getModel(context.model.provider, context.model.modelId) : undefined;
|
|
@@ -225,6 +236,15 @@ export class SessionInspectionRegistry {
|
|
|
225
236
|
activityEvents: () => safeRead(() => readContextTelemetry(info.cwd, { limit: 5_000 }), []),
|
|
226
237
|
sessionEntries: () => entries,
|
|
227
238
|
protectedPaths: () => runtimeProtectedPaths(this.#packageRoot, info.cwd),
|
|
239
|
+
// The document workspace lists the project plus whatever the operator
|
|
240
|
+
// granted in the profile. Without this it silently shows the project only,
|
|
241
|
+
// which reads as "the grant does not work" rather than "it was not asked for".
|
|
242
|
+
documentReadRoots: () => safeRead(() => runtimeDocumentReadRoots(this.#packageRoot, info.cwd), []),
|
|
243
|
+
// Derived from the model and the host, with no store instance involved:
|
|
244
|
+
// the store for this session is built from the very snapshot this call
|
|
245
|
+
// helps produce, so asking the store would be circular.
|
|
246
|
+
attachmentCapability: () => attachmentCapability({
|
|
247
|
+
images: Array.isArray(model?.input) && model.input.includes("image"), now: Date.now() }),
|
|
228
248
|
contextUsage: () => contextUsage,
|
|
229
249
|
model: () => model,
|
|
230
250
|
thinkingLevel: () => context.thinkingLevel,
|
|
@@ -7,7 +7,7 @@ import { activeSessionTask } from "../../piagent-core/extensions/task-state.js";
|
|
|
7
7
|
import { inspectTaskControlState } from "../../piagent-core/runtime/inspection/task-control-journal.ts";
|
|
8
8
|
import { piApprovalBroker, type ApprovalAuthority, type ApprovalBrokerEvent } from "../../piagent-core/runtime/inspection/approval-broker.ts";
|
|
9
9
|
import type { SessionOwnerProjection, PiSessionInfo } from "./session-catalog.ts";
|
|
10
|
-
import { projectRefForCwd, sessionRefForPath } from "./session-catalog.ts";
|
|
10
|
+
import { isUserConversationSession, projectRefForCwd, sessionRefForPath } from "./session-catalog.ts";
|
|
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";
|
|
@@ -20,7 +20,7 @@ type RuntimeHandle = { dispose(): Promise<void>; session?: any };
|
|
|
20
20
|
type RuntimeFactory = (info: PiSessionInfo, runtimeInstanceRef: string, sessionManager?: any) => Promise<RuntimeHandle>;
|
|
21
21
|
type ActiveRuntime = { runtime: RuntimeHandle; lease: SessionLeaseSnapshot; info: PiSessionInfo; operationRef: string | null;
|
|
22
22
|
unsubscribe: (() => void) | null; completion: Promise<void> | null; settling: boolean; approvalWaiting: boolean;
|
|
23
|
-
unbindApproval: (() => void) | null; unsubscribeApproval: (() => void) | null };
|
|
23
|
+
unbindApproval: (() => void) | null; unsubscribeApproval: (() => void) | null; sessionManager: any | null };
|
|
24
24
|
type Projection = { sessionRevision: string; liveState: "offline" | "idle" | "running" | "paused" | "waiting-approval" | "uncertain" };
|
|
25
25
|
|
|
26
26
|
function rpcUiContext(): object {
|
|
@@ -129,7 +129,7 @@ export class SessionRuntimeSupervisor {
|
|
|
129
129
|
setProjectionReader(reader: (sessionRef: string) => Promise<Projection>): void { this.#readProjection = reader; }
|
|
130
130
|
|
|
131
131
|
async listSessions(): Promise<PiSessionInfo[]> {
|
|
132
|
-
const persisted = await this.#listSessions();
|
|
132
|
+
const persisted = (await this.#listSessions()).filter(isUserConversationSession);
|
|
133
133
|
const paths = new Set(persisted.map((info) => info.path));
|
|
134
134
|
for (const [sessionRef, info] of this.#created) {
|
|
135
135
|
if (paths.has(info.path)) this.#created.delete(sessionRef);
|
|
@@ -140,7 +140,7 @@ export class SessionRuntimeSupervisor {
|
|
|
140
140
|
async create(projectRef: string, placeRef: string, modelRef: string | null, thinkingLevel: string): Promise<string> {
|
|
141
141
|
if (this.#closed || !this.#host) throw new Error("session-create-unavailable");
|
|
142
142
|
if (placeRef !== projectRef) throw new Error("session-place-mismatch");
|
|
143
|
-
const sessions = await this.#listSessions();
|
|
143
|
+
const sessions = (await this.#listSessions()).filter(isUserConversationSession);
|
|
144
144
|
const imported = this.#resolveProject?.(projectRef) ?? null;
|
|
145
145
|
const candidates = [...new Set([...sessions.filter((info) => projectRefForCwd(this.#key, info.cwd) === projectRef).map((info) => info.cwd),
|
|
146
146
|
...(imported ? [imported] : [])])];
|
|
@@ -166,6 +166,8 @@ export class SessionRuntimeSupervisor {
|
|
|
166
166
|
return sessionRef;
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
liveSessionManager(sessionRef: string): any | null { return this.#active.get(sessionRef)?.sessionManager ?? null; }
|
|
170
|
+
|
|
169
171
|
async acquire(sessionRef: string): Promise<SessionLeaseSnapshot> {
|
|
170
172
|
if (this.#closed) throw new Error("session-runtime-supervisor-closed");
|
|
171
173
|
const active = this.#active.get(sessionRef);
|
|
@@ -179,7 +181,8 @@ export class SessionRuntimeSupervisor {
|
|
|
179
181
|
|
|
180
182
|
async #acquire(sessionRef: string): Promise<SessionLeaseSnapshot> {
|
|
181
183
|
if (this.#active.size + this.#opening.size > MAX_WARM_RUNTIMES) throw new Error("session-runtime-capacity");
|
|
182
|
-
const found = (await this.#listSessions()).filter(
|
|
184
|
+
const found = (await this.#listSessions()).filter(isUserConversationSession)
|
|
185
|
+
.filter((info) => sessionRefForPath(this.#key, info.path) === sessionRef);
|
|
183
186
|
if (found.length !== 1) throw new Error(found.length ? "session-ref-ambiguous" : "session-not-found");
|
|
184
187
|
return await this.#activate(sessionRef, found[0]!);
|
|
185
188
|
}
|
|
@@ -201,7 +204,8 @@ export class SessionRuntimeSupervisor {
|
|
|
201
204
|
throw new Error("session-owner-continuity-lost");
|
|
202
205
|
}
|
|
203
206
|
const active: ActiveRuntime = { runtime, lease: current, info, operationRef: null, unsubscribe: null, completion: null,
|
|
204
|
-
settling: false, approvalWaiting: false, unbindApproval: null, unsubscribeApproval: null
|
|
207
|
+
settling: false, approvalWaiting: false, unbindApproval: null, unsubscribeApproval: null,
|
|
208
|
+
sessionManager: sessionManager ?? runtime.session?.sessionManager ?? null };
|
|
205
209
|
this.#active.set(sessionRef, active);
|
|
206
210
|
this.#bindApproval(sessionRef, active);
|
|
207
211
|
return current;
|
|
@@ -271,15 +275,20 @@ export class SessionRuntimeSupervisor {
|
|
|
271
275
|
};
|
|
272
276
|
}
|
|
273
277
|
|
|
278
|
+
// `message` arrives already carrying any attached document text, and `images`
|
|
279
|
+
// already split out: the command controller claims staged refs before it calls
|
|
280
|
+
// here, so this layer only forwards what the host prompt takes.
|
|
274
281
|
async send(sessionRef: string, payload: { delivery: "new-operation" | "follow-up" | "steer"; message: string;
|
|
275
|
-
expectedOperationRef: string | null }, sessionRevision: string):
|
|
282
|
+
expectedOperationRef: string | null; images?: unknown[] }, sessionRevision: string):
|
|
283
|
+
Promise<{ resultCode: "started" | "queued" | "steered"; operationRef: string }> {
|
|
276
284
|
await this.acquire(sessionRef);
|
|
277
285
|
const active = this.#active.get(sessionRef), session = active?.runtime.session;
|
|
278
286
|
if (!active || !session) throw new Error("session-runtime-unavailable");
|
|
279
287
|
if (payload.delivery !== "new-operation") {
|
|
280
288
|
if (!active.operationRef || active.operationRef !== payload.expectedOperationRef || !session.isStreaming) throw new Error("session-operation-conflict");
|
|
281
|
-
|
|
282
|
-
await session.
|
|
289
|
+
const images = payload.images?.length ? payload.images : undefined;
|
|
290
|
+
if (payload.delivery === "follow-up") { await session.followUp(payload.message, images); return { resultCode: "queued", operationRef: active.operationRef }; }
|
|
291
|
+
await session.steer(payload.message, images); return { resultCode: "steered", operationRef: active.operationRef };
|
|
283
292
|
}
|
|
284
293
|
if (payload.expectedOperationRef !== null || active.operationRef || active.settling || !session.isIdle) throw new Error("session-operation-conflict");
|
|
285
294
|
const operationRef = `operation_${randomBytes(24).toString("base64url")}`;
|
|
@@ -292,7 +301,7 @@ export class SessionRuntimeSupervisor {
|
|
|
292
301
|
created.firstMessage = payload.message; created.allMessagesText = payload.message; created.messageCount = 1; created.modified = new Date();
|
|
293
302
|
}
|
|
294
303
|
let prompt: Promise<void>;
|
|
295
|
-
try { prompt = session.prompt(payload.message); }
|
|
304
|
+
try { prompt = session.prompt(payload.message, payload.images?.length ? { images: payload.images } : undefined); }
|
|
296
305
|
catch (error) { await this.#finishOperation(sessionRef, operationRef, stream); throw error; }
|
|
297
306
|
active.completion = prompt.then(() => this.#finishOperation(sessionRef, operationRef, stream),
|
|
298
307
|
() => this.#finishOperation(sessionRef, operationRef, stream));
|
|
@@ -17,7 +17,9 @@
|
|
|
17
17
|
"@mui/icons-material": "^9.3.1",
|
|
18
18
|
"@mui/material": "^9.3.1",
|
|
19
19
|
"react": "19.2.8",
|
|
20
|
-
"react-dom": "19.2.8"
|
|
20
|
+
"react-dom": "19.2.8",
|
|
21
|
+
"react-markdown": "^10.1.0",
|
|
22
|
+
"remark-gfm": "^4.0.1"
|
|
21
23
|
},
|
|
22
24
|
"devDependencies": {
|
|
23
25
|
"@types/react": "19.2.18",
|
|
@@ -22,7 +22,7 @@ type EventStore = {
|
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
async function loadCoreInspection() {
|
|
25
|
-
const [{ collectFileDiff }, { buildWebUiInspectionProjection }, { matchesProtectedPath }, reviewProjection, reviewStore, mutationProjection, revertProjection, openProjection, commitSummaryProjection, taskIndexProjection, taskTimelineProjection, recoveryHistoryProjection, handoffHistoryProjection, subagentTreeProjection, releaseMonitorProjection] = await Promise.all([
|
|
25
|
+
const [{ collectFileDiff }, { buildWebUiInspectionProjection }, { matchesProtectedPath }, reviewProjection, reviewStore, mutationProjection, revertProjection, openProjection, commitSummaryProjection, taskIndexProjection, taskTimelineProjection, recoveryHistoryProjection, handoffHistoryProjection, subagentTreeProjection, releaseMonitorProjection, documentWorkspaceProjection] = await Promise.all([
|
|
26
26
|
import(`${CORE_ROOT}/runtime/inspection/diff-projection.ts`),
|
|
27
27
|
import(`${CORE_ROOT}/runtime/inspection/webui-snapshot.ts`),
|
|
28
28
|
import(`${CORE_ROOT}/extensions/policy-core.js`),
|
|
@@ -37,9 +37,10 @@ async function loadCoreInspection() {
|
|
|
37
37
|
import(`${CORE_ROOT}/runtime/inspection/task-compaction-history.ts`),
|
|
38
38
|
import(`${CORE_ROOT}/runtime/inspection/task-handoff-history.ts`),
|
|
39
39
|
import(`${CORE_ROOT}/runtime/inspection/task-subagent-tree.ts`),
|
|
40
|
-
import("./benchmark-release-monitor.ts")
|
|
40
|
+
import("./benchmark-release-monitor.ts"),
|
|
41
|
+
import(`${CORE_ROOT}/runtime/inspection/document-workspace-projection.ts`)
|
|
41
42
|
]);
|
|
42
|
-
return { collectFileDiff, buildWebUiInspectionProjection, matchesProtectedPath, ...reviewProjection, ...reviewStore, ...mutationProjection, ...revertProjection, ...openProjection, ...commitSummaryProjection, ...taskIndexProjection, ...taskTimelineProjection, ...recoveryHistoryProjection, ...handoffHistoryProjection, ...subagentTreeProjection, ...releaseMonitorProjection };
|
|
43
|
+
return { collectFileDiff, buildWebUiInspectionProjection, matchesProtectedPath, ...reviewProjection, ...reviewStore, ...mutationProjection, ...revertProjection, ...openProjection, ...commitSummaryProjection, ...taskIndexProjection, ...taskTimelineProjection, ...recoveryHistoryProjection, ...handoffHistoryProjection, ...subagentTreeProjection, ...releaseMonitorProjection, ...documentWorkspaceProjection };
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
export type CoreInspectionInput = {
|
|
@@ -52,12 +53,16 @@ export type CoreInspectionInput = {
|
|
|
52
53
|
currentActivity?: () => unknown[];
|
|
53
54
|
sessionEntries?: () => unknown[];
|
|
54
55
|
protectedPaths?: () => string[];
|
|
56
|
+
// Extra directories the operator granted through the profile. The document
|
|
57
|
+
// workspace lists them beside the project; without them it shows the project
|
|
58
|
+
// only, which is the same answer a host with no grants gives.
|
|
59
|
+
documentReadRoots?: () => unknown;
|
|
55
60
|
contextUsage?: () => { tokens: number | null; contextWindow: number; percent: number | null } | undefined;
|
|
56
61
|
model?: () => { provider?: unknown; id?: unknown; name?: unknown; reasoning?: unknown; input?: unknown; thinkingLevelMap?: unknown; contextWindow?: unknown; maxTokens?: unknown } | undefined;
|
|
57
62
|
thinkingLevel?: () => unknown;
|
|
58
63
|
queueProjection?: () => unknown;
|
|
59
64
|
modelCatalog?: () => unknown;
|
|
60
|
-
attachmentCapability?: () => { kinds: Array<"file" | "image">; mimeTypes: string[] };
|
|
65
|
+
attachmentCapability?: () => { kinds: Array<"file" | "image" | "document">; mimeTypes: string[] };
|
|
61
66
|
approvalProjection?: () => { revision: string | null; summary: Record<string, unknown> };
|
|
62
67
|
approvalDetail?: (approvalRef: string) => unknown | null;
|
|
63
68
|
sourceMutationGuardAvailable?: () => boolean;
|
|
@@ -75,6 +80,7 @@ export class CoreInspectionProvider implements WebUiReadModelProvider {
|
|
|
75
80
|
#cachedProjection: { at: number; value: any } | null = null;
|
|
76
81
|
#invalidationEpoch = 0;
|
|
77
82
|
readonly #diffCache = new Map<string, { at: number; value: unknown }>();
|
|
83
|
+
#documentCache: { at: number; value: unknown } | null = null;
|
|
78
84
|
readonly #sourceRevertConfirmationKey = randomBytes(32);
|
|
79
85
|
|
|
80
86
|
constructor(input: CoreInspectionInput) { this.#input = input; }
|
|
@@ -83,6 +89,7 @@ export class CoreInspectionProvider implements WebUiReadModelProvider {
|
|
|
83
89
|
this.#invalidationEpoch += 1;
|
|
84
90
|
this.#cachedProjection = null;
|
|
85
91
|
this.#diffCache.clear();
|
|
92
|
+
this.#documentCache = null;
|
|
86
93
|
}
|
|
87
94
|
|
|
88
95
|
async #projection() {
|
|
@@ -97,6 +104,17 @@ export class CoreInspectionProvider implements WebUiReadModelProvider {
|
|
|
97
104
|
contextUsage: this.#input.contextUsage?.(), model: this.#input.model?.(), thinkingLevel: this.#input.thinkingLevel?.(),
|
|
98
105
|
eventCursor: this.#input.eventStore.currentCursor(), resyncRequired: this.#input.eventStore.resyncRequired(), eventReplay: retention
|
|
99
106
|
});
|
|
107
|
+
// What the host will accept as an attachment is a property of the machine and
|
|
108
|
+
// the model, not of chat control. The Gateway drives sessions without a chat
|
|
109
|
+
// bridge at all, and a capability published only alongside that bridge left
|
|
110
|
+
// its composer believing no file type was supported.
|
|
111
|
+
if (this.#input.attachmentCapability) {
|
|
112
|
+
const attachment = this.#input.attachmentCapability();
|
|
113
|
+
value.snapshot.capabilities.capabilities.attachments = { status: "available", version: 1, reason: null,
|
|
114
|
+
kinds: attachment.kinds, mimeTypes: attachment.mimeTypes };
|
|
115
|
+
Object.assign(value.snapshot.capabilities.limits, { maxRequestBodyBytes: 11_250_000, maxAttachmentCount: 4,
|
|
116
|
+
maxAttachmentFileBytes: 8_388_608, maxAttachmentTotalBytes: 16_777_216 });
|
|
117
|
+
}
|
|
100
118
|
const control = this.#input.chatControl?.();
|
|
101
119
|
if (control?.state === "ready" && control.identity && control.revisions && !this.#input.eventStore.resyncRequired()) {
|
|
102
120
|
const available = { available: true, reasonCode: null }, disabled = (reasonCode: string) => ({ available: false, reasonCode });
|
|
@@ -113,13 +131,6 @@ export class CoreInspectionProvider implements WebUiReadModelProvider {
|
|
|
113
131
|
deleteHeld: available, dispatchHeld: dispatchBlocked ? disabled("task-control-gate-closed") : available,
|
|
114
132
|
interruptAndSend: dispatchBlocked ? disabled("task-control-gate-closed") : control.liveness === "running" ? available : disabled("agent-not-running") }
|
|
115
133
|
};
|
|
116
|
-
if (this.#input.attachmentCapability) {
|
|
117
|
-
const attachment = this.#input.attachmentCapability();
|
|
118
|
-
value.snapshot.capabilities.capabilities.attachments = { status: "available", version: 1, reason: null,
|
|
119
|
-
kinds: attachment.kinds, mimeTypes: attachment.mimeTypes };
|
|
120
|
-
Object.assign(value.snapshot.capabilities.limits, { maxRequestBodyBytes: 11_250_000, maxAttachmentCount: 4,
|
|
121
|
-
maxAttachmentFileBytes: 8_388_608, maxAttachmentTotalBytes: 16_777_216 });
|
|
122
|
-
}
|
|
123
134
|
let modelCatalog: any;
|
|
124
135
|
try { modelCatalog = this.#input.modelCatalog?.(); }
|
|
125
136
|
catch { modelCatalog = null; }
|
|
@@ -190,6 +201,32 @@ export class CoreInspectionProvider implements WebUiReadModelProvider {
|
|
|
190
201
|
const source = (await this.#projection()).sourceViews;
|
|
191
202
|
return view === "working-tree" ? source.workingTree : source[view];
|
|
192
203
|
}
|
|
204
|
+
// The listing walks the project and every granted root, so it costs real
|
|
205
|
+
// filesystem work — hundreds of milliseconds on a large repository. It also
|
|
206
|
+
// changes only when files change, so a short cache keeps a browser that
|
|
207
|
+
// re-renders from re-walking the disk, without holding a stale view long
|
|
208
|
+
// enough for the operator to notice.
|
|
209
|
+
async documents(): Promise<unknown> {
|
|
210
|
+
const cached = this.#documentCache;
|
|
211
|
+
if (cached && Date.now() - cached.at <= 2_000) return cached.value;
|
|
212
|
+
const core = await loadCoreInspection();
|
|
213
|
+
const value = core.projectDocumentWorkspaceListing(this.#documentWorkspaceInput(core.matchesProtectedPath));
|
|
214
|
+
this.#documentCache = { at: Date.now(), value };
|
|
215
|
+
return value;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async document(documentRef: string): Promise<unknown> {
|
|
219
|
+
const core = await loadCoreInspection();
|
|
220
|
+
return core.projectDocumentWorkspaceDocument({ ...this.#documentWorkspaceInput(core.matchesProtectedPath), documentRef });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
#documentWorkspaceInput(matchesProtectedPath: (candidate: string, patterns: string[]) => unknown) {
|
|
224
|
+
const protectedPaths = this.#input.protectedPaths?.() ?? [];
|
|
225
|
+
return { cwd: this.#input.cwd, profileRoots: this.#input.documentReadRoots?.(),
|
|
226
|
+
environmentRoots: process.env.PIAGENT_ADDITIONAL_READ_ROOTS,
|
|
227
|
+
isProtectedPath: (candidate: string) => Boolean(matchesProtectedPath(candidate, protectedPaths)) };
|
|
228
|
+
}
|
|
229
|
+
|
|
193
230
|
async activity(): Promise<unknown> { return (await this.#projection()).snapshot.activity; }
|
|
194
231
|
async logPreview(activityRef: string): Promise<unknown> {
|
|
195
232
|
const activity = (await this.#projection()).snapshot.activity;
|
|
@@ -62,6 +62,8 @@ export class IpcReadModelClient implements WebUiReadModelProvider {
|
|
|
62
62
|
handoffHistory(runRef: string): Promise<unknown> { return this.#request("handoffHistory", [runRef]); }
|
|
63
63
|
subagentTree(runRef: string): Promise<unknown> { return this.#request("subagentTree", [runRef]); }
|
|
64
64
|
releaseMonitor(): Promise<unknown> { return this.#request("releaseMonitor"); }
|
|
65
|
+
documents(): Promise<unknown> { return this.#request("documents"); }
|
|
66
|
+
document(documentRef: string): Promise<unknown> { return this.#request("document", [documentRef]); }
|
|
65
67
|
activity(): Promise<unknown> { return this.#request("activity"); }
|
|
66
68
|
logPreview(activityRef: string): Promise<unknown> { return this.#request("logPreview", [activityRef]); }
|
|
67
69
|
transcript(beforeCursor: string | null, limit: number): Promise<unknown> { return this.#request("transcript", [beforeCursor, limit]); }
|