@ai-ecoverse/slicc-agent 5.5.4 → 5.7.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 +10 -4
- package/dist/agent.js +4 -0
- package/dist/agent.js.map +2 -2
- package/dist/approvals/index.js +87 -45
- package/dist/approvals/index.js.map +2 -2
- package/dist/approvals/judge.js +7 -1
- package/dist/approvals/judge.js.map +2 -2
- package/dist/approvals/record.js +7 -2
- package/dist/approvals/record.js.map +2 -2
- package/dist/approvals/sudo-client.js +112 -0
- package/dist/approvals/sudo-client.js.map +7 -0
- package/dist/approvals/sudo.js +166 -0
- package/dist/approvals/sudo.js.map +7 -0
- package/dist/guests/attribution.js +19 -0
- package/dist/guests/attribution.js.map +2 -2
- package/dist/guests/client.js +4 -3
- package/dist/guests/client.js.map +2 -2
- package/dist/guests/index.js +116 -63
- package/dist/guests/index.js.map +2 -2
- package/dist/guests/review.js +262 -0
- package/dist/guests/review.js.map +7 -0
- package/dist/guests/seats.js.map +2 -2
- package/dist/guests/serve.js +13 -13
- package/dist/guests/serve.js.map +2 -2
- package/dist/host.js +48 -14
- package/dist/host.js.map +2 -2
- package/dist/index.js.map +2 -2
- package/dist/owner.js +22 -1
- package/dist/owner.js.map +2 -2
- package/dist/page.js +2 -0
- package/dist/page.js.map +2 -2
- package/dist/services.js +10 -0
- package/dist/services.js.map +2 -2
- package/dist/serving.js +2 -0
- package/dist/serving.js.map +2 -2
- package/dist/spectrum/agent-port.js +1 -1
- package/dist/spectrum/agent-port.js.map +2 -2
- package/dist/spectrum/messages.js +50 -6
- package/dist/spectrum/messages.js.map +2 -2
- package/dist/spectrum/proposals.js +7 -3
- package/dist/spectrum/proposals.js.map +2 -2
- package/dist/types/agent.d.ts +2 -0
- package/dist/types/approvals/index.d.ts +11 -3
- package/dist/types/approvals/judge.d.ts +10 -0
- package/dist/types/approvals/record.d.ts +6 -0
- package/dist/types/approvals/sudo-client.d.ts +12 -0
- package/dist/types/approvals/sudo.d.ts +52 -0
- package/dist/types/guests/attribution.d.ts +16 -0
- package/dist/types/guests/index.d.ts +7 -4
- package/dist/types/guests/review.d.ts +68 -0
- package/dist/types/guests/seats.d.ts +12 -1
- package/dist/types/guests/serve.d.ts +3 -3
- package/dist/types/host.d.ts +5 -2
- package/dist/types/index.d.ts +2 -1
- package/dist/types/owner.d.ts +4 -0
- package/dist/types/page.d.ts +2 -0
- package/dist/types/services.d.ts +3 -1
- package/dist/types/serving.d.ts +2 -0
- package/dist/types/spectrum/messages.d.ts +2 -0
- package/dist/types/spectrum/proposals.d.ts +2 -1
- package/dist/worker.js +7 -1
- package/dist/worker.js.map +2 -2
- package/package.json +2 -2
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { BACKGROUND_CONTEXT, withCancel } from "@earendil-works/chord/context";
|
|
2
|
+
import {
|
|
3
|
+
defineDoc,
|
|
4
|
+
defineExtension,
|
|
5
|
+
defineTask
|
|
6
|
+
} from "@earendil-works/pi-durable";
|
|
7
|
+
import { ApprovalsDoc } from "../approvals/record.js";
|
|
8
|
+
import {
|
|
9
|
+
GUEST_INPUT_KIND,
|
|
10
|
+
guestContent,
|
|
11
|
+
REVIEWED_KIND
|
|
12
|
+
} from "./attribution.js";
|
|
13
|
+
import { GUEST_REQUEST } from "./guard.js";
|
|
14
|
+
import { GuestsDoc, liveSeat } from "./seats.js";
|
|
15
|
+
const GuestReviewsDoc = defineDoc({
|
|
16
|
+
kind: "slicc.guest-reviews",
|
|
17
|
+
version: 1,
|
|
18
|
+
scope: "session",
|
|
19
|
+
initial: () => ({ next: 1, reviews: {} })
|
|
20
|
+
});
|
|
21
|
+
const GUEST_REVIEWS = "slicc-guest-reviews";
|
|
22
|
+
const REVIEW_TASK = "slicc.guest-review";
|
|
23
|
+
const MAX_QUEUED = 8;
|
|
24
|
+
const KEPT_REVIEWS = 100;
|
|
25
|
+
const KEPT_SUBMISSIONS = 100;
|
|
26
|
+
const DONE = { status: "terminal", outcome: { status: "completed", result: null } };
|
|
27
|
+
const liveReviews = (state, seat) => Object.values(state?.reviews ?? {}).filter((review) => review.seat === seat && !review.ended).sort((a, b) => a.n - b.n);
|
|
28
|
+
function endReview(review, state, reason, at) {
|
|
29
|
+
review.state = state;
|
|
30
|
+
review.reason = reason;
|
|
31
|
+
review.decidedAt = review.decidedAt ?? at;
|
|
32
|
+
review.ended = true;
|
|
33
|
+
}
|
|
34
|
+
async function told(tx, review) {
|
|
35
|
+
if (review.told) return;
|
|
36
|
+
review.told = true;
|
|
37
|
+
const data = { review: review.id, outcome: review.state };
|
|
38
|
+
await tx.appendEntry(review.conversation, {
|
|
39
|
+
kind: REVIEWED_KIND,
|
|
40
|
+
data: { ...data }
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function pruneReviews(state, kept = KEPT_REVIEWS) {
|
|
44
|
+
const ended2 = Object.values(state.reviews).filter((review) => review.ended).sort((a, b) => a.n - b.n);
|
|
45
|
+
for (const review of ended2.slice(0, Math.max(0, ended2.length - kept)))
|
|
46
|
+
delete state.reviews[review.id];
|
|
47
|
+
}
|
|
48
|
+
const deliveryId = (seat, review) => `${GUEST_REQUEST}${seat}:${review}`;
|
|
49
|
+
const reviewOf = async (tx, id) => (await tx.doc(GuestReviewsDoc)).reviews[id];
|
|
50
|
+
const ended = (task) => task.status === "terminal" || task.status === "completing";
|
|
51
|
+
async function turn(id, runtime, context) {
|
|
52
|
+
await runtime.commit(async (tx) => {
|
|
53
|
+
const doc = await tx.doc(GuestReviewsDoc);
|
|
54
|
+
const review = doc.reviews[id];
|
|
55
|
+
if (!review || review.ended) return DONE;
|
|
56
|
+
const seat = (await tx.doc(GuestsDoc)).seats[review.seat];
|
|
57
|
+
if (!liveSeat(seat, runtime.now())) {
|
|
58
|
+
endReview(review, "withdrawn", "revoked", runtime.now());
|
|
59
|
+
await told(tx, review);
|
|
60
|
+
return DONE;
|
|
61
|
+
}
|
|
62
|
+
const before = liveReviews(doc, review.seat).filter((other) => other.n < review.n).reverse();
|
|
63
|
+
for (const prior of before) {
|
|
64
|
+
const record = await tx.task(prior.task);
|
|
65
|
+
if (record && !ended(record.state))
|
|
66
|
+
return {
|
|
67
|
+
status: "waiting",
|
|
68
|
+
checkpoint: { phase: "turn", after: prior.task },
|
|
69
|
+
on: [prior.task],
|
|
70
|
+
policy: "allSettled"
|
|
71
|
+
};
|
|
72
|
+
endReview(prior, "unanswered", "failed", runtime.now());
|
|
73
|
+
await told(tx, prior);
|
|
74
|
+
}
|
|
75
|
+
return { status: "running", checkpoint: { phase: "ask" } };
|
|
76
|
+
}, context);
|
|
77
|
+
}
|
|
78
|
+
async function ask(id, runtime, host, context) {
|
|
79
|
+
const first = (await runtime.snapshot(GuestReviewsDoc, context))?.reviews[id];
|
|
80
|
+
if (!first) return runtime.commit(async () => DONE, context);
|
|
81
|
+
return host.line(first.seat, () => asking(id, runtime, host, context));
|
|
82
|
+
}
|
|
83
|
+
async function asking(id, runtime, host, context) {
|
|
84
|
+
const review = (await runtime.snapshot(GuestReviewsDoc, context))?.reviews[id];
|
|
85
|
+
const seat = review && (await runtime.snapshot(GuestsDoc, context))?.seats[review.seat];
|
|
86
|
+
if (seat?.gates.messages === "off")
|
|
87
|
+
return runtime.commit(async (tx) => {
|
|
88
|
+
const kept = await reviewOf(tx, id);
|
|
89
|
+
if (!kept || kept.ended) return DONE;
|
|
90
|
+
kept.state = "approved";
|
|
91
|
+
kept.reason = "always";
|
|
92
|
+
kept.auto = true;
|
|
93
|
+
kept.decidedAt = runtime.now();
|
|
94
|
+
await told(tx, kept);
|
|
95
|
+
return { status: "running", checkpoint: { phase: "deliver" } };
|
|
96
|
+
}, context);
|
|
97
|
+
if (review && !review.ended)
|
|
98
|
+
await host.approvals.request(
|
|
99
|
+
{
|
|
100
|
+
id: review.id,
|
|
101
|
+
kind: "guest-message",
|
|
102
|
+
seat: review.seat,
|
|
103
|
+
agentId: review.agentId,
|
|
104
|
+
requester: { kind: "guest", name: review.label, seat: review.seat },
|
|
105
|
+
tier: review.tier,
|
|
106
|
+
detail: { text: review.text, attachments: [] }
|
|
107
|
+
},
|
|
108
|
+
BACKGROUND_CONTEXT
|
|
109
|
+
);
|
|
110
|
+
await runtime.commit(async (tx) => {
|
|
111
|
+
const kept = await reviewOf(tx, id);
|
|
112
|
+
if (!kept || kept.ended) return DONE;
|
|
113
|
+
kept.state = "pending";
|
|
114
|
+
return { status: "running", checkpoint: { phase: "decide" } };
|
|
115
|
+
}, context);
|
|
116
|
+
}
|
|
117
|
+
const itemIn = (doc, id) => doc?.items.find((item) => item.id === id);
|
|
118
|
+
async function settledOrDue(runtime, id, context) {
|
|
119
|
+
const { context: using, cancel } = withCancel(context);
|
|
120
|
+
const watch = await runtime.watchDoc(ApprovalsDoc, using);
|
|
121
|
+
try {
|
|
122
|
+
const now = itemIn(watch?.value, id);
|
|
123
|
+
if (now?.state !== "pending" || now.expiresAt <= runtime.now()) return { item: now };
|
|
124
|
+
const slept = runtime.sleep(now.expiresAt, using);
|
|
125
|
+
slept.catch(() => void 0);
|
|
126
|
+
const changed = new Promise((resolve) => {
|
|
127
|
+
watch?.start(async (value) => {
|
|
128
|
+
if (itemIn(value, id)?.state !== "pending") resolve();
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
await Promise.race([slept, changed]);
|
|
132
|
+
return null;
|
|
133
|
+
} finally {
|
|
134
|
+
cancel();
|
|
135
|
+
await watch?.stop();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async function decide(id, runtime, host, context) {
|
|
139
|
+
let item;
|
|
140
|
+
for (; ; ) {
|
|
141
|
+
const seen = await settledOrDue(runtime, id, context);
|
|
142
|
+
if (!seen) continue;
|
|
143
|
+
item = seen.item;
|
|
144
|
+
if (item?.state !== "pending") break;
|
|
145
|
+
item = await host.approvals.expire(id, BACKGROUND_CONTEXT);
|
|
146
|
+
if (item?.state !== "pending") break;
|
|
147
|
+
}
|
|
148
|
+
const settled = item;
|
|
149
|
+
await runtime.commit(async (tx) => {
|
|
150
|
+
const review = await reviewOf(tx, id);
|
|
151
|
+
if (!review || review.ended) return DONE;
|
|
152
|
+
const at = settled?.decidedAt ?? runtime.now();
|
|
153
|
+
if (settled?.state === "approved") {
|
|
154
|
+
review.state = "approved";
|
|
155
|
+
review.decidedAt = at;
|
|
156
|
+
await told(tx, review);
|
|
157
|
+
if (settled.answer === "always" && host.alwaysAllowed()) {
|
|
158
|
+
const record = (await tx.doc(GuestsDoc)).seats[review.seat];
|
|
159
|
+
if (record && liveSeat(record, runtime.now())) {
|
|
160
|
+
record.gates.messages = "off";
|
|
161
|
+
record.always = { by: settled.decidedBy, approval: settled.id, at };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return { status: "running", checkpoint: { phase: "deliver" } };
|
|
165
|
+
}
|
|
166
|
+
endReview(review, settled?.state ?? "unanswered", settled?.reason ?? null, at);
|
|
167
|
+
await told(tx, review);
|
|
168
|
+
return DONE;
|
|
169
|
+
}, context);
|
|
170
|
+
}
|
|
171
|
+
async function deliver(id, runtime, host, context) {
|
|
172
|
+
const review = (await runtime.snapshot(GuestReviewsDoc, context))?.reviews[id];
|
|
173
|
+
if (!review || review.ended) return runtime.commit(async () => DONE, context);
|
|
174
|
+
await host.line(review.seat, async () => {
|
|
175
|
+
const seat = (await runtime.snapshot(GuestsDoc, context))?.seats[review.seat];
|
|
176
|
+
const live = liveSeat(seat, runtime.now());
|
|
177
|
+
const conversation = live ? await host.agents.conversation(review.agentId, BACKGROUND_CONTEXT) : void 0;
|
|
178
|
+
const approval = review.auto ? null : review.id;
|
|
179
|
+
const data = { seat: review.seat, name: review.label, approval };
|
|
180
|
+
const submitted = conversation ? await conversation.submit(
|
|
181
|
+
{
|
|
182
|
+
type: "input",
|
|
183
|
+
content: guestContent(review.label, review.text),
|
|
184
|
+
whenBusy: "followUp",
|
|
185
|
+
entry: { kind: GUEST_INPUT_KIND, data: { ...data } },
|
|
186
|
+
requestId: deliveryId(review.seat, review.id)
|
|
187
|
+
},
|
|
188
|
+
BACKGROUND_CONTEXT
|
|
189
|
+
) : void 0;
|
|
190
|
+
await runtime.commit(async (tx) => {
|
|
191
|
+
const kept = await reviewOf(tx, id);
|
|
192
|
+
if (!kept) return DONE;
|
|
193
|
+
kept.ended = true;
|
|
194
|
+
if (!submitted) {
|
|
195
|
+
kept.reason = live ? "gone" : "revoked";
|
|
196
|
+
return DONE;
|
|
197
|
+
}
|
|
198
|
+
kept.submissionId = String(submitted.id);
|
|
199
|
+
const record = (await tx.doc(GuestsDoc)).seats[kept.seat];
|
|
200
|
+
if (record) {
|
|
201
|
+
record.delivered = [...record.delivered ?? [], kept.id].slice(-KEPT_SUBMISSIONS);
|
|
202
|
+
record.submissions = [
|
|
203
|
+
...record.submissions ?? [],
|
|
204
|
+
{ id: kept.submissionId, approval, tools: kept.tools }
|
|
205
|
+
].slice(-KEPT_SUBMISSIONS);
|
|
206
|
+
}
|
|
207
|
+
return DONE;
|
|
208
|
+
}, context);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
function reviewTask(lookup) {
|
|
212
|
+
return defineTask({
|
|
213
|
+
name: REVIEW_TASK,
|
|
214
|
+
version: 1,
|
|
215
|
+
initial: () => ({ phase: "turn" }),
|
|
216
|
+
phases: {
|
|
217
|
+
turn: (task, runtime, context) => turn(task.input.review, runtime, context),
|
|
218
|
+
ask: async (task, runtime, context) => ask(task.input.review, runtime, await lookup(), context),
|
|
219
|
+
decide: async (task, runtime, context) => decide(task.input.review, runtime, await lookup(), context),
|
|
220
|
+
deliver: async (task, runtime, context) => deliver(task.input.review, runtime, await lookup(), context)
|
|
221
|
+
},
|
|
222
|
+
async abort(task, runtime, context) {
|
|
223
|
+
const host = await lookup();
|
|
224
|
+
await host.approvals.withdraw(task.input.review, "aborted", BACKGROUND_CONTEXT);
|
|
225
|
+
await runtime.commit(async (tx) => {
|
|
226
|
+
const review = await reviewOf(tx, task.input.review);
|
|
227
|
+
if (review && !review.ended) {
|
|
228
|
+
endReview(review, "withdrawn", "aborted", runtime.now());
|
|
229
|
+
await told(tx, review);
|
|
230
|
+
}
|
|
231
|
+
return { status: "terminal", outcome: { status: "aborted" } };
|
|
232
|
+
}, context);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
function guestReviews() {
|
|
237
|
+
let bind;
|
|
238
|
+
const bound = new Promise((resolve) => {
|
|
239
|
+
bind = resolve;
|
|
240
|
+
});
|
|
241
|
+
const task = reviewTask(() => bound);
|
|
242
|
+
return {
|
|
243
|
+
extension: defineExtension({ name: GUEST_REVIEWS, tasks: [task] }),
|
|
244
|
+
task,
|
|
245
|
+
bind: (host) => bind(host)
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
export {
|
|
249
|
+
GUEST_REVIEWS,
|
|
250
|
+
GuestReviewsDoc,
|
|
251
|
+
KEPT_REVIEWS,
|
|
252
|
+
KEPT_SUBMISSIONS,
|
|
253
|
+
MAX_QUEUED,
|
|
254
|
+
REVIEW_TASK,
|
|
255
|
+
deliveryId,
|
|
256
|
+
endReview,
|
|
257
|
+
guestReviews,
|
|
258
|
+
liveReviews,
|
|
259
|
+
pruneReviews,
|
|
260
|
+
told
|
|
261
|
+
};
|
|
262
|
+
//# sourceMappingURL=review.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/guests/review.ts"],
|
|
4
|
+
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT, withCancel } from '@earendil-works/chord/context';\nimport {\n type ConversationId,\n defineDoc,\n defineExtension,\n defineTask,\n type Extension,\n type NextTaskState,\n type TaskId,\n type TaskRuntime,\n type Tx,\n} from '@earendil-works/pi-durable';\nimport type { Agents } from '../agents.ts';\nimport type { Approval, Approvals, ApprovalsState } from '../approvals/index.ts';\nimport { ApprovalsDoc } from '../approvals/record.ts';\nimport {\n GUEST_INPUT_KIND,\n type GuestInput,\n type GuestOutcome,\n type GuestReviewed,\n guestContent,\n REVIEWED_KIND,\n} from './attribution.ts';\nimport { GUEST_REQUEST } from './guard.ts';\nimport { type GuestGate, GuestsDoc, liveSeat } from './seats.ts';\n\nexport type ReviewState =\n | 'queued'\n | 'pending'\n | 'approved'\n | 'rejected'\n | 'unanswered'\n | 'withdrawn';\n\nexport type Review = {\n id: string;\n seat: string;\n agentId: string;\n label: string;\n text: string;\n request: string;\n tier: GuestGate;\n tools: GuestGate;\n n: number;\n task: number;\n state: ReviewState;\n reason: string | null;\n createdAt: number;\n decidedAt: number | null;\n submissionId: string | null;\n ended: boolean;\n conversation: number;\n auto?: boolean;\n told?: boolean;\n};\n\nexport type ReviewsState = { next: number; reviews: Record<string, Review> };\n\nexport const GuestReviewsDoc = defineDoc<ReviewsState>({\n kind: 'slicc.guest-reviews',\n version: 1,\n scope: 'session',\n initial: () => ({ next: 1, reviews: {} }),\n});\n\nexport const GUEST_REVIEWS = 'slicc-guest-reviews';\nexport const REVIEW_TASK = 'slicc.guest-review';\nexport const MAX_QUEUED = 8;\nexport const KEPT_REVIEWS = 100;\nexport const KEPT_SUBMISSIONS = 100;\n\nexport interface ReviewHost {\n approvals: Pick<Approvals, 'request' | 'expire' | 'withdraw'>;\n agents: Pick<Agents, 'conversation'>;\n line<T>(seat: string, run: () => Promise<T>): Promise<T>;\n alwaysAllowed(): boolean;\n}\n\ntype Input = { review: string };\ntype Phase =\n | { phase: 'turn'; after?: number }\n | { phase: 'ask' }\n | { phase: 'decide' }\n | { phase: 'deliver' };\ntype Runtime = TaskRuntime<Input, Phase, null, object>;\ntype Next = NextTaskState<Phase, null>;\n\nconst DONE: Next = { status: 'terminal', outcome: { status: 'completed', result: null } };\n\nexport const liveReviews = (state: ReviewsState | null | undefined, seat: string): Review[] =>\n Object.values(state?.reviews ?? {})\n .filter((review) => review.seat === seat && !review.ended)\n .sort((a, b) => a.n - b.n);\n\nexport function endReview(review: Review, state: ReviewState, reason: string | null, at: number) {\n review.state = state;\n review.reason = reason;\n review.decidedAt = review.decidedAt ?? at;\n review.ended = true;\n}\n\nexport async function told(tx: Pick<Tx, 'appendEntry'>, review: Review): Promise<void> {\n if (review.told) return;\n review.told = true;\n const data: GuestReviewed = { review: review.id, outcome: review.state as GuestOutcome };\n await tx.appendEntry(review.conversation as unknown as ConversationId, {\n kind: REVIEWED_KIND,\n data: { ...data },\n });\n}\n\nexport function pruneReviews(state: ReviewsState, kept = KEPT_REVIEWS) {\n const ended = Object.values(state.reviews)\n .filter((review) => review.ended)\n .sort((a, b) => a.n - b.n);\n for (const review of ended.slice(0, Math.max(0, ended.length - kept)))\n delete state.reviews[review.id];\n}\n\nexport const deliveryId = (seat: string, review: string): string =>\n `${GUEST_REQUEST}${seat}:${review}`;\n\nconst reviewOf = async (tx: Tx, id: string) => (await tx.doc(GuestReviewsDoc)).reviews[id];\n\nconst ended = (task: { status: string }) =>\n task.status === 'terminal' || task.status === 'completing';\n\nasync function turn(id: string, runtime: Runtime, context: Context) {\n await runtime.commit(async (tx): Promise<Next> => {\n const doc = await tx.doc(GuestReviewsDoc);\n const review = doc.reviews[id];\n if (!review || review.ended) return DONE;\n const seat = (await tx.doc(GuestsDoc)).seats[review.seat];\n if (!liveSeat(seat, runtime.now())) {\n endReview(review, 'withdrawn', 'revoked', runtime.now());\n await told(tx, review);\n return DONE;\n }\n const before = liveReviews(doc, review.seat)\n .filter((other) => other.n < review.n)\n .reverse();\n for (const prior of before) {\n const record = await tx.task(prior.task as unknown as TaskId);\n if (record && !ended(record.state))\n return {\n status: 'waiting',\n checkpoint: { phase: 'turn', after: prior.task },\n on: [prior.task as unknown as TaskId],\n policy: 'allSettled',\n };\n endReview(prior, 'unanswered', 'failed', runtime.now());\n await told(tx, prior);\n }\n return { status: 'running', checkpoint: { phase: 'ask' } };\n }, context);\n}\n\nasync function ask(id: string, runtime: Runtime, host: ReviewHost, context: Context) {\n const first = (await runtime.snapshot(GuestReviewsDoc, context))?.reviews[id];\n if (!first) return runtime.commit(async () => DONE, context);\n return host.line(first.seat, () => asking(id, runtime, host, context));\n}\n\nasync function asking(id: string, runtime: Runtime, host: ReviewHost, context: Context) {\n const review = (await runtime.snapshot(GuestReviewsDoc, context))?.reviews[id];\n const seat = review && (await runtime.snapshot(GuestsDoc, context))?.seats[review.seat];\n if (seat?.gates.messages === 'off')\n return runtime.commit(async (tx): Promise<Next> => {\n const kept = await reviewOf(tx, id);\n if (!kept || kept.ended) return DONE;\n kept.state = 'approved';\n kept.reason = 'always';\n kept.auto = true;\n kept.decidedAt = runtime.now();\n await told(tx, kept);\n return { status: 'running', checkpoint: { phase: 'deliver' } };\n }, context);\n if (review && !review.ended)\n await host.approvals.request(\n {\n id: review.id,\n kind: 'guest-message',\n seat: review.seat,\n agentId: review.agentId,\n requester: { kind: 'guest', name: review.label, seat: review.seat },\n tier: review.tier,\n detail: { text: review.text, attachments: [] },\n },\n BACKGROUND_CONTEXT\n );\n await runtime.commit(async (tx): Promise<Next> => {\n const kept = await reviewOf(tx, id);\n if (!kept || kept.ended) return DONE;\n kept.state = 'pending';\n return { status: 'running', checkpoint: { phase: 'decide' } };\n }, context);\n}\n\nconst itemIn = (doc: Readonly<ApprovalsState> | null | undefined, id: string) =>\n doc?.items.find((item) => item.id === id);\n\nasync function settledOrDue(\n runtime: Runtime,\n id: string,\n context: Context\n): Promise<{ item: Approval | undefined } | null> {\n const { context: using, cancel } = withCancel(context);\n const watch = await runtime.watchDoc(ApprovalsDoc, using);\n try {\n const now = itemIn(watch?.value, id);\n if (now?.state !== 'pending' || now.expiresAt <= runtime.now()) return { item: now };\n const slept = runtime.sleep(now.expiresAt, using);\n slept.catch(() => undefined);\n const changed = new Promise<void>((resolve) => {\n watch?.start(async (value) => {\n if (itemIn(value, id)?.state !== 'pending') resolve();\n });\n });\n await Promise.race([slept, changed]);\n return null;\n } finally {\n cancel();\n await watch?.stop();\n }\n}\n\nasync function decide(id: string, runtime: Runtime, host: ReviewHost, context: Context) {\n let item: Approval | undefined;\n for (;;) {\n const seen = await settledOrDue(runtime, id, context);\n if (!seen) continue;\n item = seen.item;\n if (item?.state !== 'pending') break;\n item = await host.approvals.expire(id, BACKGROUND_CONTEXT);\n if (item?.state !== 'pending') break;\n }\n const settled = item;\n await runtime.commit(async (tx): Promise<Next> => {\n const review = await reviewOf(tx, id);\n if (!review || review.ended) return DONE;\n const at = settled?.decidedAt ?? runtime.now();\n if (settled?.state === 'approved') {\n review.state = 'approved';\n review.decidedAt = at;\n await told(tx, review);\n if (settled.answer === 'always' && host.alwaysAllowed()) {\n const record = (await tx.doc(GuestsDoc)).seats[review.seat];\n if (record && liveSeat(record, runtime.now())) {\n record.gates.messages = 'off';\n record.always = { by: settled.decidedBy, approval: settled.id, at };\n }\n }\n return { status: 'running', checkpoint: { phase: 'deliver' } };\n }\n endReview(review, settled?.state ?? 'unanswered', settled?.reason ?? null, at);\n await told(tx, review);\n return DONE;\n }, context);\n}\n\nasync function deliver(id: string, runtime: Runtime, host: ReviewHost, context: Context) {\n const review = (await runtime.snapshot(GuestReviewsDoc, context))?.reviews[id];\n if (!review || review.ended) return runtime.commit(async () => DONE, context);\n await host.line(review.seat, async () => {\n const seat = (await runtime.snapshot(GuestsDoc, context))?.seats[review.seat];\n const live = liveSeat(seat, runtime.now());\n const conversation = live\n ? await host.agents.conversation(review.agentId, BACKGROUND_CONTEXT)\n : undefined;\n const approval = review.auto ? null : review.id;\n const data: GuestInput = { seat: review.seat, name: review.label, approval };\n const submitted = conversation\n ? await conversation.submit(\n {\n type: 'input',\n content: guestContent(review.label, review.text),\n whenBusy: 'followUp',\n entry: { kind: GUEST_INPUT_KIND, data: { ...data } },\n requestId: deliveryId(review.seat, review.id),\n },\n BACKGROUND_CONTEXT\n )\n : undefined;\n await runtime.commit(async (tx): Promise<Next> => {\n const kept = await reviewOf(tx, id);\n if (!kept) return DONE;\n kept.ended = true;\n if (!submitted) {\n kept.reason = live ? 'gone' : 'revoked';\n return DONE;\n }\n kept.submissionId = String(submitted.id);\n const record = (await tx.doc(GuestsDoc)).seats[kept.seat];\n if (record) {\n record.delivered = [...(record.delivered ?? []), kept.id].slice(-KEPT_SUBMISSIONS);\n record.submissions = [\n ...(record.submissions ?? []),\n { id: kept.submissionId, approval, tools: kept.tools },\n ].slice(-KEPT_SUBMISSIONS);\n }\n return DONE;\n }, context);\n });\n}\n\nexport interface GuestReviews {\n readonly extension: Extension;\n readonly task: ReturnType<typeof reviewTask>;\n bind(host: ReviewHost): void;\n}\n\nfunction reviewTask(lookup: () => Promise<ReviewHost>) {\n return defineTask<Input, Phase, null>({\n name: REVIEW_TASK,\n version: 1,\n initial: () => ({ phase: 'turn' }),\n phases: {\n turn: (task, runtime, context) => turn(task.input.review, runtime, context),\n ask: async (task, runtime, context) =>\n ask(task.input.review, runtime, await lookup(), context),\n decide: async (task, runtime, context) =>\n decide(task.input.review, runtime, await lookup(), context),\n deliver: async (task, runtime, context) =>\n deliver(task.input.review, runtime, await lookup(), context),\n },\n async abort(task, runtime, context) {\n const host = await lookup();\n await host.approvals.withdraw(task.input.review, 'aborted', BACKGROUND_CONTEXT);\n await runtime.commit(async (tx) => {\n const review = await reviewOf(tx, task.input.review);\n if (review && !review.ended) {\n endReview(review, 'withdrawn', 'aborted', runtime.now());\n await told(tx, review);\n }\n return { status: 'terminal', outcome: { status: 'aborted' } };\n }, context);\n },\n });\n}\n\nexport function guestReviews(): GuestReviews {\n let bind: (host: ReviewHost) => void;\n const bound = new Promise<ReviewHost>((resolve) => {\n bind = resolve;\n });\n const task = reviewTask(() => bound);\n return {\n extension: defineExtension({ name: GUEST_REVIEWS, tasks: [task] }),\n task,\n bind: (host) => bind(host),\n };\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,oBAAoB,kBAAkB;AAC/C;AAAA,EAEE;AAAA,EACA;AAAA,EACA;AAAA,OAMK;AAGP,SAAS,oBAAoB;AAC7B;AAAA,EACE;AAAA,EAIA;AAAA,EACA;AAAA,OACK;AACP,SAAS,qBAAqB;AAC9B,SAAyB,WAAW,gBAAgB;AAkC7C,MAAM,kBAAkB,UAAwB;AAAA,EACrD,MAAM;AAAA,EACN,SAAS;AAAA,EACT,OAAO;AAAA,EACP,SAAS,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC,EAAE;AACzC,CAAC;AAEM,MAAM,gBAAgB;AACtB,MAAM,cAAc;AACpB,MAAM,aAAa;AACnB,MAAM,eAAe;AACrB,MAAM,mBAAmB;AAkBhC,MAAM,OAAa,EAAE,QAAQ,YAAY,SAAS,EAAE,QAAQ,aAAa,QAAQ,KAAK,EAAE;AAEjF,MAAM,cAAc,CAAC,OAAwC,SAClE,OAAO,OAAO,OAAO,WAAW,CAAC,CAAC,EAC/B,OAAO,CAAC,WAAW,OAAO,SAAS,QAAQ,CAAC,OAAO,KAAK,EACxD,KAAK,CAAC,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC;AAEtB,SAAS,UAAU,QAAgB,OAAoB,QAAuB,IAAY;AAC/F,SAAO,QAAQ;AACf,SAAO,SAAS;AAChB,SAAO,YAAY,OAAO,aAAa;AACvC,SAAO,QAAQ;AACjB;AAEA,eAAsB,KAAK,IAA6B,QAA+B;AACrF,MAAI,OAAO,KAAM;AACjB,SAAO,OAAO;AACd,QAAM,OAAsB,EAAE,QAAQ,OAAO,IAAI,SAAS,OAAO,MAAsB;AACvF,QAAM,GAAG,YAAY,OAAO,cAA2C;AAAA,IACrE,MAAM;AAAA,IACN,MAAM,EAAE,GAAG,KAAK;AAAA,EAClB,CAAC;AACH;AAEO,SAAS,aAAa,OAAqB,OAAO,cAAc;AACrE,QAAMA,SAAQ,OAAO,OAAO,MAAM,OAAO,EACtC,OAAO,CAAC,WAAW,OAAO,KAAK,EAC/B,KAAK,CAAC,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC;AAC3B,aAAW,UAAUA,OAAM,MAAM,GAAG,KAAK,IAAI,GAAGA,OAAM,SAAS,IAAI,CAAC;AAClE,WAAO,MAAM,QAAQ,OAAO,EAAE;AAClC;AAEO,MAAM,aAAa,CAAC,MAAc,WACvC,GAAG,aAAa,GAAG,IAAI,IAAI,MAAM;AAEnC,MAAM,WAAW,OAAO,IAAQ,QAAgB,MAAM,GAAG,IAAI,eAAe,GAAG,QAAQ,EAAE;AAEzF,MAAM,QAAQ,CAAC,SACb,KAAK,WAAW,cAAc,KAAK,WAAW;AAEhD,eAAe,KAAK,IAAY,SAAkB,SAAkB;AAClE,QAAM,QAAQ,OAAO,OAAO,OAAsB;AAChD,UAAM,MAAM,MAAM,GAAG,IAAI,eAAe;AACxC,UAAM,SAAS,IAAI,QAAQ,EAAE;AAC7B,QAAI,CAAC,UAAU,OAAO,MAAO,QAAO;AACpC,UAAM,QAAQ,MAAM,GAAG,IAAI,SAAS,GAAG,MAAM,OAAO,IAAI;AACxD,QAAI,CAAC,SAAS,MAAM,QAAQ,IAAI,CAAC,GAAG;AAClC,gBAAU,QAAQ,aAAa,WAAW,QAAQ,IAAI,CAAC;AACvD,YAAM,KAAK,IAAI,MAAM;AACrB,aAAO;AAAA,IACT;AACA,UAAM,SAAS,YAAY,KAAK,OAAO,IAAI,EACxC,OAAO,CAAC,UAAU,MAAM,IAAI,OAAO,CAAC,EACpC,QAAQ;AACX,eAAW,SAAS,QAAQ;AAC1B,YAAM,SAAS,MAAM,GAAG,KAAK,MAAM,IAAyB;AAC5D,UAAI,UAAU,CAAC,MAAM,OAAO,KAAK;AAC/B,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,YAAY,EAAE,OAAO,QAAQ,OAAO,MAAM,KAAK;AAAA,UAC/C,IAAI,CAAC,MAAM,IAAyB;AAAA,UACpC,QAAQ;AAAA,QACV;AACF,gBAAU,OAAO,cAAc,UAAU,QAAQ,IAAI,CAAC;AACtD,YAAM,KAAK,IAAI,KAAK;AAAA,IACtB;AACA,WAAO,EAAE,QAAQ,WAAW,YAAY,EAAE,OAAO,MAAM,EAAE;AAAA,EAC3D,GAAG,OAAO;AACZ;AAEA,eAAe,IAAI,IAAY,SAAkB,MAAkB,SAAkB;AACnF,QAAM,SAAS,MAAM,QAAQ,SAAS,iBAAiB,OAAO,IAAI,QAAQ,EAAE;AAC5E,MAAI,CAAC,MAAO,QAAO,QAAQ,OAAO,YAAY,MAAM,OAAO;AAC3D,SAAO,KAAK,KAAK,MAAM,MAAM,MAAM,OAAO,IAAI,SAAS,MAAM,OAAO,CAAC;AACvE;AAEA,eAAe,OAAO,IAAY,SAAkB,MAAkB,SAAkB;AACtF,QAAM,UAAU,MAAM,QAAQ,SAAS,iBAAiB,OAAO,IAAI,QAAQ,EAAE;AAC7E,QAAM,OAAO,WAAW,MAAM,QAAQ,SAAS,WAAW,OAAO,IAAI,MAAM,OAAO,IAAI;AACtF,MAAI,MAAM,MAAM,aAAa;AAC3B,WAAO,QAAQ,OAAO,OAAO,OAAsB;AACjD,YAAM,OAAO,MAAM,SAAS,IAAI,EAAE;AAClC,UAAI,CAAC,QAAQ,KAAK,MAAO,QAAO;AAChC,WAAK,QAAQ;AACb,WAAK,SAAS;AACd,WAAK,OAAO;AACZ,WAAK,YAAY,QAAQ,IAAI;AAC7B,YAAM,KAAK,IAAI,IAAI;AACnB,aAAO,EAAE,QAAQ,WAAW,YAAY,EAAE,OAAO,UAAU,EAAE;AAAA,IAC/D,GAAG,OAAO;AACZ,MAAI,UAAU,CAAC,OAAO;AACpB,UAAM,KAAK,UAAU;AAAA,MACnB;AAAA,QACE,IAAI,OAAO;AAAA,QACX,MAAM;AAAA,QACN,MAAM,OAAO;AAAA,QACb,SAAS,OAAO;AAAA,QAChB,WAAW,EAAE,MAAM,SAAS,MAAM,OAAO,OAAO,MAAM,OAAO,KAAK;AAAA,QAClE,MAAM,OAAO;AAAA,QACb,QAAQ,EAAE,MAAM,OAAO,MAAM,aAAa,CAAC,EAAE;AAAA,MAC/C;AAAA,MACA;AAAA,IACF;AACF,QAAM,QAAQ,OAAO,OAAO,OAAsB;AAChD,UAAM,OAAO,MAAM,SAAS,IAAI,EAAE;AAClC,QAAI,CAAC,QAAQ,KAAK,MAAO,QAAO;AAChC,SAAK,QAAQ;AACb,WAAO,EAAE,QAAQ,WAAW,YAAY,EAAE,OAAO,SAAS,EAAE;AAAA,EAC9D,GAAG,OAAO;AACZ;AAEA,MAAM,SAAS,CAAC,KAAkD,OAChE,KAAK,MAAM,KAAK,CAAC,SAAS,KAAK,OAAO,EAAE;AAE1C,eAAe,aACb,SACA,IACA,SACgD;AAChD,QAAM,EAAE,SAAS,OAAO,OAAO,IAAI,WAAW,OAAO;AACrD,QAAM,QAAQ,MAAM,QAAQ,SAAS,cAAc,KAAK;AACxD,MAAI;AACF,UAAM,MAAM,OAAO,OAAO,OAAO,EAAE;AACnC,QAAI,KAAK,UAAU,aAAa,IAAI,aAAa,QAAQ,IAAI,EAAG,QAAO,EAAE,MAAM,IAAI;AACnF,UAAM,QAAQ,QAAQ,MAAM,IAAI,WAAW,KAAK;AAChD,UAAM,MAAM,MAAM,MAAS;AAC3B,UAAM,UAAU,IAAI,QAAc,CAAC,YAAY;AAC7C,aAAO,MAAM,OAAO,UAAU;AAC5B,YAAI,OAAO,OAAO,EAAE,GAAG,UAAU,UAAW,SAAQ;AAAA,MACtD,CAAC;AAAA,IACH,CAAC;AACD,UAAM,QAAQ,KAAK,CAAC,OAAO,OAAO,CAAC;AACnC,WAAO;AAAA,EACT,UAAE;AACA,WAAO;AACP,UAAM,OAAO,KAAK;AAAA,EACpB;AACF;AAEA,eAAe,OAAO,IAAY,SAAkB,MAAkB,SAAkB;AACtF,MAAI;AACJ,aAAS;AACP,UAAM,OAAO,MAAM,aAAa,SAAS,IAAI,OAAO;AACpD,QAAI,CAAC,KAAM;AACX,WAAO,KAAK;AACZ,QAAI,MAAM,UAAU,UAAW;AAC/B,WAAO,MAAM,KAAK,UAAU,OAAO,IAAI,kBAAkB;AACzD,QAAI,MAAM,UAAU,UAAW;AAAA,EACjC;AACA,QAAM,UAAU;AAChB,QAAM,QAAQ,OAAO,OAAO,OAAsB;AAChD,UAAM,SAAS,MAAM,SAAS,IAAI,EAAE;AACpC,QAAI,CAAC,UAAU,OAAO,MAAO,QAAO;AACpC,UAAM,KAAK,SAAS,aAAa,QAAQ,IAAI;AAC7C,QAAI,SAAS,UAAU,YAAY;AACjC,aAAO,QAAQ;AACf,aAAO,YAAY;AACnB,YAAM,KAAK,IAAI,MAAM;AACrB,UAAI,QAAQ,WAAW,YAAY,KAAK,cAAc,GAAG;AACvD,cAAM,UAAU,MAAM,GAAG,IAAI,SAAS,GAAG,MAAM,OAAO,IAAI;AAC1D,YAAI,UAAU,SAAS,QAAQ,QAAQ,IAAI,CAAC,GAAG;AAC7C,iBAAO,MAAM,WAAW;AACxB,iBAAO,SAAS,EAAE,IAAI,QAAQ,WAAW,UAAU,QAAQ,IAAI,GAAG;AAAA,QACpE;AAAA,MACF;AACA,aAAO,EAAE,QAAQ,WAAW,YAAY,EAAE,OAAO,UAAU,EAAE;AAAA,IAC/D;AACA,cAAU,QAAQ,SAAS,SAAS,cAAc,SAAS,UAAU,MAAM,EAAE;AAC7E,UAAM,KAAK,IAAI,MAAM;AACrB,WAAO;AAAA,EACT,GAAG,OAAO;AACZ;AAEA,eAAe,QAAQ,IAAY,SAAkB,MAAkB,SAAkB;AACvF,QAAM,UAAU,MAAM,QAAQ,SAAS,iBAAiB,OAAO,IAAI,QAAQ,EAAE;AAC7E,MAAI,CAAC,UAAU,OAAO,MAAO,QAAO,QAAQ,OAAO,YAAY,MAAM,OAAO;AAC5E,QAAM,KAAK,KAAK,OAAO,MAAM,YAAY;AACvC,UAAM,QAAQ,MAAM,QAAQ,SAAS,WAAW,OAAO,IAAI,MAAM,OAAO,IAAI;AAC5E,UAAM,OAAO,SAAS,MAAM,QAAQ,IAAI,CAAC;AACzC,UAAM,eAAe,OACjB,MAAM,KAAK,OAAO,aAAa,OAAO,SAAS,kBAAkB,IACjE;AACJ,UAAM,WAAW,OAAO,OAAO,OAAO,OAAO;AAC7C,UAAM,OAAmB,EAAE,MAAM,OAAO,MAAM,MAAM,OAAO,OAAO,SAAS;AAC3E,UAAM,YAAY,eACd,MAAM,aAAa;AAAA,MACjB;AAAA,QACE,MAAM;AAAA,QACN,SAAS,aAAa,OAAO,OAAO,OAAO,IAAI;AAAA,QAC/C,UAAU;AAAA,QACV,OAAO,EAAE,MAAM,kBAAkB,MAAM,EAAE,GAAG,KAAK,EAAE;AAAA,QACnD,WAAW,WAAW,OAAO,MAAM,OAAO,EAAE;AAAA,MAC9C;AAAA,MACA;AAAA,IACF,IACA;AACJ,UAAM,QAAQ,OAAO,OAAO,OAAsB;AAChD,YAAM,OAAO,MAAM,SAAS,IAAI,EAAE;AAClC,UAAI,CAAC,KAAM,QAAO;AAClB,WAAK,QAAQ;AACb,UAAI,CAAC,WAAW;AACd,aAAK,SAAS,OAAO,SAAS;AAC9B,eAAO;AAAA,MACT;AACA,WAAK,eAAe,OAAO,UAAU,EAAE;AACvC,YAAM,UAAU,MAAM,GAAG,IAAI,SAAS,GAAG,MAAM,KAAK,IAAI;AACxD,UAAI,QAAQ;AACV,eAAO,YAAY,CAAC,GAAI,OAAO,aAAa,CAAC,GAAI,KAAK,EAAE,EAAE,MAAM,CAAC,gBAAgB;AACjF,eAAO,cAAc;AAAA,UACnB,GAAI,OAAO,eAAe,CAAC;AAAA,UAC3B,EAAE,IAAI,KAAK,cAAc,UAAU,OAAO,KAAK,MAAM;AAAA,QACvD,EAAE,MAAM,CAAC,gBAAgB;AAAA,MAC3B;AACA,aAAO;AAAA,IACT,GAAG,OAAO;AAAA,EACZ,CAAC;AACH;AAQA,SAAS,WAAW,QAAmC;AACrD,SAAO,WAA+B;AAAA,IACpC,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SAAS,OAAO,EAAE,OAAO,OAAO;AAAA,IAChC,QAAQ;AAAA,MACN,MAAM,CAAC,MAAM,SAAS,YAAY,KAAK,KAAK,MAAM,QAAQ,SAAS,OAAO;AAAA,MAC1E,KAAK,OAAO,MAAM,SAAS,YACzB,IAAI,KAAK,MAAM,QAAQ,SAAS,MAAM,OAAO,GAAG,OAAO;AAAA,MACzD,QAAQ,OAAO,MAAM,SAAS,YAC5B,OAAO,KAAK,MAAM,QAAQ,SAAS,MAAM,OAAO,GAAG,OAAO;AAAA,MAC5D,SAAS,OAAO,MAAM,SAAS,YAC7B,QAAQ,KAAK,MAAM,QAAQ,SAAS,MAAM,OAAO,GAAG,OAAO;AAAA,IAC/D;AAAA,IACA,MAAM,MAAM,MAAM,SAAS,SAAS;AAClC,YAAM,OAAO,MAAM,OAAO;AAC1B,YAAM,KAAK,UAAU,SAAS,KAAK,MAAM,QAAQ,WAAW,kBAAkB;AAC9E,YAAM,QAAQ,OAAO,OAAO,OAAO;AACjC,cAAM,SAAS,MAAM,SAAS,IAAI,KAAK,MAAM,MAAM;AACnD,YAAI,UAAU,CAAC,OAAO,OAAO;AAC3B,oBAAU,QAAQ,aAAa,WAAW,QAAQ,IAAI,CAAC;AACvD,gBAAM,KAAK,IAAI,MAAM;AAAA,QACvB;AACA,eAAO,EAAE,QAAQ,YAAY,SAAS,EAAE,QAAQ,UAAU,EAAE;AAAA,MAC9D,GAAG,OAAO;AAAA,IACZ;AAAA,EACF,CAAC;AACH;AAEO,SAAS,eAA6B;AAC3C,MAAI;AACJ,QAAM,QAAQ,IAAI,QAAoB,CAAC,YAAY;AACjD,WAAO;AAAA,EACT,CAAC;AACD,QAAM,OAAO,WAAW,MAAM,KAAK;AACnC,SAAO;AAAA,IACL,WAAW,gBAAgB,EAAE,MAAM,eAAe,OAAO,CAAC,IAAI,EAAE,CAAC;AAAA,IACjE;AAAA,IACA,MAAM,CAAC,SAAS,KAAK,IAAI;AAAA,EAC3B;AACF;",
|
|
6
|
+
"names": ["ended"]
|
|
7
|
+
}
|
package/dist/guests/seats.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/guests/seats.ts"],
|
|
4
|
-
"sourcesContent": ["import { defineDoc } from '@earendil-works/pi-durable';\nimport type { ApprovalTier } from '../approvals/record.ts';\nimport {\n DAY_MS,\n DEFAULT_GUEST_POLICY,\n type GuestPolicy,\n gateKind,\n gateRefusal,\n MAX_SEAT_DAYS,\n} from './policy.ts';\n\nexport type GuestGate = ApprovalTier | 'off';\n\nexport type GuestGates = {\n messages: GuestGate;\n tools: GuestGate;\n};\n\nexport type GuestDraft = {\n id: string;\n label: string;\n agentId: string;\n expiresAt: number | null;\n gates: GuestGates;\n};\n\nexport type SeatRecord = GuestDraft & {\n createdAt: number;\n epoch: number;\n revoked: number | null;\n delivered?: string[];\n};\n\nexport interface GuestSeat {\n id: string;\n label: string;\n agentId: string;\n createdAt: number;\n expiresAt: number | null;\n gates: GuestGates;\n connected: boolean;\n pending: number;\n}\n\nexport type GuestsState = { seats: Record<string, SeatRecord> };\n\nexport const GuestsDoc = defineDoc<GuestsState>({\n kind: 'slicc.guests',\n version: 1,\n scope: 'session',\n initial: () => ({ seats: {} }),\n});\n\nexport const MAX_SEAT_MS = MAX_SEAT_DAYS * DAY_MS;\nexport const MAX_LABEL = 64;\nconst MAX_ID = 256;\n\nexport function cleanLabel(raw: unknown): string {\n return String(raw ?? '')\n .replace(/[\\p{Cc}\\p{Cf}]/gu, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n .slice(0, MAX_LABEL);\n}\n\nexport function isGate(value: unknown): value is GuestGate {\n if (typeof value !== 'string') return false;\n if (value === 'user' || value === 'agent' || value === 'cone' || value === 'off') return true;\n return /^scoop:\\S+$/.test(value);\n}\n\nexport function checkDraft(\n draft: GuestDraft,\n now: number,\n policy: GuestPolicy = DEFAULT_GUEST_POLICY\n): string | null {\n if (typeof draft?.id !== 'string' || !draft.id || draft.id.length > MAX_ID)\n return 'A guest seat needs an id from the tray hub.';\n if (!cleanLabel(draft.label)) return 'A guest seat needs a label.';\n if (!isGate(draft.gates?.messages) || !isGate(draft.gates?.tools))\n return 'A guest seat needs a known gate for messages and for tools.';\n if (!policy.allowedMessageGates.includes(gateKind(draft.gates.messages)))\n return gateRefusal(\"a guest's messages\", policy.allowedMessageGates);\n if (!policy.allowedToolGates.includes(gateKind(draft.gates.tools)))\n return gateRefusal(\"a guest's tool calls\", policy.allowedToolGates);\n const { expiresAt } = draft;\n if (expiresAt !== null && (typeof expiresAt !== 'number' || !Number.isFinite(expiresAt)))\n return 'A guest seat expires at a time, or never.';\n if (expiresAt !== null && expiresAt <= now) return 'That expiry has already passed.';\n const days = policy.maxSeatDays;\n if (expiresAt !== null && expiresAt - now > days * DAY_MS)\n return `A guest seat lasts at most ${days} ${days === 1 ? 'day' : 'days'}.`;\n return null;\n}\n\nexport function liveSeat(seat: SeatRecord | undefined, now: number): boolean {\n return Boolean(\n seat && seat.revoked === null && (seat.expiresAt === null || seat.expiresAt > now)\n );\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,iBAAiB;AAE1B;AAAA,EACE;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,OACK;
|
|
4
|
+
"sourcesContent": ["import { defineDoc } from '@earendil-works/pi-durable';\nimport type { ApprovalTier, DecidedBy } from '../approvals/record.ts';\nimport {\n DAY_MS,\n DEFAULT_GUEST_POLICY,\n type GuestPolicy,\n gateKind,\n gateRefusal,\n MAX_SEAT_DAYS,\n} from './policy.ts';\n\nexport type GuestGate = ApprovalTier | 'off';\n\nexport type GuestGates = {\n messages: GuestGate;\n tools: GuestGate;\n};\n\nexport type GuestDraft = {\n id: string;\n label: string;\n agentId: string;\n expiresAt: number | null;\n gates: GuestGates;\n};\n\nexport type SeatSubmission = { id: string; approval: string | null; tools: GuestGate };\n\nexport type SeatRecord = GuestDraft & {\n createdAt: number;\n epoch: number;\n revoked: number | null;\n delivered?: string[];\n submissions?: SeatSubmission[];\n always?: { by: DecidedBy | null; approval: string; at: number };\n};\n\nexport interface GuestSeat {\n id: string;\n label: string;\n agentId: string;\n createdAt: number;\n expiresAt: number | null;\n gates: GuestGates;\n connected: boolean;\n pending: number;\n}\n\nexport type GuestsState = { seats: Record<string, SeatRecord> };\n\nexport const GuestsDoc = defineDoc<GuestsState>({\n kind: 'slicc.guests',\n version: 1,\n scope: 'session',\n initial: () => ({ seats: {} }),\n});\n\nexport const MAX_SEAT_MS = MAX_SEAT_DAYS * DAY_MS;\nexport const MAX_LABEL = 64;\nconst MAX_ID = 256;\n\nexport function cleanLabel(raw: unknown): string {\n return String(raw ?? '')\n .replace(/[\\p{Cc}\\p{Cf}]/gu, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n .slice(0, MAX_LABEL);\n}\n\nexport function isGate(value: unknown): value is GuestGate {\n if (typeof value !== 'string') return false;\n if (value === 'user' || value === 'agent' || value === 'cone' || value === 'off') return true;\n return /^scoop:\\S+$/.test(value);\n}\n\nexport function checkDraft(\n draft: GuestDraft,\n now: number,\n policy: GuestPolicy = DEFAULT_GUEST_POLICY\n): string | null {\n if (typeof draft?.id !== 'string' || !draft.id || draft.id.length > MAX_ID)\n return 'A guest seat needs an id from the tray hub.';\n if (!cleanLabel(draft.label)) return 'A guest seat needs a label.';\n if (!isGate(draft.gates?.messages) || !isGate(draft.gates?.tools))\n return 'A guest seat needs a known gate for messages and for tools.';\n if (!policy.allowedMessageGates.includes(gateKind(draft.gates.messages)))\n return gateRefusal(\"a guest's messages\", policy.allowedMessageGates);\n if (!policy.allowedToolGates.includes(gateKind(draft.gates.tools)))\n return gateRefusal(\"a guest's tool calls\", policy.allowedToolGates);\n const { expiresAt } = draft;\n if (expiresAt !== null && (typeof expiresAt !== 'number' || !Number.isFinite(expiresAt)))\n return 'A guest seat expires at a time, or never.';\n if (expiresAt !== null && expiresAt <= now) return 'That expiry has already passed.';\n const days = policy.maxSeatDays;\n if (expiresAt !== null && expiresAt - now > days * DAY_MS)\n return `A guest seat lasts at most ${days} ${days === 1 ? 'day' : 'days'}.`;\n return null;\n}\n\nexport function liveSeat(seat: SeatRecord | undefined, now: number): boolean {\n return Boolean(\n seat && seat.revoked === null && (seat.expiresAt === null || seat.expiresAt > now)\n );\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,iBAAiB;AAE1B;AAAA,EACE;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAyCA,MAAM,YAAY,UAAuB;AAAA,EAC9C,MAAM;AAAA,EACN,SAAS;AAAA,EACT,OAAO;AAAA,EACP,SAAS,OAAO,EAAE,OAAO,CAAC,EAAE;AAC9B,CAAC;AAEM,MAAM,cAAc,gBAAgB;AACpC,MAAM,YAAY;AACzB,MAAM,SAAS;AAER,SAAS,WAAW,KAAsB;AAC/C,SAAO,OAAO,OAAO,EAAE,EACpB,QAAQ,oBAAoB,GAAG,EAC/B,QAAQ,QAAQ,GAAG,EACnB,KAAK,EACL,MAAM,GAAG,SAAS;AACvB;AAEO,SAAS,OAAO,OAAoC;AACzD,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,UAAU,UAAU,UAAU,WAAW,UAAU,UAAU,UAAU,MAAO,QAAO;AACzF,SAAO,cAAc,KAAK,KAAK;AACjC;AAEO,SAAS,WACd,OACA,KACA,SAAsB,sBACP;AACf,MAAI,OAAO,OAAO,OAAO,YAAY,CAAC,MAAM,MAAM,MAAM,GAAG,SAAS;AAClE,WAAO;AACT,MAAI,CAAC,WAAW,MAAM,KAAK,EAAG,QAAO;AACrC,MAAI,CAAC,OAAO,MAAM,OAAO,QAAQ,KAAK,CAAC,OAAO,MAAM,OAAO,KAAK;AAC9D,WAAO;AACT,MAAI,CAAC,OAAO,oBAAoB,SAAS,SAAS,MAAM,MAAM,QAAQ,CAAC;AACrE,WAAO,YAAY,sBAAsB,OAAO,mBAAmB;AACrE,MAAI,CAAC,OAAO,iBAAiB,SAAS,SAAS,MAAM,MAAM,KAAK,CAAC;AAC/D,WAAO,YAAY,wBAAwB,OAAO,gBAAgB;AACpE,QAAM,EAAE,UAAU,IAAI;AACtB,MAAI,cAAc,SAAS,OAAO,cAAc,YAAY,CAAC,OAAO,SAAS,SAAS;AACpF,WAAO;AACT,MAAI,cAAc,QAAQ,aAAa,IAAK,QAAO;AACnD,QAAM,OAAO,OAAO;AACpB,MAAI,cAAc,QAAQ,YAAY,MAAM,OAAO;AACjD,WAAO,8BAA8B,IAAI,IAAI,SAAS,IAAI,QAAQ,MAAM;AAC1E,SAAO;AACT;AAEO,SAAS,SAAS,MAA8B,KAAsB;AAC3E,SAAO;AAAA,IACL,QAAQ,KAAK,YAAY,SAAS,KAAK,cAAc,QAAQ,KAAK,YAAY;AAAA,EAChF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/guests/serve.js
CHANGED
|
@@ -2,20 +2,20 @@ import { forbidden, replicatedState } from "@earendil-works/chord";
|
|
|
2
2
|
import { PortLink } from "../wire.js";
|
|
3
3
|
import { GUESTS_MANAGE_DENIED, GUESTS_MANAGE_LATER } from "./policy.js";
|
|
4
4
|
import { guestView } from "./view.js";
|
|
5
|
-
function proposalsOf(
|
|
6
|
-
return (
|
|
7
|
-
id:
|
|
8
|
-
text:
|
|
9
|
-
state:
|
|
10
|
-
reason:
|
|
11
|
-
createdAt:
|
|
12
|
-
decidedAt:
|
|
5
|
+
function proposalsOf(state, seat) {
|
|
6
|
+
return Object.values(state?.reviews ?? {}).filter((review) => review.seat === seat).sort((a, b) => a.n - b.n).map((review) => ({
|
|
7
|
+
id: review.id,
|
|
8
|
+
text: review.text,
|
|
9
|
+
state: review.state,
|
|
10
|
+
reason: review.reason,
|
|
11
|
+
createdAt: review.createdAt,
|
|
12
|
+
decidedAt: review.decidedAt
|
|
13
13
|
}));
|
|
14
14
|
}
|
|
15
15
|
const managing = (guests) => Promise.reject(
|
|
16
16
|
forbidden(guests.policy().manageGuests === "deny" ? GUESTS_MANAGE_DENIED : GUESTS_MANAGE_LATER)
|
|
17
17
|
);
|
|
18
|
-
function guestOpener(guests, agents, views, context
|
|
18
|
+
function guestOpener(guests, agents, views, context) {
|
|
19
19
|
return (principal) => {
|
|
20
20
|
const record = guests.current(principal);
|
|
21
21
|
if (!record) return void 0;
|
|
@@ -35,10 +35,10 @@ function guestOpener(guests, agents, views, context, approvals) {
|
|
|
35
35
|
expiresAt: record.expiresAt,
|
|
36
36
|
gates: { ...record.gates }
|
|
37
37
|
});
|
|
38
|
-
let listed = JSON.stringify(proposalsOf(
|
|
38
|
+
let listed = JSON.stringify(proposalsOf(guests.reviews.value, record.id));
|
|
39
39
|
const proposals = replicatedState(JSON.parse(listed));
|
|
40
|
-
const offProposals =
|
|
41
|
-
const next = JSON.stringify(proposalsOf(
|
|
40
|
+
const offProposals = guests.reviews.subscribe((state) => {
|
|
41
|
+
const next = JSON.stringify(proposalsOf(state, record.id));
|
|
42
42
|
if (next === listed) return;
|
|
43
43
|
listed = next;
|
|
44
44
|
proposals.replace(context, JSON.parse(next));
|
|
@@ -54,7 +54,7 @@ function guestOpener(guests, agents, views, context, approvals) {
|
|
|
54
54
|
},
|
|
55
55
|
dispose() {
|
|
56
56
|
off();
|
|
57
|
-
offProposals
|
|
57
|
+
offProposals();
|
|
58
58
|
}
|
|
59
59
|
};
|
|
60
60
|
};
|
package/dist/guests/serve.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/guests/serve.ts"],
|
|
4
|
-
"sourcesContent": ["import type { Context, Host, ReplicatedState } from '@earendil-works/chord';\nimport { forbidden, replicatedState } from '@earendil-works/chord';\nimport type { ConversationView } from '@earendil-works/pi-durable';\nimport type { Agents } from '../agents.ts';\nimport type {
|
|
5
|
-
"mappings": "AACA,SAAS,WAAW,uBAAuB;
|
|
4
|
+
"sourcesContent": ["import type { Context, Host, ReplicatedState } from '@earendil-works/chord';\nimport { forbidden, replicatedState } from '@earendil-works/chord';\nimport type { ConversationView } from '@earendil-works/pi-durable';\nimport type { Agents } from '../agents.ts';\nimport type { AgentGuest, GuestProposal, GuestShown } from '../services.ts';\nimport type { AgentPrincipal, GuestPrincipal } from '../serving.ts';\nimport { type PortEndpoint, PortLink } from '../wire.ts';\nimport type { Guests } from './index.ts';\nimport { GUESTS_MANAGE_DENIED, GUESTS_MANAGE_LATER } from './policy.ts';\nimport type { ReviewsState } from './review.ts';\nimport { guestView } from './view.ts';\n\nexport function proposalsOf(state: ReviewsState | null | undefined, seat: string): GuestProposal[] {\n return Object.values(state?.reviews ?? {})\n .filter((review) => review.seat === seat)\n .sort((a, b) => a.n - b.n)\n .map((review) => ({\n id: review.id,\n text: review.text,\n state: review.state,\n reason: review.reason,\n createdAt: review.createdAt,\n decidedAt: review.decidedAt,\n }));\n}\n\nconst managing = (guests: Pick<Guests, 'policy'>): Promise<never> =>\n Promise.reject(\n forbidden(guests.policy().manageGuests === 'deny' ? GUESTS_MANAGE_DENIED : GUESTS_MANAGE_LATER)\n );\n\nexport function guestOpener(\n guests: Guests,\n agents: Pick<Agents, 'state'>,\n views: ReplicatedState<Record<string, ConversationView>>,\n context: Context\n) {\n return (principal: GuestPrincipal): { value: AgentGuest; dispose(): void } | undefined => {\n const record = guests.current(principal);\n if (!record) return undefined;\n const pick = (all: Record<string, ConversationView> | undefined) => all?.[record.agentId];\n let shown = pick(views.value);\n const transcript = replicatedState<ConversationView>(guestView(shown, record.id));\n const off = views.subscribe((all) => {\n if (pick(all) === shown) return;\n shown = pick(all);\n transcript.replace(context, guestView(shown, record.id));\n });\n const seat = replicatedState<GuestShown>({\n id: record.id,\n label: record.label,\n agentId: record.agentId,\n name: agents.state().cones[record.agentId]?.name ?? record.agentId,\n expiresAt: record.expiresAt,\n gates: { ...record.gates },\n });\n let listed = JSON.stringify(proposalsOf(guests.reviews.value, record.id));\n const proposals = replicatedState<GuestProposal[]>(JSON.parse(listed) as GuestProposal[]);\n const offProposals = guests.reviews.subscribe((state) => {\n const next = JSON.stringify(proposalsOf(state, record.id));\n if (next === listed) return;\n listed = next;\n proposals.replace(context, JSON.parse(next) as GuestProposal[]);\n });\n return {\n value: {\n transcript,\n seat,\n proposals,\n propose: (request, using) => guests.propose(principal, request, using),\n guestOpen: () => managing(guests),\n guestRevoke: () => managing(guests),\n },\n dispose() {\n off();\n offProposals();\n },\n };\n };\n}\n\nexport function admitGuest(\n guests: Guests,\n endpoint: PortEndpoint,\n asked: { id: string; label: string },\n serverId: string,\n link: number,\n host: Pick<Host, 'accept'>\n): void {\n const port = new PortLink(endpoint);\n const guest = guests.admit(String(asked.id), String(asked.label));\n if (!guest) {\n port.close('this guest seat is not valid');\n return;\n }\n void port.ended.then(guests.joined(guest.id));\n port.greet(serverId);\n host.accept(port, { principal: { seat: 'guest', link, guest } satisfies AgentPrincipal });\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,WAAW,uBAAuB;AAK3C,SAA4B,gBAAgB;AAE5C,SAAS,sBAAsB,2BAA2B;AAE1D,SAAS,iBAAiB;AAEnB,SAAS,YAAY,OAAwC,MAA+B;AACjG,SAAO,OAAO,OAAO,OAAO,WAAW,CAAC,CAAC,EACtC,OAAO,CAAC,WAAW,OAAO,SAAS,IAAI,EACvC,KAAK,CAAC,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC,EACxB,IAAI,CAAC,YAAY;AAAA,IAChB,IAAI,OAAO;AAAA,IACX,MAAM,OAAO;AAAA,IACb,OAAO,OAAO;AAAA,IACd,QAAQ,OAAO;AAAA,IACf,WAAW,OAAO;AAAA,IAClB,WAAW,OAAO;AAAA,EACpB,EAAE;AACN;AAEA,MAAM,WAAW,CAAC,WAChB,QAAQ;AAAA,EACN,UAAU,OAAO,OAAO,EAAE,iBAAiB,SAAS,uBAAuB,mBAAmB;AAChG;AAEK,SAAS,YACd,QACA,QACA,OACA,SACA;AACA,SAAO,CAAC,cAAkF;AACxF,UAAM,SAAS,OAAO,QAAQ,SAAS;AACvC,QAAI,CAAC,OAAQ,QAAO;AACpB,UAAM,OAAO,CAAC,QAAsD,MAAM,OAAO,OAAO;AACxF,QAAI,QAAQ,KAAK,MAAM,KAAK;AAC5B,UAAM,aAAa,gBAAkC,UAAU,OAAO,OAAO,EAAE,CAAC;AAChF,UAAM,MAAM,MAAM,UAAU,CAAC,QAAQ;AACnC,UAAI,KAAK,GAAG,MAAM,MAAO;AACzB,cAAQ,KAAK,GAAG;AAChB,iBAAW,QAAQ,SAAS,UAAU,OAAO,OAAO,EAAE,CAAC;AAAA,IACzD,CAAC;AACD,UAAM,OAAO,gBAA4B;AAAA,MACvC,IAAI,OAAO;AAAA,MACX,OAAO,OAAO;AAAA,MACd,SAAS,OAAO;AAAA,MAChB,MAAM,OAAO,MAAM,EAAE,MAAM,OAAO,OAAO,GAAG,QAAQ,OAAO;AAAA,MAC3D,WAAW,OAAO;AAAA,MAClB,OAAO,EAAE,GAAG,OAAO,MAAM;AAAA,IAC3B,CAAC;AACD,QAAI,SAAS,KAAK,UAAU,YAAY,OAAO,QAAQ,OAAO,OAAO,EAAE,CAAC;AACxE,UAAM,YAAY,gBAAiC,KAAK,MAAM,MAAM,CAAoB;AACxF,UAAM,eAAe,OAAO,QAAQ,UAAU,CAAC,UAAU;AACvD,YAAM,OAAO,KAAK,UAAU,YAAY,OAAO,OAAO,EAAE,CAAC;AACzD,UAAI,SAAS,OAAQ;AACrB,eAAS;AACT,gBAAU,QAAQ,SAAS,KAAK,MAAM,IAAI,CAAoB;AAAA,IAChE,CAAC;AACD,WAAO;AAAA,MACL,OAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA,SAAS,CAAC,SAAS,UAAU,OAAO,QAAQ,WAAW,SAAS,KAAK;AAAA,QACrE,WAAW,MAAM,SAAS,MAAM;AAAA,QAChC,aAAa,MAAM,SAAS,MAAM;AAAA,MACpC;AAAA,MACA,UAAU;AACR,YAAI;AACJ,qBAAa;AAAA,MACf;AAAA,IACF;AAAA,EACF;AACF;AAEO,SAAS,WACd,QACA,UACA,OACA,UACA,MACA,MACM;AACN,QAAM,OAAO,IAAI,SAAS,QAAQ;AAClC,QAAM,QAAQ,OAAO,MAAM,OAAO,MAAM,EAAE,GAAG,OAAO,MAAM,KAAK,CAAC;AAChE,MAAI,CAAC,OAAO;AACV,SAAK,MAAM,8BAA8B;AACzC;AAAA,EACF;AACA,OAAK,KAAK,MAAM,KAAK,OAAO,OAAO,MAAM,EAAE,CAAC;AAC5C,OAAK,MAAM,QAAQ;AACnB,OAAK,OAAO,MAAM,EAAE,WAAW,EAAE,MAAM,SAAS,MAAM,MAAM,EAA2B,CAAC;AAC1F;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/host.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
attachApprovals
|
|
10
10
|
} from "./approvals/index.js";
|
|
11
11
|
import { alwaysFor, attachApprovalPolicy } from "./approvals/policy.js";
|
|
12
|
+
import { createSudo, ownedSudo } from "./approvals/sudo.js";
|
|
12
13
|
import { createAgentControl } from "./control.js";
|
|
13
14
|
import { trackDeliveries } from "./deliveries.js";
|
|
14
15
|
import { createGuests } from "./guests/index.js";
|
|
@@ -23,6 +24,7 @@ import {
|
|
|
23
24
|
AgentPagesService,
|
|
24
25
|
AgentRemoteService,
|
|
25
26
|
AgentSettings,
|
|
27
|
+
AgentSudoService,
|
|
26
28
|
AgentTranscript,
|
|
27
29
|
CONTROL_METHODS,
|
|
28
30
|
CONTROL_READS,
|
|
@@ -31,10 +33,12 @@ import {
|
|
|
31
33
|
PAGE_METHODS,
|
|
32
34
|
REMOTE_METHODS,
|
|
33
35
|
REMOTE_READS,
|
|
34
|
-
SETTINGS_METHODS
|
|
36
|
+
SETTINGS_METHODS,
|
|
37
|
+
SUDO_METHODS
|
|
35
38
|
} from "./services.js";
|
|
36
39
|
import {
|
|
37
40
|
AGENT_HOST,
|
|
41
|
+
deviceOf,
|
|
38
42
|
guestOf,
|
|
39
43
|
principalKey,
|
|
40
44
|
readable,
|
|
@@ -94,6 +98,17 @@ function gatedControls(control, shown, views, labels) {
|
|
|
94
98
|
const viewed = { ...control, image: gated(visible), toolLabel: watchLabel(labels) };
|
|
95
99
|
return { trusted, viewed };
|
|
96
100
|
}
|
|
101
|
+
function approving(agent, policy, judge, context) {
|
|
102
|
+
return attachApprovals(
|
|
103
|
+
{
|
|
104
|
+
harness: agent.harness,
|
|
105
|
+
guestAlways: alwaysFor(policy),
|
|
106
|
+
sudoApprover: () => policy.current().sudo.approver,
|
|
107
|
+
...judge ? { judge } : {}
|
|
108
|
+
},
|
|
109
|
+
context
|
|
110
|
+
);
|
|
111
|
+
}
|
|
97
112
|
const noRevision = () => replicatedState({ revision: 0 });
|
|
98
113
|
async function hostAgent(agent, options = {}) {
|
|
99
114
|
const context = options.context ?? BACKGROUND_CONTEXT;
|
|
@@ -113,14 +128,7 @@ async function hostAgent(agent, options = {}) {
|
|
|
113
128
|
const pages = options.pages ?? createPages();
|
|
114
129
|
const deliveries = trackDeliveries(agent.harness, state, context);
|
|
115
130
|
const policy = await attachApprovalPolicy(agent.harness, context);
|
|
116
|
-
const approvals = options.approvals ?? await
|
|
117
|
-
{
|
|
118
|
-
harness: agent.harness,
|
|
119
|
-
guestAlways: alwaysFor(policy),
|
|
120
|
-
...options.judge ? { judge: options.judge } : {}
|
|
121
|
-
},
|
|
122
|
-
context
|
|
123
|
-
);
|
|
131
|
+
const approvals = options.approvals ?? await approving(agent, policy, options.judge, context);
|
|
124
132
|
const monitor = createMonitor({
|
|
125
133
|
...options.monitor,
|
|
126
134
|
harness: agent.harness,
|
|
@@ -133,6 +141,7 @@ async function hostAgent(agent, options = {}) {
|
|
|
133
141
|
agents: agent.agents,
|
|
134
142
|
approvals,
|
|
135
143
|
policy,
|
|
144
|
+
reviews: agent.reviews,
|
|
136
145
|
context
|
|
137
146
|
});
|
|
138
147
|
const control = {
|
|
@@ -188,6 +197,11 @@ async function hostAgent(agent, options = {}) {
|
|
|
188
197
|
approvals: approvals.state,
|
|
189
198
|
approvalPolicy: policy.state
|
|
190
199
|
};
|
|
200
|
+
const sudo = createSudo({
|
|
201
|
+
approvals,
|
|
202
|
+
agents: () => agent.agents.state(),
|
|
203
|
+
...options.licks ? { licks: options.licks.licks } : {}
|
|
204
|
+
});
|
|
191
205
|
const settings = options.settings ?? noSettings;
|
|
192
206
|
const gates = gatedControls(control, slims.shown, shownViews, transcriptService.labels);
|
|
193
207
|
const { trusted, viewed } = gates;
|
|
@@ -196,10 +210,11 @@ async function hostAgent(agent, options = {}) {
|
|
|
196
210
|
viewed,
|
|
197
211
|
transcript: transcriptService,
|
|
198
212
|
viewMonitor: monitor.viewState,
|
|
213
|
+
sudo,
|
|
199
214
|
settings,
|
|
200
215
|
remote,
|
|
201
216
|
pages,
|
|
202
|
-
guest: guestOpener(guests, agent.agents, mounted.views, context
|
|
217
|
+
guest: guestOpener(guests, agent.agents, mounted.views, context),
|
|
203
218
|
decide: (by) => (id, decision, reason, using) => approvals.decide(id, decision, reason, by, using)
|
|
204
219
|
});
|
|
205
220
|
const offGuests = guests.onEnd((id) => {
|
|
@@ -223,7 +238,10 @@ async function hostAgent(agent, options = {}) {
|
|
|
223
238
|
links += 1;
|
|
224
239
|
const link = new PortLink(endpoint);
|
|
225
240
|
link.greet(serverId);
|
|
226
|
-
|
|
241
|
+
const device = connectOptions?.device ? { device: connectOptions.device } : {};
|
|
242
|
+
host.accept(link, {
|
|
243
|
+
principal: { seat, link: links, ...device }
|
|
244
|
+
});
|
|
227
245
|
},
|
|
228
246
|
async close() {
|
|
229
247
|
offGuests();
|
|
@@ -233,6 +251,7 @@ async function hostAgent(agent, options = {}) {
|
|
|
233
251
|
await monitor.dispose();
|
|
234
252
|
await remote.dispose();
|
|
235
253
|
deliveries.dispose();
|
|
254
|
+
sudo.close();
|
|
236
255
|
approvals.close();
|
|
237
256
|
transcript.dispose();
|
|
238
257
|
mounted.dispose();
|
|
@@ -285,9 +304,12 @@ function lazily(state) {
|
|
|
285
304
|
events: (listener) => readable(state()).events(listener)
|
|
286
305
|
};
|
|
287
306
|
}
|
|
288
|
-
const seated = (serving, seat) => ({
|
|
307
|
+
const seated = (serving, seat, device) => ({
|
|
289
308
|
...seat === "view" ? serving.viewed : serving.trusted,
|
|
290
|
-
decide: serving.decide(
|
|
309
|
+
decide: serving.decide({
|
|
310
|
+
by: seat === "owner" ? "owner" : "follower",
|
|
311
|
+
...device ? { device } : {}
|
|
312
|
+
})
|
|
291
313
|
});
|
|
292
314
|
const VIEW_METHODS = [...CONTROL_READS, ...OWNER_DEVICE_METHODS];
|
|
293
315
|
function serveAgent(serving) {
|
|
@@ -295,7 +317,7 @@ function serveAgent(serving) {
|
|
|
295
317
|
const viewing = { ...serving.transcript, monitor: serving.viewMonitor };
|
|
296
318
|
host.serve(AgentControl, ((_input, principal) => {
|
|
297
319
|
const seat = seatOf(principal);
|
|
298
|
-
const control = seated(serving, seat);
|
|
320
|
+
const control = seated(serving, seat, deviceOf(principal));
|
|
299
321
|
if (seat === "view") return { kind: "view", ...served(() => control, VIEW_METHODS) };
|
|
300
322
|
return { kind: "control", ...served(() => control, CONTROL_METHODS) };
|
|
301
323
|
}));
|
|
@@ -345,6 +367,18 @@ function serveAgent(serving) {
|
|
|
345
367
|
};
|
|
346
368
|
})
|
|
347
369
|
);
|
|
370
|
+
host.serve(AgentSudoService, ((_input, principal, self) => {
|
|
371
|
+
const sudo = serving.sudo;
|
|
372
|
+
if (seatOf(principal) !== "owner" || !sudo) return { kind: "none" };
|
|
373
|
+
const held = self.resources({
|
|
374
|
+
mine: resource({ rev: 1, open: () => ownedSudo(sudo), close: (mine) => mine.release() })
|
|
375
|
+
});
|
|
376
|
+
return {
|
|
377
|
+
kind: "sudo",
|
|
378
|
+
approvals: readable(sudo.approvals),
|
|
379
|
+
...served(() => held.mine.value, SUDO_METHODS)
|
|
380
|
+
};
|
|
381
|
+
}));
|
|
348
382
|
return host;
|
|
349
383
|
}
|
|
350
384
|
export {
|