@ai-ecoverse/slicc-agent 5.9.0 → 5.10.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/dist/agent.js +4 -0
- package/dist/agent.js.map +2 -2
- package/dist/approvals/index.js +14 -0
- package/dist/approvals/index.js.map +2 -2
- package/dist/approvals/record.js +14 -6
- package/dist/approvals/record.js.map +2 -2
- package/dist/guests/client.js +0 -2
- package/dist/guests/client.js.map +2 -2
- package/dist/guests/guard.js +19 -5
- package/dist/guests/guard.js.map +2 -2
- package/dist/guests/index.js +1 -2
- package/dist/guests/index.js.map +2 -2
- package/dist/guests/policy.js +2 -4
- package/dist/guests/policy.js.map +2 -2
- package/dist/guests/scoop.js +268 -0
- package/dist/guests/scoop.js.map +7 -0
- package/dist/host.js +13 -4
- package/dist/host.js.map +2 -2
- package/dist/main.js +8 -4
- package/dist/main.js.map +2 -2
- package/dist/scoops/cli.js +15 -0
- package/dist/scoops/cli.js.map +2 -2
- package/dist/types/agent.d.ts +2 -0
- package/dist/types/approvals/index.d.ts +5 -0
- package/dist/types/approvals/record.d.ts +8 -2
- package/dist/types/guests/guard.d.ts +8 -2
- package/dist/types/guests/policy.d.ts +0 -1
- package/dist/types/guests/scoop.d.ts +45 -0
- package/dist/types/host.d.ts +3 -0
- package/dist/types/main.d.ts +1 -0
- package/package.json +2 -2
package/dist/agent.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
import { openAgents } from "./agents.js";
|
|
9
9
|
import { guestToolGate } from "./guests/guard.js";
|
|
10
10
|
import { guestReviews } from "./guests/review.js";
|
|
11
|
+
import { scoopReviews } from "./guests/scoop.js";
|
|
11
12
|
function answerText(content) {
|
|
12
13
|
if (typeof content === "string") return content;
|
|
13
14
|
if (!Array.isArray(content)) return "";
|
|
@@ -29,6 +30,8 @@ async function openAgent(options) {
|
|
|
29
30
|
registry.install(gate.extension);
|
|
30
31
|
const reviews = guestReviews();
|
|
31
32
|
registry.install(reviews.extension);
|
|
33
|
+
const scooped = scoopReviews();
|
|
34
|
+
registry.install(scooped.extension);
|
|
32
35
|
const harness = await Harness.open(
|
|
33
36
|
options.storage ?? new MemoryStorage(),
|
|
34
37
|
{
|
|
@@ -51,6 +54,7 @@ async function openAgent(options) {
|
|
|
51
54
|
agents,
|
|
52
55
|
reviews,
|
|
53
56
|
gate,
|
|
57
|
+
scoopReviews: scooped,
|
|
54
58
|
cone: () => agents.cone(),
|
|
55
59
|
switchCone: (conversation, switching) => agents.switchCone(conversation, switching),
|
|
56
60
|
onCone: (listener) => agents.onCone(listener),
|
package/dist/agent.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/agent.ts"],
|
|
4
|
-
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { Models } from '@earendil-works/pi-ai/models';\nimport {\n AssistantEntry,\n type Conversation,\n createRegistry,\n type EntryId,\n Harness,\n type HarnessOptions,\n MemoryStorage,\n type ModelRef,\n type Registry,\n type Storage,\n} from '@earendil-works/pi-durable';\nimport { type Agents, openAgents } from './agents.ts';\nimport type { Cones } from './cone.ts';\nimport { type GuestToolGate, guestToolGate } from './guests/guard.ts';\nimport { type GuestReviews, guestReviews } from './guests/review.ts';\n\nexport interface AgentOptions {\n models: Models;\n model: ModelRef;\n storage?: Storage;\n registry?: Registry;\n settings?: HarnessOptions['settings'];\n env?: HarnessOptions['env'];\n conversationCreated?: HarnessOptions['conversationCreated'];\n context?: Context;\n beforeResume?: (harness: Harness, context: Context) => Promise<unknown>;\n}\n\nexport interface Agent extends Cones {\n readonly harness: Harness;\n readonly root: Conversation;\n readonly agents: Agents;\n readonly reviews: GuestReviews;\n readonly gate: GuestToolGate;\n onCone(listener: (conversation: Conversation) => void | Promise<void>): () => void;\n prompt(text: string): Promise<string>;\n close(): Promise<void>;\n}\n\ninterface Block {\n type: string;\n text?: string;\n}\n\nexport function answerText(content: unknown): string {\n if (typeof content === 'string') return content;\n if (!Array.isArray(content)) return '';\n return (content as Block[])\n .filter((block) => block.type === 'text')\n .map((block) => block.text ?? '')\n .join('');\n}\n\nasync function prompt(root: Conversation, text: string, context: Context): Promise<string> {\n const submission = await root.submit({ type: 'input', content: text }, context);\n const settled = await submission.wait(context);\n const { answer, reason } = settled as { answer?: EntryId; reason?: string };\n if (answer === undefined) throw new Error(`unanswered: ${reason}`);\n const entry = await root.commit((tx) => tx.entry(AssistantEntry, answer), context);\n return answerText(entry?.model?.[0]?.content);\n}\n\nexport async function openAgent(options: AgentOptions): Promise<Agent> {\n const context = options.context ?? BACKGROUND_CONTEXT;\n const registry = options.registry ?? createRegistry();\n let opened: Harness | undefined;\n const gate = guestToolGate(() => opened);\n registry.install(gate.extension);\n const reviews = guestReviews();\n registry.install(reviews.extension);\n const harness = await Harness.open(\n options.storage ?? new MemoryStorage(),\n {\n models: options.models,\n registry,\n ...(options.settings ? { settings: options.settings } : {}),\n ...(options.env ? { env: options.env } : {}),\n ...(options.conversationCreated ? { conversationCreated: options.conversationCreated } : {}),\n },\n context\n );\n opened = harness;\n const root = await harness.root(context, { agent: { model: options.model } });\n const agents = await openAgents(harness, root, context);\n await options.beforeResume?.(harness, context);\n harness.resume();\n return {\n harness,\n root,\n agents,\n reviews,\n gate,\n cone: () => agents.cone(),\n switchCone: (conversation, switching) => agents.switchCone(conversation, switching),\n onCone: (listener) => agents.onCone(listener),\n prompt: (text) => prompt(agents.cone(), text, context),\n close: () => harness.close(context),\n };\n}\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,0BAA0B;AAEnC;AAAA,EACE;AAAA,EAEA;AAAA,EAEA;AAAA,EAEA;AAAA,OAIK;AACP,SAAsB,kBAAkB;AAExC,SAA6B,qBAAqB;AAClD,SAA4B,oBAAoB;
|
|
4
|
+
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { Models } from '@earendil-works/pi-ai/models';\nimport {\n AssistantEntry,\n type Conversation,\n createRegistry,\n type EntryId,\n Harness,\n type HarnessOptions,\n MemoryStorage,\n type ModelRef,\n type Registry,\n type Storage,\n} from '@earendil-works/pi-durable';\nimport { type Agents, openAgents } from './agents.ts';\nimport type { Cones } from './cone.ts';\nimport { type GuestToolGate, guestToolGate } from './guests/guard.ts';\nimport { type GuestReviews, guestReviews } from './guests/review.ts';\nimport { type ScoopReviews, scoopReviews } from './guests/scoop.ts';\n\nexport interface AgentOptions {\n models: Models;\n model: ModelRef;\n storage?: Storage;\n registry?: Registry;\n settings?: HarnessOptions['settings'];\n env?: HarnessOptions['env'];\n conversationCreated?: HarnessOptions['conversationCreated'];\n context?: Context;\n beforeResume?: (harness: Harness, context: Context) => Promise<unknown>;\n}\n\nexport interface Agent extends Cones {\n readonly harness: Harness;\n readonly root: Conversation;\n readonly agents: Agents;\n readonly reviews: GuestReviews;\n readonly gate: GuestToolGate;\n readonly scoopReviews: ScoopReviews;\n onCone(listener: (conversation: Conversation) => void | Promise<void>): () => void;\n prompt(text: string): Promise<string>;\n close(): Promise<void>;\n}\n\ninterface Block {\n type: string;\n text?: string;\n}\n\nexport function answerText(content: unknown): string {\n if (typeof content === 'string') return content;\n if (!Array.isArray(content)) return '';\n return (content as Block[])\n .filter((block) => block.type === 'text')\n .map((block) => block.text ?? '')\n .join('');\n}\n\nasync function prompt(root: Conversation, text: string, context: Context): Promise<string> {\n const submission = await root.submit({ type: 'input', content: text }, context);\n const settled = await submission.wait(context);\n const { answer, reason } = settled as { answer?: EntryId; reason?: string };\n if (answer === undefined) throw new Error(`unanswered: ${reason}`);\n const entry = await root.commit((tx) => tx.entry(AssistantEntry, answer), context);\n return answerText(entry?.model?.[0]?.content);\n}\n\nexport async function openAgent(options: AgentOptions): Promise<Agent> {\n const context = options.context ?? BACKGROUND_CONTEXT;\n const registry = options.registry ?? createRegistry();\n let opened: Harness | undefined;\n const gate = guestToolGate(() => opened);\n registry.install(gate.extension);\n const reviews = guestReviews();\n registry.install(reviews.extension);\n const scooped = scoopReviews();\n registry.install(scooped.extension);\n const harness = await Harness.open(\n options.storage ?? new MemoryStorage(),\n {\n models: options.models,\n registry,\n ...(options.settings ? { settings: options.settings } : {}),\n ...(options.env ? { env: options.env } : {}),\n ...(options.conversationCreated ? { conversationCreated: options.conversationCreated } : {}),\n },\n context\n );\n opened = harness;\n const root = await harness.root(context, { agent: { model: options.model } });\n const agents = await openAgents(harness, root, context);\n await options.beforeResume?.(harness, context);\n harness.resume();\n return {\n harness,\n root,\n agents,\n reviews,\n gate,\n scoopReviews: scooped,\n cone: () => agents.cone(),\n switchCone: (conversation, switching) => agents.switchCone(conversation, switching),\n onCone: (listener) => agents.onCone(listener),\n prompt: (text) => prompt(agents.cone(), text, context),\n close: () => harness.close(context),\n };\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,0BAA0B;AAEnC;AAAA,EACE;AAAA,EAEA;AAAA,EAEA;AAAA,EAEA;AAAA,OAIK;AACP,SAAsB,kBAAkB;AAExC,SAA6B,qBAAqB;AAClD,SAA4B,oBAAoB;AAChD,SAA4B,oBAAoB;AA+BzC,SAAS,WAAW,SAA0B;AACnD,MAAI,OAAO,YAAY,SAAU,QAAO;AACxC,MAAI,CAAC,MAAM,QAAQ,OAAO,EAAG,QAAO;AACpC,SAAQ,QACL,OAAO,CAAC,UAAU,MAAM,SAAS,MAAM,EACvC,IAAI,CAAC,UAAU,MAAM,QAAQ,EAAE,EAC/B,KAAK,EAAE;AACZ;AAEA,eAAe,OAAO,MAAoB,MAAc,SAAmC;AACzF,QAAM,aAAa,MAAM,KAAK,OAAO,EAAE,MAAM,SAAS,SAAS,KAAK,GAAG,OAAO;AAC9E,QAAM,UAAU,MAAM,WAAW,KAAK,OAAO;AAC7C,QAAM,EAAE,QAAQ,OAAO,IAAI;AAC3B,MAAI,WAAW,OAAW,OAAM,IAAI,MAAM,eAAe,MAAM,EAAE;AACjE,QAAM,QAAQ,MAAM,KAAK,OAAO,CAAC,OAAO,GAAG,MAAM,gBAAgB,MAAM,GAAG,OAAO;AACjF,SAAO,WAAW,OAAO,QAAQ,CAAC,GAAG,OAAO;AAC9C;AAEA,eAAsB,UAAU,SAAuC;AACrE,QAAM,UAAU,QAAQ,WAAW;AACnC,QAAM,WAAW,QAAQ,YAAY,eAAe;AACpD,MAAI;AACJ,QAAM,OAAO,cAAc,MAAM,MAAM;AACvC,WAAS,QAAQ,KAAK,SAAS;AAC/B,QAAM,UAAU,aAAa;AAC7B,WAAS,QAAQ,QAAQ,SAAS;AAClC,QAAM,UAAU,aAAa;AAC7B,WAAS,QAAQ,QAAQ,SAAS;AAClC,QAAM,UAAU,MAAM,QAAQ;AAAA,IAC5B,QAAQ,WAAW,IAAI,cAAc;AAAA,IACrC;AAAA,MACE,QAAQ,QAAQ;AAAA,MAChB;AAAA,MACA,GAAI,QAAQ,WAAW,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;AAAA,MACzD,GAAI,QAAQ,MAAM,EAAE,KAAK,QAAQ,IAAI,IAAI,CAAC;AAAA,MAC1C,GAAI,QAAQ,sBAAsB,EAAE,qBAAqB,QAAQ,oBAAoB,IAAI,CAAC;AAAA,IAC5F;AAAA,IACA;AAAA,EACF;AACA,WAAS;AACT,QAAM,OAAO,MAAM,QAAQ,KAAK,SAAS,EAAE,OAAO,EAAE,OAAO,QAAQ,MAAM,EAAE,CAAC;AAC5E,QAAM,SAAS,MAAM,WAAW,SAAS,MAAM,OAAO;AACtD,QAAM,QAAQ,eAAe,SAAS,OAAO;AAC7C,UAAQ,OAAO;AACf,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd,MAAM,MAAM,OAAO,KAAK;AAAA,IACxB,YAAY,CAAC,cAAc,cAAc,OAAO,WAAW,cAAc,SAAS;AAAA,IAClF,QAAQ,CAAC,aAAa,OAAO,OAAO,QAAQ;AAAA,IAC5C,QAAQ,CAAC,SAAS,OAAO,OAAO,KAAK,GAAG,MAAM,OAAO;AAAA,IACrD,OAAO,MAAM,QAAQ,MAAM,OAAO;AAAA,EACpC;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/approvals/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
REASONS,
|
|
12
12
|
routable,
|
|
13
13
|
ruled,
|
|
14
|
+
scoopTier,
|
|
14
15
|
sudoApprover,
|
|
15
16
|
tierOf
|
|
16
17
|
} from "./record.js";
|
|
@@ -176,6 +177,11 @@ async function attachApprovals(options, context = BACKGROUND_CONTEXT) {
|
|
|
176
177
|
}
|
|
177
178
|
}
|
|
178
179
|
arm(startup.items);
|
|
180
|
+
const toScoop = (item) => {
|
|
181
|
+
if (options.scoops && item.state === "pending" && scoopTier(item.tier))
|
|
182
|
+
void options.scoops.route(item).catch(() => void 0);
|
|
183
|
+
};
|
|
184
|
+
for (const item of startup.items) toScoop(item);
|
|
179
185
|
const withdrawWhere = (match, reason) => withdrawIn(match, reason, now);
|
|
180
186
|
const judging = async (judge, item, using) => {
|
|
181
187
|
const signal = using.abortSignal;
|
|
@@ -213,6 +219,7 @@ async function attachApprovals(options, context = BACKGROUND_CONTEXT) {
|
|
|
213
219
|
}, using);
|
|
214
220
|
if (created && options.judge && made.state === "pending" && made.tier === "agent")
|
|
215
221
|
void judging(options.judge, made, using);
|
|
222
|
+
if (created) toScoop(made);
|
|
216
223
|
return made;
|
|
217
224
|
},
|
|
218
225
|
decide(id, answer, reason, decider, using) {
|
|
@@ -231,6 +238,13 @@ async function attachApprovals(options, context = BACKGROUND_CONTEXT) {
|
|
|
231
238
|
}, using);
|
|
232
239
|
},
|
|
233
240
|
settled: (id, using) => settledIn(state, id, using),
|
|
241
|
+
assign(id, reviewer, using) {
|
|
242
|
+
return serial((doc) => {
|
|
243
|
+
const item = doc.items.find((entry) => entry.id === id);
|
|
244
|
+
if (item?.state === "pending" && scoopTier(item.tier)) item.reviewer ??= reviewer;
|
|
245
|
+
return item ? copy([item])[0] : void 0;
|
|
246
|
+
}, using);
|
|
247
|
+
},
|
|
234
248
|
async withdraw(id, reason, using) {
|
|
235
249
|
const [hit] = await serial(
|
|
236
250
|
withdrawWhere((item) => item.id === id, cleanReason(reason)),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/approvals/index.ts"],
|
|
4
|
-
"sourcesContent": ["import { type Context, type ReplicatedState, replicatedState } from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { DocumentState, Harness } from '@earendil-works/pi-durable';\nimport type { ApprovalJudge, Judgement } from './judge.ts';\nimport {\n APPROVAL_MS,\n type Approval,\n type ApprovalDraft,\n type ApprovalKind,\n type ApprovalState,\n ApprovalsDoc,\n type ApprovalsState,\n cleanReason,\n type DecidedBy,\n guestKind,\n knownTier,\n MAX_DELAY,\n prune,\n REASONS,\n routable,\n ruled,\n type Settle,\n sudoApprover,\n tierOf,\n} from './record.ts';\n\nexport * from './intent.ts';\nexport * from './judge.ts';\nexport * from './record.ts';\n\nexport type ApprovalDecided =\n | { done: true; state: ApprovalState; error: null }\n | { done: false; state: null; error: string };\n\nexport type Decider = DecidedBy | { by: DecidedBy; device?: string };\n\nconst deciderOf = (decider: Decider) => (typeof decider === 'string' ? { by: decider } : decider);\n\nexport interface Approvals {\n readonly state: ReplicatedState<Approval[]>;\n request(draft: ApprovalDraft & { id?: string }, context: Context): Promise<Approval>;\n decide(\n id: string,\n answer: unknown,\n reason: string | null,\n by: Decider,\n context: Context\n ): Promise<ApprovalDecided>;\n settled(id: string, context: Context): Promise<Approval>;\n withdraw(id: string, reason: string | null, context: Context): Promise<Approval | undefined>;\n withdrawSeat(seat: string, context: Context): Promise<number>;\n expire(id: string, context: Context): Promise<Approval | undefined>;\n close(): void;\n}\n\nexport interface ApprovalsAttach {\n harness: Pick<Harness, 'commit' | 'documentState'>;\n now?: () => number;\n ttlMs?: number;\n judge?: ApprovalJudge;\n guestAlways?: (kind: ApprovalKind) => boolean;\n sudoApprover?: () => unknown;\n}\n\nconst copy = (items: readonly Approval[]): Approval[] => JSON.parse(JSON.stringify(items));\n\nfunction mark(item: Approval, how: Settle, at: number) {\n item.state = how.state;\n item.decidedBy = how.by;\n item.decidedAt = at;\n if (how.reason) item.reason = how.reason;\n}\n\nconst offered = (options: ApprovalsAttach, draft: ApprovalDraft): boolean =>\n guestKind(draft.kind) ? options.guestAlways?.(draft.kind) === true : draft.always === true;\n\nfunction fresh(\n draft: ApprovalDraft & { id?: string },\n at: number,\n ttl: number,\n options: ApprovalsAttach\n): Approval {\n const item = {\n ...draft,\n tier: tierOf(draft.kind, draft.tier, sudoApprover(options.sudoApprover?.())),\n id: draft.id ?? `apr-${crypto.randomUUID()}`,\n state: 'pending',\n decidedBy: null,\n always: offered(options, draft),\n createdAt: at,\n expiresAt: draft.expiresAt ?? at + ttl,\n } as Approval;\n if (!knownTier(item.tier))\n mark(item, { state: 'unanswered', by: 'host', reason: REASONS.unknownTier }, at);\n else if (!routable(draft.kind, item.tier))\n mark(item, { state: 'unanswered', by: 'host', reason: REASONS.unroutable }, at);\n return item;\n}\n\nconst withdrawIn =\n (match: (item: Approval) => boolean, reason: string | undefined, now: () => number) =>\n (doc: ApprovalsState) => {\n const hit = doc.items.filter((item) => item.state === 'pending' && match(item));\n for (const item of hit)\n mark(item, { state: 'withdrawn', by: 'host', ...(reason ? { reason } : {}) }, now());\n return hit.map((item) => copy([item])[0] as Approval);\n };\n\nconst judgedIn = (id: string, judgement: Judgement, now: () => number) => (doc: ApprovalsState) => {\n const item = doc.items.find((entry) => entry.id === id);\n if (item?.tier !== 'agent') return undefined;\n const late = item.state === 'unanswered' && item.reason === REASONS.timeout && !item.verdict;\n if (item.state !== 'pending' && !(late && judgement.verdict)) return undefined;\n if (judgement.verdict) item.verdict = judgement.verdict;\n if (late) return copy([item])[0] as Approval;\n if (judgement.outcome === 'ask') item.tier = 'user';\n else {\n const at = now();\n const ruling = ruled(\n item,\n judgement.outcome === 'approve' ? 'once' : 'deny',\n null,\n 'judge',\n at\n );\n if (ruling.answer) item.answer = ruling.answer;\n if (ruling.settle) mark(item, ruling.settle, at);\n }\n return copy([item])[0] as Approval;\n};\n\nfunction following(\n committed: DocumentState<ApprovalsState>,\n changed: (using: Context, items: Approval[]) => void\n) {\n const items = () => (committed.value as ApprovalsState).items;\n let shown = items();\n return (using: Context) => {\n const now = items();\n if (now === shown) return;\n shown = now;\n changed(using, copy(now));\n };\n}\n\nfunction settledIn(\n state: ReplicatedState<Approval[]>,\n id: string,\n using: Context\n): Promise<Approval> {\n return new Promise((resolve, reject) => {\n const signal = using.abortSignal;\n let done = false;\n let off: (() => void) | undefined;\n const finish = (end: () => void) => {\n done = true;\n off?.();\n signal?.removeEventListener('abort', abort);\n end();\n };\n const abort = () => finish(() => reject(signal?.reason));\n const check = (items: readonly Approval[] | undefined) => {\n const item = items?.find((entry) => entry.id === id);\n if (!item) finish(() => reject(new Error(`There is no approval ${id}.`)));\n else if (item.state !== 'pending') finish(() => resolve(item));\n };\n if (signal?.aborted) return abort();\n check(state.value);\n if (done) return;\n signal?.addEventListener('abort', abort, { once: true });\n off = state.subscribe((items) => check(items));\n });\n}\n\nexport async function attachApprovals(\n options: ApprovalsAttach,\n context: Context = BACKGROUND_CONTEXT\n): Promise<Approvals> {\n const { harness } = options;\n const now = options.now ?? Date.now;\n const ttl = options.ttlMs ?? APPROVAL_MS;\n const timers = new Map<string, ReturnType<typeof setTimeout>>();\n let line: Promise<unknown> = Promise.resolve();\n let closed = false;\n const settle = (item: Approval, how: Settle) => mark(item, how, now());\n const write = <R>(\n change: (doc: ApprovalsState) => R,\n using: Context\n ): Promise<{ result: R; items: Approval[] }> =>\n harness.commit(async (tx) => {\n const doc = await tx.doc(ApprovalsDoc);\n const result = change(doc);\n doc.items = prune(doc.items);\n return { result, items: copy(doc.items) };\n }, using);\n const startup = await write((doc) => {\n for (const item of doc.items) {\n if (item.state !== 'pending') continue;\n if (item.kind === 'sudo')\n settle(item, { state: 'withdrawn', by: 'host', reason: REASONS.restart });\n else if (item.expiresAt <= now())\n settle(item, { state: 'unanswered', by: 'host', reason: REASONS.timeout });\n else if (options.judge && item.tier === 'agent') item.tier = 'user';\n }\n }, context);\n const state = replicatedState<Approval[]>(startup.items);\n const committed = (await harness.documentState(\n ApprovalsDoc,\n context\n )) as DocumentState<ApprovalsState>;\n const follow = following(committed, (using, items) => {\n state.replace(using, items);\n arm(items);\n });\n committed.subscribe((_value, using) => follow(using));\n const serial = <R>(change: (doc: ApprovalsState) => R, using: Context): Promise<R> => {\n const run = line.then(async () => {\n const { result } = await write(change, using);\n follow(using);\n return result;\n });\n line = run.catch(() => undefined);\n return run;\n };\n const expire = (id: string) => {\n timers.delete(id);\n const due = (state.value as Approval[]).find((entry) => entry.id === id);\n if (due?.state === 'pending' && due.expiresAt > now()) return arm(state.value as Approval[]);\n void serial((doc) => {\n const item = doc.items.find((entry) => entry.id === id);\n if (item?.state === 'pending' && item.expiresAt <= now())\n settle(item, { state: 'unanswered', by: 'host', reason: REASONS.timeout });\n }, BACKGROUND_CONTEXT).catch(() => undefined);\n };\n function arm(items: readonly Approval[]) {\n const pending = new Set<string>();\n for (const item of items) {\n if (item.state !== 'pending') continue;\n pending.add(item.id);\n if (timers.has(item.id) || closed) continue;\n timers.set(\n item.id,\n setTimeout(() => expire(item.id), Math.min(MAX_DELAY, Math.max(0, item.expiresAt - now())))\n );\n }\n for (const [id, timer] of timers)\n if (!pending.has(id)) {\n clearTimeout(timer);\n timers.delete(id);\n }\n }\n arm(startup.items);\n const withdrawWhere = (match: (item: Approval) => boolean, reason: string | undefined) =>\n withdrawIn(match, reason, now);\n const judging = async (judge: ApprovalJudge, item: Approval, using: Context) => {\n const signal = using.abortSignal;\n let gone = false;\n const abort = () => {\n gone = true;\n void serial(\n withdrawWhere((entry) => entry.id === item.id && entry.tier === 'agent', REASONS.aborted),\n BACKGROUND_CONTEXT\n ).catch(() => undefined);\n };\n if (signal?.aborted) return abort();\n signal?.addEventListener('abort', abort, { once: true });\n const judgement = await judge\n .judge(item, using)\n .catch((error: unknown): Judgement => ({ outcome: 'ask', error: String(error) }));\n signal?.removeEventListener('abort', abort);\n if (gone || closed) return;\n const after = await serial(judgedIn(item.id, judgement, now), BACKGROUND_CONTEXT).catch(\n () => undefined\n );\n if (after) await judge.record?.(after, judgement, BACKGROUND_CONTEXT).catch(() => undefined);\n };\n return {\n state,\n async request(draft, using) {\n let created = true;\n const made = await serial((doc) => {\n const kept = draft.id && doc.items.find((entry) => entry.id === draft.id);\n if (kept) {\n created = false;\n return copy([kept])[0] as Approval;\n }\n const item = fresh(draft, now(), ttl, options);\n doc.items.push(item);\n return copy([item])[0] as Approval;\n }, using);\n if (created && options.judge && made.state === 'pending' && made.tier === 'agent')\n void judging(options.judge, made, using);\n return made;\n },\n decide(id, answer, reason, decider, using) {\n const { by, device } = deciderOf(decider);\n return serial((doc): ApprovalDecided => {\n const item = doc.items.find((entry) => entry.id === id);\n const ruling = item\n ? ruled(item, answer, reason, by, now())\n : { error: `There is no approval ${id}.` };\n if (item && ruling.settle) {\n if (ruling.answer) item.answer = ruling.answer;\n if (device && ruling.settle.by === by) item.device = device;\n settle(item, ruling.settle);\n }\n if ('error' in ruling && ruling.error)\n return { done: false, state: null, error: ruling.error };\n return { done: true, state: (item as Approval).state, error: null };\n }, using);\n },\n settled: (id, using) => settledIn(state, id, using),\n async withdraw(id, reason, using) {\n const [hit] = await serial(\n withdrawWhere((item) => item.id === id, cleanReason(reason)),\n using\n );\n return hit;\n },\n async withdrawSeat(seat, using) {\n return (\n await serial(\n withdrawWhere((item) => item.seat === seat, REASONS.revoked),\n using\n )\n ).length;\n },\n async expire(id, using) {\n return serial((doc) => {\n const item = doc.items.find((entry) => entry.id === id);\n if (item?.state === 'pending' && item.expiresAt <= now())\n settle(item, { state: 'unanswered', by: 'host', reason: REASONS.timeout });\n return item ? (copy([item])[0] as Approval) : undefined;\n }, using);\n },\n close() {\n closed = true;\n for (const timer of timers.values()) clearTimeout(timer);\n timers.clear();\n },\n };\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAA6C,uBAAuB;AACpE,SAAS,0BAA0B;AAGnC;AAAA,EACE;AAAA,EAKA;AAAA,EAEA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,OACK;AAEP,cAAc;AACd,cAAc;AACd,cAAc;AAQd,MAAM,YAAY,CAAC,YAAsB,OAAO,YAAY,WAAW,EAAE,IAAI,QAAQ,IAAI;
|
|
4
|
+
"sourcesContent": ["import { type Context, type ReplicatedState, replicatedState } from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { DocumentState, Harness } from '@earendil-works/pi-durable';\nimport type { ApprovalJudge, Judgement } from './judge.ts';\nimport {\n APPROVAL_MS,\n type Approval,\n type ApprovalDraft,\n type ApprovalKind,\n type ApprovalState,\n ApprovalsDoc,\n type ApprovalsState,\n cleanReason,\n type DecidedBy,\n guestKind,\n knownTier,\n MAX_DELAY,\n prune,\n REASONS,\n routable,\n ruled,\n type Settle,\n scoopTier,\n sudoApprover,\n tierOf,\n} from './record.ts';\n\nexport * from './intent.ts';\nexport * from './judge.ts';\nexport * from './record.ts';\n\nexport type ApprovalDecided =\n | { done: true; state: ApprovalState; error: null }\n | { done: false; state: null; error: string };\n\nexport type Decider = DecidedBy | { by: DecidedBy; device?: string };\n\nconst deciderOf = (decider: Decider) => (typeof decider === 'string' ? { by: decider } : decider);\n\nexport interface Approvals {\n readonly state: ReplicatedState<Approval[]>;\n request(draft: ApprovalDraft & { id?: string }, context: Context): Promise<Approval>;\n decide(\n id: string,\n answer: unknown,\n reason: string | null,\n by: Decider,\n context: Context\n ): Promise<ApprovalDecided>;\n settled(id: string, context: Context): Promise<Approval>;\n assign(id: string, reviewer: string, context: Context): Promise<Approval | undefined>;\n withdraw(id: string, reason: string | null, context: Context): Promise<Approval | undefined>;\n withdrawSeat(seat: string, context: Context): Promise<number>;\n expire(id: string, context: Context): Promise<Approval | undefined>;\n close(): void;\n}\n\nexport interface ScoopRouter {\n route(item: Approval): Promise<void>;\n}\n\nexport interface ApprovalsAttach {\n harness: Pick<Harness, 'commit' | 'documentState'>;\n now?: () => number;\n ttlMs?: number;\n judge?: ApprovalJudge;\n scoops?: ScoopRouter;\n guestAlways?: (kind: ApprovalKind) => boolean;\n sudoApprover?: () => unknown;\n}\n\nconst copy = (items: readonly Approval[]): Approval[] => JSON.parse(JSON.stringify(items));\n\nfunction mark(item: Approval, how: Settle, at: number) {\n item.state = how.state;\n item.decidedBy = how.by;\n item.decidedAt = at;\n if (how.reason) item.reason = how.reason;\n}\n\nconst offered = (options: ApprovalsAttach, draft: ApprovalDraft): boolean =>\n guestKind(draft.kind) ? options.guestAlways?.(draft.kind) === true : draft.always === true;\n\nfunction fresh(\n draft: ApprovalDraft & { id?: string },\n at: number,\n ttl: number,\n options: ApprovalsAttach\n): Approval {\n const item = {\n ...draft,\n tier: tierOf(draft.kind, draft.tier, sudoApprover(options.sudoApprover?.())),\n id: draft.id ?? `apr-${crypto.randomUUID()}`,\n state: 'pending',\n decidedBy: null,\n always: offered(options, draft),\n createdAt: at,\n expiresAt: draft.expiresAt ?? at + ttl,\n } as Approval;\n if (!knownTier(item.tier))\n mark(item, { state: 'unanswered', by: 'host', reason: REASONS.unknownTier }, at);\n else if (!routable(draft.kind, item.tier))\n mark(item, { state: 'unanswered', by: 'host', reason: REASONS.unroutable }, at);\n return item;\n}\n\nconst withdrawIn =\n (match: (item: Approval) => boolean, reason: string | undefined, now: () => number) =>\n (doc: ApprovalsState) => {\n const hit = doc.items.filter((item) => item.state === 'pending' && match(item));\n for (const item of hit)\n mark(item, { state: 'withdrawn', by: 'host', ...(reason ? { reason } : {}) }, now());\n return hit.map((item) => copy([item])[0] as Approval);\n };\n\nconst judgedIn = (id: string, judgement: Judgement, now: () => number) => (doc: ApprovalsState) => {\n const item = doc.items.find((entry) => entry.id === id);\n if (item?.tier !== 'agent') return undefined;\n const late = item.state === 'unanswered' && item.reason === REASONS.timeout && !item.verdict;\n if (item.state !== 'pending' && !(late && judgement.verdict)) return undefined;\n if (judgement.verdict) item.verdict = judgement.verdict;\n if (late) return copy([item])[0] as Approval;\n if (judgement.outcome === 'ask') item.tier = 'user';\n else {\n const at = now();\n const ruling = ruled(\n item,\n judgement.outcome === 'approve' ? 'once' : 'deny',\n null,\n 'judge',\n at\n );\n if (ruling.answer) item.answer = ruling.answer;\n if (ruling.settle) mark(item, ruling.settle, at);\n }\n return copy([item])[0] as Approval;\n};\n\nfunction following(\n committed: DocumentState<ApprovalsState>,\n changed: (using: Context, items: Approval[]) => void\n) {\n const items = () => (committed.value as ApprovalsState).items;\n let shown = items();\n return (using: Context) => {\n const now = items();\n if (now === shown) return;\n shown = now;\n changed(using, copy(now));\n };\n}\n\nfunction settledIn(\n state: ReplicatedState<Approval[]>,\n id: string,\n using: Context\n): Promise<Approval> {\n return new Promise((resolve, reject) => {\n const signal = using.abortSignal;\n let done = false;\n let off: (() => void) | undefined;\n const finish = (end: () => void) => {\n done = true;\n off?.();\n signal?.removeEventListener('abort', abort);\n end();\n };\n const abort = () => finish(() => reject(signal?.reason));\n const check = (items: readonly Approval[] | undefined) => {\n const item = items?.find((entry) => entry.id === id);\n if (!item) finish(() => reject(new Error(`There is no approval ${id}.`)));\n else if (item.state !== 'pending') finish(() => resolve(item));\n };\n if (signal?.aborted) return abort();\n check(state.value);\n if (done) return;\n signal?.addEventListener('abort', abort, { once: true });\n off = state.subscribe((items) => check(items));\n });\n}\n\nexport async function attachApprovals(\n options: ApprovalsAttach,\n context: Context = BACKGROUND_CONTEXT\n): Promise<Approvals> {\n const { harness } = options;\n const now = options.now ?? Date.now;\n const ttl = options.ttlMs ?? APPROVAL_MS;\n const timers = new Map<string, ReturnType<typeof setTimeout>>();\n let line: Promise<unknown> = Promise.resolve();\n let closed = false;\n const settle = (item: Approval, how: Settle) => mark(item, how, now());\n const write = <R>(\n change: (doc: ApprovalsState) => R,\n using: Context\n ): Promise<{ result: R; items: Approval[] }> =>\n harness.commit(async (tx) => {\n const doc = await tx.doc(ApprovalsDoc);\n const result = change(doc);\n doc.items = prune(doc.items);\n return { result, items: copy(doc.items) };\n }, using);\n const startup = await write((doc) => {\n for (const item of doc.items) {\n if (item.state !== 'pending') continue;\n if (item.kind === 'sudo')\n settle(item, { state: 'withdrawn', by: 'host', reason: REASONS.restart });\n else if (item.expiresAt <= now())\n settle(item, { state: 'unanswered', by: 'host', reason: REASONS.timeout });\n else if (options.judge && item.tier === 'agent') item.tier = 'user';\n }\n }, context);\n const state = replicatedState<Approval[]>(startup.items);\n const committed = (await harness.documentState(\n ApprovalsDoc,\n context\n )) as DocumentState<ApprovalsState>;\n const follow = following(committed, (using, items) => {\n state.replace(using, items);\n arm(items);\n });\n committed.subscribe((_value, using) => follow(using));\n const serial = <R>(change: (doc: ApprovalsState) => R, using: Context): Promise<R> => {\n const run = line.then(async () => {\n const { result } = await write(change, using);\n follow(using);\n return result;\n });\n line = run.catch(() => undefined);\n return run;\n };\n const expire = (id: string) => {\n timers.delete(id);\n const due = (state.value as Approval[]).find((entry) => entry.id === id);\n if (due?.state === 'pending' && due.expiresAt > now()) return arm(state.value as Approval[]);\n void serial((doc) => {\n const item = doc.items.find((entry) => entry.id === id);\n if (item?.state === 'pending' && item.expiresAt <= now())\n settle(item, { state: 'unanswered', by: 'host', reason: REASONS.timeout });\n }, BACKGROUND_CONTEXT).catch(() => undefined);\n };\n function arm(items: readonly Approval[]) {\n const pending = new Set<string>();\n for (const item of items) {\n if (item.state !== 'pending') continue;\n pending.add(item.id);\n if (timers.has(item.id) || closed) continue;\n timers.set(\n item.id,\n setTimeout(() => expire(item.id), Math.min(MAX_DELAY, Math.max(0, item.expiresAt - now())))\n );\n }\n for (const [id, timer] of timers)\n if (!pending.has(id)) {\n clearTimeout(timer);\n timers.delete(id);\n }\n }\n arm(startup.items);\n const toScoop = (item: Approval) => {\n if (options.scoops && item.state === 'pending' && scoopTier(item.tier))\n void options.scoops.route(item).catch(() => undefined);\n };\n for (const item of startup.items) toScoop(item);\n const withdrawWhere = (match: (item: Approval) => boolean, reason: string | undefined) =>\n withdrawIn(match, reason, now);\n const judging = async (judge: ApprovalJudge, item: Approval, using: Context) => {\n const signal = using.abortSignal;\n let gone = false;\n const abort = () => {\n gone = true;\n void serial(\n withdrawWhere((entry) => entry.id === item.id && entry.tier === 'agent', REASONS.aborted),\n BACKGROUND_CONTEXT\n ).catch(() => undefined);\n };\n if (signal?.aborted) return abort();\n signal?.addEventListener('abort', abort, { once: true });\n const judgement = await judge\n .judge(item, using)\n .catch((error: unknown): Judgement => ({ outcome: 'ask', error: String(error) }));\n signal?.removeEventListener('abort', abort);\n if (gone || closed) return;\n const after = await serial(judgedIn(item.id, judgement, now), BACKGROUND_CONTEXT).catch(\n () => undefined\n );\n if (after) await judge.record?.(after, judgement, BACKGROUND_CONTEXT).catch(() => undefined);\n };\n return {\n state,\n async request(draft, using) {\n let created = true;\n const made = await serial((doc) => {\n const kept = draft.id && doc.items.find((entry) => entry.id === draft.id);\n if (kept) {\n created = false;\n return copy([kept])[0] as Approval;\n }\n const item = fresh(draft, now(), ttl, options);\n doc.items.push(item);\n return copy([item])[0] as Approval;\n }, using);\n if (created && options.judge && made.state === 'pending' && made.tier === 'agent')\n void judging(options.judge, made, using);\n if (created) toScoop(made);\n return made;\n },\n decide(id, answer, reason, decider, using) {\n const { by, device } = deciderOf(decider);\n return serial((doc): ApprovalDecided => {\n const item = doc.items.find((entry) => entry.id === id);\n const ruling = item\n ? ruled(item, answer, reason, by, now())\n : { error: `There is no approval ${id}.` };\n if (item && ruling.settle) {\n if (ruling.answer) item.answer = ruling.answer;\n if (device && ruling.settle.by === by) item.device = device;\n settle(item, ruling.settle);\n }\n if ('error' in ruling && ruling.error)\n return { done: false, state: null, error: ruling.error };\n return { done: true, state: (item as Approval).state, error: null };\n }, using);\n },\n settled: (id, using) => settledIn(state, id, using),\n assign(id, reviewer, using) {\n return serial((doc) => {\n const item = doc.items.find((entry) => entry.id === id);\n if (item?.state === 'pending' && scoopTier(item.tier)) item.reviewer ??= reviewer;\n return item ? (copy([item])[0] as Approval) : undefined;\n }, using);\n },\n async withdraw(id, reason, using) {\n const [hit] = await serial(\n withdrawWhere((item) => item.id === id, cleanReason(reason)),\n using\n );\n return hit;\n },\n async withdrawSeat(seat, using) {\n return (\n await serial(\n withdrawWhere((item) => item.seat === seat, REASONS.revoked),\n using\n )\n ).length;\n },\n async expire(id, using) {\n return serial((doc) => {\n const item = doc.items.find((entry) => entry.id === id);\n if (item?.state === 'pending' && item.expiresAt <= now())\n settle(item, { state: 'unanswered', by: 'host', reason: REASONS.timeout });\n return item ? (copy([item])[0] as Approval) : undefined;\n }, using);\n },\n close() {\n closed = true;\n for (const timer of timers.values()) clearTimeout(timer);\n timers.clear();\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/approvals/record.js
CHANGED
|
@@ -17,12 +17,17 @@ const REASONS = {
|
|
|
17
17
|
restart: "restart",
|
|
18
18
|
revoked: "revoked",
|
|
19
19
|
aborted: "aborted",
|
|
20
|
-
disconnected: "disconnected"
|
|
20
|
+
disconnected: "disconnected",
|
|
21
|
+
noScoop: "no-scoop",
|
|
22
|
+
scoopStopped: "scoop-stopped",
|
|
23
|
+
scoopAsked: "scoop-asked",
|
|
24
|
+
scoopUndecided: "scoop-undecided"
|
|
21
25
|
};
|
|
22
26
|
const guestKind = (kind) => kind !== "sudo";
|
|
27
|
+
const scoopTier = (tier) => typeof tier === "string" && /^scoop:\S+$/.test(tier);
|
|
23
28
|
function knownTier(tier) {
|
|
24
29
|
if (tier === "user" || tier === "agent" || tier === "cone") return true;
|
|
25
|
-
return
|
|
30
|
+
return scoopTier(tier);
|
|
26
31
|
}
|
|
27
32
|
function routable(kind, tier) {
|
|
28
33
|
return !(kind === "guest-tool" && tier === "cone");
|
|
@@ -31,9 +36,10 @@ function answerOf(value) {
|
|
|
31
36
|
return value === "once" || value === "always" || value === "deny" ? value : null;
|
|
32
37
|
}
|
|
33
38
|
const personal = (by) => by === "owner" || by === "follower";
|
|
34
|
-
function answerer(by,
|
|
35
|
-
if (personal(by)) return tier === "user";
|
|
36
|
-
if (by === "judge") return tier === "agent";
|
|
39
|
+
function answerer(by, item) {
|
|
40
|
+
if (personal(by)) return item.tier === "user";
|
|
41
|
+
if (by === "judge") return item.tier === "agent";
|
|
42
|
+
if (scoopTier(by)) return scoopTier(item.tier) && item.reviewer === by;
|
|
37
43
|
return true;
|
|
38
44
|
}
|
|
39
45
|
const sudoApprover = (value) => value === "agent" ? "agent" : "user";
|
|
@@ -63,8 +69,9 @@ function ruled(item, answer, reason, by, at) {
|
|
|
63
69
|
error: gone("unanswered"),
|
|
64
70
|
settle: { state: "unanswered", by: "host", reason: REASONS.timeout }
|
|
65
71
|
};
|
|
66
|
-
if (!answerer(by, item
|
|
72
|
+
if (!answerer(by, item)) return { error: "This request isn't yours to answer." };
|
|
67
73
|
const known = answerOf(answer);
|
|
74
|
+
if (known === "always" && scoopTier(by)) return { error: "A scoop answers once or deny." };
|
|
68
75
|
if (known === "always" && !item.always)
|
|
69
76
|
return { error: "Always isn't offered for this request." };
|
|
70
77
|
if (!known) return { settle: { state: "rejected", by, reason: REASONS.unknownAnswer } };
|
|
@@ -89,6 +96,7 @@ export {
|
|
|
89
96
|
prune,
|
|
90
97
|
routable,
|
|
91
98
|
ruled,
|
|
99
|
+
scoopTier,
|
|
92
100
|
sudoApprover,
|
|
93
101
|
tierOf
|
|
94
102
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/approvals/record.ts"],
|
|
4
|
-
"sourcesContent": ["import { defineDoc } from '@earendil-works/pi-durable';\n\nexport type ApprovalKind = 'guest-message' | 'guest-tool' | 'sudo';\n\nexport type ApprovalState = 'pending' | 'approved' | 'rejected' | 'unanswered' | 'withdrawn';\n\nexport type ApprovalTier = 'user' | 'agent' | 'cone' | `scoop:${string}`;\n\nexport type ApprovalAnswer = 'once' | 'always' | 'deny';\n\nexport type DecidedBy = 'owner' | 'follower' | 'judge' | 'host'
|
|
5
|
-
"mappings": "AAAA,SAAS,iBAAiB;
|
|
4
|
+
"sourcesContent": ["import { defineDoc } from '@earendil-works/pi-durable';\n\nexport type ApprovalKind = 'guest-message' | 'guest-tool' | 'sudo';\n\nexport type ApprovalState = 'pending' | 'approved' | 'rejected' | 'unanswered' | 'withdrawn';\n\nexport type ApprovalTier = 'user' | 'agent' | 'cone' | `scoop:${string}`;\n\nexport type ApprovalAnswer = 'once' | 'always' | 'deny';\n\nexport type DecidedBy = 'owner' | 'follower' | 'judge' | 'host' | `scoop:${string}`;\n\nexport type ApprovalRequester = {\n kind: 'guest' | 'agent' | 'user';\n name: string;\n seat?: string;\n agentId?: string;\n where?: string;\n device?: string;\n};\n\nexport type ApprovalVerdict = {\n decision: 'allow' | 'deny' | 'unsure';\n reason: string;\n risk?: number;\n aligned?: number;\n model?: string;\n};\n\nexport type ApprovalAttachment = { name: string; mimeType: string; size: number };\n\nexport type GuestMessageDetail = { text: string; attachments: ApprovalAttachment[] };\n\nexport type GuestToolDetail = { tool: string; summary: string; arguments?: string; task?: number };\n\nexport type SudoDetail = {\n command: string;\n target?: string;\n cwd?: string;\n alwaysScope?: 'cwd';\n};\n\ntype Base = {\n id: string;\n seat?: string;\n agentId: string;\n requester: ApprovalRequester;\n tier: string;\n state: ApprovalState;\n decidedBy: DecidedBy | null;\n device?: string;\n answer?: 'once' | 'always';\n reviewer?: string;\n reason?: string;\n verdict?: ApprovalVerdict;\n always: boolean;\n createdAt: number;\n expiresAt: number;\n decidedAt?: number;\n};\n\nexport type GuestMessageApproval = Base & { kind: 'guest-message'; detail: GuestMessageDetail };\nexport type GuestToolApproval = Base & { kind: 'guest-tool'; detail: GuestToolDetail };\nexport type SudoApproval = Base & { kind: 'sudo'; detail: SudoDetail };\n\nexport type Approval = GuestMessageApproval | GuestToolApproval | SudoApproval;\n\ntype DraftOf<T extends Approval> = Pick<T, 'kind' | 'detail' | 'agentId' | 'requester'> & {\n seat?: string;\n tier: string;\n always?: boolean;\n expiresAt?: number;\n};\n\nexport type ApprovalDraft =\n | DraftOf<GuestMessageApproval>\n | DraftOf<GuestToolApproval>\n | DraftOf<SudoApproval>;\n\nexport type ApprovalsState = { items: Approval[] };\n\nexport const ApprovalsDoc = defineDoc<ApprovalsState>({\n kind: 'slicc.approvals',\n version: 1,\n scope: 'session',\n initial: () => ({ items: [] }),\n});\n\nexport const APPROVAL_MS = 300_000;\nexport const APPROVALS_KEPT = 100;\nexport const MAX_DELAY = 2_147_483_647;\nexport const REASON_LENGTH = 500;\n\nexport const REASONS = {\n timeout: 'timeout',\n unknownTier: 'unknown-tier',\n unroutable: 'unroutable',\n unknownAnswer: 'unknown-answer',\n restart: 'restart',\n revoked: 'revoked',\n aborted: 'aborted',\n disconnected: 'disconnected',\n noScoop: 'no-scoop',\n scoopStopped: 'scoop-stopped',\n scoopAsked: 'scoop-asked',\n scoopUndecided: 'scoop-undecided',\n} as const;\n\nexport const guestKind = (kind: ApprovalKind): boolean => kind !== 'sudo';\n\nexport const scoopTier = (tier: unknown): tier is `scoop:${string}` =>\n typeof tier === 'string' && /^scoop:\\S+$/.test(tier);\n\nexport function knownTier(tier: unknown): tier is ApprovalTier {\n if (tier === 'user' || tier === 'agent' || tier === 'cone') return true;\n return scoopTier(tier);\n}\n\nexport function routable(kind: ApprovalKind, tier: ApprovalTier): boolean {\n return !(kind === 'guest-tool' && tier === 'cone');\n}\n\nexport function answerOf(value: unknown): ApprovalAnswer | null {\n return value === 'once' || value === 'always' || value === 'deny' ? value : null;\n}\n\nexport const personal = (by: DecidedBy): boolean => by === 'owner' || by === 'follower';\n\nexport function answerer(by: DecidedBy, item: Pick<Approval, 'tier' | 'reviewer'>): boolean {\n if (personal(by)) return item.tier === 'user';\n if (by === 'judge') return item.tier === 'agent';\n if (scoopTier(by)) return scoopTier(item.tier) && item.reviewer === by;\n return true;\n}\n\nexport type SudoApprover = 'user' | 'agent';\n\nexport const sudoApprover = (value: unknown): SudoApprover =>\n value === 'agent' ? 'agent' : 'user';\n\nexport const tierOf = (kind: ApprovalKind, tier: string, sudo: SudoApprover): string =>\n kind === 'sudo' ? sudo : tier;\n\nexport function gone(state: ApprovalState): string {\n if (state === 'unanswered') return 'This request timed out.';\n if (state === 'withdrawn') return 'This request was withdrawn.';\n return 'Someone else already answered this.';\n}\n\nexport function cleanReason(reason: unknown): string | undefined {\n if (typeof reason !== 'string') return undefined;\n const text = reason.trim().slice(0, REASON_LENGTH);\n return text || undefined;\n}\n\nconst decided = (item: Approval) => item.decidedAt ?? item.createdAt;\n\nexport function prune(items: Approval[], kept = APPROVALS_KEPT): Approval[] {\n const settled = items.filter((item) => item.state !== 'pending');\n settled.sort((a, b) => decided(a) - decided(b));\n const drop = new Set(settled.slice(0, Math.max(0, settled.length - kept)));\n return items.filter((item) => !drop.has(item));\n}\n\nexport type Settle = { state: Exclude<ApprovalState, 'pending'>; by: DecidedBy; reason?: string };\n\nexport type Ruling = { error?: string; settle?: Settle; answer?: 'once' | 'always' };\n\nconst why = (reason: string | undefined) => (reason ? { reason } : {});\n\nexport function ruled(\n item: Approval,\n answer: unknown,\n reason: unknown,\n by: DecidedBy,\n at: number\n): Ruling {\n if (item.state !== 'pending') return { error: gone(item.state) };\n if (item.expiresAt <= at)\n return {\n error: gone('unanswered'),\n settle: { state: 'unanswered', by: 'host', reason: REASONS.timeout },\n };\n if (!answerer(by, item)) return { error: \"This request isn't yours to answer.\" };\n const known = answerOf(answer);\n if (known === 'always' && scoopTier(by)) return { error: 'A scoop answers once or deny.' };\n if (known === 'always' && !item.always)\n return { error: \"Always isn't offered for this request.\" };\n if (!known) return { settle: { state: 'rejected', by, reason: REASONS.unknownAnswer } };\n const given = why(cleanReason(reason));\n if (known === 'deny') return { settle: { state: 'rejected', by, ...given } };\n return { settle: { state: 'approved', by, ...given }, answer: known };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/guests/client.js
CHANGED
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
} from "../services.js";
|
|
7
7
|
import { AGENT_HOST, kept, plain, replicated } from "../serving.js";
|
|
8
8
|
import { PortLink } from "../wire.js";
|
|
9
|
-
import { SCOOP_REVIEW_LATER } from "./policy.js";
|
|
10
9
|
const GUEST_ONLY = "a guest can only watch and send messages";
|
|
11
10
|
const SEAT_REFUSED = "This guest link isn't valid any more.";
|
|
12
11
|
const SAID = {
|
|
@@ -15,7 +14,6 @@ const SAID = {
|
|
|
15
14
|
"too-long": "That message is too long for a guest seat.",
|
|
16
15
|
gone: "The cone this seat shares is gone.",
|
|
17
16
|
unavailable: "Messages from guests need a review, and this agent can't review them yet.",
|
|
18
|
-
scoop: SCOOP_REVIEW_LATER,
|
|
19
17
|
overflow: "Too many of your messages are waiting for approval. Try again once one is answered."
|
|
20
18
|
};
|
|
21
19
|
const proposedSentence = (reason) => SAID[reason] ?? "The message was not delivered.";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/guests/client.ts"],
|
|
4
|
-
"sourcesContent": ["import { createHost, isChordError, type Readable, replicatedState } from '@earendil-works/chord';\nimport type { ConversationView } from '@earendil-works/pi-durable';\nimport { type AgentConnection, final, lasting } from '../client.ts';\nimport {\n type AgentControl,\n AgentGuestService,\n type AgentSettings,\n CONTROL_METHODS,\n type GUEST_METHODS,\n type GuestProposal,\n type GuestShown,\n type SendResponse,\n} from '../services.ts';\nimport { AGENT_HOST, kept, plain, type Remote, replicated } from '../serving.ts';\nimport { type PortEndpoint, PortLink } from '../wire.ts';\nimport type { GuestOpened, Proposed, ProposeRequest } from './index.ts';\
|
|
5
|
-
"mappings": "AAAA,SAAS,YAAY,cAA6B,uBAAuB;AAEzE,SAA+B,OAAO,eAAe;AACrD;AAAA,EAEE;AAAA,EAEA;AAAA,OAKK;AACP,SAAS,YAAY,MAAM,OAAoB,kBAAkB;AACjE,SAA4B,gBAAgB;
|
|
4
|
+
"sourcesContent": ["import { createHost, isChordError, type Readable, replicatedState } from '@earendil-works/chord';\nimport type { ConversationView } from '@earendil-works/pi-durable';\nimport { type AgentConnection, final, lasting } from '../client.ts';\nimport {\n type AgentControl,\n AgentGuestService,\n type AgentSettings,\n CONTROL_METHODS,\n type GUEST_METHODS,\n type GuestProposal,\n type GuestShown,\n type SendResponse,\n} from '../services.ts';\nimport { AGENT_HOST, kept, plain, type Remote, replicated } from '../serving.ts';\nimport { type PortEndpoint, PortLink } from '../wire.ts';\nimport type { GuestOpened, Proposed, ProposeRequest } from './index.ts';\n\nexport const GUEST_ONLY = 'a guest can only watch and send messages';\nexport const SEAT_REFUSED = \"This guest link isn't valid any more.\";\n\nconst SAID: Record<string, string> = {\n revoked: 'This guest seat was revoked.',\n empty: 'There is nothing to send.',\n 'too-long': 'That message is too long for a guest seat.',\n gone: 'The cone this seat shares is gone.',\n unavailable: \"Messages from guests need a review, and this agent can't review them yet.\",\n overflow: 'Too many of your messages are waiting for approval. Try again once one is answered.',\n};\n\nexport const proposedSentence = (reason: string): string =>\n SAID[reason] ?? 'The message was not delivered.';\n\nconst refusal = (message: string, code = 'EACCES') => Object.assign(new Error(message), { code });\n\nexport interface GuestConnection extends AgentConnection {\n readonly guest: GuestShown;\n propose(request: ProposeRequest): Promise<Proposed>;\n}\n\nexport async function connectGuest(endpoint: PortEndpoint): Promise<GuestConnection> {\n const link = new PortLink(endpoint);\n let serverId: string;\n try {\n serverId = await link.hello;\n } catch {\n throw refusal(SEAT_REFUSED);\n }\n const host = createHost({ name: `guest:${crypto.randomUUID()}`, kind: 'client' });\n host.connect(link);\n const handle = host.get(AgentGuestService, {}, { at: AGENT_HOST }) as unknown as Remote;\n const fail = async (error: unknown): Promise<never> => {\n handle.release();\n link.close('handshake failed');\n await host.close();\n throw error;\n };\n try {\n await Promise.race([\n handle.ready,\n link.ended.then(() => {\n throw refusal(SEAT_REFUSED);\n }),\n ]);\n } catch (error) {\n return fail(\n isChordError(error) && error.code === 'forbidden' ? refusal(SEAT_REFUSED) : final(error)\n );\n }\n if (handle.kind !== 'guest') return fail(refusal(SEAT_REFUSED));\n const transcriptMember = handle.transcript as Readable<ConversationView | undefined>;\n const seatMember = handle.seat as Readable<GuestShown | undefined>;\n const proposalsMember = handle.proposals as Readable<GuestProposal[] | undefined>;\n const keeping = [transcriptMember, seatMember, proposalsMember].map(kept);\n await Promise.race([Promise.all(keeping.map((keep) => keep.ready)), link.ended]);\n const through =\n <T>(name: (typeof GUEST_METHODS)[number]) =>\n async (...args: unknown[]): Promise<T> => {\n const call = handle[name] as (args: unknown[], ctx: unknown) => Promise<T>;\n try {\n return await host.root((ctx) => call(args, ctx), {});\n } catch (error) {\n throw plain(error);\n }\n };\n const propose = (request: ProposeRequest) => through<Proposed>('propose')(request);\n const guest = seatMember.value as GuestShown;\n const refuse = () => Promise.reject(refusal(GUEST_ONLY));\n const control = Object.fromEntries(\n CONTROL_METHODS.map((name) => [name, refuse])\n ) as unknown as AgentControl;\n control.guestOpen = (draft) => through<GuestOpened>('guestOpen')(draft);\n control.guestRevoke = (id) => through<{ revoked: boolean }>('guestRevoke')(id);\n control.send = async (request): Promise<SendResponse> => {\n if (request.attachments?.length)\n return {\n accepted: false,\n submissionId: null,\n error: { code: 'EACCES', message: \"A guest can't attach files.\" },\n };\n const proposed = await propose({ text: request.text, requestId: request.requestId });\n if (proposed.state === 'submitted')\n return { accepted: true, submissionId: proposed.submissionId, error: null };\n if (proposed.state === 'queued')\n return { accepted: true, submissionId: proposed.review, error: null };\n return {\n accepted: false,\n submissionId: null,\n error: { code: 'EACCES', message: proposedSentence(proposed.reason) },\n };\n };\n const transcript = lasting(replicated(transcriptMember));\n const proposals = lasting(replicated(proposalsMember));\n let open = true;\n const finish = () => {\n if (!open) return;\n open = false;\n transcript.freeze();\n proposals.freeze();\n for (const keep of keeping) keep.stop();\n handle.release();\n };\n const closed = link.ended.then((reason) => {\n finish();\n return reason;\n });\n const settings: AgentSettings = {\n state: replicatedState({ models: [], accounts: [] }),\n connect: refuse,\n disconnect: refuse,\n signIn: refuse,\n dismissMoved: refuse,\n };\n return {\n serverId,\n guest,\n control,\n transcript: transcript.state,\n proposals: proposals.state,\n deliveries: replicatedState({}),\n agents: replicatedState({\n active: guest.agentId,\n agents: [\n { id: guest.agentId, name: guest.name, kind: 'cone' as const, parentId: null, role: null },\n ],\n }),\n settings,\n closed,\n propose,\n prompt: refuse,\n webhook: refuse,\n async close() {\n finish();\n link.close('closed');\n await closed;\n await host.close();\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/guests/guard.js
CHANGED
|
@@ -5,8 +5,8 @@ import {
|
|
|
5
5
|
LiveDoc,
|
|
6
6
|
ToolTask
|
|
7
7
|
} from "@earendil-works/pi-durable";
|
|
8
|
-
import { REASONS } from "../approvals/record.js";
|
|
9
|
-
import { gateKind, gateRefusal
|
|
8
|
+
import { REASONS, scoopTier } from "../approvals/record.js";
|
|
9
|
+
import { gateKind, gateRefusal } from "./policy.js";
|
|
10
10
|
import { GuestsDoc, isGate, liveSeat } from "./seats.js";
|
|
11
11
|
const GUEST_GUARD = "slicc-guest-guard";
|
|
12
12
|
const GUEST_REQUEST = "guest:";
|
|
@@ -19,7 +19,6 @@ const GATE_REASONS = {
|
|
|
19
19
|
judged: "The automatic check declined this tool call in a run a guest started",
|
|
20
20
|
unanswered: "Nobody approved this tool call in a run a guest started in time.",
|
|
21
21
|
unroutable: "The cone can't review a tool call it is itself waiting on.",
|
|
22
|
-
scoop: SCOOP_REVIEW_LATER,
|
|
23
22
|
withdrawn: "The review of this tool call was withdrawn."
|
|
24
23
|
};
|
|
25
24
|
function canonical(value) {
|
|
@@ -33,7 +32,22 @@ function summaryOf(args) {
|
|
|
33
32
|
const text = values.length === 1 && typeof values[0] === "string" ? values[0] : canonical(args);
|
|
34
33
|
return text.length > SUMMARY_LENGTH ? `${text.slice(0, SUMMARY_LENGTH - 1)}\u2026` : text;
|
|
35
34
|
}
|
|
35
|
+
const SCOOP_SAID = {
|
|
36
|
+
[REASONS.noScoop]: (name) => `there is no scoop ${name} in this cone to review it.`,
|
|
37
|
+
[REASONS.scoopStopped]: (name) => `the scoop ${name} is stopped, so it can't review it.`,
|
|
38
|
+
[REASONS.scoopAsked]: (name) => `the scoop ${name} can't review a call that asks something of it.`,
|
|
39
|
+
[REASONS.scoopUndecided]: (name) => `the scoop ${name} ended its review without deciding.`
|
|
40
|
+
};
|
|
41
|
+
function scoopReason(item) {
|
|
42
|
+
const name = item.tier.slice("scoop:".length);
|
|
43
|
+
if (item.decidedBy?.startsWith("scoop:"))
|
|
44
|
+
return `The scoop ${name} declined this tool call in a run a guest started${item.reason ? `: ${item.reason}` : "."}`;
|
|
45
|
+
const said = SCOOP_SAID[item.reason ?? ""];
|
|
46
|
+
return said ? `A guest started this run, and ${said(name)}` : null;
|
|
47
|
+
}
|
|
36
48
|
function reasonOf(item) {
|
|
49
|
+
const scooped = item.state === "rejected" && scoopTier(item.tier) ? scoopReason(item) : null;
|
|
50
|
+
if (scooped) return scooped;
|
|
37
51
|
if (item.state === "rejected" && item.decidedBy === "judge")
|
|
38
52
|
return item.verdict ? `${GATE_REASONS.judged}: ${item.verdict.reason}` : `${GATE_REASONS.judged}.`;
|
|
39
53
|
if (item.state === "rejected")
|
|
@@ -62,7 +76,6 @@ ${tier}`, { seat, tier: String(tier) });
|
|
|
62
76
|
return [...found.values()];
|
|
63
77
|
}
|
|
64
78
|
function settledEarly(seat, tier, policy, at, tool, args) {
|
|
65
|
-
if (gateKind(tier) === "scoop") return GATE_REASONS.scoop;
|
|
66
79
|
if (!policy.allowedToolGates.includes(gateKind(tier)))
|
|
67
80
|
return gateRefusal("a guest's tool calls", policy.allowedToolGates);
|
|
68
81
|
if (!liveSeat(seat, at)) return GATE_REASONS.ended;
|
|
@@ -178,6 +191,7 @@ export {
|
|
|
178
191
|
canonical,
|
|
179
192
|
guestToolGate,
|
|
180
193
|
reasonOf,
|
|
181
|
-
summaryOf
|
|
194
|
+
summaryOf,
|
|
195
|
+
taintsOf
|
|
182
196
|
};
|
|
183
197
|
//# sourceMappingURL=guard.js.map
|
package/dist/guests/guard.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/guests/guard.ts"],
|
|
4
|
-
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport { awaitWithContext, BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport {\n defineExtension,\n type Extension,\n type Harness,\n type HookApi,\n hook,\n LiveDoc,\n type SubmissionId,\n type TaskId,\n type ToolHookCall,\n ToolTask,\n} from '@earendil-works/pi-durable';\nimport type { Approval, Approvals } from '../approvals/index.ts';\nimport { REASONS } from '../approvals/record.ts';\nimport { type GuestPolicy, gateKind, gateRefusal
|
|
5
|
-
"mappings": "AACA,SAAS,kBAAkB,0BAA0B;AACrD;AAAA,EACE;AAAA,EAIA;AAAA,EACA;AAAA,EAIA;AAAA,OACK;AAEP,SAAS,
|
|
4
|
+
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport { awaitWithContext, BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport {\n defineExtension,\n type Extension,\n type Harness,\n type HookApi,\n hook,\n LiveDoc,\n type SubmissionId,\n type TaskId,\n type ToolHookCall,\n ToolTask,\n} from '@earendil-works/pi-durable';\nimport type { Approval, Approvals } from '../approvals/index.ts';\nimport { REASONS, scoopTier } from '../approvals/record.ts';\nimport { type GuestPolicy, gateKind, gateRefusal } from './policy.ts';\nimport { type AlwaysTool, GuestsDoc, isGate, liveSeat, type SeatRecord } from './seats.ts';\n\nexport const GUEST_GUARD = 'slicc-guest-guard';\nexport const GUEST_REQUEST = 'guest:';\nexport const GUEST_TOOL_MEMO = 'slicc.guest-tool';\nexport const SUMMARY_LENGTH = 2000;\n\nexport const GATE_REASONS = {\n unknown:\n 'A guest started this run, and its seat names no review for tool calls that this agent knows.',\n ended: \"A guest started this run, and that guest's seat has ended.\",\n rejected: 'The owner declined this tool call in a run a guest started.',\n judged: 'The automatic check declined this tool call in a run a guest started',\n unanswered: 'Nobody approved this tool call in a run a guest started in time.',\n unroutable: \"The cone can't review a tool call it is itself waiting on.\",\n withdrawn: 'The review of this tool call was withdrawn.',\n} as const;\n\nexport interface GateHost {\n approvals: Pick<Approvals, 'state' | 'request' | 'settled' | 'withdraw'>;\n policy(): GuestPolicy;\n now?: () => number;\n}\n\nexport interface GuestToolGate {\n readonly extension: Extension;\n bind(host: GateHost): void;\n}\n\ntype Held = Pick<Harness, 'submission' | 'commit' | 'waitForTask'>;\n\ntype Taint = { seat: SeatRecord | null; tier: string };\n\nexport function canonical(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`;\n if (value && typeof value === 'object')\n return `{${Object.keys(value)\n .sort()\n .map((key) => `${JSON.stringify(key)}:${canonical((value as Record<string, unknown>)[key])}`)\n .join(',')}}`;\n return JSON.stringify(value ?? null);\n}\n\nexport function summaryOf(args: Record<string, unknown>): string {\n const values = Object.values(args);\n const text = values.length === 1 && typeof values[0] === 'string' ? values[0] : canonical(args);\n return text.length > SUMMARY_LENGTH ? `${text.slice(0, SUMMARY_LENGTH - 1)}\u2026` : text;\n}\n\nconst SCOOP_SAID: Record<string, (name: string) => string> = {\n [REASONS.noScoop]: (name) => `there is no scoop ${name} in this cone to review it.`,\n [REASONS.scoopStopped]: (name) => `the scoop ${name} is stopped, so it can't review it.`,\n [REASONS.scoopAsked]: (name) =>\n `the scoop ${name} can't review a call that asks something of it.`,\n [REASONS.scoopUndecided]: (name) => `the scoop ${name} ended its review without deciding.`,\n};\n\nfunction scoopReason(item: Approval): string | null {\n const name = item.tier.slice('scoop:'.length);\n if (item.decidedBy?.startsWith('scoop:'))\n return `The scoop ${name} declined this tool call in a run a guest started${item.reason ? `: ${item.reason}` : '.'}`;\n const said = SCOOP_SAID[item.reason ?? ''];\n return said ? `A guest started this run, and ${said(name)}` : null;\n}\n\nexport function reasonOf(item: Approval): string {\n const scooped = item.state === 'rejected' && scoopTier(item.tier) ? scoopReason(item) : null;\n if (scooped) return scooped;\n if (item.state === 'rejected' && item.decidedBy === 'judge')\n return item.verdict\n ? `${GATE_REASONS.judged}: ${item.verdict.reason}`\n : `${GATE_REASONS.judged}.`;\n if (item.state === 'rejected')\n return item.reason && item.reason !== REASONS.unknownAnswer\n ? `${GATE_REASONS.rejected} ${item.reason}`\n : GATE_REASONS.rejected;\n if (item.state === 'withdrawn') return GATE_REASONS.withdrawn;\n if (item.reason === REASONS.unknownTier) return GATE_REASONS.unknown;\n if (item.reason === REASONS.unroutable) return GATE_REASONS.unroutable;\n return GATE_REASONS.unanswered;\n}\n\nexport async function taintsOf(\n inputs: readonly SubmissionId[],\n seats: Readonly<Record<string, SeatRecord>>,\n harness: Pick<Harness, 'submission'>,\n context: Context\n): Promise<Taint[]> {\n const found = new Map<string, Taint>();\n for (const id of inputs) {\n const seat = Object.values(seats).find((record) =>\n record.submissions?.some((entry) => entry.id === String(id))\n );\n const tier = seat?.submissions?.find((entry) => entry.id === String(id))?.tools;\n if (seat) {\n found.set(`${seat.id}\\n${tier}`, { seat, tier: String(tier) });\n continue;\n }\n const submission = await harness.submission(id, context);\n const record = submission ? await submission.status(context) : undefined;\n if (record?.requestId?.startsWith(GUEST_REQUEST)) return [{ seat: null, tier: '' }];\n }\n return [...found.values()];\n}\n\nfunction settledEarly(\n seat: SeatRecord,\n tier: string,\n policy: GuestPolicy,\n at: number,\n tool: string,\n args: string\n): string | null {\n if (!policy.allowedToolGates.includes(gateKind(tier)))\n return gateRefusal(\"a guest's tool calls\", policy.allowedToolGates);\n if (!liveSeat(seat, at)) return GATE_REASONS.ended;\n if (tier === 'off') return '';\n const kept = seat.alwaysTools?.some((rule) => rule.tool === tool && rule.arguments === args);\n return policy.always.tools && kept ? '' : null;\n}\n\nconst ended = (status: string) => status === 'terminal' || status === 'completing';\n\nexport function guestToolGate(harness: () => Held | undefined): GuestToolGate {\n let resolveHost: (host: GateHost) => void = () => {};\n const bound = new Promise<GateHost>((resolve) => {\n resolveHost = resolve;\n });\n const watched = new Set<string>();\n const watch = (host: GateHost, id: string, task: number) => {\n const held = harness();\n if (!held || watched.has(id)) return;\n watched.add(id);\n void held\n .waitForTask(task as unknown as TaskId, BACKGROUND_CONTEXT)\n .then((record) =>\n ended(record.state.status)\n ? host.approvals.withdraw(id, REASONS.aborted, BACKGROUND_CONTEXT)\n : undefined\n )\n .catch(() => undefined)\n .finally(() => watched.delete(id));\n };\n const remember = (seat: string, rule: AlwaysTool) =>\n (harness() as Held).commit(async (tx) => {\n const record = (await tx.doc(GuestsDoc)).seats[seat];\n if (!record || record.revoked !== null) return;\n const kept = (record.alwaysTools ?? []).filter(\n (entry) => entry.tool !== rule.tool || entry.arguments !== rule.arguments\n );\n record.alwaysTools = [...kept, rule];\n }, BACKGROUND_CONTEXT);\n const review = async (\n taint: Taint,\n call: ToolHookCall,\n api: HookApi,\n context: Context\n ): Promise<string | undefined> => {\n const { seat } = taint;\n if (seat === null || !isGate(taint.tier)) return GATE_REASONS.unknown;\n const host = await awaitWithContext(bound, context);\n const policy = host.policy();\n const now = host.now ?? Date.now;\n const args = canonical(call.arguments);\n const always = policy.always.tools;\n const early = settledEarly(seat, taint.tier, policy, now(), call.name, args);\n if (early !== null) return early || undefined;\n const id = await api.memo<string>(\n `${GUEST_TOOL_MEMO}:${seat.id}:${taint.tier}`,\n `apr-${crypto.randomUUID()}`,\n context\n );\n const task = Number(api.taskId);\n const asked = await host.approvals.request(\n {\n id,\n kind: 'guest-tool',\n seat: seat.id,\n agentId: seat.agentId,\n requester: { kind: 'guest', name: seat.label, seat: seat.id },\n tier: taint.tier,\n detail: { tool: call.name, summary: summaryOf(call.arguments), arguments: args, task },\n },\n BACKGROUND_CONTEXT\n );\n watch(host, id, task);\n const live = async () =>\n liveSeat((await api.snapshot(GuestsDoc, context))?.seats[seat.id], now());\n if (!(await live())) {\n await host.approvals.withdraw(id, REASONS.revoked, BACKGROUND_CONTEXT);\n return GATE_REASONS.ended;\n }\n const item = asked.state === 'pending' ? await host.approvals.settled(id, context) : asked;\n if (item.state !== 'approved') return reasonOf(item);\n if (!(await live())) return GATE_REASONS.ended;\n if (item.answer === 'always' && always)\n await remember(seat.id, {\n approval: item.id,\n tool: call.name,\n arguments: args,\n by: item.decidedBy,\n at: item.decidedAt ?? now(),\n });\n return undefined;\n };\n return {\n extension: defineExtension({\n name: GUEST_GUARD,\n hooks: [\n hook(ToolTask, {\n async beforeTool(call, api, context) {\n const live = await api.snapshot(LiveDoc, api.conversationId, context);\n const inputs = live?.run?.inputs ?? [];\n if (!inputs.length) return undefined;\n const seats = (await api.snapshot(GuestsDoc, context))?.seats ?? {};\n const held = harness() as Held;\n for (const taint of await taintsOf(inputs, seats, held, context)) {\n const block = await review(taint, call, api, context);\n if (block) return { block };\n }\n return undefined;\n },\n }),\n ],\n }),\n bind(host) {\n resolveHost(host);\n for (const item of host.approvals.state.value ?? [])\n if (item.kind === 'guest-tool' && item.state === 'pending' && item.detail.task)\n watch(host, item.id, item.detail.task);\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["record"]
|
|
7
7
|
}
|
package/dist/guests/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
guestContent
|
|
9
9
|
} from "./attribution.js";
|
|
10
10
|
import { GUEST_REQUEST } from "./guard.js";
|
|
11
|
-
import { DEFAULT_GUEST_POLICY
|
|
11
|
+
import { DEFAULT_GUEST_POLICY } from "./policy.js";
|
|
12
12
|
import {
|
|
13
13
|
endReview,
|
|
14
14
|
GuestReviewsDoc,
|
|
@@ -86,7 +86,6 @@ async function prepare(record, request) {
|
|
|
86
86
|
}
|
|
87
87
|
async function place(record, prepared, options, using, at) {
|
|
88
88
|
const gate = record.gates.messages;
|
|
89
|
-
if (gateKind(gate) === "scoop") return unanswered("scoop");
|
|
90
89
|
if (gate === "off") return deliver(record, prepared.text, prepared.requestId, options, using);
|
|
91
90
|
const { reviews } = options;
|
|
92
91
|
if (!reviews || !options.approvals) return unanswered("unavailable");
|