Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Corollary — **extend, never duplicate.** Before writing anything that complete
| `/interactions` | human-in-the-loop ask channel, both halves: the shared wire/persisted-part contract (`ChatInteraction`, part codecs, composer-as-answer routing, content-signature dedupe) + the server side — structural sidecar `/agents/sessions/{id}/interactions` client and `createInteractionAnswerRoute()` (list/answer endpoint factory: body validation, gone→410 mapping, duplicate-answer safety net, unblock verification, `resolveConnection` product seam) | peer `@tangle-network/agent-interface` (schema types only); connection is structural — no sandbox-SDK import. Server half must never reach client bundles (`tests/interactions-browser-safe.test.ts`) |
| `/plans` | browser-safe durable-plan chat projection: lifecycle event parsing, persisted `type:'plan'` codec, revision-aware transcript keys, and monotonic per-revision transitions | structurally byte-matches the durable plan returned by peer `@tangle-network/sandbox`'s `SandboxSession.plan()`; authority remains SDK-owned |
| `/durable-chat` | reusable server workflow around the authoritative plan/question channels: authorization-scoped state/command ports, CAS plan decisions, stable follow-up receipts, independently retryable product effects, interaction answer intent/ack/finalize settlement, producer/route adapters, and a reference process-local test store | structural Sandbox plan authority + `/interactions` sidecar route; products supply auth scope, production storage, Sandbox connection, idempotent effects, and follow-up transport |
| `/chat-routes` | the assembled server chat vertical (#188 Phase 1): `createChatTurnRoutes()` — body parse/validate → injected `authorize` seam → producer seam → turn-buffer tap wired BY DEFAULT → NDJSON `Response` + replay endpoint + composed `/interactions` answer endpoints + `/chat-store` persistence (user row on send, assistant row with parts/usage on completion); six optional product seams (`turnLock`/`contextGate`/`beforeTurn`/`onRawEvent` are `@experimental` — single-consumer, proven by a reference consumer, kept FLAT for back-compat; `lifecycle`/`heartbeat` are stable) — `turnLock` (single-flight acquire/release), `contextGate` (pre-producer domain-readiness short-circuit), `beforeTurn` (observe/augment producer input), `lifecycle` (deterministic start/complete/error telemetry, idempotent + settled even on a synchronous pre-stream throw), `heartbeat` (keepalive during silent producer waits), `onRawEvent` (raw producer-event tap) — plus `transformFinalText` (pre-persist redaction applied to BOTH the final-text scalar AND every persisted assistant TEXT part, so `/redact` closes the at-rest PII leak, not just the streamed scalar) and run-failure surfacing (`onTurnComplete` receives `failed`/`failureReason` from a terminal `error`/`session.run.failed` event so an errored turn is skipped for billing + rendered as an error row, never billed/marked-complete with empty text); `createSandboxChatProducer` (raw sandbox events → flattened client vocabulary + persisted parts; authoritative rich usage from `result`/`done`; composed visible raw-error text; severed-stream catch that preserves partial content and ends normally with structured `sandbox.stream_failed` so `onTurnComplete({failed:true})` fires; live dangling-tool terminalization; pending-interaction finalization; non-renderable-ask auto-decline + warning notices, with per-turn plan gating through `isRenderableInteraction`); `runDetachedTurn` (autonomous/detached turn → live buffer bridge: taps the SAME turn-event buffer the interactive lane uses so a browser opening a session MID autonomous-run — mission step, queue job, inbound-email review — tails it token-by-token exactly like an interactive turn; idempotent [an already-`complete` turn returns its cached result without re-streaming a second event sequence] AND crash-safe [a `running` turn — a prior attempt died mid-tap — consults `completedResult` to detect a run that finished server-side, else clears the partial buffer via the `resetBuffer` seam before re-streaming so restarted seqs don't corrupt], marks `running` under `scopeId` for `listRunning` re-attach, surfaces the structured `assistantParts` projection (`result.parts`) so the durable row keeps tool/file/plan parts and not just flat text, forwards the producer's `declineInteraction`/`isRenderableInteraction`/`promoteFilePart` seams — an UNATTENDED run MUST wire `declineInteraction` or a non-renderable ask deadlocks the broker, usage/text/parts fallback via `completedResult` when the stream carries no receipt, terminal-error event → `failed` (including a raw sandbox stream throw normalized by the producer; buffer/tap failures still re-throw); the caller supplies only the raw sandbox stream + store + ids — box/prompt/tooling stay in the product); `createUploadRoute` (the `--chat` scaffold's inline-`data:`-or-ephemeral-sandbox upload path; multipart → inline `data:` part ≤700 KiB, else base64 write through a structural sandbox sink + path-ref part — the >1 MiB gateway cap makes the two-step mandatory); the store-backed attachment vertical (#224) — `resolveChatAttachments` (validates a turn's `attachments` field, re-deriving size from the STORED body, never the wire-reported one), `buildDispatchParts` (assembles attachments + mentions into dispatched `PromptInputPart[]`, inlining under the `DISPATCH_*` byte/parts budget or demoting to a path part), `promoteAgentFilePart` (a harness-emitted file part → a durable store-backed attachment, wired opt-in through `createSandboxChatProducer`'s `promoteFilePart` seam) — all storage-parameterized via `ReadAttachmentFn`/`WriteAttachmentFn` (`./attachment-store`): REQUIRED injection, no default store; `createAttachmentUploadRoute` (#234): hardened durable-store upload factory shared by fleet apps in place of a hand-rolled vault route — two-phase atomic batch, content-sniffed type/kind gate (`sniffBinary`/`checkAttachmentType`, shared client+server), per-kind + aggregate size caps, sanitized filenames; storage via the injected `WriteAttachmentFn`, auth/rate-limit/scope via the injected `authorize`, returns the full `ChatAttachmentInput[]`; import-free `./wire` contract (`ChatTurnRequestPayload`, inline-part byte gate) re-exported via `/web-react`'s chat-stream | peer `@tangle-network/agent-runtime` (`handleChatTurn` IS the turn engine — zero loop logic here; subpath-only, not in the root barrel); composes `/stream`, `/chat-store`, `/interactions`, `/web`. Reference assembly: `examples/chat-app.md` |
| `/chat-routes` | the assembled server chat vertical (#188 Phase 1): `createChatTurnRoutes()` — body parse/validate → injected `authorize` seam → producer seam → turn-buffer tap wired BY DEFAULT → NDJSON `Response` + replay endpoint + composed `/interactions` answer endpoints + `/chat-store` persistence (user row on send, assistant row with parts/usage on completion); six optional product seams (`turnLock`/`contextGate`/`beforeTurn`/`onRawEvent` are `@experimental` — single-consumer, proven by a reference consumer, kept FLAT for back-compat; `lifecycle`/`heartbeat` are stable) — `turnLock` (single-flight acquire/release), `contextGate` (pre-producer domain-readiness short-circuit), `beforeTurn` (observe/augment producer input), `lifecycle` (deterministic start/complete/error telemetry, idempotent + settled even on a synchronous pre-stream throw), `heartbeat` (keepalive during silent producer waits), `onRawEvent` (raw producer-event tap) — plus `transformFinalText` (pre-persist redaction applied to BOTH the final-text scalar AND every persisted assistant TEXT part, so `/redact` closes the at-rest PII leak, not just the streamed scalar) and run-failure surfacing (`onTurnComplete` receives `failed`/`failureReason` from a terminal `error`/`session.run.failed` event so an errored turn is skipped for billing + rendered as an error row, never billed/marked-complete with empty text); `createSandboxChatProducer` (raw sandbox events → flattened client vocabulary + persisted parts; authoritative rich usage from `result`/`done`; composed visible raw-error text; severed-stream catch that preserves partial content and ends normally with structured `sandbox.stream_failed` so `onTurnComplete({failed:true})` fires; live dangling-tool terminalization; pending-interaction finalization; non-renderable-ask auto-decline + warning notices, with per-turn plan gating through `isRenderableInteraction`); `runDetachedTurn` (autonomous/detached turn → live buffer bridge: taps the SAME turn-event buffer the interactive lane uses so a browser opening a session MID autonomous-run — mission step, queue job, inbound-email review — tails it token-by-token exactly like an interactive turn; idempotent [an already-`complete` turn returns its cached result without re-streaming a second event sequence] AND crash-safe [a `running` turn — a prior attempt died mid-tap — consults `completedResult` to detect a run that finished server-side, else clears the partial buffer via the `resetBuffer` seam before re-streaming so restarted seqs don't corrupt], marks `running` under `scopeId` for `listRunning` re-attach, surfaces the structured `assistantParts` projection (`result.parts`) so the durable row keeps tool/file/plan parts and not just flat text, forwards the producer's `declineInteraction`/`isRenderableInteraction`/`promoteFilePart` seams — an UNATTENDED run MUST wire `declineInteraction` or a non-renderable ask deadlocks the broker, usage/text/parts fallback via `completedResult` when the stream carries no receipt, terminal-error event → `failed` (including a raw sandbox stream throw normalized by the producer; buffer/tap failures still re-throw); the caller supplies only the raw sandbox stream + store + ids — box/prompt/tooling stay in the product); `createUploadRoute` (the `--chat` scaffold's inline-`data:`-or-ephemeral-sandbox upload path; multipart → inline `data:` part ≤700 KiB, else base64 write through a structural sandbox sink + path-ref part — the >1 MiB gateway cap makes the two-step mandatory); the store-backed attachment vertical (#224) — `resolveChatAttachments` (validates a turn's `attachments` field, re-deriving size from the STORED body, never the wire-reported one), `buildDispatchParts` (assembles attachments + mentions into dispatched `PromptInputPart[]`, inlining under the `DISPATCH_*` byte/parts budget or demoting to a path part), `promoteAgentFilePart` (a harness-emitted file part → a durable store-backed attachment, wired opt-in through `createSandboxChatProducer`'s `promoteFilePart` seam) — all storage-parameterized via `ReadAttachmentFn`/`WriteAttachmentFn` (`./attachment-store`): REQUIRED injection, no default store; `createAttachmentUploadRoute` (#234): hardened durable-store upload factory shared by fleet apps in place of a hand-rolled vault route — two-phase atomic batch, content-sniffed type/kind gate (`sniffBinary`/`checkAttachmentType`, shared client+server), per-kind + aggregate size caps, sanitized filenames; storage via the injected `WriteAttachmentFn`, auth/rate-limit/scope via the injected `authorize`, returns the full `ChatAttachmentInput[]`; import-free `./wire` contract (`ChatTurnRequestPayload`, inline-part byte gate) re-exported via `/web-react`'s chat-stream; INCREMENTAL assistant persistence (`./draft-persistence`) — the assistant row is inserted early and coalesce-patched as the turn streams (time-floored default 2 s + dirty gate + single-flight + best-effort, tuned via `incrementalPersistence`), so a viewer arriving mid-run is served from DURABLE storage instead of the streaming gateway's hot event buffer; that is what lets the buffer stay a short reconnect window (its Redis cost is linear in `ttl x concurrent sessions`, so raising the TTL to cover late viewers is a category error). Idempotency reuses the turn's OWN identity — the row id derives from `deriveExecutionId` (interactive) / `turnId` (detached), so a re-entered turn patches the row a crashed attempt started instead of duplicating parts; the final write is insert-or-patch under that same id and produces the row today's single write produces. Mid-stream parts come from `/stream`'s `draftAssistantParts` (finalize MINUS the dangling-tool terminalizer — a running tool must never persist as a failure). Enabled by default when the store has `updateMessage`; `false` opts out; a store without it is byte-unchanged. `runDetachedTurn`'s `persist` seam transfers row OWNERSHIP to the shell (returns `messageId`; the caller stops inserting) | peer `@tangle-network/agent-runtime` (`handleChatTurn` IS the turn engine — zero loop logic here; subpath-only, not in the root barrel); composes `/stream`, `/chat-store`, `/interactions`, `/web`. Reference assembly: `examples/chat-app.md` |
| `/turn-stream` | shared durable turn replay/broadcast/lock on a Cloudflare DO (#221): pure segment/lock core, `TurnStreamDO` transport shell (thread + workspace channels, WS hibernation fanout, `sync`/`afterSeq` reconnect replay, durable turn-event rows + running-turn index, product extension seams `handleProductRequest`/`shouldDeferLockRelease`/`productSyncEvents`), and the vertical seam adapters — `createDurableObjectTurnEventStore` (the real `turnStore`), `createDurableTurnLock` (dual-scope single-flight `turnLock`, one reconcile-then-retry pass via `reconcileStaleDurableTurnLock`), `createTurnStreamUpgradeHandler`, broadcast helpers, memory harness. Server-only, subpath-only; Cloudflare is structural (bind by re-exporting the class) | composes `/stream`'s `TurnEventStore` contract + `/chat-routes`' `reconcileStaleTurnLock` policy — no new peer |
| `/tangle` | app-registration consent URL + cached broker-token provider | structural `TangleAppsClient` (from agent-integrations) |
| `/billing` | per-workspace budget-capped key manager (mint/rotate/rollover/usage) | structural tcloud provisioner + store + crypto seams |
Expand Down
24 changes: 12 additions & 12 deletions docs/CODEMAP.md

Large diffs are not rendered by default.

90 changes: 89 additions & 1 deletion docs/api/chat-routes.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

Source: `src/chat-routes/index.ts`

131 exports.
142 exports.

### `ALLOWED_ATTACHMENT_SNIFFED_MIMES`

Expand All @@ -22,6 +22,54 @@ ReadonlySet<string>
(parts: ChatTurnPartInput[], maxBytes?: number) => void
```

### `AssistantDraftSnapshot`

`interface` — Live snapshot of the assistant body, taken from the producer's own accumulators.

```ts
interface AssistantDraftSnapshot
```

### `AssistantDraftStore`

`interface` — The store capability incremental persistence needs on top of `appendMessage`.

```ts
interface AssistantDraftStore
```

### `AssistantDraftWriter`

`interface` — Coalescing writer that keeps one durable assistant row in step with a streaming turn.

```ts
interface AssistantDraftWriter
```

### `AssistantDraftWriterOptions`

`interface` — Define the inputs required to construct an assistant draft writer

```ts
interface AssistantDraftWriterOptions
```

### `assistantRowIdForTurn`

`function` — The default deterministic assistant-row id for a turn.

```ts
(turnKey: string) => string
```

### `AssistantRowValues`

`interface` — Values written to the assistant row — the intersection of the append and patch shapes, so one snapshot serves both.

```ts
interface AssistantRowValues
```

### `ATTACHMENT_ACCEPT`

`const` — Accept list for the composer file picker + type validation, same grammar as the native `<input accept>` attribute.
Expand Down Expand Up @@ -366,6 +414,14 @@ interface ChatTurnUsage
(fileName: string, sniff: SniffResult, allowed?: ReadonlySet<string>) => AttachmentTypeCheckResult
```

### `createAssistantDraftWriter`

`function` — Build the coalescing draft writer for one turn.

```ts
(options: AssistantDraftWriterOptions) => AssistantDraftWriter
```

### `createAttachmentUploadRoute`

`function` — Resolve an attachment upload route handler with customizable limits and validation options
Expand Down Expand Up @@ -534,6 +590,22 @@ number
type DispatchPartsOutcome
```

### `DraftPersistenceTuning`

`interface` — Product-tunable cadence.

```ts
interface DraftPersistenceTuning
```

### `DraftStoredMessage`

`interface` — Message row shape the writer reads back when re-entering a turn.

```ts
interface DraftStoredMessage
```

### `FileIndexAuthorization`

`type` — Define authorization details and parameters for indexing a file workspace with optional caching and ignore rules
Expand Down Expand Up @@ -622,6 +694,14 @@ type FilePartPromotionOutcome
950000
```

### `isDraftContentEvent`

`function` — True when this event should arm a draft write.

```ts
(event: { type?: unknown; }) => boolean
```

### `MAX_ATTACHMENT_TOTAL_BYTES`

`const` — Aggregate raw-byte ceiling across one message's attachments.
Expand Down Expand Up @@ -998,6 +1078,14 @@ type StaleTurnLockSandboxProbeResult
type StaleTurnLockSessionProbeResult
```

### `storeSupportsDraftPersistence`

`function` — True when a store can support incremental persistence at all.

```ts
(store: AssistantDraftStore) => boolean
```

### `UPLOAD_INLINE_MAX_BYTES`

`const` — 700 KiB: base64 inflates ~4/3, so an inline part stays comfortably under the ~1 MiB gateway body cap alongside the JSON envelope.
Expand Down
10 changes: 9 additions & 1 deletion docs/api/chat-store.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

Source: `src/chat-store/index.ts`

59 exports.
60 exports.

### `AppendMessageInput`

Expand Down Expand Up @@ -470,6 +470,14 @@ type StorableHarnessPartKind
(parts: Record<string, unknown>[]) => ChatMessagePart[]
```

### `UpdateMessageInput`

`interface` — Fields an existing message row may be patched with.

```ts
interface UpdateMessageInput
```

### `WorkspaceAccessCheck`

`type` — Product-injected access check.
Expand Down
12 changes: 10 additions & 2 deletions docs/api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

Source: `src/index.ts`

766 exports.
767 exports.

### `AddCitationArgs`

Expand Down Expand Up @@ -1886,6 +1886,14 @@ interface DispatchOptions
interface DistributionSummary
```

### `draftAssistantParts`

`function` — The MID-STREAM twin of {@link finalizeAssistantParts}: the same assembled, collapsed projection MINUS the dangling-tool terminalizer.

```ts
(partOrder: string[], partMap: Map<string, JsonRecord>, finalText: string) => JsonRecord[]
```

### `driveSandboxTurn`

`function` — Resolve a sandbox turn by processing a message with given configuration and options
Expand Down Expand Up @@ -4499,7 +4507,7 @@ interface RequestContext
`function` — Resolve a chat turn by determining message reuse and constructing user message parts

```ts
(input: { existingMessages: PersistedChatMessageForTurn[]; userContent: string; turnId?: string | undefined; }) => Reso
(input: { existingMessages: PersistedChatMessageForTurn[]; userContent: string; turnId?: string | undefined; hasRunning
```

### `ResolvedAgentProfile`
Expand Down
12 changes: 10 additions & 2 deletions docs/api/stream.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

Source: `src/stream/index.ts`

44 exports.
45 exports.

### `asRecord`

Expand Down Expand Up @@ -118,6 +118,14 @@ interface BufferedTurnTap
interface D1LikeForTurns
```

### `draftAssistantParts`

`function` — The MID-STREAM twin of {@link finalizeAssistantParts}: the same assembled, collapsed projection MINUS the dangling-tool terminalizer.

```ts
(partOrder: string[], partMap: Map<string, JsonRecord>, finalText: string) => JsonRecord[]
```

### `encodeEvent`

`function` — Encode a StreamEvent object into a Uint8Array using the provided TextEncoder
Expand Down Expand Up @@ -267,7 +275,7 @@ interface ReplayTurnEventsOptions
`function` — Resolve a chat turn by determining message reuse and constructing user message parts

```ts
(input: { existingMessages: PersistedChatMessageForTurn[]; userContent: string; turnId?: string | undefined; }) => Reso
(input: { existingMessages: PersistedChatMessageForTurn[]; userContent: string; turnId?: string | undefined; hasRunning
```

### `ResolvedChatTurn`
Expand Down
Loading