@ai-ecoverse/slicc-agent 5.8.2 → 5.8.3

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 CHANGED
@@ -159,7 +159,7 @@ app.model = { ...createKernelModel({ kernel, root }), ...createAgentModel(agent,
159
159
 
160
160
  The adapter keeps the replicated transcript and maps it on every update. User, assistant and compaction entries become messages; tool results fill in the tool calls they answer; the generation in flight shows as a streaming message; retries and running compactions show as system messages. A failed generation ends in an error card whose action fits the error: credential errors (an invalid or missing key, 401/403, not authorized) open settings, a model the account can't use offers the model picker, and anything else offers Retry. A content-filter stop is different: every later request resends the content that tripped the filter, so Retry, and any new message, would be stopped again. Content-filter stops are Bedrock's `content_filtered` and `guardrail_intervened`, Anthropic's `sensitive` and refusals, and OpenAI's `content_filter`. Their card leads with "The model's content filter stopped this reply." and offers **Drop the last turn** (spectrum ≥ 1.12.0). `AgentControl.rewind(messageId)` finds the user turn before that message and forks the cone's conversation at the entry before it (`Conversation.fork`, so `pi.agent` comes back as it was there). It points the cone at the fork and returns the turn's prompt, which spectrum puts back into the composer. Forking the very first turn creates a fresh conversation with the same model and thinking level. The filtered branch stays in storage, off the active path. The fork starts with a `slicc.rewound` entry that shows as a "Rewound 1 turn" notice, and clicking the card of an earlier filtered turn rewinds that turn.
161
161
 
162
- Each cone's active conversation is in the session document `slicc.agents` (the root until the first rewind). The host's transcript follows it across forks. Rewinding is refused while the cone itself is running or has queued input, because a fork starts with an empty inbox, but never because its scoops are busy (see Cones and scoops). A background compaction still running on the old branch finishes there. A fork also starts with fresh `pi.usage`. Credential and model errors lead with a plain sentence ("Bedrock rejected the API key.", "This model isn't available with the current account.") and keep the provider's raw error as `detail`, which spectrum (≥ 1.11.0) shows underneath. `busy()` and `queue()` read durable's live and inbox documents. Steering is the default (#4): a send or a steer goes out with `whenBusy: 'steer'`, so while a run is going it joins after the current tool round, and a `queue` send is a follow-up. User messages carry `delivered` from the host's deliveries; entries the host didn't record (from before a reload) count as steered when they follow a tool result. Cones and scoops are below; the freezer and questions come later. Licks show as spectrum's lick cards (below). slicc-spectrum is an optional peer dependency (≥ 1.79.0, the lowest whose monitor, tray, panel and guest seat types match the agent's; `npm run typecheck` fails on anything older), used for types only, so package managers don't install it next to the agent.
162
+ Each cone's active conversation is in the session document `slicc.agents` (the root until the first rewind). The host's transcript follows it across forks. Rewinding is refused while the cone itself is running or has queued input, because a fork starts with an empty inbox, but never because its scoops are busy (see Cones and scoops). A background compaction still running on the old branch finishes there. A fork also starts with fresh `pi.usage`. Credential and model errors lead with a plain sentence ("Bedrock rejected the API key.", "This model isn't available with the current account.") and keep the provider's raw error as `detail`, which spectrum (≥ 1.11.0) shows underneath. `busy()` and `queue()` read durable's live and inbox documents. Steering is the default (#4): a send or a steer goes out with `whenBusy: 'steer'`, so while a run is going it joins after the current tool round, and a `queue` send is a follow-up. User messages carry `delivered` from the host's deliveries; entries the host didn't record (from before a reload) count as steered when they follow a tool result. Cones and scoops are below; the freezer and questions come later. Licks show as spectrum's lick cards (below). slicc-spectrum is an optional peer dependency (≥ 1.84.0, the lowest whose monitor, tray, panel, guest seat and guest approval types match the agent's; `npm run typecheck` fails on anything older), used for types only, so package managers don't install it next to the agent.
163
163
 
164
164
  Each agent in `list()` has a real `contextFill` and `unread`:
165
165
  - **`contextFill`** is the context the agent uses, as a share of its model's `contextWindow` from the settings' model list. The tokens are those of the newest assistant's usage after the active head, counted the way pi-ai's `calculateContextTokens` does (`totalTokens`, or input, output and both cache counts). Aborted and failed turns don't count. After a compaction, the kept turns' usage no longer counts. Until the next turn reports its usage, the fill is estimated the way pi does it, at four characters a token, over the summary and the kept entries (without the system prompt and tools). An unknown model also reads 0. The Monitor's "Fullest context" and its context alerts use the same functions (`src/context.ts`).
@@ -611,7 +611,7 @@ Every approval, of a guest's prompt, of a tool call in a run a guest started, or
611
611
  - **Lifetime:** an item waits 5 minutes unless its requester sets `expiresAt`, then settles `unanswered`. Pending guest items survive a reload of the worker; pending `sudo` items are withdrawn (`restart`), because a reload ends the kernel's processes. The document keeps every pending item and the last 100 settled ones.
612
612
  - **In the worker,** `AgentHost.approvals` has `request(draft, ctx)`, `decide(id, answer, reason, by, ctx)` (`by` is a `DecidedBy`, or `{ by, device }` to record the deciding device), `settled(id, ctx)` (resolves when the item leaves `pending`), `withdraw(id, reason, ctx)` and `withdrawSeat(seat, ctx)`, which withdraws a revoked seat's pending items.
613
613
  - **Over the link,** `AgentTranscript.approvals` replicates the items, and `AgentControl.decide(id, decision, reason, ctx)` answers one, for every owner device: the owner's pages and followers with or without Control, all with the same powers. `decidedBy` comes from the link's seat (`owner` for the owner's pages, `follower` for the others), and is only a record of who answered. A guest never gets `decide`.
614
- - **In spectrum** (1.80 on, which shows guest approvals in `<slicc-approvals>` without Always, as notices without `decide`, and an `unsure` verdict), `createAgentModel` returns `approvals`, an `ApprovalsAdapter` for `SliccModel.approvals`. `list()` has the pending `user` items, oldest first, as spectrum's `Approval`s, with the cone's name as `agentName`. `decide(id, decision, reason?)` rejects with the agent's message, and `ended` reports `answered`, `expired` or `withdrawn` for an item that left without this tab's decision. Every owner device's adapter has `decide`; a guest connection's doesn't. So a notice instead of a dialog shows only where nothing can answer.
614
+ - **In spectrum** (1.84 on, which shows guest approvals in `<slicc-approvals>` with Always allow when `always` is true, as notices without `decide`, and an `unsure` verdict), `createAgentModel` returns `approvals`, an `ApprovalsAdapter` for `SliccModel.approvals`. `list()` has the pending `user` items, oldest first, as spectrum's `Approval`s, with the cone's name as `agentName` and the item's own `always`, so a guest approval offers Always allow only when the policy did when it was asked. `decide(id, decision, reason?)` rejects with the agent's message, and `ended` reports `answered`, `expired` or `withdrawn` for an item that left without this tab's decision. Every owner device's adapter has `decide`; a guest connection's doesn't. So a notice instead of a dialog shows only where nothing can answer.
615
615
  - **Kernel sudo:** `AgentOwner.approve(req, { signal, where? })` from `startAgent` takes slicc-kernel's sudo request as it comes to the kernel's `approve` hook and resolves `once`, `always` or `deny`. bios hands its hook to it while the agent runs, and to its own dialog otherwise. It rejects with `code: 'EAGAIN'` before anything is recorded when the agent can't take the request (released, restarting, or an older worker), so that's when to fall back; any other rejection is a deny. The request goes through `slicc.agent.sudo`, a service only the owner's pages get, and becomes a `sudo` item on the `user` tier with the kernel's `expiresAt`:
616
616
  - **Requester** comes from the kernel's process tag: `{ kind: 'agent', agentId }` is the cone or scoop by its name, `{ kind: 'user', where }` is the kernel user with `where` (`terminal:…` reads "a terminal", `follower:<device>` the `where` the page passes, such as "on Pat's phone", or "a linked device", with the device id kept as `requester.device`), and anything else is the kernel user alone.
617
617
  - **Detail:** the command as the kernel resolved it (`path` and the arguments, shell-quoted), `target`, `cwd`, and `alwaysScope: 'cwd'` when the request offers Always (`req.always === true`, which the page sets from its own checks), since the kernel's Always writes a rule for that directory.
@@ -666,7 +666,7 @@ All the limits on approvals and guests live in one owner-changeable policy, the
666
666
 
667
667
  A guest seat (plan D13, PR 19) is a revocable seat on one cone: the guest sees that cone's live transcript and has a composer, and nothing else.
668
668
 
669
- - **Minting.** The tray hub mints the seat and its one-time URL (v6's routes, D10); the page hands the agent adapter its routes: `createAgentModel(connection, { guests: { mint({ label, expiresAt, gates, agentId }) → { id, url }, revoke(id) } })`. Its `guests` port (spectrum's `GuestPort`) mints through the hub, then opens the seat in the worker with `AgentControl.guestOpen({ id, label, agentId, expiresAt, gates })`; if the worker refuses, the hub's seat is revoked again. `list()` is the transcript's `guests` state (`{ id, label, agentId, createdAt, expiresAt, gates, connected, pending, alwaysTools }`), and the URL is returned once and never kept. `unavailable()` says "Inviting guests needs the tray hub." without a hub, and "Only the owner's devices can invite guests." on a guest. Every owner device can invite, with or without Control. `gates()` lists the gates the approval policy allows for messages and tools, in display order, with `scoop:*` for "any scoop", for the invite dialog.
669
+ - **Minting.** The tray hub mints the seat and its one-time URL (v6's routes, D10); the page hands the agent adapter its routes: `createAgentModel(connection, { guests: { mint({ label, expiresAt, gates, agentId }) → { id, url }, revoke(id) } })`. Its `guests` port (spectrum's `GuestPort`) mints through the hub, then opens the seat in the worker with `AgentControl.guestOpen({ id, label, agentId, expiresAt, gates })`; if the worker refuses, the hub's seat is revoked again. `list()` is the transcript's `guests` state (`{ id, label, agentId, createdAt, expiresAt, gates, connected, pending, alwaysTools }`), and the URL is returned once and never kept. `unavailable()` says "Inviting guests needs the tray hub." without a hub, and "Only the owner's devices can invite guests." on a guest. Every owner device can invite, with or without Control. `gates()` (spectrum's `GuestPort.gates?()`) lists the gates the approval policy allows for messages and tools, in display order, for the invite pickers; `scoop:*`, for "any scoop", shows only when the policy offers the scoop tier.
670
670
  - **The record.** Seats live in the session document `slicc.guests`, so they survive reloads and restarts: id, label (control characters stripped, at most 64 characters), cone, expiry (at most the policy's `guests.maxSeatDays`, 30 by default), the two gates (`user`, `agent`, `cone`, `scoop:<name>` or `off`, fixed when the seat is opened and checked then against the policy's `allowedMessageGates` and `allowedToolGates`; by default the tools gate can't be `off` or `cone`, from any caller: "A guest's tool calls need a review: by a person, the judge or a scoop.") and an epoch. Revoking (`guestRevoke(id)`) keeps a tombstone, bumps the epoch, withdraws the guest's messages still queued in the cone, and ends every guest link of the seat (`invalidatePrincipal`). An expired seat ends the same way at its expiry. Tombstones go 365 days after they end, the longest a seat can be opened for.
671
671
  - **Joining.** The hub resolves the guest's token to the seat's id and label, and the owner page opens `owner.port({ guest: { id, label } })` and bridges it like a follower's. Before Chord sees the link, the worker checks that the seat exists, isn't revoked or expired, and has that label; otherwise it closes the port. The link's principal is `{ seat: 'guest', link, guest: { id, name, epoch } }`, and anything else the port asks for (`readOnly: false`, `pages`) is ignored.
672
672
  - **What a guest gets.** Every contract served from the agent's host refuses a guest principal (`forbidden`) unless its factory is opened with `openToGuests`, so a contract added later is closed to guests until someone opens it. The only open one is `slicc.agent.guest`: its `guest` variant has the shared cone's transcript (its user, assistant, tool and compaction entries; `pi.live`; `pi.agent`'s model and thinking level; and only the guest's own queued messages) and the seat, plus `propose({ text, requestId })`. Everyone else gets its empty `none` variant.
@@ -38,7 +38,7 @@ function shown(item, agentName) {
38
38
  ...base,
39
39
  agentId: item.agentId,
40
40
  ...agentName ? { agentName } : {},
41
- always: false,
41
+ always: item.always,
42
42
  ...item.verdict ? { verdict: { decision: item.verdict.decision, reason: item.verdict.reason } } : {}
43
43
  };
44
44
  if (item.kind === "guest-tool")
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/spectrum/approvals-port.ts"],
4
- "sourcesContent": ["import type {\n Approval,\n ApprovalDecision,\n ApprovalEnd,\n ApprovalRequester,\n ApprovalsEvents,\n ApprovalsPort,\n} from '@ai-ecoverse/slicc-spectrum/ui';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { Approval as Item } from '../approvals/record.ts';\nimport type { AgentConnection } from '../client.ts';\nimport { Emitter } from './emitter.ts';\n\nconst ENDS: Record<string, ApprovalEnd> = {\n approved: 'answered',\n rejected: 'answered',\n unanswered: 'expired',\n withdrawn: 'withdrawn',\n};\n\nfunction requesterOf(item: Item): ApprovalRequester {\n const { kind, name, agentId, where } = item.requester;\n return {\n kind,\n name,\n ...(agentId ? { agentId } : {}),\n ...(where ? { where } : {}),\n };\n}\n\nexport function shown(item: Item, agentName: string | undefined): Approval {\n const base = {\n id: item.id,\n requester: requesterOf(item),\n createdAt: item.createdAt,\n expiresAt: item.expiresAt,\n };\n if (item.kind === 'sudo') {\n const { command, target, cwd, alwaysScope } = item.detail;\n return {\n ...base,\n kind: 'sudo',\n command,\n ...(target ? { target } : {}),\n ...(cwd ? { cwd } : {}),\n always: item.always,\n ...(alwaysScope ? { alwaysScope } : {}),\n };\n }\n const guest = {\n ...base,\n agentId: item.agentId,\n ...(agentName ? { agentName } : {}),\n always: false as const,\n ...(item.verdict\n ? { verdict: { decision: item.verdict.decision, reason: item.verdict.reason } }\n : {}),\n };\n if (item.kind === 'guest-tool')\n return { ...guest, kind: 'guest-tool', tool: item.detail.tool, summary: item.detail.summary };\n return {\n ...guest,\n kind: 'guest-message',\n text: item.detail.text,\n ...(item.detail.attachments.length\n ? { attachments: item.detail.attachments.map((file) => ({ ...file })) }\n : {}),\n };\n}\n\nexport type HostApprovals = ApprovalsPort;\n\nexport const NOT_HERE = \"This request can't be answered in this tab.\";\n\nconst asked = (item: Item) => item.state === 'pending' && item.tier === 'user';\n\nexport class ApprovalsAdapter extends Emitter<ApprovalsEvents> implements ApprovalsPort {\n readonly #connection: AgentConnection;\n readonly #host: HostApprovals | undefined;\n readonly #ours: boolean;\n readonly #mine = new Set<string>();\n readonly #deciding = new Set<string>();\n #listed = new Set<string>();\n readonly decide?: (id: string, decision: ApprovalDecision, reason?: string) => Promise<void>;\n\n constructor(connection: AgentConnection, host?: HostApprovals) {\n super();\n this.#connection = connection;\n this.#host = host;\n this.#ours = !connection.guest;\n if (this.#ours || host?.decide) this.decide = (...args) => this.#route(...args);\n this.#listed = new Set(this.#agent().map((approval) => approval.id));\n connection.approvals?.subscribe(() => this.#changed());\n host?.on('approvals', () => this.emit('approvals', this.list()));\n host?.on('ended', (detail) => this.emit('ended', detail));\n }\n\n #items(): readonly Item[] {\n return this.#connection.approvals?.value ?? [];\n }\n\n list(): readonly Approval[] {\n return [...(this.#host?.list() ?? []), ...this.#agent()].sort(\n (a, b) => a.createdAt - b.createdAt\n );\n }\n\n #agent(): readonly Approval[] {\n const names = new Map(\n (this.#connection.agents?.value?.agents ?? []).map((agent) => [agent.id, agent.name])\n );\n return this.#items()\n .filter((item) => asked(item) && !this.#mine.has(item.id))\n .sort((a, b) => a.createdAt - b.createdAt)\n .map((item) => shown(item, names.get(item.agentId)));\n }\n\n #changed(): void {\n const items = new Map(this.#items().map((item) => [item.id, item]));\n const listed = this.#agent();\n const now = new Set(listed.map((approval) => approval.id));\n for (const id of this.#listed) {\n if (now.has(id) || this.#mine.has(id) || this.#deciding.has(id)) continue;\n const state = items.get(id)?.state;\n this.emit('ended', { id, reason: (state && ENDS[state]) || 'answered' });\n }\n for (const id of this.#mine) {\n const item = items.get(id);\n if (!item || !asked(item)) this.#mine.delete(id);\n }\n this.#listed = now;\n this.emit('approvals', this.list());\n }\n\n #route(id: string, decision: ApprovalDecision, reason?: string): Promise<void> {\n const host = this.#host;\n if (host?.list().some((approval) => approval.id === id)) {\n if (!host.decide) return Promise.reject(new Error(NOT_HERE));\n return host.decide(id, decision, reason);\n }\n if (!this.#ours) return Promise.reject(new Error(NOT_HERE));\n return this.#decide(id, decision, reason);\n }\n\n async #decide(id: string, decision: ApprovalDecision, reason?: string): Promise<void> {\n this.#deciding.add(id);\n try {\n const answer = await this.#connection.control.decide(\n id,\n decision,\n reason ?? null,\n BACKGROUND_CONTEXT\n );\n if (!answer.done) throw new Error(answer.error);\n this.#mine.add(id);\n } finally {\n this.#deciding.delete(id);\n }\n this.#changed();\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type {\n Approval,\n ApprovalDecision,\n ApprovalEnd,\n ApprovalRequester,\n ApprovalsEvents,\n ApprovalsPort,\n} from '@ai-ecoverse/slicc-spectrum/ui';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { Approval as Item } from '../approvals/record.ts';\nimport type { AgentConnection } from '../client.ts';\nimport { Emitter } from './emitter.ts';\n\nconst ENDS: Record<string, ApprovalEnd> = {\n approved: 'answered',\n rejected: 'answered',\n unanswered: 'expired',\n withdrawn: 'withdrawn',\n};\n\nfunction requesterOf(item: Item): ApprovalRequester {\n const { kind, name, agentId, where } = item.requester;\n return {\n kind,\n name,\n ...(agentId ? { agentId } : {}),\n ...(where ? { where } : {}),\n };\n}\n\nexport function shown(item: Item, agentName: string | undefined): Approval {\n const base = {\n id: item.id,\n requester: requesterOf(item),\n createdAt: item.createdAt,\n expiresAt: item.expiresAt,\n };\n if (item.kind === 'sudo') {\n const { command, target, cwd, alwaysScope } = item.detail;\n return {\n ...base,\n kind: 'sudo',\n command,\n ...(target ? { target } : {}),\n ...(cwd ? { cwd } : {}),\n always: item.always,\n ...(alwaysScope ? { alwaysScope } : {}),\n };\n }\n const guest = {\n ...base,\n agentId: item.agentId,\n ...(agentName ? { agentName } : {}),\n always: item.always,\n ...(item.verdict\n ? { verdict: { decision: item.verdict.decision, reason: item.verdict.reason } }\n : {}),\n };\n if (item.kind === 'guest-tool')\n return { ...guest, kind: 'guest-tool', tool: item.detail.tool, summary: item.detail.summary };\n return {\n ...guest,\n kind: 'guest-message',\n text: item.detail.text,\n ...(item.detail.attachments.length\n ? { attachments: item.detail.attachments.map((file) => ({ ...file })) }\n : {}),\n };\n}\n\nexport type HostApprovals = ApprovalsPort;\n\nexport const NOT_HERE = \"This request can't be answered in this tab.\";\n\nconst asked = (item: Item) => item.state === 'pending' && item.tier === 'user';\n\nexport class ApprovalsAdapter extends Emitter<ApprovalsEvents> implements ApprovalsPort {\n readonly #connection: AgentConnection;\n readonly #host: HostApprovals | undefined;\n readonly #ours: boolean;\n readonly #mine = new Set<string>();\n readonly #deciding = new Set<string>();\n #listed = new Set<string>();\n readonly decide?: (id: string, decision: ApprovalDecision, reason?: string) => Promise<void>;\n\n constructor(connection: AgentConnection, host?: HostApprovals) {\n super();\n this.#connection = connection;\n this.#host = host;\n this.#ours = !connection.guest;\n if (this.#ours || host?.decide) this.decide = (...args) => this.#route(...args);\n this.#listed = new Set(this.#agent().map((approval) => approval.id));\n connection.approvals?.subscribe(() => this.#changed());\n host?.on('approvals', () => this.emit('approvals', this.list()));\n host?.on('ended', (detail) => this.emit('ended', detail));\n }\n\n #items(): readonly Item[] {\n return this.#connection.approvals?.value ?? [];\n }\n\n list(): readonly Approval[] {\n return [...(this.#host?.list() ?? []), ...this.#agent()].sort(\n (a, b) => a.createdAt - b.createdAt\n );\n }\n\n #agent(): readonly Approval[] {\n const names = new Map(\n (this.#connection.agents?.value?.agents ?? []).map((agent) => [agent.id, agent.name])\n );\n return this.#items()\n .filter((item) => asked(item) && !this.#mine.has(item.id))\n .sort((a, b) => a.createdAt - b.createdAt)\n .map((item) => shown(item, names.get(item.agentId)));\n }\n\n #changed(): void {\n const items = new Map(this.#items().map((item) => [item.id, item]));\n const listed = this.#agent();\n const now = new Set(listed.map((approval) => approval.id));\n for (const id of this.#listed) {\n if (now.has(id) || this.#mine.has(id) || this.#deciding.has(id)) continue;\n const state = items.get(id)?.state;\n this.emit('ended', { id, reason: (state && ENDS[state]) || 'answered' });\n }\n for (const id of this.#mine) {\n const item = items.get(id);\n if (!item || !asked(item)) this.#mine.delete(id);\n }\n this.#listed = now;\n this.emit('approvals', this.list());\n }\n\n #route(id: string, decision: ApprovalDecision, reason?: string): Promise<void> {\n const host = this.#host;\n if (host?.list().some((approval) => approval.id === id)) {\n if (!host.decide) return Promise.reject(new Error(NOT_HERE));\n return host.decide(id, decision, reason);\n }\n if (!this.#ours) return Promise.reject(new Error(NOT_HERE));\n return this.#decide(id, decision, reason);\n }\n\n async #decide(id: string, decision: ApprovalDecision, reason?: string): Promise<void> {\n this.#deciding.add(id);\n try {\n const answer = await this.#connection.control.decide(\n id,\n decision,\n reason ?? null,\n BACKGROUND_CONTEXT\n );\n if (!answer.done) throw new Error(answer.error);\n this.#mine.add(id);\n } finally {\n this.#deciding.delete(id);\n }\n this.#changed();\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/spectrum/guest-port.ts"],
4
- "sourcesContent": ["import type {\n GuestDraft,\n GuestEvents,\n GuestGate,\n GuestPort,\n GuestSeat,\n} from '@ai-ecoverse/slicc-spectrum/ui';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { AgentConnection } from '../client.ts';\nimport { DEFAULT_GUEST_POLICY, type GateKind } from '../guests/policy.ts';\nimport { Emitter } from './emitter.ts';\n\nexport interface GuestHub {\n mint(request: GuestDraft & { agentId: string }): Promise<{ id: string; url: string }>;\n revoke(id: string): Promise<void>;\n}\n\nexport const NO_HUB = 'Inviting guests needs the tray hub.';\nexport const NOT_OWNER_DEVICE = \"Only the owner's devices can invite guests.\";\nexport const NO_CONTROL = NOT_OWNER_DEVICE;\nexport const NO_OWNER = NOT_OWNER_DEVICE;\n\nconst offered = (kinds: readonly GateKind[]): GuestGate[] =>\n kinds.map((kind) => (kind === 'scoop' ? 'scoop:*' : kind));\n\nexport class GuestAdapter extends Emitter<GuestEvents> implements GuestPort {\n readonly #connection: AgentConnection;\n readonly #hub: GuestHub | null;\n\n constructor(connection: AgentConnection, hub: GuestHub | null) {\n super();\n this.#connection = connection;\n this.#hub = hub;\n connection.guests?.subscribe(() => this.emit('seats', this.list()));\n }\n\n list(): readonly GuestSeat[] {\n return this.#connection.guests?.value ?? [];\n }\n\n gates(): { messages: GuestGate[]; tools: GuestGate[] } {\n const policy = this.#connection.approvalPolicy?.value?.guests ?? DEFAULT_GUEST_POLICY;\n return {\n messages: offered(policy.allowedMessageGates),\n tools: offered(policy.allowedToolGates),\n };\n }\n\n unavailable(): string | null {\n const connection = this.#connection;\n if (connection.guest || !connection.guests) return NOT_OWNER_DEVICE;\n return this.#hub ? null : NO_HUB;\n }\n\n async mint(agentId: string, draft: GuestDraft): Promise<{ seat: GuestSeat; url: string }> {\n const problem = this.unavailable();\n if (problem) throw new Error(problem);\n const hub = this.#hub as GuestHub;\n const gates = { messages: draft.gates.messages, tools: draft.gates.tools };\n const minted = await hub.mint({\n label: draft.label,\n expiresAt: draft.expiresAt,\n gates,\n agentId,\n });\n const opened = await this.#connection.control\n .guestOpen(\n { id: minted.id, label: draft.label, agentId, expiresAt: draft.expiresAt, gates },\n BACKGROUND_CONTEXT\n )\n .catch((error: Error) => ({ seat: null, error: error.message }));\n if (!opened.seat) {\n await hub.revoke(minted.id).catch(() => undefined);\n throw new Error(opened.error);\n }\n return { seat: opened.seat, url: minted.url };\n }\n\n async revoke(id: string): Promise<void> {\n await this.#connection.control.guestRevoke(id, BACKGROUND_CONTEXT);\n await this.#hub?.revoke(id);\n }\n}\n"],
5
- "mappings": "AAOA,SAAS,0BAA0B;AAEnC,SAAS,4BAA2C;AACpD,SAAS,eAAe;AAOjB,MAAM,SAAS;AACf,MAAM,mBAAmB;AACzB,MAAM,aAAa;AACnB,MAAM,WAAW;AAExB,MAAM,UAAU,CAAC,UACf,MAAM,IAAI,CAAC,SAAU,SAAS,UAAU,YAAY,IAAK;AAEpD,MAAM,qBAAqB,QAA0C;AAAA,EACjE;AAAA,EACA;AAAA,EAET,YAAY,YAA6B,KAAsB;AAC7D,UAAM;AACN,SAAK,cAAc;AACnB,SAAK,OAAO;AACZ,eAAW,QAAQ,UAAU,MAAM,KAAK,KAAK,SAAS,KAAK,KAAK,CAAC,CAAC;AAAA,EACpE;AAAA,EAEA,OAA6B;AAC3B,WAAO,KAAK,YAAY,QAAQ,SAAS,CAAC;AAAA,EAC5C;AAAA,EAEA,QAAuD;AACrD,UAAM,SAAS,KAAK,YAAY,gBAAgB,OAAO,UAAU;AACjE,WAAO;AAAA,MACL,UAAU,QAAQ,OAAO,mBAAmB;AAAA,MAC5C,OAAO,QAAQ,OAAO,gBAAgB;AAAA,IACxC;AAAA,EACF;AAAA,EAEA,cAA6B;AAC3B,UAAM,aAAa,KAAK;AACxB,QAAI,WAAW,SAAS,CAAC,WAAW,OAAQ,QAAO;AACnD,WAAO,KAAK,OAAO,OAAO;AAAA,EAC5B;AAAA,EAEA,MAAM,KAAK,SAAiB,OAA8D;AACxF,UAAM,UAAU,KAAK,YAAY;AACjC,QAAI,QAAS,OAAM,IAAI,MAAM,OAAO;AACpC,UAAM,MAAM,KAAK;AACjB,UAAM,QAAQ,EAAE,UAAU,MAAM,MAAM,UAAU,OAAO,MAAM,MAAM,MAAM;AACzE,UAAM,SAAS,MAAM,IAAI,KAAK;AAAA,MAC5B,OAAO,MAAM;AAAA,MACb,WAAW,MAAM;AAAA,MACjB;AAAA,MACA;AAAA,IACF,CAAC;AACD,UAAM,SAAS,MAAM,KAAK,YAAY,QACnC;AAAA,MACC,EAAE,IAAI,OAAO,IAAI,OAAO,MAAM,OAAO,SAAS,WAAW,MAAM,WAAW,MAAM;AAAA,MAChF;AAAA,IACF,EACC,MAAM,CAAC,WAAkB,EAAE,MAAM,MAAM,OAAO,MAAM,QAAQ,EAAE;AACjE,QAAI,CAAC,OAAO,MAAM;AAChB,YAAM,IAAI,OAAO,OAAO,EAAE,EAAE,MAAM,MAAM,MAAS;AACjD,YAAM,IAAI,MAAM,OAAO,KAAK;AAAA,IAC9B;AACA,WAAO,EAAE,MAAM,OAAO,MAAM,KAAK,OAAO,IAAI;AAAA,EAC9C;AAAA,EAEA,MAAM,OAAO,IAA2B;AACtC,UAAM,KAAK,YAAY,QAAQ,YAAY,IAAI,kBAAkB;AACjE,UAAM,KAAK,MAAM,OAAO,EAAE;AAAA,EAC5B;AACF;",
4
+ "sourcesContent": ["import type {\n GuestDraft,\n GuestEvents,\n GuestGate,\n GuestPort,\n GuestSeat,\n} from '@ai-ecoverse/slicc-spectrum/ui';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { AgentConnection } from '../client.ts';\nimport { DEFAULT_GUEST_POLICY, type GateKind } from '../guests/policy.ts';\nimport { Emitter } from './emitter.ts';\n\nexport interface GuestHub {\n mint(request: GuestDraft & { agentId: string }): Promise<{ id: string; url: string }>;\n revoke(id: string): Promise<void>;\n}\n\nexport const NO_HUB = 'Inviting guests needs the tray hub.';\nexport const NOT_OWNER_DEVICE = \"Only the owner's devices can invite guests.\";\nexport const NO_CONTROL = NOT_OWNER_DEVICE;\nexport const NO_OWNER = NOT_OWNER_DEVICE;\n\nconst offered = (kinds: readonly GateKind[]): GuestGate[] =>\n kinds.map((kind) => (kind === 'scoop' ? 'scoop:*' : kind));\n\nexport class GuestAdapter extends Emitter<GuestEvents> implements GuestPort {\n readonly #connection: AgentConnection;\n readonly #hub: GuestHub | null;\n\n constructor(connection: AgentConnection, hub: GuestHub | null) {\n super();\n this.#connection = connection;\n this.#hub = hub;\n connection.guests?.subscribe(() => this.emit('seats', this.list()));\n }\n\n list(): readonly GuestSeat[] {\n return this.#connection.guests?.value ?? [];\n }\n\n gates(): ReturnType<NonNullable<GuestPort['gates']>> {\n const policy = this.#connection.approvalPolicy?.value?.guests ?? DEFAULT_GUEST_POLICY;\n return {\n messages: offered(policy.allowedMessageGates),\n tools: offered(policy.allowedToolGates),\n };\n }\n\n unavailable(): string | null {\n const connection = this.#connection;\n if (connection.guest || !connection.guests) return NOT_OWNER_DEVICE;\n return this.#hub ? null : NO_HUB;\n }\n\n async mint(agentId: string, draft: GuestDraft): Promise<{ seat: GuestSeat; url: string }> {\n const problem = this.unavailable();\n if (problem) throw new Error(problem);\n const hub = this.#hub as GuestHub;\n const gates = { messages: draft.gates.messages, tools: draft.gates.tools };\n const minted = await hub.mint({\n label: draft.label,\n expiresAt: draft.expiresAt,\n gates,\n agentId,\n });\n const opened = await this.#connection.control\n .guestOpen(\n { id: minted.id, label: draft.label, agentId, expiresAt: draft.expiresAt, gates },\n BACKGROUND_CONTEXT\n )\n .catch((error: Error) => ({ seat: null, error: error.message }));\n if (!opened.seat) {\n await hub.revoke(minted.id).catch(() => undefined);\n throw new Error(opened.error);\n }\n return { seat: opened.seat, url: minted.url };\n }\n\n async revoke(id: string): Promise<void> {\n await this.#connection.control.guestRevoke(id, BACKGROUND_CONTEXT);\n await this.#hub?.revoke(id);\n }\n}\n"],
5
+ "mappings": "AAOA,SAAS,0BAA0B;AAEnC,SAAS,4BAA2C;AACpD,SAAS,eAAe;AAOjB,MAAM,SAAS;AACf,MAAM,mBAAmB;AACzB,MAAM,aAAa;AACnB,MAAM,WAAW;AAExB,MAAM,UAAU,CAAC,UACf,MAAM,IAAI,CAAC,SAAU,SAAS,UAAU,YAAY,IAAK;AAEpD,MAAM,qBAAqB,QAA0C;AAAA,EACjE;AAAA,EACA;AAAA,EAET,YAAY,YAA6B,KAAsB;AAC7D,UAAM;AACN,SAAK,cAAc;AACnB,SAAK,OAAO;AACZ,eAAW,QAAQ,UAAU,MAAM,KAAK,KAAK,SAAS,KAAK,KAAK,CAAC,CAAC;AAAA,EACpE;AAAA,EAEA,OAA6B;AAC3B,WAAO,KAAK,YAAY,QAAQ,SAAS,CAAC;AAAA,EAC5C;AAAA,EAEA,QAAqD;AACnD,UAAM,SAAS,KAAK,YAAY,gBAAgB,OAAO,UAAU;AACjE,WAAO;AAAA,MACL,UAAU,QAAQ,OAAO,mBAAmB;AAAA,MAC5C,OAAO,QAAQ,OAAO,gBAAgB;AAAA,IACxC;AAAA,EACF;AAAA,EAEA,cAA6B;AAC3B,UAAM,aAAa,KAAK;AACxB,QAAI,WAAW,SAAS,CAAC,WAAW,OAAQ,QAAO;AACnD,WAAO,KAAK,OAAO,OAAO;AAAA,EAC5B;AAAA,EAEA,MAAM,KAAK,SAAiB,OAA8D;AACxF,UAAM,UAAU,KAAK,YAAY;AACjC,QAAI,QAAS,OAAM,IAAI,MAAM,OAAO;AACpC,UAAM,MAAM,KAAK;AACjB,UAAM,QAAQ,EAAE,UAAU,MAAM,MAAM,UAAU,OAAO,MAAM,MAAM,MAAM;AACzE,UAAM,SAAS,MAAM,IAAI,KAAK;AAAA,MAC5B,OAAO,MAAM;AAAA,MACb,WAAW,MAAM;AAAA,MACjB;AAAA,MACA;AAAA,IACF,CAAC;AACD,UAAM,SAAS,MAAM,KAAK,YAAY,QACnC;AAAA,MACC,EAAE,IAAI,OAAO,IAAI,OAAO,MAAM,OAAO,SAAS,WAAW,MAAM,WAAW,MAAM;AAAA,MAChF;AAAA,IACF,EACC,MAAM,CAAC,WAAkB,EAAE,MAAM,MAAM,OAAO,MAAM,QAAQ,EAAE;AACjE,QAAI,CAAC,OAAO,MAAM;AAChB,YAAM,IAAI,OAAO,OAAO,EAAE,EAAE,MAAM,MAAM,MAAS;AACjD,YAAM,IAAI,MAAM,OAAO,KAAK;AAAA,IAC9B;AACA,WAAO,EAAE,MAAM,OAAO,MAAM,KAAK,OAAO,IAAI;AAAA,EAC9C;AAAA,EAEA,MAAM,OAAO,IAA2B;AACtC,UAAM,KAAK,YAAY,QAAQ,YAAY,IAAI,kBAAkB;AACjE,UAAM,KAAK,MAAM,OAAO,EAAE;AAAA,EAC5B;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,4 +1,4 @@
1
- import type { GuestDraft, GuestEvents, GuestGate, GuestPort, GuestSeat } from '@ai-ecoverse/slicc-spectrum/ui';
1
+ import type { GuestDraft, GuestEvents, GuestPort, GuestSeat } from '@ai-ecoverse/slicc-spectrum/ui';
2
2
  import type { AgentConnection } from '../client.ts';
3
3
  import { Emitter } from './emitter.ts';
4
4
  export interface GuestHub {
@@ -18,10 +18,7 @@ export declare class GuestAdapter extends Emitter<GuestEvents> implements GuestP
18
18
  #private;
19
19
  constructor(connection: AgentConnection, hub: GuestHub | null);
20
20
  list(): readonly GuestSeat[];
21
- gates(): {
22
- messages: GuestGate[];
23
- tools: GuestGate[];
24
- };
21
+ gates(): ReturnType<NonNullable<GuestPort['gates']>>;
25
22
  unavailable(): string | null;
26
23
  mint(agentId: string, draft: GuestDraft): Promise<{
27
24
  seat: GuestSeat;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-ecoverse/slicc-agent",
3
- "version": "5.8.2",
3
+ "version": "5.8.3",
4
4
  "description": "SLICC's agent core on pi durable: an agent worker, session storage, remoting and pi extensions",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -63,12 +63,12 @@
63
63
  "release": "semantic-release"
64
64
  },
65
65
  "devDependencies": {
66
- "@ai-ecoverse/slicc-kernel": "1.100.3",
66
+ "@ai-ecoverse/slicc-kernel": "1.103.4",
67
67
  "@ai-ecoverse/slicc-scrub": "1.0.0",
68
68
  "@ai-ecoverse/slicc-shared-web": "1.21.1",
69
- "@ai-ecoverse/slicc-spectrum": "1.83.0",
69
+ "@ai-ecoverse/slicc-spectrum": "1.84.0",
70
70
  "@ai-ecoverse/wasm-bash": "5.3.0-18",
71
- "@ai-ecoverse/wasm-coreutils": "9.12.0-7",
71
+ "@ai-ecoverse/wasm-coreutils": "9.12.0-8",
72
72
  "@ai-ecoverse/wasm-git": "2.55.0-15",
73
73
  "@biomejs/biome": "2.5.15",
74
74
  "@types/node": "24.19.1",
@@ -93,7 +93,7 @@
93
93
  },
94
94
  "peerDependencies": {
95
95
  "@ai-ecoverse/slicc-kernel": ">=1.90.0 <2",
96
- "@ai-ecoverse/slicc-spectrum": "^1.81.0"
96
+ "@ai-ecoverse/slicc-spectrum": "^1.84.0"
97
97
  },
98
98
  "peerDependenciesMeta": {
99
99
  "@ai-ecoverse/slicc-spectrum": {