@fungi.computer/parakeet 0.4.0 → 0.6.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.
- package/README.md +17 -1
- package/dist/client-contributions.d.ts +1 -0
- package/dist/commands/awaiting-driver.d.ts +20 -0
- package/dist/commands/awaiting-driver.js +121 -0
- package/dist/commands/builtins.js +6 -4
- package/dist/commands/interactions.d.ts +25 -0
- package/dist/commands/interactions.js +76 -2
- package/dist/commands/run.js +3 -3
- package/dist/commands/slash.d.ts +7 -3
- package/dist/commands/slash.js +2 -0
- package/dist/commands/use-slash-commands.d.ts +13 -0
- package/dist/commands/use-slash-commands.js +58 -1
- package/dist/components/ChatComposer.d.ts +2 -0
- package/dist/components/ChatComposer.js +158 -40
- package/dist/components/ClientRows.js +42 -26
- package/dist/components/CodeBlock.js +3 -2
- package/dist/components/CompletionMenu.d.ts +54 -0
- package/dist/components/CompletionMenu.js +52 -0
- package/dist/components/ComposerModelKey.d.ts +11 -0
- package/dist/components/ComposerModelKey.js +9 -0
- package/dist/components/ComposerShell.d.ts +47 -0
- package/dist/components/ComposerShell.js +29 -0
- package/dist/components/DecisionComposer.d.ts +30 -0
- package/dist/components/DecisionComposer.js +145 -0
- package/dist/components/InteractionPanel.d.ts +10 -1
- package/dist/components/InteractionPanel.js +9 -4
- package/dist/components/SlashMenu.d.ts +1 -3
- package/dist/components/SlashMenu.js +5 -13
- package/dist/components/TimelineItem.d.ts +1 -0
- package/dist/components/TimelineItem.js +4 -4
- package/dist/components/ToolGroup.d.ts +1 -0
- package/dist/components/ToolGroup.js +8 -8
- package/dist/conversation-opening.d.ts +1 -1
- package/dist/conversation-opening.js +2 -2
- package/dist/conversation.d.ts +5 -1
- package/dist/conversation.js +84 -29
- package/dist/core/approval-decision.d.ts +198 -0
- package/dist/core/approval-decision.js +291 -0
- package/dist/core/awaiting-answer.d.ts +229 -0
- package/dist/core/awaiting-answer.js +303 -0
- package/dist/core/build-timeline.js +61 -7
- package/dist/core/client-actions.d.ts +1 -0
- package/dist/core/client-actions.js +7 -1
- package/dist/core/composer-model-name.d.ts +3 -0
- package/dist/core/composer-model-name.js +21 -0
- package/dist/core/display-text.d.ts +2 -0
- package/dist/core/display-text.js +7 -0
- package/dist/core/draft-storage.d.ts +6 -0
- package/dist/core/draft-storage.js +25 -5
- package/dist/core/session-projection.d.ts +1 -0
- package/dist/core/session-projection.js +3 -2
- package/dist/core/session-query.d.ts +8 -0
- package/dist/core/session-query.js +13 -11
- package/dist/core/session-view-state.d.ts +2 -4
- package/dist/core/session-view-state.js +27 -9
- package/dist/core/tool-groups.d.ts +3 -0
- package/dist/core/tool-groups.js +37 -1
- package/dist/core/tool-view.d.ts +6 -0
- package/dist/core/tool-view.js +33 -0
- package/dist/core/use-timeline-item-key.d.ts +3 -4
- package/dist/core/use-timeline-item-key.js +8 -14
- package/dist/headless.d.ts +10 -5
- package/dist/headless.js +9 -4
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/parakeet.d.ts +6 -2
- package/dist/parakeet.js +24 -17
- package/dist/store/types.d.ts +14 -1
- package/dist/styles.css +911 -162
- package/dist/use-approval-awaiting.d.ts +22 -0
- package/dist/use-approval-awaiting.js +81 -0
- package/dist/use-decision.d.ts +20 -0
- package/dist/use-decision.js +79 -0
- package/docs/api/commands/slash.md +30 -26
- package/docs/api/headless.md +917 -54
- package/docs/api/index.md +166 -5
- package/package.json +17 -8
- package/src/completion-menu.css +72 -0
- package/src/composer-shell.css +125 -0
- package/src/styles.css +99 -53
- package/dist/components/WaitingNotice.d.ts +0 -7
- package/dist/components/WaitingNotice.js +0 -8
package/dist/conversation.js
CHANGED
|
@@ -9,6 +9,7 @@ import { atom, createStore, } from "jotai/vanilla";
|
|
|
9
9
|
import { atomWithInfiniteQuery, atomWithMutation, atomWithQuery, } from "jotai-tanstack-query";
|
|
10
10
|
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useSyncExternalStore, } from "react";
|
|
11
11
|
import { buildDurableTimeline, buildTimelineTail, } from "./core/build-timeline.js";
|
|
12
|
+
import { overlayToolOutput } from "./core/tool-groups.js";
|
|
12
13
|
import { mergeCompactionNotices, mergeCompactions, mergeMessages, } from "./core/session-projection.js";
|
|
13
14
|
import { sessionHistoryQueryOptions, sessionQueryOptions, } from "./core/session-query.js";
|
|
14
15
|
import { sameSessionViewState, sessionActivityStatus, sessionViewState, } from "./core/session-view-state.js";
|
|
@@ -66,7 +67,7 @@ function composerStatus(snapshot, admissions, failure) {
|
|
|
66
67
|
return status;
|
|
67
68
|
if (failure !== undefined || view.kind === "error")
|
|
68
69
|
return "error";
|
|
69
|
-
if (admissions.some(({ status: state }) => state === "accepted"))
|
|
70
|
+
if (admissions.some(({ status: state }) => state === "accepted" || state === "queued-local"))
|
|
70
71
|
return "queued";
|
|
71
72
|
if (admissions.some(({ status: state }) => state === "admitting"))
|
|
72
73
|
return "sending";
|
|
@@ -100,12 +101,15 @@ function acquireDraftStorage(supplied) {
|
|
|
100
101
|
const browser = globalThis;
|
|
101
102
|
const storage = browser.localStorage;
|
|
102
103
|
return storage === undefined
|
|
103
|
-
? {
|
|
104
|
+
? {
|
|
105
|
+
status: "unavailable",
|
|
106
|
+
cause: new Error("Browser draft storage is unavailable"),
|
|
107
|
+
}
|
|
104
108
|
: { status: "available", storage };
|
|
105
109
|
}
|
|
106
|
-
catch {
|
|
110
|
+
catch (cause) {
|
|
107
111
|
// An opaque-origin sandbox denies even reading the browser's getter.
|
|
108
|
-
return { status: "unavailable" };
|
|
112
|
+
return { status: "unavailable", cause };
|
|
109
113
|
}
|
|
110
114
|
}
|
|
111
115
|
function createConversationAtoms(session, queryClient, authorOf, interactions, draftStorage, draftScope, contributions, shown) {
|
|
@@ -113,13 +117,14 @@ function createConversationAtoms(session, queryClient, authorOf, interactions, d
|
|
|
113
117
|
const saved = journal.read();
|
|
114
118
|
let mounted = false;
|
|
115
119
|
const timers = new Map();
|
|
116
|
-
const recoveryReasons = new Map(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
+
const recoveryReasons = new Map();
|
|
121
|
+
for (const pending of saved.pending)
|
|
122
|
+
if (pending.status === "admitting")
|
|
123
|
+
recoveryReasons.set(pending.requestId, "Closed before confirmation");
|
|
120
124
|
const recovering = new Set();
|
|
121
125
|
const queryAtom = atomWithQuery(() => sessionQueryOptions(session), () => queryClient);
|
|
122
126
|
const snapshotAtom = atom((get) => get(queryAtom).data?.snapshot);
|
|
127
|
+
const lastReplyAtAtom = atom((get) => get(queryAtom).data?.lastReplyAt);
|
|
123
128
|
const pinnedHistoryAtom = atom(undefined);
|
|
124
129
|
const historyBeforeAtom = atom((get) => (get(pinnedHistoryAtom) ?? get(snapshotAtom))?.oldestCursor ?? null);
|
|
125
130
|
const historyAtom = atomWithInfiniteQuery((get) => sessionHistoryQueryOptions(session, get(historyBeforeAtom)), () => queryClient);
|
|
@@ -279,10 +284,7 @@ function createConversationAtoms(session, queryClient, authorOf, interactions, d
|
|
|
279
284
|
const recallStateAtom = atom({ kind: "idle" });
|
|
280
285
|
let recallOwner;
|
|
281
286
|
const draftRevisionAtom = atom(0);
|
|
282
|
-
const rawAdmissionsAtom = atom(saved.pending
|
|
283
|
-
...pending,
|
|
284
|
-
status: "admitting",
|
|
285
|
-
})));
|
|
287
|
+
const rawAdmissionsAtom = atom(saved.pending);
|
|
286
288
|
const persistAtom = atom(null, (get, _set, state) => {
|
|
287
289
|
// Unavailable storage is session-only, not a pretend durable journal.
|
|
288
290
|
// A supplied adapter's write failures still refuse durable operations.
|
|
@@ -299,16 +301,19 @@ function createConversationAtoms(session, queryClient, authorOf, interactions, d
|
|
|
299
301
|
recall: state?.recall === null
|
|
300
302
|
? undefined
|
|
301
303
|
: (state?.recall ?? get(recallReservationAtom)),
|
|
302
|
-
pending: (state?.admissions ?? get(rawAdmissionsAtom))
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
304
|
+
pending: (state?.admissions ?? get(rawAdmissionsAtom)).flatMap(({ status, requestId, content, submittedDraft, delivery, images, restoreDraft, }) => status === "admitting" || status === "queued-local"
|
|
305
|
+
? [
|
|
306
|
+
{
|
|
307
|
+
status,
|
|
308
|
+
requestId,
|
|
309
|
+
content,
|
|
310
|
+
submittedDraft,
|
|
311
|
+
delivery,
|
|
312
|
+
images,
|
|
313
|
+
restoreDraft,
|
|
314
|
+
},
|
|
315
|
+
]
|
|
316
|
+
: []),
|
|
312
317
|
});
|
|
313
318
|
});
|
|
314
319
|
const recallAtom = atom(null, (get, set) => {
|
|
@@ -436,6 +441,24 @@ function createConversationAtoms(session, queryClient, authorOf, interactions, d
|
|
|
436
441
|
set(draftAtom, value);
|
|
437
442
|
set(admissionErrorAtom, undefined);
|
|
438
443
|
});
|
|
444
|
+
const setImagesAtom = atom(null, (get, set, images) => {
|
|
445
|
+
const next = images === undefined || images.length === 0 ? undefined : images;
|
|
446
|
+
try {
|
|
447
|
+
set(persistAtom, {
|
|
448
|
+
draftPayload: { images: next, delivery: get(draftDeliveryAtom) },
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
catch (cause) {
|
|
452
|
+
reportFailure("save draft images", cause);
|
|
453
|
+
interactions.toast({
|
|
454
|
+
variant: "error",
|
|
455
|
+
message: `Images not saved: ${errorMessage(cause)}`,
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
set(draftImagesAtom, next);
|
|
459
|
+
set(draftRevisionAtom, (revision) => revision + 1);
|
|
460
|
+
set(admissionErrorAtom, undefined);
|
|
461
|
+
});
|
|
439
462
|
/** Consume only the exact draft a command submitted. */
|
|
440
463
|
const clearSubmittedDraftAtom = atom(null, (get, set, submitted) => {
|
|
441
464
|
if (get(draftRevisionAtom) !== submitted.revision ||
|
|
@@ -516,7 +539,9 @@ function createConversationAtoms(session, queryClient, authorOf, interactions, d
|
|
|
516
539
|
hasMoreHistory: get(hasMoreHistoryAtom),
|
|
517
540
|
hasNewerHistory: get(hasNewerHistoryAtom),
|
|
518
541
|
loadingOlderHistory: get(loadingOlderHistoryAtom),
|
|
519
|
-
settled: durable.items,
|
|
542
|
+
settled: overlayToolOutput(durable.items, get(toolActivityAtom) === undefined
|
|
543
|
+
? []
|
|
544
|
+
: (get(snapshotAtom)?.toolOutput ?? [])),
|
|
520
545
|
tail: get(tailAtom),
|
|
521
546
|
};
|
|
522
547
|
return durable.model === undefined || durable.usage === undefined
|
|
@@ -616,6 +641,10 @@ function createConversationAtoms(session, queryClient, authorOf, interactions, d
|
|
|
616
641
|
set(draftDeliveryAtom, undefined);
|
|
617
642
|
set(setDraftAtom, "");
|
|
618
643
|
}
|
|
644
|
+
// The offline prompt is saved alongside the draft. Await a fresh owner
|
|
645
|
+
// baseline before attempting admission.
|
|
646
|
+
if (admission.status === "queued-local")
|
|
647
|
+
return;
|
|
619
648
|
const prompt = {
|
|
620
649
|
type: "prompt",
|
|
621
650
|
text: admission.content,
|
|
@@ -730,6 +759,20 @@ function createConversationAtoms(session, queryClient, authorOf, interactions, d
|
|
|
730
759
|
const maintainRecoveryAtom = atom(null, (get, set) => {
|
|
731
760
|
const observation = get(observationAtom).status;
|
|
732
761
|
const admissions = get(rawAdmissionsAtom);
|
|
762
|
+
if (observation === "live") {
|
|
763
|
+
if (get(admissionErrorAtom) === "offline_queue_full")
|
|
764
|
+
set(admissionErrorAtom, undefined);
|
|
765
|
+
const queued = admissions.find(({ status }) => status === "queued-local");
|
|
766
|
+
if (queued !== undefined) {
|
|
767
|
+
const sending = {
|
|
768
|
+
...queued,
|
|
769
|
+
status: "admitting",
|
|
770
|
+
};
|
|
771
|
+
set(rawAdmissionsAtom, (current) => current.map((candidate) => candidate.requestId === queued.requestId ? sending : candidate));
|
|
772
|
+
// Preserve the idempotent request id when reconnect admits the prompt.
|
|
773
|
+
set(dispatchAdmissionAtom, { admission: sending, consumeDraft: false });
|
|
774
|
+
}
|
|
775
|
+
}
|
|
733
776
|
for (const [id, timer] of timers) {
|
|
734
777
|
if (!admissions.some(({ requestId, status }) => requestId === id && status === "admitting")) {
|
|
735
778
|
clearTimeout(timer);
|
|
@@ -774,6 +817,12 @@ function createConversationAtoms(session, queryClient, authorOf, interactions, d
|
|
|
774
817
|
return;
|
|
775
818
|
const admissions = get(admissionsAtom);
|
|
776
819
|
const unresolved = admissions.filter(({ status }) => status !== "failed");
|
|
820
|
+
const offline = get(observationAtom).status !== "live";
|
|
821
|
+
if (offline &&
|
|
822
|
+
unresolved.some(({ status }) => status === "queued-local")) {
|
|
823
|
+
set(admissionErrorAtom, "offline_queue_full");
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
777
826
|
if (unresolved.length >= MAX_UNRESOLVED_LOCAL_ADMISSIONS) {
|
|
778
827
|
set(admissionErrorAtom, "queue_full");
|
|
779
828
|
return;
|
|
@@ -785,7 +834,7 @@ function createConversationAtoms(session, queryClient, authorOf, interactions, d
|
|
|
785
834
|
requestId: nanoid(),
|
|
786
835
|
submittedDraft: draft,
|
|
787
836
|
restoreDraft: true,
|
|
788
|
-
status: "admitting",
|
|
837
|
+
status: offline ? "queued-local" : "admitting",
|
|
789
838
|
};
|
|
790
839
|
set(rawAdmissionsAtom, [...admissions, admission]);
|
|
791
840
|
set(latestSubmissionIdAtom, admission.requestId);
|
|
@@ -991,6 +1040,7 @@ function createConversationAtoms(session, queryClient, authorOf, interactions, d
|
|
|
991
1040
|
jumpToLatestAtom,
|
|
992
1041
|
loadOlderHistoryAtom,
|
|
993
1042
|
loadNewerHistoryAtom,
|
|
1043
|
+
lastReplyAtAtom,
|
|
994
1044
|
observationAtom,
|
|
995
1045
|
observedCommandIdsAtom,
|
|
996
1046
|
rawAdmissionsAtom,
|
|
@@ -1002,6 +1052,7 @@ function createConversationAtoms(session, queryClient, authorOf, interactions, d
|
|
|
1002
1052
|
reconnectAtom,
|
|
1003
1053
|
retryAtom,
|
|
1004
1054
|
setDraftAtom,
|
|
1055
|
+
setImagesAtom,
|
|
1005
1056
|
sendAtom,
|
|
1006
1057
|
snapshotAtom,
|
|
1007
1058
|
statusAtom,
|
|
@@ -1080,11 +1131,8 @@ export function ParakeetProvider({ children, session, clients = noClients, inter
|
|
|
1080
1131
|
}, [scope, onToast]);
|
|
1081
1132
|
useEffect(() => {
|
|
1082
1133
|
if (acquiredDraftStorage.status === "unavailable")
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
message: "Drafts are session-only because browser storage is unavailable. They will be lost when this conversation closes.",
|
|
1086
|
-
});
|
|
1087
|
-
}, [scope, acquiredDraftStorage]);
|
|
1134
|
+
reportFailure("acquire draft storage", acquiredDraftStorage.cause);
|
|
1135
|
+
}, [acquiredDraftStorage]);
|
|
1088
1136
|
useEffect(() => {
|
|
1089
1137
|
const reconcile = () => {
|
|
1090
1138
|
scope.store.set(scope.atoms.reconcileQueuedCancellationAtom);
|
|
@@ -1180,6 +1228,7 @@ export function useComposer() {
|
|
|
1180
1228
|
const failure = useStoreAtom(atoms.failureAtom);
|
|
1181
1229
|
const admissionError = useStoreAtom(atoms.admissionErrorAtom);
|
|
1182
1230
|
const setDraft = useStoreSetter(atoms.setDraftAtom);
|
|
1231
|
+
const setImages = useStoreSetter(atoms.setImagesAtom);
|
|
1183
1232
|
const checkpointDraft = useStoreSetter(atoms.checkpointDraftAtom);
|
|
1184
1233
|
const send = useStoreSetter(atoms.sendAtom);
|
|
1185
1234
|
const submitCommand = useStoreSetter(atoms.submitCommandAtom);
|
|
@@ -1202,6 +1251,7 @@ export function useComposer() {
|
|
|
1202
1251
|
retry: retryLatest,
|
|
1203
1252
|
send,
|
|
1204
1253
|
setDraft,
|
|
1254
|
+
setImages,
|
|
1205
1255
|
submitCommand,
|
|
1206
1256
|
}), [
|
|
1207
1257
|
admissionError,
|
|
@@ -1216,6 +1266,7 @@ export function useComposer() {
|
|
|
1216
1266
|
retryLatest,
|
|
1217
1267
|
send,
|
|
1218
1268
|
setDraft,
|
|
1269
|
+
setImages,
|
|
1219
1270
|
submitCommand,
|
|
1220
1271
|
]);
|
|
1221
1272
|
}
|
|
@@ -1250,6 +1301,10 @@ export function useStatus() {
|
|
|
1250
1301
|
viewState,
|
|
1251
1302
|
}), [commandId, context, observation, reconnect, status, viewState]);
|
|
1252
1303
|
}
|
|
1304
|
+
/** Time of the last delivered watch snapshot or frame, not an SSE comment. */
|
|
1305
|
+
export function useLastSessionReplyAt() {
|
|
1306
|
+
return useStoreAtom(useConversationScope().atoms.lastReplyAtAtom);
|
|
1307
|
+
}
|
|
1253
1308
|
/** Presentation admitted once from the selected Session's already observed slots. */
|
|
1254
1309
|
export function useClientContributions() {
|
|
1255
1310
|
return useStoreAtom(useConversationScope().contributions);
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import type { WhistleInteraction } from "@fungi.computer/whistle";
|
|
2
|
+
/** The ledger facts an approval card reads; the approvals ledger's own view satisfies it. */
|
|
3
|
+
export type ApprovalFacts = Readonly<{
|
|
4
|
+
/** The durable record. */
|
|
5
|
+
record: Readonly<{
|
|
6
|
+
/** Identity of the record. */
|
|
7
|
+
recordId: string;
|
|
8
|
+
/** Changes whenever the record does; an answer names the revision it saw. */
|
|
9
|
+
revision: number;
|
|
10
|
+
/** Who asked. */
|
|
11
|
+
requester: Readonly<{
|
|
12
|
+
/** Name shown for the requesting Agent. */
|
|
13
|
+
displayName: string;
|
|
14
|
+
/** The Agent, when one asked. */
|
|
15
|
+
agentId: string | null;
|
|
16
|
+
/** The Session, when one asked. */
|
|
17
|
+
sessionId: string | null;
|
|
18
|
+
/** The principal that holds the request. */
|
|
19
|
+
principalId: string;
|
|
20
|
+
}>;
|
|
21
|
+
/** The tool's module. */
|
|
22
|
+
module: string;
|
|
23
|
+
/** The tool's operation. */
|
|
24
|
+
operation: string;
|
|
25
|
+
/** Whether the tool writes inside Fungi or reaches outside it. */
|
|
26
|
+
access: string;
|
|
27
|
+
/** What the tool would be given, as the ledger describes it. */
|
|
28
|
+
description: Readonly<{
|
|
29
|
+
/** Size of the input in bytes. */
|
|
30
|
+
inputBytes: number;
|
|
31
|
+
/** Digest of the input. */
|
|
32
|
+
inputSha256: string;
|
|
33
|
+
}>;
|
|
34
|
+
/** Whether the effect can be undone. */
|
|
35
|
+
reversibility: string;
|
|
36
|
+
/** When the request lapses. */
|
|
37
|
+
expiresAt: number;
|
|
38
|
+
}>;
|
|
39
|
+
/** The holder may answer it. */
|
|
40
|
+
canAnswer: boolean;
|
|
41
|
+
}>;
|
|
42
|
+
/** What the host knows about a command the Agent asked to run outside the sandbox. */
|
|
43
|
+
export type ApprovalReview = Readonly<{
|
|
44
|
+
/** The command line. */
|
|
45
|
+
command: string;
|
|
46
|
+
/** Where it would run. */
|
|
47
|
+
cwd: string;
|
|
48
|
+
/** Why the Agent says it needs it. */
|
|
49
|
+
justification: string;
|
|
50
|
+
/** A direct program, or shell text. */
|
|
51
|
+
form: "direct" | "shell";
|
|
52
|
+
/** A command, or an extension install. */
|
|
53
|
+
kind: "exec" | "extension-install";
|
|
54
|
+
/** The program and its arguments, when direct. */
|
|
55
|
+
argv?: readonly string[];
|
|
56
|
+
/** The command words an Always-allow rule would match. */
|
|
57
|
+
prefix?: readonly string[];
|
|
58
|
+
/** A warning shown when no rule is offered. */
|
|
59
|
+
warning?: string;
|
|
60
|
+
/** Everything an Always-allow rule would cover. */
|
|
61
|
+
alwaysAllowCoverage?: string;
|
|
62
|
+
/** Why no Always-allow rule is offered. */
|
|
63
|
+
alwaysAllowUnavailableReason?: string;
|
|
64
|
+
/** What a reusable rule risks. */
|
|
65
|
+
risk?: Readonly<{
|
|
66
|
+
/** The risk in one sentence, named once above the confirmation. */
|
|
67
|
+
summary: string;
|
|
68
|
+
/** The risk in full, shown under Details. */
|
|
69
|
+
detail: string;
|
|
70
|
+
}>;
|
|
71
|
+
}>;
|
|
72
|
+
/** One labelled value in a card's facts or details. */
|
|
73
|
+
export type ApprovalEntry = Readonly<{
|
|
74
|
+
/** What the value is. */
|
|
75
|
+
label: string;
|
|
76
|
+
/** The value. */
|
|
77
|
+
value: string;
|
|
78
|
+
}>;
|
|
79
|
+
/** The words an approval prompt carries above its choices. */
|
|
80
|
+
export type ApprovalContent = Readonly<{
|
|
81
|
+
/** The title. */
|
|
82
|
+
title: string;
|
|
83
|
+
/** One sentence under it. */
|
|
84
|
+
summary?: string | undefined;
|
|
85
|
+
/** Short labelled lines. */
|
|
86
|
+
facts?: readonly ApprovalEntry[] | undefined;
|
|
87
|
+
/** Long text, when the command is too long for a title. */
|
|
88
|
+
body?: string | undefined;
|
|
89
|
+
/** A short name for `body`. */
|
|
90
|
+
bodyLabel?: string | undefined;
|
|
91
|
+
/** Everything else, shown on demand. */
|
|
92
|
+
details?: readonly ApprovalEntry[] | undefined;
|
|
93
|
+
}>;
|
|
94
|
+
/** Everything one approval shows, and what Always allow would confirm. */
|
|
95
|
+
export type ApprovalCard = Readonly<{
|
|
96
|
+
/** The first prompt's words. */
|
|
97
|
+
content: ApprovalContent;
|
|
98
|
+
/** The Always-allow choice, when a rule is offered. */
|
|
99
|
+
always?: string | undefined;
|
|
100
|
+
/** What the Always-allow confirmation says. */
|
|
101
|
+
confirmation?: (ApprovalContent & {
|
|
102
|
+
/** The rule's coverage, whole. */
|
|
103
|
+
body: string;
|
|
104
|
+
}) | undefined;
|
|
105
|
+
/** The risk named above the confirmation, once. */
|
|
106
|
+
confirmWarning?: string | undefined;
|
|
107
|
+
/** The confirming choice. */
|
|
108
|
+
confirmYes?: string | undefined;
|
|
109
|
+
}>;
|
|
110
|
+
/**
|
|
111
|
+
* The words of one approval. A connected tool gets its effect; a command the
|
|
112
|
+
* Agent wants to run outside the sandbox gets its folder, reason and rule.
|
|
113
|
+
* `home` shortens paths inside it to `~`; `now` dates the expiry.
|
|
114
|
+
*/
|
|
115
|
+
export declare function approvalCardCopy(view: ApprovalFacts, review: ApprovalReview | undefined, options: Readonly<{
|
|
116
|
+
width: number;
|
|
117
|
+
home: string;
|
|
118
|
+
now: number;
|
|
119
|
+
}>): ApprovalCard;
|
|
120
|
+
/** What an answer was refused for, in words for the person. */
|
|
121
|
+
export declare function approvalRefusal(reason: string, decision: "approve" | "deny" | "always-allow"): string;
|
|
122
|
+
/** What the person decided. */
|
|
123
|
+
export type ApprovalDecision = "approve" | "deny" | "always-allow";
|
|
124
|
+
/** A decision, pinned to the revision the person saw. */
|
|
125
|
+
export type ApprovalAnswerInput = Readonly<{
|
|
126
|
+
/** The record being answered. */
|
|
127
|
+
recordId: string;
|
|
128
|
+
/** Approve once, deny, or approve and keep a rule. */
|
|
129
|
+
decision: ApprovalDecision;
|
|
130
|
+
/** The revision on screen; a changed record refuses the answer. */
|
|
131
|
+
expectedRevision: number;
|
|
132
|
+
/** Why it was denied, when the person said. */
|
|
133
|
+
denialNote?: string;
|
|
134
|
+
}>;
|
|
135
|
+
/** How the owner took an answer; a refusal names its reason. */
|
|
136
|
+
export type ApprovalAnswerResult = Readonly<{
|
|
137
|
+
/** `refused`, or whatever else the owner calls an accepted answer. */
|
|
138
|
+
kind: string;
|
|
139
|
+
/** Why it was refused. */
|
|
140
|
+
reason?: string;
|
|
141
|
+
}>;
|
|
142
|
+
/** Where approvals come from and where answers go. */
|
|
143
|
+
export type ApprovalSource = Readonly<{
|
|
144
|
+
/** The record being shown, when there is one. */
|
|
145
|
+
snapshot: () => ApprovalFacts | undefined;
|
|
146
|
+
/** Observe the record changing. */
|
|
147
|
+
subscribe: (listener: () => void) => () => void;
|
|
148
|
+
/** What the host knows about the command, for an escape review. */
|
|
149
|
+
review?: ApprovalReview | undefined;
|
|
150
|
+
/** Answer it; a refusal names its reason. */
|
|
151
|
+
answer: (input: ApprovalAnswerInput) => Promise<ApprovalAnswerResult>;
|
|
152
|
+
/** Let the whole family of this tool through from now on, where the surface can. */
|
|
153
|
+
family?: Readonly<{
|
|
154
|
+
/** The choice's name. */
|
|
155
|
+
label: string;
|
|
156
|
+
/** Turn the rule on; false when it was refused. */
|
|
157
|
+
enable: () => Promise<boolean>;
|
|
158
|
+
}> | undefined;
|
|
159
|
+
}>;
|
|
160
|
+
/** The answer was sent. */
|
|
161
|
+
export type ApprovalAnswered = Readonly<{
|
|
162
|
+
/** Discriminant. */
|
|
163
|
+
kind: "answered";
|
|
164
|
+
}>;
|
|
165
|
+
/** The answer was refused, or could not be sent. */
|
|
166
|
+
export type ApprovalRefused = Readonly<{
|
|
167
|
+
/** Discriminant. */
|
|
168
|
+
kind: "refused";
|
|
169
|
+
/** What to tell the person. */
|
|
170
|
+
message: string;
|
|
171
|
+
}>;
|
|
172
|
+
/** The record changed or went away before an answer was sent. */
|
|
173
|
+
export type ApprovalAbandoned = Readonly<{
|
|
174
|
+
/** Discriminant. */
|
|
175
|
+
kind: "abandoned";
|
|
176
|
+
}>;
|
|
177
|
+
/** How asking ended. */
|
|
178
|
+
export type ApprovalOutcome = ApprovalAnswered | ApprovalRefused | ApprovalAbandoned;
|
|
179
|
+
/**
|
|
180
|
+
* Ask the person about one approval through a conversation's decision
|
|
181
|
+
* prompts, then send the answer. Nothing is sent until Enter answers, Always
|
|
182
|
+
* allow and the family rule each need a second, separate confirmation whose
|
|
183
|
+
* default is Back, and an
|
|
184
|
+
* answer is only sent while the record is still the one that was shown.
|
|
185
|
+
*/
|
|
186
|
+
export declare function askApproval(interaction: WhistleInteraction, input: Readonly<{
|
|
187
|
+
view: ApprovalFacts;
|
|
188
|
+
source: ApprovalSource;
|
|
189
|
+
home: string;
|
|
190
|
+
width: number;
|
|
191
|
+
now: number;
|
|
192
|
+
/** The longest denial note. */
|
|
193
|
+
noteLimit: number;
|
|
194
|
+
/** The record on screen is still the current one. */
|
|
195
|
+
stillCurrent: () => boolean;
|
|
196
|
+
/** An error reached the person as a refusal; report its cause. */
|
|
197
|
+
onFailure: (cause: unknown) => void;
|
|
198
|
+
}>): Promise<ApprovalOutcome>;
|