@cydm/happy-elves 0.1.0-beta.48 → 0.1.0-beta.49
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/apps/cli/dist/commands/app.js +5 -1
- package/apps/cli/dist/commands/config.js +2 -2
- package/apps/cli/dist/commands/lib/args.js +7 -0
- package/apps/cli/dist/commands/lib/config.d.ts +1 -0
- package/apps/cli/dist/commands/lib/config.js +15 -2
- package/apps/cli/dist/commands/lib/doctor.js +3 -3
- package/apps/cli/dist/commands/lib/exit.js +2 -1
- package/apps/cli/dist/commands/lib/index.d.ts +1 -0
- package/apps/cli/dist/commands/lib/index.js +1 -0
- package/apps/cli/dist/commands/lib/json.d.ts +1 -1
- package/apps/cli/dist/commands/lib/json.js +2 -2
- package/apps/cli/dist/commands/lib/orchestrator.d.ts +30 -0
- package/apps/cli/dist/commands/lib/orchestrator.js +146 -0
- package/apps/cli/dist/commands/lib/relay-http.js +15 -4
- package/apps/cli/dist/commands/lib/session-output.d.ts +1 -0
- package/apps/cli/dist/commands/lib/session-output.js +42 -10
- package/apps/cli/dist/commands/lib/status.js +3 -3
- package/apps/cli/dist/commands/lib/types.d.ts +1 -0
- package/apps/cli/dist/commands/lib/usage.js +46 -16
- package/apps/cli/dist/commands/orchestrator.d.ts +2 -0
- package/apps/cli/dist/commands/orchestrator.js +411 -0
- package/apps/cli/dist/commands/relay.js +5 -5
- package/apps/cli/dist/commands/remote.js +2 -2
- package/apps/cli/dist/commands/skill.d.ts +2 -0
- package/apps/cli/dist/commands/skill.js +214 -0
- package/apps/cli/dist/commands/turn.js +5 -2
- package/apps/daemon/package.json +1 -1
- package/apps/relay/dist/connections.d.ts +1 -0
- package/apps/relay/dist/controller-handlers.js +4 -1
- package/apps/relay/dist/http-routes.js +32 -1
- package/apps/relay/dist/http-schemas.d.ts +16 -0
- package/apps/relay/dist/http-schemas.js +5 -0
- package/apps/relay/dist/relay-context.js +2 -0
- package/apps/relay/dist/session-projection-reducer.js +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/packages/client/dist/client.d.ts +5 -0
- package/packages/client/dist/client.js +35 -7
- package/packages/client/dist/http.d.ts +1 -0
- package/packages/client/dist/http.js +15 -3
- package/packages/client/dist/live-session-state.d.ts +15 -0
- package/packages/client/dist/live-session-state.js +60 -0
- package/packages/client/dist/live-state.d.ts +8 -0
- package/packages/client/dist/live-state.js +33 -0
- package/packages/client/dist/live-types.d.ts +113 -0
- package/packages/client/dist/live-types.js +1 -0
- package/packages/client/dist/live.d.ts +2 -99
- package/packages/client/dist/live.js +16 -77
- package/packages/client/dist/types.d.ts +4 -1
- package/packages/client/dist/validation.d.ts +1 -0
- package/packages/client/dist/validation.js +11 -0
- package/packages/shared/dist/protocol.d.ts +3 -0
- package/packages/shared/dist/protocol.js +1 -0
|
@@ -1,103 +1,6 @@
|
|
|
1
|
-
import { type MachineSnapshot, type SessionSnapshot } from "../../shared/dist/index.js";
|
|
2
1
|
import type { ControllerClient } from "./client.js";
|
|
3
|
-
import type {
|
|
4
|
-
export type ControllerLiveSessionPageState
|
|
5
|
-
error?: string;
|
|
6
|
-
hasOlderRelayEvents?: boolean;
|
|
7
|
-
hasOlderEvents: boolean;
|
|
8
|
-
initialized: boolean;
|
|
9
|
-
latestCursor?: string;
|
|
10
|
-
loadingOlderEvents: boolean;
|
|
11
|
-
olderCursor?: string;
|
|
12
|
-
olderRelayVersion?: number;
|
|
13
|
-
oldestEventId?: number;
|
|
14
|
-
projectionHeadKey?: string;
|
|
15
|
-
runtimeCanSync?: boolean;
|
|
16
|
-
runtimeHistoryChecked?: boolean;
|
|
17
|
-
};
|
|
18
|
-
export type ControllerLiveState = {
|
|
19
|
-
connectionState: "offline" | "connecting" | "online";
|
|
20
|
-
events: DecodedSessionEvent[];
|
|
21
|
-
lastError?: string;
|
|
22
|
-
lastSnapshotAt: number;
|
|
23
|
-
lastSnapshotMachineCount: number;
|
|
24
|
-
lastSnapshotSessionCount: number;
|
|
25
|
-
lastSnapshotSource: string;
|
|
26
|
-
machineMetadata: Record<string, MachineMetadata>;
|
|
27
|
-
machines: MachineSnapshot[];
|
|
28
|
-
selectedSessionId?: string;
|
|
29
|
-
sessionMetadata: Record<string, SessionMetadata>;
|
|
30
|
-
sessionPages: Record<string, ControllerLiveSessionPageState>;
|
|
31
|
-
sessions: SessionSnapshot[];
|
|
32
|
-
snapshotHydrating: boolean;
|
|
33
|
-
snapshotReady: boolean;
|
|
34
|
-
};
|
|
35
|
-
export type ControllerLiveEvent = {
|
|
36
|
-
type: "change";
|
|
37
|
-
state: ControllerLiveState;
|
|
38
|
-
} | {
|
|
39
|
-
type: "connection";
|
|
40
|
-
state: ControllerLiveState["connectionState"];
|
|
41
|
-
} | {
|
|
42
|
-
type: "diagnostic";
|
|
43
|
-
kind: "snapshot" | "websocket";
|
|
44
|
-
detail: Record<string, string | number | boolean>;
|
|
45
|
-
} | {
|
|
46
|
-
type: "snapshot";
|
|
47
|
-
source: string;
|
|
48
|
-
snapshot: ControllerSnapshot;
|
|
49
|
-
} | {
|
|
50
|
-
type: "machine";
|
|
51
|
-
machine: MachineSnapshot;
|
|
52
|
-
} | {
|
|
53
|
-
type: "session";
|
|
54
|
-
session: SessionSnapshot;
|
|
55
|
-
} | {
|
|
56
|
-
type: "event";
|
|
57
|
-
event: DecodedSessionEvent;
|
|
58
|
-
} | {
|
|
59
|
-
type: "ack";
|
|
60
|
-
requestId: string;
|
|
61
|
-
sessionId?: string;
|
|
62
|
-
} | {
|
|
63
|
-
type: "error";
|
|
64
|
-
message: string;
|
|
65
|
-
code?: string;
|
|
66
|
-
requestId?: string;
|
|
67
|
-
};
|
|
68
|
-
export type ControllerLiveOptions = {
|
|
69
|
-
reconnectMs?: number;
|
|
70
|
-
sessionEventPageSize?: number;
|
|
71
|
-
olderSessionEventPageSize?: number;
|
|
72
|
-
promptAckTimeoutMs?: number;
|
|
73
|
-
};
|
|
74
|
-
export type SendPromptInput = {
|
|
75
|
-
sessionId: string;
|
|
76
|
-
text: string;
|
|
77
|
-
permissionMode?: PermissionMode;
|
|
78
|
-
timeoutMs?: number;
|
|
79
|
-
};
|
|
80
|
-
export type SendPromptResult = {
|
|
81
|
-
requestId: string;
|
|
82
|
-
turnId: string;
|
|
83
|
-
};
|
|
84
|
-
export type RefreshSessionEventHistoryInput = {
|
|
85
|
-
mode?: "preserve" | "latest";
|
|
86
|
-
};
|
|
87
|
-
export interface ControllerLiveProjection {
|
|
88
|
-
start(): void;
|
|
89
|
-
stop(): void;
|
|
90
|
-
state(): ControllerLiveState;
|
|
91
|
-
subscribe(listener: (event: ControllerLiveEvent) => void): () => void;
|
|
92
|
-
refreshSnapshot(reason: string): Promise<void>;
|
|
93
|
-
selectSession(sessionId: string): Promise<void>;
|
|
94
|
-
refreshSessionEventHistory(sessionId: string, input?: RefreshSessionEventHistoryInput): Promise<void>;
|
|
95
|
-
sendPrompt(input: SendPromptInput): Promise<SendPromptResult>;
|
|
96
|
-
loadOlderSessionEvents(sessionId: string, input?: {
|
|
97
|
-
reveal?: "older" | "preserve";
|
|
98
|
-
}): Promise<void>;
|
|
99
|
-
syncRuntimeHistoryWindow(sessionId: string): Promise<boolean>;
|
|
100
|
-
}
|
|
2
|
+
import type { ControllerLiveEvent, ControllerLiveOptions, ControllerLiveProjection, ControllerLiveState, RefreshSessionEventHistoryInput, SendPromptInput, SendPromptResult } from "./live-types.js";
|
|
3
|
+
export type { ControllerLiveEvent, ControllerLiveOptions, ControllerLiveProjection, ControllerLiveSessionPageState, ControllerLiveState, RefreshSessionEventHistoryInput, SendPromptInput, SendPromptResult, } from "./live-types.js";
|
|
101
4
|
export declare class ControllerLiveProjectionImpl implements ControllerLiveProjection {
|
|
102
5
|
private readonly client;
|
|
103
6
|
private readonly listeners;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { encryptJson, randomId, } from "../../shared/dist/index.js";
|
|
2
2
|
import { ControllerClientError, ControllerCommandError } from "./errors.js";
|
|
3
|
-
import {
|
|
3
|
+
import { cloneLiveState, errorMessage, nonEmptyLiveString, sleep, upsertById, } from "./live-state.js";
|
|
4
|
+
import { hasVisibleTranscriptPayload, isAuthoritativeSessionModel, isSelectedSessionHeadMaterialized, newestEventIdForSession, oldestEventIdForSession, runtimeHistoricalBackfillCanContinue, runtimeHistoricalBackfillCompleted, runtimeHistoricalBackfillKnown, selectedSessionHistoryHead, selectedSessionProjectionHead, sessionEventCount, sessionHasHistoricalBackfillEvents, sessionHasMissingHistoricalPrefix, } from "./live-session-state.js";
|
|
5
|
+
import { shouldHoldHistoricalEventForOlderPage } from "./session-event-order.js";
|
|
4
6
|
import { exactReplaceSessionEvents, mergeAuthoritativeSessionEventsByIdentity, mergeLatestPagePreservingLoadedOlderEvents, mergeSessionEventsById, } from "./session-event-window.js";
|
|
5
7
|
import { subscribe as subscribeToRelay } from "./transport.js";
|
|
6
8
|
import { wsUrl } from "./http.js";
|
|
@@ -67,7 +69,7 @@ export class ControllerLiveProjectionImpl {
|
|
|
67
69
|
this.setState({ connectionState: "offline" });
|
|
68
70
|
}
|
|
69
71
|
state() {
|
|
70
|
-
return
|
|
72
|
+
return cloneLiveState(this.stateValue);
|
|
71
73
|
}
|
|
72
74
|
subscribe(listener) {
|
|
73
75
|
this.listeners.add(listener);
|
|
@@ -136,8 +138,8 @@ export class ControllerLiveProjectionImpl {
|
|
|
136
138
|
await this.refreshSelectedSessionEventHistory(sessionId, input);
|
|
137
139
|
}
|
|
138
140
|
async sendPrompt(input) {
|
|
139
|
-
const sessionId =
|
|
140
|
-
const text =
|
|
141
|
+
const sessionId = nonEmptyLiveString(input.sessionId, "sessionId");
|
|
142
|
+
const text = nonEmptyLiveString(input.text, "text");
|
|
141
143
|
await this.ensureSocketOpen();
|
|
142
144
|
const socket = this.socket;
|
|
143
145
|
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
|
@@ -804,15 +806,7 @@ export class ControllerLiveProjectionImpl {
|
|
|
804
806
|
void this.loadSelectedSessionEventHistory(session, { force: true });
|
|
805
807
|
}
|
|
806
808
|
selectedSessionHeadMaterialized(session) {
|
|
807
|
-
|
|
808
|
-
if (!currentHead)
|
|
809
|
-
return true;
|
|
810
|
-
const localEvents = this.stateValue.events.filter((event) => event.sessionId === session.id);
|
|
811
|
-
const eventIdMatch = /^event:(\d+)$/.exec(currentHead);
|
|
812
|
-
if (eventIdMatch)
|
|
813
|
-
return localEvents.some((event) => event.id === Number(eventIdMatch[1]));
|
|
814
|
-
return localEvents.some((event) => event.turnId === currentHead ||
|
|
815
|
-
(event.decoded?.type === "done" && (event.decoded.currentHead === currentHead || event.decoded.lastTurnId === currentHead)));
|
|
809
|
+
return isSelectedSessionHeadMaterialized(session, this.stateValue.events);
|
|
816
810
|
}
|
|
817
811
|
clearUninitializedAuthoritativeSnapshotSessions(sessions, sessionFences) {
|
|
818
812
|
for (const session of sessions) {
|
|
@@ -905,7 +899,7 @@ export class ControllerLiveProjectionImpl {
|
|
|
905
899
|
});
|
|
906
900
|
}
|
|
907
901
|
isAuthoritativeSession(session) {
|
|
908
|
-
return session
|
|
902
|
+
return isAuthoritativeSessionModel(session);
|
|
909
903
|
}
|
|
910
904
|
isAuthoritativeSnapshotSession(session) {
|
|
911
905
|
return this.isAuthoritativeSession(session) ||
|
|
@@ -941,42 +935,29 @@ export class ControllerLiveProjectionImpl {
|
|
|
941
935
|
}
|
|
942
936
|
runtimeHistoricalBackfillCanContinue(sessionId) {
|
|
943
937
|
const metadata = this.stateValue.sessionMetadata[sessionId];
|
|
944
|
-
|
|
945
|
-
return status === "partial" || metadata?.truncatedHistoricalBackfill === true || this.sessionHasMissingHistoricalPrefix(sessionId);
|
|
938
|
+
return runtimeHistoricalBackfillCanContinue(metadata, this.sessionHasMissingHistoricalPrefix(sessionId));
|
|
946
939
|
}
|
|
947
940
|
runtimeHistoricalBackfillKnown(sessionId) {
|
|
948
|
-
|
|
949
|
-
return Boolean(metadata &&
|
|
950
|
-
(metadata.historicalBackfillStatus !== undefined ||
|
|
951
|
-
metadata.historicalBackfill !== undefined ||
|
|
952
|
-
metadata.historicalBackfillCursor !== undefined ||
|
|
953
|
-
metadata.truncatedHistoricalBackfill !== undefined));
|
|
941
|
+
return runtimeHistoricalBackfillKnown(this.stateValue.sessionMetadata[sessionId]);
|
|
954
942
|
}
|
|
955
943
|
runtimeHistoricalBackfillCompleted(sessionId) {
|
|
956
|
-
|
|
957
|
-
const status = metadata?.historicalBackfillStatus ?? metadata?.historicalBackfill;
|
|
958
|
-
return status === "completed" || metadata?.historicalBackfillCursor === 0;
|
|
944
|
+
return runtimeHistoricalBackfillCompleted(this.stateValue.sessionMetadata[sessionId]);
|
|
959
945
|
}
|
|
960
946
|
oldestEventIdForSession(sessionId) {
|
|
961
|
-
return
|
|
947
|
+
return oldestEventIdForSession(this.stateValue.events, sessionId);
|
|
962
948
|
}
|
|
963
949
|
newestEventIdForSession(sessionId) {
|
|
964
|
-
return
|
|
950
|
+
return newestEventIdForSession(this.stateValue.events, sessionId);
|
|
965
951
|
}
|
|
966
952
|
sessionEventCount(sessionId) {
|
|
967
|
-
return this.stateValue.events
|
|
953
|
+
return sessionEventCount(this.stateValue.events, sessionId);
|
|
968
954
|
}
|
|
969
955
|
sessionHasHistoricalBackfillEvents(sessionId) {
|
|
970
|
-
return this.stateValue.events
|
|
956
|
+
return sessionHasHistoricalBackfillEvents(this.stateValue.events, sessionId);
|
|
971
957
|
}
|
|
972
958
|
sessionHasMissingHistoricalPrefix(sessionId) {
|
|
973
959
|
const session = this.stateValue.sessions.find((item) => item.id === sessionId);
|
|
974
|
-
|
|
975
|
-
return false;
|
|
976
|
-
const turnSeqs = this.stateValue.events
|
|
977
|
-
.filter((event) => event.sessionId === sessionId && event.messageId?.startsWith("hist_") && typeof event.turnSeq === "number")
|
|
978
|
-
.map((event) => event.turnSeq);
|
|
979
|
-
return turnSeqs.length > 0 && Math.min(...turnSeqs) > 0;
|
|
960
|
+
return sessionHasMissingHistoricalPrefix(session, this.stateValue.events);
|
|
980
961
|
}
|
|
981
962
|
captureSessionHistoryFence(sessionId, session = this.stateValue.sessions.find((item) => item.id === sessionId), options = {}) {
|
|
982
963
|
return {
|
|
@@ -1073,45 +1054,3 @@ export class ControllerLiveProjectionImpl {
|
|
|
1073
1054
|
this.emit({ type: "error", message: errorMessage(error), requestId });
|
|
1074
1055
|
}
|
|
1075
1056
|
}
|
|
1076
|
-
function upsertById(items, next) {
|
|
1077
|
-
const copy = [...items];
|
|
1078
|
-
const index = copy.findIndex((item) => item.id === next.id);
|
|
1079
|
-
if (index >= 0)
|
|
1080
|
-
copy[index] = next;
|
|
1081
|
-
else
|
|
1082
|
-
copy.unshift(next);
|
|
1083
|
-
return copy;
|
|
1084
|
-
}
|
|
1085
|
-
function selectedSessionHistoryHead(session) {
|
|
1086
|
-
return [session.currentHead ?? "", session.lastTurnId ?? "", session.status, String(session.updatedAt)].join(":");
|
|
1087
|
-
}
|
|
1088
|
-
function selectedSessionProjectionHead(session) {
|
|
1089
|
-
return [session.currentHead ?? "", session.lastTurnId ?? ""].join(":");
|
|
1090
|
-
}
|
|
1091
|
-
function hasVisibleTranscriptPayload(event) {
|
|
1092
|
-
const type = event.decoded?.type;
|
|
1093
|
-
return type === "user_prompt" || type === "text_delta" || type === "tool_call";
|
|
1094
|
-
}
|
|
1095
|
-
function cloneState(state) {
|
|
1096
|
-
return {
|
|
1097
|
-
...state,
|
|
1098
|
-
events: [...state.events],
|
|
1099
|
-
machineMetadata: { ...state.machineMetadata },
|
|
1100
|
-
machines: [...state.machines],
|
|
1101
|
-
sessionMetadata: { ...state.sessionMetadata },
|
|
1102
|
-
sessionPages: Object.fromEntries(Object.entries(state.sessionPages).map(([key, value]) => [key, { ...value }])),
|
|
1103
|
-
sessions: [...state.sessions],
|
|
1104
|
-
};
|
|
1105
|
-
}
|
|
1106
|
-
function sleep(ms) {
|
|
1107
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1108
|
-
}
|
|
1109
|
-
function nonEmpty(value, label) {
|
|
1110
|
-
const trimmed = value.trim();
|
|
1111
|
-
if (!trimmed)
|
|
1112
|
-
throw new ControllerClientError(`${label} must be a non-empty string`, "INVALID_ARGUMENT");
|
|
1113
|
-
return trimmed;
|
|
1114
|
-
}
|
|
1115
|
-
function errorMessage(error) {
|
|
1116
|
-
return error instanceof Error ? error.message : String(error);
|
|
1117
|
-
}
|
|
@@ -143,6 +143,9 @@ export type CreateSessionInput = {
|
|
|
143
143
|
export type RunOptions = {
|
|
144
144
|
wait?: boolean;
|
|
145
145
|
permissionMode?: PermissionMode;
|
|
146
|
+
requestId?: string;
|
|
147
|
+
turnId?: string;
|
|
148
|
+
promptHash?: string;
|
|
146
149
|
waitTimeoutMs?: number;
|
|
147
150
|
ackTimeoutMs?: number;
|
|
148
151
|
runtimeTimeoutMs?: number;
|
|
@@ -204,7 +207,7 @@ export type WaitInput = {
|
|
|
204
207
|
export type CollectInput = {
|
|
205
208
|
before?: EventCursor;
|
|
206
209
|
beforeCursor?: string;
|
|
207
|
-
since?: EventCursor;
|
|
210
|
+
since?: EventCursor | string;
|
|
208
211
|
limit?: number;
|
|
209
212
|
turnId?: string;
|
|
210
213
|
};
|
|
@@ -9,6 +9,7 @@ export declare function requireNonEmpty(value: string, field: string): string;
|
|
|
9
9
|
export declare function matchesWaitCondition(session: SessionSnapshot, until: WaitCondition): boolean;
|
|
10
10
|
export declare function isWaitCondition(value: unknown): value is WaitCondition;
|
|
11
11
|
export declare function parseEventCursor(value: EventCursor | undefined): number | undefined;
|
|
12
|
+
export declare function parseSinceEventCursor(value: EventCursor | string | undefined): number | string | undefined;
|
|
12
13
|
export declare function parseEventLimit(value: number | undefined): number | undefined;
|
|
13
14
|
export declare function parsePositiveDuration(value: number, field: string): number;
|
|
14
15
|
export declare function sleep(ms: number): Promise<void>;
|
|
@@ -69,6 +69,17 @@ export function parseEventCursor(value) {
|
|
|
69
69
|
}
|
|
70
70
|
return Number(value.slice("cursor_".length));
|
|
71
71
|
}
|
|
72
|
+
export function parseSinceEventCursor(value) {
|
|
73
|
+
if (value === undefined)
|
|
74
|
+
return undefined;
|
|
75
|
+
if (typeof value === "number")
|
|
76
|
+
return parseEventCursor(value);
|
|
77
|
+
if (/^cursor_\d+$/.test(value))
|
|
78
|
+
return parseEventCursor(value);
|
|
79
|
+
if (value.trim())
|
|
80
|
+
return value;
|
|
81
|
+
throw new ControllerClientError(`Invalid event cursor: ${value}`, "INVALID_ARGUMENT");
|
|
82
|
+
}
|
|
72
83
|
export function parseEventLimit(value) {
|
|
73
84
|
if (value === undefined)
|
|
74
85
|
return undefined;
|
|
@@ -17,6 +17,7 @@ export type ControllerClientMessage = {
|
|
|
17
17
|
turnId: string;
|
|
18
18
|
encryptedPrompt: EncryptedEnvelope;
|
|
19
19
|
encryptedMetadata?: EncryptedEnvelope;
|
|
20
|
+
promptHash?: string;
|
|
20
21
|
permissionMode?: PermissionMode;
|
|
21
22
|
timeoutMs?: number;
|
|
22
23
|
} | {
|
|
@@ -441,6 +442,7 @@ export type JsonEnvelope<T = unknown> = {
|
|
|
441
442
|
code: string;
|
|
442
443
|
message: string;
|
|
443
444
|
capability?: string;
|
|
445
|
+
retryable?: boolean;
|
|
444
446
|
};
|
|
445
447
|
meta: {
|
|
446
448
|
requestId?: string;
|
|
@@ -450,6 +452,7 @@ export type JsonEnvelope<T = unknown> = {
|
|
|
450
452
|
activeTurnId?: string;
|
|
451
453
|
latestCompletedTurnId?: string;
|
|
452
454
|
commandAcked?: boolean;
|
|
455
|
+
rawStatus?: string;
|
|
453
456
|
};
|
|
454
457
|
};
|
|
455
458
|
export declare function parseClientMessage(value: unknown): ClientMessage;
|
|
@@ -19,6 +19,7 @@ const controllerMessageSchema = z.discriminatedUnion("type", [
|
|
|
19
19
|
turnId: z.string().min(1),
|
|
20
20
|
encryptedPrompt: encryptedEnvelopeSchema,
|
|
21
21
|
encryptedMetadata: encryptedEnvelopeSchema.optional(),
|
|
22
|
+
promptHash: z.string().min(1).optional(),
|
|
22
23
|
permissionMode: z.enum(["approve-all", "approve-reads", "deny-all"]).optional(),
|
|
23
24
|
timeoutMs: z.number().positive().optional(),
|
|
24
25
|
}),
|