@tangle-network/agent-app 0.43.70 → 0.43.72
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/chat-routes/index.d.ts +56 -15
- package/dist/chat-routes/index.js +32 -15
- package/dist/chat-routes/index.js.map +1 -1
- package/dist/chunk-IVUN7FL7.js +72 -0
- package/dist/chunk-IVUN7FL7.js.map +1 -0
- package/dist/{chunk-3ALFBTIW.js → chunk-NWYIACBB.js} +9 -1
- package/dist/chunk-NWYIACBB.js.map +1 -0
- package/dist/{chunk-YN7QR7MJ.js → chunk-RK2MKLBT.js} +4 -2
- package/dist/chunk-RK2MKLBT.js.map +1 -0
- package/dist/fingerprint-DbmOgy0n.d.ts +69 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -2
- package/dist/profile/index.d.ts +2 -0
- package/dist/profile/index.js +8 -0
- package/dist/profile/index.js.map +1 -1
- package/dist/sandbox/index.d.ts +2 -0
- package/dist/sandbox/index.js +2 -1
- package/dist/stream/index.d.ts +7 -0
- package/dist/stream/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-3ALFBTIW.js.map +0 -1
- package/dist/chunk-YN7QR7MJ.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/stream/turn-identity.ts","../src/stream/turn-buffer.ts"],"sourcesContent":["import type { JsonRecord } from './stream-normalizer'\n\n/** Define the structure of a chat message stored for a specific conversation turn */\nexport interface PersistedChatMessageForTurn {\n id: string\n role: 'user' | 'assistant' | 'system' | 'tool'\n content: string\n parts: Array<Record<string, unknown>> | null\n}\n\n/** Represent a chat turn with resolved user message insertion and prior message context */\nexport interface ResolvedChatTurn {\n turnIndex: number\n shouldInsertUserMessage: boolean\n priorMessages: PersistedChatMessageForTurn[]\n userParts: JsonRecord[]\n /** The id of the user row this turn REUSES (retry dedup), when one was\n * found. Absent on the insert path, where no row exists yet.\n *\n * The reused row is deliberately EXCLUDED from `priorMessages` (it is this\n * turn's own user message, not prior context), so this field is the only\n * way a caller can name it. */\n reusedUserMessageId?: string\n}\n\n/** Normalize and validate a client turn ID string ensuring it meets format and length requirements */\nexport function normalizeClientTurnId(value: unknown): string | undefined {\n if (value === undefined || value === null) return undefined\n if (typeof value !== 'string') throw new Error('turnId must be a string')\n const trimmed = value.trim()\n if (!trimmed) throw new Error('turnId must not be blank')\n if (trimmed.length > 160) throw new Error('turnId is too long')\n if (!/^[A-Za-z0-9:_-]+$/.test(trimmed)) {\n throw new Error('turnId contains unsupported characters')\n }\n return trimmed\n}\n\n/** Build an array of text parts with optional turn ID for user input */\nexport function buildUserTextParts(text: string, turnId: string | undefined): JsonRecord[] {\n const part: JsonRecord = { type: 'text', text }\n if (turnId) part.turnId = turnId\n return [part]\n}\n\n/** Resolve whether a message contains any part with the specified turn ID */\nexport function messageHasTurnId(message: PersistedChatMessageForTurn, turnId: string): boolean {\n for (const part of message.parts ?? []) {\n if (part && typeof part === 'object' && String(part.turnId ?? '') === turnId) {\n return true\n }\n }\n return false\n}\n\n/** Resolve a chat turn by determining message reuse and constructing user message parts */\nexport function resolveChatTurn(input: {\n existingMessages: PersistedChatMessageForTurn[]\n userContent: string\n turnId?: string\n /** True when the thread has a turn still RUNNING in the turn-event buffer\n * (`turnStore.listRunning(threadId)`).\n *\n * Without incremental persistence the trailing row of a thread mid-turn is\n * always the user row, so the content fallback below could assume it. With\n * incremental persistence the assistant row lands seconds into the turn, so\n * a retry of that same turn finds an ASSISTANT row trailing and would\n * insert a duplicate user row.\n *\n * This flag is the discriminator that keeps both cases right, and it needs\n * no new state: an assistant row trailing a turn that is still running is\n * that turn's in-flight draft (walk past it — this is a retry), whereas an\n * assistant row trailing a SETTLED turn is a completed answer (stop — the\n * user genuinely repeated a message and deserves a new turn). */\n hasRunningTurn?: boolean\n}): ResolvedChatTurn {\n const { existingMessages, userContent, turnId } = input\n const reusableIndex = findReusableUserMessageIndex(\n existingMessages,\n userContent,\n turnId,\n input.hasRunningTurn === true,\n )\n if (reusableIndex >= 0) {\n // Guarded read: `id` is typed `string`, but these rows come from a product\n // store the shell does not validate, so an adapter that omits it must\n // surface as \"no id\" rather than as the string \"undefined\".\n const reusedId = existingMessages[reusableIndex]?.id\n return {\n turnIndex: countUserMessages(existingMessages.slice(0, reusableIndex)),\n shouldInsertUserMessage: false,\n priorMessages: existingMessages.slice(0, reusableIndex),\n userParts: buildUserTextParts(userContent, turnId),\n ...(typeof reusedId === 'string' && reusedId ? { reusedUserMessageId: reusedId } : {}),\n }\n }\n\n return {\n turnIndex: countUserMessages(existingMessages),\n shouldInsertUserMessage: true,\n priorMessages: existingMessages,\n userParts: buildUserTextParts(userContent, turnId),\n }\n}\n\nfunction findReusableUserMessageIndex(\n messages: PersistedChatMessageForTurn[],\n userContent: string,\n turnId: string | undefined,\n hasRunningTurn: boolean,\n): number {\n if (turnId) {\n for (let index = messages.length - 1; index >= 0; index -= 1) {\n const message = messages[index]\n if (message?.role === 'user' && messageHasTurnId(message, turnId)) return index\n }\n }\n\n // Content fallback for a client that sends no turnId. Only the trailing rows\n // of a turn still RUNNING are walked past (they are that turn's incrementally\n // persisted assistant draft); a settled assistant row still ends the scan, so\n // a user who genuinely repeats a message gets a new turn exactly as before.\n let index = messages.length - 1\n if (hasRunningTurn) {\n while (index >= 0 && messages[index]?.role === 'assistant') index -= 1\n }\n const latest = index >= 0 ? messages[index] : undefined\n if (latest?.role === 'user' && latest.content === userContent) return index\n\n return -1\n}\n\nfunction countUserMessages(messages: PersistedChatMessageForTurn[]): number {\n return messages.filter((message) => message.role === 'user').length\n}\n","/**\n * Resumable chat turns — the router-path answer to \"streams resume on\n * disconnect\" (issue #27). A turn's loop events are teed into a store as they\n * stream; the turn keeps running under `ctx.waitUntil` when the client drops;\n * a reconnecting client replays the buffered tail by sequence number and\n * keeps following until the turn completes.\n *\n * POST /chat/stream → pumpBufferedTurn(...) + live NDJSON\n * GET /chat/stream/:turnId → replayTurnEvents({ fromSeq }) → NDJSON\n *\n * Storage is a structural seam ({@link TurnEventStore}); a D1 implementation\n * ships here because that's what Cloudflare products have (KV is unsuitable:\n * eventually consistent cross-isolate). Per-token deltas would mean hundreds\n * of rows per turn, so consecutive text/reasoning deltas are coalesced within\n * a flush window before they are persisted — replay yields slightly chunkier\n * deltas with identical concatenation.\n */\n\nexport type TurnStatus = 'running' | 'complete' | 'error'\n\n/** Represent a buffered turn event with a sequence number and serialized event data */\nexport interface BufferedTurnEvent {\n seq: number\n /** The serialized event line (JSON string, no trailing newline). */\n event: string\n}\n\n/** Manage and query turn events and their lifecycle statuses within a scoped event store */\nexport interface TurnEventStore {\n append(turnId: string, events: BufferedTurnEvent[]): Promise<void>\n read(turnId: string, fromSeq: number): Promise<BufferedTurnEvent[]>\n /** Record turn lifecycle. `scopeId` (a thread/session id) is optional and lets\n * {@link TurnEventStore.listRunning} rediscover this turn after a client reload\n * loses the turnId; stores that don't track scope ignore it. */\n setStatus(turnId: string, status: TurnStatus, scopeId?: string): Promise<void>\n getStatus(turnId: string): Promise<TurnStatus | null>\n /** Running turnIds for a scope, newest first — so a reloaded client (clientRunId\n * lost) can find and resume the in-flight turn. Optional: a store records it\n * only if `setStatus` was given a `scopeId`. */\n listRunning?(scopeId: string): Promise<string[]>\n}\n\n// ── coalescing ────────────────────────────────────────────────────────────\n\ntype AnyRecord = Record<string, unknown>\n\nfunction deltaTypeOf(ev: unknown): 'text' | 'reasoning' | null {\n const e = ev as AnyRecord | null\n if (!e || typeof e !== 'object') return null\n const inner = (e.kind === 'event' ? (e.event as AnyRecord | undefined) : e) as AnyRecord | undefined\n if (!inner || typeof inner !== 'object') return null\n if ((inner.type === 'text' || inner.type === 'reasoning') && typeof inner.text === 'string') {\n return inner.type\n }\n return null\n}\n\n/** Merge consecutive text/reasoning deltas of the same type into one event.\n * Concatenation-preserving: replaying the coalesced stream produces the same\n * accumulated text as the original. */\nexport function coalesceDeltas(events: unknown[]): unknown[] {\n const out: unknown[] = []\n for (const ev of events) {\n const type = deltaTypeOf(ev)\n const prev = out[out.length - 1]\n if (type && prev && deltaTypeOf(prev) === type) {\n const read = (x: unknown): AnyRecord =>\n ((x as AnyRecord).kind === 'event' ? (x as AnyRecord).event : x) as AnyRecord\n const merged = JSON.parse(JSON.stringify(prev)) as AnyRecord\n read(merged).text = String(read(prev).text) + String(read(ev).text)\n out[out.length - 1] = merged\n continue\n }\n out.push(ev)\n }\n return out\n}\n\nfunction asPartUpdate(ev: unknown): { partId: unknown; delta: unknown } | null {\n const e = ev as AnyRecord | null\n if (!e || typeof e !== 'object' || e.type !== 'message.part.updated') return null\n const data = e.data as AnyRecord | undefined\n if (!data || typeof data !== 'object') return null\n const part = data.part as AnyRecord | undefined\n const partId = part?.id ?? data.partId ?? part?.partId ?? null\n return { partId, delta: data.delta }\n}\n\n/**\n * Coalesce consecutive `message.part.updated` deltas for the SAME part into one\n * event. agent-runtime products stream `ChatStreamEvent` NDJSON\n * (`{type:'message.part.updated', data:{part, delta}}`); pumped through the\n * buffer with the default tool-loop coalescer, every per-token delta persists as\n * its own row because that coalescer never recognizes the shape. Pass this as\n * {@link PumpBufferedTurnOptions.coalesce} instead.\n *\n * Concatenation-preserving for BOTH consumer styles: the merged event keeps the\n * LATEST event's `data.part` (already the cumulative accumulation) and sets\n * `data.delta` to the concatenation of the merged deltas, so a client that\n * appends `delta` and one that reads the cumulative `part` both reconstruct the\n * identical final text.\n */\nexport function coalesceChatStreamEvents(events: unknown[]): unknown[] {\n const out: unknown[] = []\n for (const ev of events) {\n const cur = asPartUpdate(ev)\n const prevEv = out[out.length - 1]\n const prev = prevEv ? asPartUpdate(prevEv) : null\n if (cur && prev && cur.partId != null && cur.partId === prev.partId) {\n // Base the merged row on the latest event (its `part` is the most complete\n // accumulation); carry forward the summed delta.\n const merged = JSON.parse(JSON.stringify(ev)) as AnyRecord\n ;(merged.data as AnyRecord).delta = String(prev.delta ?? '') + String(cur.delta ?? '')\n out[out.length - 1] = merged\n continue\n }\n out.push(ev)\n }\n return out\n}\n\n// ── buffering core (the tap) ────────────────────────────────────────────────\n\n/** Define options for buffering and flushing turn events with optional live client delivery and event coalescing */\nexport interface BufferedTurnOptions {\n store: TurnEventStore\n turnId: string\n /** Deliver one serialized line to the live client. Throwing here (client\n * disconnected) does NOT stop buffering — events keep persisting. */\n write?: (line: string) => Promise<void> | void\n /** Flush buffered events to the store at most this often. Default 400ms. */\n flushIntervalMs?: number\n /** Per-flush coalescer. Default {@link coalesceDeltas} (tool-loop text/reasoning\n * deltas). agent-runtime products streaming `ChatStreamEvent` pass\n * {@link coalesceChatStreamEvents} so per-token deltas don't each persist as a\n * row. Must be concatenation-preserving. */\n coalesce?: (events: unknown[]) => unknown[]\n /** Optional scope (thread/session id) recorded with the turn status, so\n * {@link TurnEventStore.listRunning} can find this turn after a reload. */\n scopeId?: string\n}\n\n/** A push-driven buffer for a turn whose producer the caller does NOT own. */\nexport interface BufferedTurnTap {\n /** Buffer one event: persist (coalesced, on the flush window) + best-effort\n * live-deliver. Wire to a push source's per-event hook (e.g. agent-runtime\n * `handleChatTurn`'s `hooks.onEvent`). Marks the turn 'running' on first call. */\n onEvent(raw: unknown): Promise<void>\n /** Settle the turn: final flush + set status. Call after the producer resolves\n * ('complete') or rejects ('error'). 'error' flushes what was produced first. */\n done(status?: Extract<TurnStatus, 'complete' | 'error'>): Promise<void>\n}\n\n/**\n * The buffering core. Sequence-numbers every event, delivers it to `write`\n * (best-effort — a disconnected client never stops buffering), and flushes to\n * the store in coalesced batches. Drives both transports:\n *\n * • {@link pumpBufferedTurn} — when you OWN an `AsyncIterable` producer.\n * • this tap (`onEvent`/`done`) — when the producer owns iteration and only\n * hands you a push callback (agent-runtime `handleChatTurn`'s `hooks.onEvent`\n * + the finished body). Durability stays here in the shell; the engine needs\n * no `TurnEventStore` seam.\n */\nexport function createBufferedTurnTap(opts: BufferedTurnOptions): BufferedTurnTap {\n const flushIntervalMs = opts.flushIntervalMs ?? 400\n const coalesce = opts.coalesce ?? coalesceDeltas\n const startedAt = Date.now()\n let seq = 0\n let clientGone = false\n let pending: unknown[] = []\n let lastFlush = Date.now()\n let started = false\n\n async function flush(): Promise<void> {\n if (pending.length === 0) return\n const batch = coalesce(pending)\n pending = []\n const rows = batch.map((ev) => ({ seq: ++seq, event: JSON.stringify(ev) }))\n await opts.store.append(opts.turnId, rows)\n lastFlush = Date.now()\n }\n\n async function ensureStarted(): Promise<void> {\n if (started) return\n started = true\n await opts.store.setStatus(opts.turnId, 'running', opts.scopeId)\n }\n\n return {\n async onEvent(raw) {\n await ensureStarted()\n // Stamp ms-since-turn-start so any stored turn is replayable AND traceable\n // (see ../trace) from the same buffered rows.\n const ev = raw && typeof raw === 'object' ? { ...(raw as Record<string, unknown>), _t: Date.now() - startedAt } : raw\n pending.push(ev)\n if (!clientGone && opts.write) {\n try {\n // Live delivery carries a provisional ordering hint, not the persisted\n // seq (coalescing changes seq assignment); clients resume with the\n // seqs from replay, or 0 for \"everything\".\n await opts.write(JSON.stringify(ev))\n } catch {\n clientGone = true\n }\n }\n if (Date.now() - lastFlush >= flushIntervalMs) await flush()\n },\n async done(status = 'complete') {\n await ensureStarted()\n if (status === 'error') {\n await flush().catch(() => {})\n await opts.store.setStatus(opts.turnId, 'error', opts.scopeId).catch(() => {})\n return\n }\n await flush()\n await opts.store.setStatus(opts.turnId, 'complete', opts.scopeId)\n },\n }\n}\n\n// ── pump (producer side) ──────────────────────────────────────────────────\n\n/** Define options to pump data from an asynchronous iterable source with buffered turn control */\nexport interface PumpBufferedTurnOptions extends BufferedTurnOptions {\n source: AsyncIterable<unknown>\n}\n\n/**\n * Drive a turn to completion regardless of the live client, when you OWN the\n * producer as an `AsyncIterable`. A thin driver over {@link createBufferedTurnTap}.\n * Returns a promise that resolves when the turn finishes — hand it to\n * `ctx.waitUntil` so a disconnect can't kill the turn. Never rejects on\n * client-write failure; a source error marks the turn 'error' (after flushing\n * what was produced) and rethrows.\n */\nexport async function pumpBufferedTurn(opts: PumpBufferedTurnOptions): Promise<void> {\n const tap = createBufferedTurnTap(opts)\n try {\n for await (const raw of opts.source) await tap.onEvent(raw)\n await tap.done('complete')\n } catch (err) {\n await tap.done('error')\n throw err\n }\n}\n\n// ── replay (consumer side) ────────────────────────────────────────────────\n\n/** Define options for replaying turn events with control over sequence, polling, and timeout */\nexport interface ReplayTurnEventsOptions {\n store: TurnEventStore\n turnId: string\n /** Replay strictly after this sequence number (0 = from the beginning). */\n fromSeq?: number\n /** Poll cadence while the turn is still running. Default 500ms. */\n pollMs?: number\n /** Give up following a 'running' turn after this long. Default 120s. */\n timeoutMs?: number\n}\n\n/**\n * Yield buffered events after `fromSeq`, then keep polling while the turn is\n * still 'running' until it completes, errors, or times out. Terminates with a\n * final `{seq: -1, event: '{\"type\":\"turn_status\",...}'}` marker so clients\n * know why the replay ended.\n */\nexport async function* replayTurnEvents(opts: ReplayTurnEventsOptions): AsyncGenerator<BufferedTurnEvent> {\n const pollMs = opts.pollMs ?? 500\n const timeoutMs = opts.timeoutMs ?? 120_000\n let cursor = opts.fromSeq ?? 0\n const deadline = Date.now() + timeoutMs\n\n for (;;) {\n const batch = await opts.store.read(opts.turnId, cursor)\n for (const row of batch) {\n cursor = Math.max(cursor, row.seq)\n yield row\n }\n const status = await opts.store.getStatus(opts.turnId)\n if (status !== 'running') {\n yield { seq: -1, event: JSON.stringify({ type: 'turn_status', status: status ?? 'unknown' }) }\n return\n }\n if (Date.now() >= deadline) {\n yield { seq: -1, event: JSON.stringify({ type: 'turn_status', status: 'timeout' }) }\n return\n }\n await new Promise((r) => setTimeout(r, pollMs))\n }\n}\n\n// ── D1 store ──────────────────────────────────────────────────────────────\n\n/** Minimal structural D1 contract (Cloudflare `D1Database` satisfies it). */\nexport interface D1LikeForTurns {\n prepare(sql: string): {\n bind(...values: unknown[]): {\n run(): Promise<unknown>\n all<T = Record<string, unknown>>(): Promise<{ results: T[] }>\n first<T = Record<string, unknown>>(): Promise<T | null>\n }\n }\n}\n\n/** Schema for the D1 store — append to the product's migrations. */\nexport const TURN_EVENTS_MIGRATION_SQL = `\nCREATE TABLE IF NOT EXISTS turn_events (\n turnId TEXT NOT NULL,\n seq INTEGER NOT NULL,\n event TEXT NOT NULL,\n PRIMARY KEY (turnId, seq)\n);\nCREATE TABLE IF NOT EXISTS turn_status (\n turnId TEXT PRIMARY KEY,\n status TEXT NOT NULL,\n scopeId TEXT,\n updatedAt TEXT NOT NULL\n);\nCREATE INDEX IF NOT EXISTS idx_turn_status_scope ON turn_status (scopeId, status);\n`\n\n/** For deployments whose `turn_status` table predates `scopeId`/`listRunning` —\n * run once to add the column (the CREATE above already includes it for new\n * deployments). SQLite ignores a duplicate-add error if already applied. */\nexport const TURN_STATUS_SCOPE_MIGRATION_SQL = `ALTER TABLE turn_status ADD COLUMN scopeId TEXT;`\n\n/** Resolve a TurnEventStore that appends and reads turn events using a D1-like database interface */\nexport function createD1TurnEventStore(db: D1LikeForTurns): TurnEventStore {\n return {\n async append(turnId, events) {\n if (!events.length) return\n // One multi-row insert per flush window keeps write volume bounded.\n const placeholders = events.map(() => '(?, ?, ?)').join(', ')\n const values = events.flatMap((e) => [turnId, e.seq, e.event])\n await db.prepare(`INSERT OR IGNORE INTO turn_events (turnId, seq, event) VALUES ${placeholders}`).bind(...values).run()\n },\n async read(turnId, fromSeq) {\n const { results } = await db\n .prepare('SELECT seq, event FROM turn_events WHERE turnId = ? AND seq > ? ORDER BY seq ASC')\n .bind(turnId, fromSeq)\n .all<{ seq: number; event: string }>()\n return results\n },\n async setStatus(turnId, status, scopeId) {\n // COALESCE preserves a scopeId set on the initial 'running' write when a\n // later 'complete'/'error' write passes none.\n await db\n .prepare(\n 'INSERT INTO turn_status (turnId, status, scopeId, updatedAt) VALUES (?, ?, ?, ?) ON CONFLICT(turnId) DO UPDATE SET status = excluded.status, scopeId = COALESCE(excluded.scopeId, turn_status.scopeId), updatedAt = excluded.updatedAt',\n )\n .bind(turnId, status, scopeId ?? null, new Date().toISOString())\n .run()\n },\n async getStatus(turnId) {\n const row = await db.prepare('SELECT status FROM turn_status WHERE turnId = ?').bind(turnId).first<{ status: TurnStatus }>()\n return row?.status ?? null\n },\n async listRunning(scopeId) {\n const { results } = await db\n .prepare(\"SELECT turnId FROM turn_status WHERE scopeId = ? AND status = 'running' ORDER BY updatedAt DESC\")\n .bind(scopeId)\n .all<{ turnId: string }>()\n return results.map((r) => r.turnId)\n },\n }\n}\n\n/** In-memory store for tests and keyless local dev. */\nexport function createMemoryTurnEventStore(): TurnEventStore {\n const events = new Map<string, BufferedTurnEvent[]>()\n const status = new Map<string, TurnStatus>()\n const scopes = new Map<string, string>()\n const order: string[] = []\n return {\n async append(turnId, rows) {\n const list = events.get(turnId) ?? []\n list.push(...rows)\n events.set(turnId, list)\n },\n async read(turnId, fromSeq) {\n return (events.get(turnId) ?? []).filter((e) => e.seq > fromSeq)\n },\n async setStatus(turnId, s, scopeId) {\n status.set(turnId, s)\n if (scopeId) scopes.set(turnId, scopeId)\n if (!order.includes(turnId)) order.push(turnId)\n },\n async getStatus(turnId) {\n return status.get(turnId) ?? null\n },\n async listRunning(scopeId) {\n // Newest first, mirroring the D1 store's `ORDER BY updatedAt DESC`.\n return [...order].reverse().filter((t) => status.get(t) === 'running' && scopes.get(t) === scopeId)\n },\n }\n}\n"],"mappings":"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","names":["index"]}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { AgentProfile } from '@tangle-network/agent-interface';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Profile fingerprinting — prove WHICH profile a turn actually executed.
|
|
5
|
+
*
|
|
6
|
+
* The backtest invariant this exists to enforce: an eval's score is only worth
|
|
7
|
+
* publishing if the benchmarked profile IS the shipped profile. The failure
|
|
8
|
+
* mode is structural, not hypothetical — a product's eval composed the
|
|
9
|
+
* production profile in one module, executed a hand-rolled stub in another,
|
|
10
|
+
* and stamped the composed profile's identity onto the stub's scorecard.
|
|
11
|
+
* Nothing compared the two, so nothing could notice.
|
|
12
|
+
*
|
|
13
|
+
* A `ProfileFingerprint` is a channelled identity of the profile handed to the
|
|
14
|
+
* sandbox SDK: the system-prompt digest plus the names of every capability
|
|
15
|
+
* surface (MCP servers, subagents, file mounts, hub connections) and the
|
|
16
|
+
* model/harness the turn dispatched at. It is deliberately NOT a byte-exhaustive
|
|
17
|
+
* serialization — channels are what drift in practice, and a channelled diff
|
|
18
|
+
* names the surface that moved instead of reporting "bytes differ".
|
|
19
|
+
*
|
|
20
|
+
* The write half of the seam is `StreamSandboxPromptOptions.onProfileResolved`
|
|
21
|
+
* (`/sandbox`): the one place the final profile exists is inside
|
|
22
|
+
* `streamSandboxPrompt` after the system-prompt override, the MCP merge, and
|
|
23
|
+
* reasoning-effort attachment, so that is where the fingerprint is taken. A
|
|
24
|
+
* caller re-deriving a profile to fingerprint it would reintroduce the exact
|
|
25
|
+
* gap this closes.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/** The dispatch context a profile cannot see but a turn's identity includes. */
|
|
29
|
+
interface ProfileFingerprintContext {
|
|
30
|
+
model?: string;
|
|
31
|
+
harness?: string;
|
|
32
|
+
}
|
|
33
|
+
/** Channelled identity of one executed (or composed) profile. */
|
|
34
|
+
interface ProfileFingerprint {
|
|
35
|
+
/** sha256 over every channel below — one value to log/compare. */
|
|
36
|
+
hash: string;
|
|
37
|
+
/** sha256 of `prompt.systemPrompt` ('' when absent). */
|
|
38
|
+
promptSha: string;
|
|
39
|
+
/** UTF-8 byte length of `prompt.systemPrompt`. */
|
|
40
|
+
promptBytes: number;
|
|
41
|
+
/** Sorted MCP server keys. */
|
|
42
|
+
mcpKeys: string[];
|
|
43
|
+
/** Sorted subagent names. */
|
|
44
|
+
subagentNames: string[];
|
|
45
|
+
/** Sorted `resources.files[].path` mounts. */
|
|
46
|
+
fileMountPaths: string[];
|
|
47
|
+
/** Sorted hub connection ids (alias-qualified when present). */
|
|
48
|
+
connectionIds: string[];
|
|
49
|
+
model?: string;
|
|
50
|
+
harness?: string;
|
|
51
|
+
}
|
|
52
|
+
/** Fingerprint a profile as the SDK would receive it. */
|
|
53
|
+
declare function fingerprintAgentProfile(profile: AgentProfile, context?: ProfileFingerprintContext): Promise<ProfileFingerprint>;
|
|
54
|
+
/** One drifted channel between two fingerprints, rendered as comparable strings. */
|
|
55
|
+
interface ProfileDriftEntry {
|
|
56
|
+
channel: 'promptSha' | 'promptBytes' | 'mcpKeys' | 'subagentNames' | 'fileMountPaths' | 'connectionIds' | 'model' | 'harness';
|
|
57
|
+
a: string;
|
|
58
|
+
b: string;
|
|
59
|
+
}
|
|
60
|
+
interface ProfileDrift {
|
|
61
|
+
equal: boolean;
|
|
62
|
+
drift: ProfileDriftEntry[];
|
|
63
|
+
}
|
|
64
|
+
/** Channel-by-channel comparison of two fingerprints. */
|
|
65
|
+
declare function diffProfileFingerprints(a: ProfileFingerprint, b: ProfileFingerprint): ProfileDrift;
|
|
66
|
+
/** Human-readable drift report; exactly 'profiles identical' when equal. */
|
|
67
|
+
declare function formatProfileDrift(drift: ProfileDrift): string;
|
|
68
|
+
|
|
69
|
+
export { type ProfileDrift as P, type ProfileDriftEntry as a, type ProfileFingerprint as b, type ProfileFingerprintContext as c, diffProfileFingerprints as d, formatProfileDrift as e, fingerprintAgentProfile as f };
|
package/dist/index.d.ts
CHANGED
|
@@ -47,4 +47,5 @@ export { StorageConfig } from '@tangle-network/sandbox';
|
|
|
47
47
|
export { T as TrustItem } from './trust-gate-Dcm5xSva.js';
|
|
48
48
|
import '@tangle-network/agent-runtime/intelligence';
|
|
49
49
|
import '@tangle-network/agent-knowledge';
|
|
50
|
+
import './fingerprint-DbmOgy0n.js';
|
|
50
51
|
import 'zod';
|
package/dist/index.js
CHANGED
|
@@ -193,7 +193,7 @@ import {
|
|
|
193
193
|
pumpBufferedTurn,
|
|
194
194
|
replayTurnEvents,
|
|
195
195
|
resolveChatTurn
|
|
196
|
-
} from "./chunk-
|
|
196
|
+
} from "./chunk-RK2MKLBT.js";
|
|
197
197
|
import {
|
|
198
198
|
createInteractionAnswerRoute,
|
|
199
199
|
listSessionInteractions,
|
|
@@ -352,7 +352,8 @@ import {
|
|
|
352
352
|
verifySandboxTerminalToken,
|
|
353
353
|
verifyTerminalProxyToken,
|
|
354
354
|
writeProfileFilesToBox
|
|
355
|
-
} from "./chunk-
|
|
355
|
+
} from "./chunk-NWYIACBB.js";
|
|
356
|
+
import "./chunk-IVUN7FL7.js";
|
|
356
357
|
import {
|
|
357
358
|
DEFAULT_HARNESS,
|
|
358
359
|
KNOWN_HARNESSES,
|
package/dist/profile/index.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { profile } from '@tangle-network/agent-eval';
|
|
|
3
3
|
export { profile } from '@tangle-network/agent-eval';
|
|
4
4
|
import { SkillEntry } from '../skills/index.js';
|
|
5
5
|
export { ComposeShellResourcesInput, ComposedSkills, CorpusEntry, CorpusLoadResult, GlobModules, LoadCorpusOptions, ParsedSkill, SkillDeliveryMode, SkillFrontmatter, assertSkillDeliveryDisjoint, composeShellResources, composeSkills, corpusSkills, loadMarkdownCorpus, mergeComposedSkills, parseCorpusSkills, parseSkillFrontmatter, registrySkills, renderInlineSkills, renderSkillIndex, skillEntryFromMarkdown, skillMountPath, skillRefs } from '../skills/index.js';
|
|
6
|
+
export { P as ProfileDrift, a as ProfileDriftEntry, b as ProfileFingerprint, c as ProfileFingerprintContext, d as diffProfileFingerprints, f as fingerprintAgentProfile, e as formatProfileDrift } from '../fingerprint-DbmOgy0n.js';
|
|
7
|
+
import '@tangle-network/agent-interface';
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* Profile composer + evolvable-section seam for agent products.
|
package/dist/profile/index.js
CHANGED
|
@@ -14,6 +14,11 @@ import {
|
|
|
14
14
|
skillMountPath,
|
|
15
15
|
skillRefs
|
|
16
16
|
} from "../chunk-34M7AUWO.js";
|
|
17
|
+
import {
|
|
18
|
+
diffProfileFingerprints,
|
|
19
|
+
fingerprintAgentProfile,
|
|
20
|
+
formatProfileDrift
|
|
21
|
+
} from "../chunk-IVUN7FL7.js";
|
|
17
22
|
|
|
18
23
|
// src/profile/index.ts
|
|
19
24
|
import { mergeAgentProfiles } from "@tangle-network/sandbox";
|
|
@@ -123,6 +128,9 @@ export {
|
|
|
123
128
|
composeShellResources,
|
|
124
129
|
composeSkills,
|
|
125
130
|
corpusSkills,
|
|
131
|
+
diffProfileFingerprints,
|
|
132
|
+
fingerprintAgentProfile,
|
|
133
|
+
formatProfileDrift,
|
|
126
134
|
largestPromptSections,
|
|
127
135
|
loadMarkdownCorpus,
|
|
128
136
|
makeEvolvableSection,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/profile/index.ts"],"sourcesContent":["/**\n * Profile composer + evolvable-section seam for agent products.\n *\n * The standard \"load a deployable AgentProfile, including skills, plus the\n * skills the end user added to their own instance\" entry point. A product holds\n * a canonical base `AgentProfile` (role/environment/tool-conventions rendered\n * into `prompt.systemPrompt`, baseline skills, baseline MCP). At deploy/turn\n * time it layers four file-mount channels onto `resources.files` —\n *\n * 1. skills — the always-mounted product skill corpus\n * 2. knowledge — a second always-mounted corpus (domain knowledge pack)\n * 3. registry — the tier-gated installable registry (free -> boot-mounted)\n * 4. userSkills — per-user / per-workspace skills the END USER adds to their\n * own instance, mounted at `~/.claude/skills/<id>/SKILL.md`\n * exactly like the registry's free tier\n *\n * plus an optional MCP overlay (delegation + per-turn app-tool side channel), a\n * per-turn `systemPrompt` override, and a `name` override. The merge is the SDK\n * `mergeAgentProfiles`: `mcp` is last-wins per key (base -> overlay), `resources`\n * arrays are concatenated (base ++ overlay), `prompt` is shallow-merged so an\n * overlay carrying only `systemPrompt` overrides it while keeping base\n * instructions. The compose algebra is DATA — the product injects the base\n * profile, the channel mounts (built with the `skills` subpath primitives), the\n * delegation/app-tool MCP map, and the override strings; nothing here reaches\n * for env, a glob, or a specific product's profile.\n *\n * The evolvable-section seam is the loader closure. A product's single\n * self-improvable domain section (the one `applyDomainPatch` targets) loads its\n * body from a deployed markdown override, falling back to an in-tree baseline.\n * The `import.meta.glob('<lit>', ...)` literal must stay at the CONSUMER call\n * site (Vite static-analyzes it), so `makeEvolvableSection` takes the loader as\n * a closure and a REQUIRED `baseline` — it never constructs a glob and never\n * defaults the baseline, so a product can't render an empty learned-guidance\n * section. `stripComments` is the shared \"is this addendum really empty?\" test.\n */\n\nimport type {\n AgentProfile,\n AgentProfileFileMount,\n AgentProfileMcpServer,\n AgentProfileResourceRef,\n} from '@tangle-network/sandbox'\nimport { mergeAgentProfiles } from '@tangle-network/sandbox'\nimport { profile } from '@tangle-network/agent-eval'\nimport {\n composeShellResources,\n registrySkills,\n skillMountPath,\n type ComposeShellResourcesInput,\n type SkillEntry,\n} from '../skills/index'\n\n/** Re-expose the agent-eval section/render substrate so a product wires the\n * evolvable surface through ONE subpath: `makeEvolvableSection` builds the\n * section, `profile.renderProfile` renders it, `profile.applyDomainPatch` lets\n * the loop patch it by id. The rendering/patching engine stays in agent-eval;\n * reach it through this namespace (re-exporting the bare fns would leak\n * agent-eval's un-nameable AgentProfile type into our generated d.ts). */\nexport { profile }\n\n/** The file-mount channels layered onto `resources.files`. The first three\n * mirror {@link ComposeShellResourcesInput}; `userSkills` is the per-user /\n * per-workspace channel — skills the END USER added to their own instance,\n * mounted at the harness skill-discovery path like the registry's free tier. */\nexport interface ProfileChannels {\n /** Always-mounted skill corpus (pass `corpusSkills(...)`). */\n skills?: AgentProfileFileMount[]\n /** Always-mounted knowledge corpus (pass `corpusSkills(...)` for the pack). */\n knowledge?: AgentProfileFileMount[]\n /** Single-file evolvable / learned-guidance corpora, if mounted as files. */\n evolvable?: AgentProfileFileMount[]\n /** Tier-gated installable registry (pass the registry array; free tier is\n * mounted, paid is install-on-demand). Gated through {@link registrySkills}. */\n registry?: SkillEntry[]\n /** Per-user / per-workspace skills the end user adds to their own instance.\n * Mounted at `~/.claude/skills/<id>/SKILL.md`, the same harness path the\n * registry uses, so a user skill and a registry skill with the same id\n * collide deterministically (the user skill, appended last, wins). */\n userSkills?: UserSkill[]\n /** Final skip filter applied to the composed mount list by mount `path`. */\n filesPredicate?: (mount: AgentProfileFileMount) => boolean\n /** Typed `resources.skills` channel — refs the platform materializer places\n * at the harness-native skill dir (see {@link skillRefs} and\n * `@tangle-network/agent-app/skills-placement`'s `composeSkillsForHarness`).\n * The successor to path-baked mounts: `registry`/`userSkills` above mount\n * files at the hardcoded claude-code path via {@link skillMountPath};\n * `skillRefs` instead rides the provider-neutral `resources.skills` field\n * the platform resolves per harness. */\n skillRefs?: AgentProfileResourceRef[]\n /** Tier passed to {@link registrySkills} for the `registry` channel.\n * Previously hardcoded `'free'`; default unchanged. */\n registryTier?: string\n}\n\n/** A per-user / per-workspace skill: an id and an inline `SKILL.md` body. The\n * user-facing analogue of a registry {@link SkillEntry} with no tier gate —\n * every user skill is mounted (the user opted in by adding it). */\nexport interface UserSkill {\n id: string\n /** Inline `SKILL.md` body mounted at {@link skillMountPath}. */\n skillMd: string\n}\n\n/** Overlay overrides applied on top of the channel mounts. */\nexport interface ProfileOverlay {\n /** Extra MCP servers merged into the profile `mcp` map (last-wins per key over\n * the base servers). The product builds this from its delegation MCP entry\n * and any per-turn app-tool side-channel servers. An absent/`undefined` entry\n * is dropped — pass only the servers that resolved (fail-closed at the seam,\n * not here). */\n mcp?: Record<string, AgentProfileMcpServer>\n /** Per-turn system-prompt override. When set, replaces the base\n * `prompt.systemPrompt` while keeping base `prompt.instructions`. When unset,\n * the base prompt passes through unchanged. */\n systemPrompt?: string\n /** Extra instruction lines merged onto the active prompt (e.g. a per-turn\n * domain/integration directive). Appended to base `prompt.instructions` by\n * the SDK merge. */\n instructions?: string[]\n /** Profile `name` override. When unset, the base name is kept. */\n name?: string\n}\n\n/** Byte budget on the FINAL composed `prompt.systemPrompt`. Past this the\n * model degrades sharply (a 122,659-byte prompt shipped once and the model\n * returned empty answers), so the default gate throws well before that. */\nexport const DEFAULT_MAX_SYSTEM_PROMPT_BYTES = 40_000\n\n/** Budget config for the composed system prompt. */\nexport interface ComposeProfileBudget {\n /** Byte cap on the composed `prompt.systemPrompt`.\n * Default {@link DEFAULT_MAX_SYSTEM_PROMPT_BYTES}. */\n maxSystemPromptBytes?: number\n /** Downgrade the over-budget throw to a `console.warn` — the escape hatch\n * for a product with a known-big prompt that must still ship (it yells on\n * every compose instead of blocking). */\n warnOnly?: boolean\n /** Required to raise {@link maxSystemPromptBytes} above\n * {@link DEFAULT_MAX_SYSTEM_PROMPT_BYTES} or to set {@link warnOnly}: a\n * written reason naming what stays inline and why it cannot be mounted.\n * Weakening the cap is a product decision that outlives the person making\n * it, and the usual cause is reference material concatenated into the prompt\n * that belongs in `resources.files`; demanding the sentence here keeps that\n * from happening by accident. */\n overBudgetReason?: string\n}\n\n/** Reject a budget that weakens the cap without stating why. Runs before the\n * size check so it fires on every compose, not only once a prompt has already\n * grown past the raised ceiling. */\nfunction assertBudgetPolicy(budget: ComposeProfileBudget): void {\n const raisedCap =\n budget.maxSystemPromptBytes !== undefined &&\n budget.maxSystemPromptBytes > DEFAULT_MAX_SYSTEM_PROMPT_BYTES\n if (!raisedCap && !budget.warnOnly) return\n if ((budget.overBudgetReason ?? '').trim() !== '') return\n const weakened = raisedCap\n ? `maxSystemPromptBytes ${budget.maxSystemPromptBytes} exceeds the ${DEFAULT_MAX_SYSTEM_PROMPT_BYTES}-byte default`\n : 'warnOnly downgrades the over-budget throw to a warning'\n throw new Error(\n `${weakened} without an overBudgetReason. Oversized system prompts degrade toward empty answers, so the cap is not a formality. ` +\n 'Before raising it: rank the prompt with largestPromptSections() — reference material (playbooks, checklists, corpora) belongs in resources.files ' +\n \"via corpusSkills()/userSkillMounts() or composeSkills({ mode: 'mounted' }), which puts the bodies on disk in the sandbox and leaves a short index in the prompt. \" +\n 'Only content the agent must obey without a tool call should stay inline. If the prompt is genuinely irreducible, set overBudgetReason to the sentence that says so.',\n )\n}\n\n/** Largest markdown-heading-delimited sections of a prompt, by UTF-8 bytes.\n * Cheap heuristic: split on `#`-heading lines; the preamble before the first\n * heading reports as \"(preamble)\". */\nexport function largestPromptSections(\n prompt: string,\n top = 3,\n): Array<{ title: string; bytes: number }> {\n const encoder = new TextEncoder()\n const sections: Array<{ title: string; bytes: number }> = []\n let title = '(preamble)'\n let start = 0\n const flush = (end: number) => {\n const body = prompt.slice(start, end)\n if (body.trim()) sections.push({ title, bytes: encoder.encode(body).byteLength })\n }\n const headingRe = /^#{1,6}\\s+(.+)$/gm\n for (const match of prompt.matchAll(headingRe)) {\n flush(match.index)\n title = (match[1] ?? '').trim() || '(untitled section)'\n start = match.index\n }\n flush(prompt.length)\n return sections.sort((a, b) => b.bytes - a.bytes).slice(0, top)\n}\n\n/** Enforce {@link ComposeProfileBudget} on a composed system prompt: over\n * budget throws (or warns with `warnOnly`) with the actual size and the\n * top-3 largest sections. Exported so a product assembling its prompt\n * outside {@link composeAgentProfile} (e.g. via the `/prompt` assembler) can\n * run the same gate at its own final-composition point. */\nexport function assertSystemPromptWithinBudget(\n systemPrompt: string,\n budget: ComposeProfileBudget = {},\n): void {\n assertBudgetPolicy(budget)\n const max = budget.maxSystemPromptBytes ?? DEFAULT_MAX_SYSTEM_PROMPT_BYTES\n const bytes = new TextEncoder().encode(systemPrompt).byteLength\n if (bytes <= max) return\n const sections = largestPromptSections(systemPrompt)\n .map((s) => `\"${s.title}\" (${s.bytes}B)`)\n .join(', ')\n const message =\n `composed systemPrompt is ${bytes} bytes — over the ${max}-byte budget ` +\n `(oversized prompts degrade to empty answers). ` +\n (sections ? `Largest sections: ${sections}. ` : '') +\n `Move reference material to resources.files (corpusSkills/userSkillMounts, or composeSkills({ mode: 'mounted' })) so the bodies land on disk in the sandbox ` +\n `and the prompt keeps only an index; keep inline only what the agent must obey without a tool call. Raising maxSystemPromptBytes requires an overBudgetReason.`\n if (budget.warnOnly) {\n console.warn(`[profile] ${message}`)\n return\n }\n throw new Error(message)\n}\n\n/** Project per-user skills onto SDK file mounts at the harness skill-discovery\n * path. No tier gate — a user skill is mounted because the user added it.\n * Sorted by path for determinism (matches {@link registrySkills}). */\nexport function userSkillMounts(userSkills: UserSkill[]): AgentProfileFileMount[] {\n return userSkills\n .map(\n (s) =>\n ({\n path: skillMountPath(s.id),\n resource: { kind: 'inline', name: s.id, content: s.skillMd },\n }) satisfies AgentProfileFileMount,\n )\n .sort((a, b) => a.path.localeCompare(b.path))\n}\n\n/**\n * Compose a deployable `AgentProfile` from a canonical base plus the four\n * file-mount channels and the overlay overrides.\n *\n * Files: base `resources.files` come first; the four channels follow in\n * `skills -> knowledge -> evolvable -> registry -> userSkills` order (so a\n * userSkill that mounts at the same path as a registry skill is the last write\n * and wins). MCP: base servers first, the overlay `mcp` last (last-wins per\n * key). Prompt: the overlay `systemPrompt`, when set, replaces the base one;\n * base instructions are preserved. Name: the overlay `name`, when set, wins.\n *\n * The merge delegates to the SDK `mergeAgentProfiles` (overlay-wins on records,\n * arrays concatenated) — the deterministic algebra is the overlay we hand it,\n * not a hand-rolled spread. `mergeAgentProfiles(base, overlay)` returns\n * `undefined` only when BOTH are `undefined`; `base` is always defined here, so\n * the result is non-`undefined` by construction and we assert that to the caller.\n *\n * The composed `prompt.systemPrompt` is byte-budgeted here — the single point\n * where the FINAL prompt exists ({@link assertSystemPromptWithinBudget};\n * default {@link DEFAULT_MAX_SYSTEM_PROMPT_BYTES}, `warnOnly` escape hatch).\n */\nexport function composeAgentProfile(\n base: AgentProfile,\n channels: ProfileChannels = {},\n overlay: ProfileOverlay = {},\n budget: ComposeProfileBudget = {},\n): AgentProfile {\n const shellInput: ComposeShellResourcesInput = {\n skills: channels.skills,\n knowledge: channels.knowledge,\n evolvable: channels.evolvable,\n registry: channels.registry\n ? registrySkills(channels.registry, channels.registryTier ?? 'free')\n : undefined,\n predicate: channels.filesPredicate,\n }\n const channelFiles = composeShellResources(shellInput)\n const userFiles = channels.userSkills ? userSkillMounts(channels.userSkills) : []\n const overlayFiles = channels.filesPredicate\n ? userFiles.filter(channels.filesPredicate)\n : userFiles\n const files = [...channelFiles, ...overlayFiles]\n\n const promptOverlay: { systemPrompt?: string; instructions?: string[] } = {}\n if (overlay.systemPrompt) promptOverlay.systemPrompt = overlay.systemPrompt\n if (overlay.instructions && overlay.instructions.length > 0) promptOverlay.instructions = overlay.instructions\n\n const overlayProfile: AgentProfile = {\n ...(overlay.name ? { name: overlay.name } : {}),\n ...(Object.keys(promptOverlay).length > 0 ? { prompt: promptOverlay } : {}),\n ...(overlay.mcp ? { mcp: overlay.mcp } : {}),\n resources: {\n files,\n ...(channels.skillRefs && channels.skillRefs.length > 0 ? { skills: channels.skillRefs } : {}),\n },\n }\n\n const merged = mergeAgentProfiles(base, overlayProfile)\n if (!merged)\n throw new Error('composeAgentProfile: mergeAgentProfiles returned undefined for a defined base')\n // Byte-budget gate on the FINAL composed systemPrompt — this is the single\n // point where every channel and overlay has been merged in.\n const systemPrompt = merged.prompt?.systemPrompt\n if (typeof systemPrompt === 'string') assertSystemPromptWithinBudget(systemPrompt, budget)\n return pruneEmptyResourceChannels(merged)\n}\n\n/** Drop empty resource channels the SDK merge normalizes in (`tools`/`skills`/\n * `agents`/`commands`: `[]`), so the composed profile's wire payload carries\n * only the channels that actually have content — one canonical shape every app\n * emits, instead of a sidecar payload full of empty arrays. */\nfunction pruneEmptyResourceChannels(profile: AgentProfile): AgentProfile {\n if (!profile.resources) return profile\n const kept = Object.fromEntries(\n Object.entries(profile.resources).filter(([, value]) => !(Array.isArray(value) && value.length === 0)),\n ) as AgentProfile['resources']\n const out: AgentProfile = { ...profile, resources: kept }\n if (kept && Object.keys(kept).length === 0) delete out.resources\n return out\n}\n\n/** True body of an addendum file with HTML comments stripped — an all-comment\n * placeholder counts as empty, so the loader falls back to the baseline. */\nexport function stripComments(raw: string): string {\n return raw.replace(/<!--[\\s\\S]*?-->/g, '').trim()\n}\n\n/** Inputs to {@link makeEvolvableSection}. */\nexport interface EvolvableSectionInput {\n /** Section id the self-improvement loop targets with `applyDomainPatch`. */\n id: string\n /** Section title rendered as `### <title>`. */\n title: string\n /**\n * Load the deployed section body. The CONSUMER supplies this closure and runs\n * its own `import.meta.glob('<lit>', { eager: true, query: '?raw', import:\n * 'default' })` inside it — the literal must stay at the call site so Vite can\n * static-analyze it; a glob constructed here would not resolve the product's\n * files. Return the raw markdown (comments and all); `makeEvolvableSection`\n * applies {@link stripComments} to decide whether it is really populated.\n */\n load: () => string\n /**\n * The in-tree fallback body, used when `load()` returns an\n * all-comments/empty placeholder. REQUIRED — no internal default — so a\n * product can never accidentally render an empty evolvable section.\n */\n baseline: string\n}\n\n/**\n * Build the one evolvable (`evolvable: true`) domain section whose body comes\n * from the product's loader, falling back to the required baseline when the\n * loaded body is empty after stripping comments. Returns the agent-eval\n * `AgentProfileSection` shape — drop it straight into `prodProfile`'s shipped\n * sections. The loader is the only seam; the empty-vs-populated rule and the\n * baseline fallback are the lifted algebra.\n */\nexport function makeEvolvableSection(input: EvolvableSectionInput): profile.AgentProfileSection {\n const loaded = input.load()\n const body = stripComments(loaded) ? loaded.trim() : input.baseline\n return { id: input.id, title: input.title, body, evolvable: true }\n}\n\nexport {\n assertSkillDeliveryDisjoint,\n composeShellResources,\n composeSkills,\n corpusSkills,\n loadMarkdownCorpus,\n mergeComposedSkills,\n parseCorpusSkills,\n parseSkillFrontmatter,\n registrySkills,\n renderInlineSkills,\n renderSkillIndex,\n skillEntryFromMarkdown,\n skillMountPath,\n skillRefs,\n} from '../skills/index'\nexport type {\n ComposedSkills,\n ComposeShellResourcesInput,\n CorpusEntry,\n CorpusLoadResult,\n GlobModules,\n LoadCorpusOptions,\n ParsedSkill,\n SkillDeliveryMode,\n SkillEntry,\n SkillFrontmatter,\n} from '../skills/index'\n"],"mappings":"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","names":["profile"]}
|
|
1
|
+
{"version":3,"sources":["../../src/profile/index.ts"],"sourcesContent":["/**\n * Profile composer + evolvable-section seam for agent products.\n *\n * The standard \"load a deployable AgentProfile, including skills, plus the\n * skills the end user added to their own instance\" entry point. A product holds\n * a canonical base `AgentProfile` (role/environment/tool-conventions rendered\n * into `prompt.systemPrompt`, baseline skills, baseline MCP). At deploy/turn\n * time it layers four file-mount channels onto `resources.files` —\n *\n * 1. skills — the always-mounted product skill corpus\n * 2. knowledge — a second always-mounted corpus (domain knowledge pack)\n * 3. registry — the tier-gated installable registry (free -> boot-mounted)\n * 4. userSkills — per-user / per-workspace skills the END USER adds to their\n * own instance, mounted at `~/.claude/skills/<id>/SKILL.md`\n * exactly like the registry's free tier\n *\n * plus an optional MCP overlay (delegation + per-turn app-tool side channel), a\n * per-turn `systemPrompt` override, and a `name` override. The merge is the SDK\n * `mergeAgentProfiles`: `mcp` is last-wins per key (base -> overlay), `resources`\n * arrays are concatenated (base ++ overlay), `prompt` is shallow-merged so an\n * overlay carrying only `systemPrompt` overrides it while keeping base\n * instructions. The compose algebra is DATA — the product injects the base\n * profile, the channel mounts (built with the `skills` subpath primitives), the\n * delegation/app-tool MCP map, and the override strings; nothing here reaches\n * for env, a glob, or a specific product's profile.\n *\n * The evolvable-section seam is the loader closure. A product's single\n * self-improvable domain section (the one `applyDomainPatch` targets) loads its\n * body from a deployed markdown override, falling back to an in-tree baseline.\n * The `import.meta.glob('<lit>', ...)` literal must stay at the CONSUMER call\n * site (Vite static-analyzes it), so `makeEvolvableSection` takes the loader as\n * a closure and a REQUIRED `baseline` — it never constructs a glob and never\n * defaults the baseline, so a product can't render an empty learned-guidance\n * section. `stripComments` is the shared \"is this addendum really empty?\" test.\n */\n\nimport type {\n AgentProfile,\n AgentProfileFileMount,\n AgentProfileMcpServer,\n AgentProfileResourceRef,\n} from '@tangle-network/sandbox'\nimport { mergeAgentProfiles } from '@tangle-network/sandbox'\nimport { profile } from '@tangle-network/agent-eval'\nimport {\n composeShellResources,\n registrySkills,\n skillMountPath,\n type ComposeShellResourcesInput,\n type SkillEntry,\n} from '../skills/index'\n\n/** Re-expose the agent-eval section/render substrate so a product wires the\n * evolvable surface through ONE subpath: `makeEvolvableSection` builds the\n * section, `profile.renderProfile` renders it, `profile.applyDomainPatch` lets\n * the loop patch it by id. The rendering/patching engine stays in agent-eval;\n * reach it through this namespace (re-exporting the bare fns would leak\n * agent-eval's un-nameable AgentProfile type into our generated d.ts). */\nexport { profile }\n\n/** The file-mount channels layered onto `resources.files`. The first three\n * mirror {@link ComposeShellResourcesInput}; `userSkills` is the per-user /\n * per-workspace channel — skills the END USER added to their own instance,\n * mounted at the harness skill-discovery path like the registry's free tier. */\nexport interface ProfileChannels {\n /** Always-mounted skill corpus (pass `corpusSkills(...)`). */\n skills?: AgentProfileFileMount[]\n /** Always-mounted knowledge corpus (pass `corpusSkills(...)` for the pack). */\n knowledge?: AgentProfileFileMount[]\n /** Single-file evolvable / learned-guidance corpora, if mounted as files. */\n evolvable?: AgentProfileFileMount[]\n /** Tier-gated installable registry (pass the registry array; free tier is\n * mounted, paid is install-on-demand). Gated through {@link registrySkills}. */\n registry?: SkillEntry[]\n /** Per-user / per-workspace skills the end user adds to their own instance.\n * Mounted at `~/.claude/skills/<id>/SKILL.md`, the same harness path the\n * registry uses, so a user skill and a registry skill with the same id\n * collide deterministically (the user skill, appended last, wins). */\n userSkills?: UserSkill[]\n /** Final skip filter applied to the composed mount list by mount `path`. */\n filesPredicate?: (mount: AgentProfileFileMount) => boolean\n /** Typed `resources.skills` channel — refs the platform materializer places\n * at the harness-native skill dir (see {@link skillRefs} and\n * `@tangle-network/agent-app/skills-placement`'s `composeSkillsForHarness`).\n * The successor to path-baked mounts: `registry`/`userSkills` above mount\n * files at the hardcoded claude-code path via {@link skillMountPath};\n * `skillRefs` instead rides the provider-neutral `resources.skills` field\n * the platform resolves per harness. */\n skillRefs?: AgentProfileResourceRef[]\n /** Tier passed to {@link registrySkills} for the `registry` channel.\n * Previously hardcoded `'free'`; default unchanged. */\n registryTier?: string\n}\n\n/** A per-user / per-workspace skill: an id and an inline `SKILL.md` body. The\n * user-facing analogue of a registry {@link SkillEntry} with no tier gate —\n * every user skill is mounted (the user opted in by adding it). */\nexport interface UserSkill {\n id: string\n /** Inline `SKILL.md` body mounted at {@link skillMountPath}. */\n skillMd: string\n}\n\n/** Overlay overrides applied on top of the channel mounts. */\nexport interface ProfileOverlay {\n /** Extra MCP servers merged into the profile `mcp` map (last-wins per key over\n * the base servers). The product builds this from its delegation MCP entry\n * and any per-turn app-tool side-channel servers. An absent/`undefined` entry\n * is dropped — pass only the servers that resolved (fail-closed at the seam,\n * not here). */\n mcp?: Record<string, AgentProfileMcpServer>\n /** Per-turn system-prompt override. When set, replaces the base\n * `prompt.systemPrompt` while keeping base `prompt.instructions`. When unset,\n * the base prompt passes through unchanged. */\n systemPrompt?: string\n /** Extra instruction lines merged onto the active prompt (e.g. a per-turn\n * domain/integration directive). Appended to base `prompt.instructions` by\n * the SDK merge. */\n instructions?: string[]\n /** Profile `name` override. When unset, the base name is kept. */\n name?: string\n}\n\n/** Byte budget on the FINAL composed `prompt.systemPrompt`. Past this the\n * model degrades sharply (a 122,659-byte prompt shipped once and the model\n * returned empty answers), so the default gate throws well before that. */\nexport const DEFAULT_MAX_SYSTEM_PROMPT_BYTES = 40_000\n\n/** Budget config for the composed system prompt. */\nexport interface ComposeProfileBudget {\n /** Byte cap on the composed `prompt.systemPrompt`.\n * Default {@link DEFAULT_MAX_SYSTEM_PROMPT_BYTES}. */\n maxSystemPromptBytes?: number\n /** Downgrade the over-budget throw to a `console.warn` — the escape hatch\n * for a product with a known-big prompt that must still ship (it yells on\n * every compose instead of blocking). */\n warnOnly?: boolean\n /** Required to raise {@link maxSystemPromptBytes} above\n * {@link DEFAULT_MAX_SYSTEM_PROMPT_BYTES} or to set {@link warnOnly}: a\n * written reason naming what stays inline and why it cannot be mounted.\n * Weakening the cap is a product decision that outlives the person making\n * it, and the usual cause is reference material concatenated into the prompt\n * that belongs in `resources.files`; demanding the sentence here keeps that\n * from happening by accident. */\n overBudgetReason?: string\n}\n\n/** Reject a budget that weakens the cap without stating why. Runs before the\n * size check so it fires on every compose, not only once a prompt has already\n * grown past the raised ceiling. */\nfunction assertBudgetPolicy(budget: ComposeProfileBudget): void {\n const raisedCap =\n budget.maxSystemPromptBytes !== undefined &&\n budget.maxSystemPromptBytes > DEFAULT_MAX_SYSTEM_PROMPT_BYTES\n if (!raisedCap && !budget.warnOnly) return\n if ((budget.overBudgetReason ?? '').trim() !== '') return\n const weakened = raisedCap\n ? `maxSystemPromptBytes ${budget.maxSystemPromptBytes} exceeds the ${DEFAULT_MAX_SYSTEM_PROMPT_BYTES}-byte default`\n : 'warnOnly downgrades the over-budget throw to a warning'\n throw new Error(\n `${weakened} without an overBudgetReason. Oversized system prompts degrade toward empty answers, so the cap is not a formality. ` +\n 'Before raising it: rank the prompt with largestPromptSections() — reference material (playbooks, checklists, corpora) belongs in resources.files ' +\n \"via corpusSkills()/userSkillMounts() or composeSkills({ mode: 'mounted' }), which puts the bodies on disk in the sandbox and leaves a short index in the prompt. \" +\n 'Only content the agent must obey without a tool call should stay inline. If the prompt is genuinely irreducible, set overBudgetReason to the sentence that says so.',\n )\n}\n\n/** Largest markdown-heading-delimited sections of a prompt, by UTF-8 bytes.\n * Cheap heuristic: split on `#`-heading lines; the preamble before the first\n * heading reports as \"(preamble)\". */\nexport function largestPromptSections(\n prompt: string,\n top = 3,\n): Array<{ title: string; bytes: number }> {\n const encoder = new TextEncoder()\n const sections: Array<{ title: string; bytes: number }> = []\n let title = '(preamble)'\n let start = 0\n const flush = (end: number) => {\n const body = prompt.slice(start, end)\n if (body.trim()) sections.push({ title, bytes: encoder.encode(body).byteLength })\n }\n const headingRe = /^#{1,6}\\s+(.+)$/gm\n for (const match of prompt.matchAll(headingRe)) {\n flush(match.index)\n title = (match[1] ?? '').trim() || '(untitled section)'\n start = match.index\n }\n flush(prompt.length)\n return sections.sort((a, b) => b.bytes - a.bytes).slice(0, top)\n}\n\n/** Enforce {@link ComposeProfileBudget} on a composed system prompt: over\n * budget throws (or warns with `warnOnly`) with the actual size and the\n * top-3 largest sections. Exported so a product assembling its prompt\n * outside {@link composeAgentProfile} (e.g. via the `/prompt` assembler) can\n * run the same gate at its own final-composition point. */\nexport function assertSystemPromptWithinBudget(\n systemPrompt: string,\n budget: ComposeProfileBudget = {},\n): void {\n assertBudgetPolicy(budget)\n const max = budget.maxSystemPromptBytes ?? DEFAULT_MAX_SYSTEM_PROMPT_BYTES\n const bytes = new TextEncoder().encode(systemPrompt).byteLength\n if (bytes <= max) return\n const sections = largestPromptSections(systemPrompt)\n .map((s) => `\"${s.title}\" (${s.bytes}B)`)\n .join(', ')\n const message =\n `composed systemPrompt is ${bytes} bytes — over the ${max}-byte budget ` +\n `(oversized prompts degrade to empty answers). ` +\n (sections ? `Largest sections: ${sections}. ` : '') +\n `Move reference material to resources.files (corpusSkills/userSkillMounts, or composeSkills({ mode: 'mounted' })) so the bodies land on disk in the sandbox ` +\n `and the prompt keeps only an index; keep inline only what the agent must obey without a tool call. Raising maxSystemPromptBytes requires an overBudgetReason.`\n if (budget.warnOnly) {\n console.warn(`[profile] ${message}`)\n return\n }\n throw new Error(message)\n}\n\n/** Project per-user skills onto SDK file mounts at the harness skill-discovery\n * path. No tier gate — a user skill is mounted because the user added it.\n * Sorted by path for determinism (matches {@link registrySkills}). */\nexport function userSkillMounts(userSkills: UserSkill[]): AgentProfileFileMount[] {\n return userSkills\n .map(\n (s) =>\n ({\n path: skillMountPath(s.id),\n resource: { kind: 'inline', name: s.id, content: s.skillMd },\n }) satisfies AgentProfileFileMount,\n )\n .sort((a, b) => a.path.localeCompare(b.path))\n}\n\n/**\n * Compose a deployable `AgentProfile` from a canonical base plus the four\n * file-mount channels and the overlay overrides.\n *\n * Files: base `resources.files` come first; the four channels follow in\n * `skills -> knowledge -> evolvable -> registry -> userSkills` order (so a\n * userSkill that mounts at the same path as a registry skill is the last write\n * and wins). MCP: base servers first, the overlay `mcp` last (last-wins per\n * key). Prompt: the overlay `systemPrompt`, when set, replaces the base one;\n * base instructions are preserved. Name: the overlay `name`, when set, wins.\n *\n * The merge delegates to the SDK `mergeAgentProfiles` (overlay-wins on records,\n * arrays concatenated) — the deterministic algebra is the overlay we hand it,\n * not a hand-rolled spread. `mergeAgentProfiles(base, overlay)` returns\n * `undefined` only when BOTH are `undefined`; `base` is always defined here, so\n * the result is non-`undefined` by construction and we assert that to the caller.\n *\n * The composed `prompt.systemPrompt` is byte-budgeted here — the single point\n * where the FINAL prompt exists ({@link assertSystemPromptWithinBudget};\n * default {@link DEFAULT_MAX_SYSTEM_PROMPT_BYTES}, `warnOnly` escape hatch).\n */\nexport function composeAgentProfile(\n base: AgentProfile,\n channels: ProfileChannels = {},\n overlay: ProfileOverlay = {},\n budget: ComposeProfileBudget = {},\n): AgentProfile {\n const shellInput: ComposeShellResourcesInput = {\n skills: channels.skills,\n knowledge: channels.knowledge,\n evolvable: channels.evolvable,\n registry: channels.registry\n ? registrySkills(channels.registry, channels.registryTier ?? 'free')\n : undefined,\n predicate: channels.filesPredicate,\n }\n const channelFiles = composeShellResources(shellInput)\n const userFiles = channels.userSkills ? userSkillMounts(channels.userSkills) : []\n const overlayFiles = channels.filesPredicate\n ? userFiles.filter(channels.filesPredicate)\n : userFiles\n const files = [...channelFiles, ...overlayFiles]\n\n const promptOverlay: { systemPrompt?: string; instructions?: string[] } = {}\n if (overlay.systemPrompt) promptOverlay.systemPrompt = overlay.systemPrompt\n if (overlay.instructions && overlay.instructions.length > 0) promptOverlay.instructions = overlay.instructions\n\n const overlayProfile: AgentProfile = {\n ...(overlay.name ? { name: overlay.name } : {}),\n ...(Object.keys(promptOverlay).length > 0 ? { prompt: promptOverlay } : {}),\n ...(overlay.mcp ? { mcp: overlay.mcp } : {}),\n resources: {\n files,\n ...(channels.skillRefs && channels.skillRefs.length > 0 ? { skills: channels.skillRefs } : {}),\n },\n }\n\n const merged = mergeAgentProfiles(base, overlayProfile)\n if (!merged)\n throw new Error('composeAgentProfile: mergeAgentProfiles returned undefined for a defined base')\n // Byte-budget gate on the FINAL composed systemPrompt — this is the single\n // point where every channel and overlay has been merged in.\n const systemPrompt = merged.prompt?.systemPrompt\n if (typeof systemPrompt === 'string') assertSystemPromptWithinBudget(systemPrompt, budget)\n return pruneEmptyResourceChannels(merged)\n}\n\n/** Drop empty resource channels the SDK merge normalizes in (`tools`/`skills`/\n * `agents`/`commands`: `[]`), so the composed profile's wire payload carries\n * only the channels that actually have content — one canonical shape every app\n * emits, instead of a sidecar payload full of empty arrays. */\nfunction pruneEmptyResourceChannels(profile: AgentProfile): AgentProfile {\n if (!profile.resources) return profile\n const kept = Object.fromEntries(\n Object.entries(profile.resources).filter(([, value]) => !(Array.isArray(value) && value.length === 0)),\n ) as AgentProfile['resources']\n const out: AgentProfile = { ...profile, resources: kept }\n if (kept && Object.keys(kept).length === 0) delete out.resources\n return out\n}\n\n/** True body of an addendum file with HTML comments stripped — an all-comment\n * placeholder counts as empty, so the loader falls back to the baseline. */\nexport function stripComments(raw: string): string {\n return raw.replace(/<!--[\\s\\S]*?-->/g, '').trim()\n}\n\n/** Inputs to {@link makeEvolvableSection}. */\nexport interface EvolvableSectionInput {\n /** Section id the self-improvement loop targets with `applyDomainPatch`. */\n id: string\n /** Section title rendered as `### <title>`. */\n title: string\n /**\n * Load the deployed section body. The CONSUMER supplies this closure and runs\n * its own `import.meta.glob('<lit>', { eager: true, query: '?raw', import:\n * 'default' })` inside it — the literal must stay at the call site so Vite can\n * static-analyze it; a glob constructed here would not resolve the product's\n * files. Return the raw markdown (comments and all); `makeEvolvableSection`\n * applies {@link stripComments} to decide whether it is really populated.\n */\n load: () => string\n /**\n * The in-tree fallback body, used when `load()` returns an\n * all-comments/empty placeholder. REQUIRED — no internal default — so a\n * product can never accidentally render an empty evolvable section.\n */\n baseline: string\n}\n\n/**\n * Build the one evolvable (`evolvable: true`) domain section whose body comes\n * from the product's loader, falling back to the required baseline when the\n * loaded body is empty after stripping comments. Returns the agent-eval\n * `AgentProfileSection` shape — drop it straight into `prodProfile`'s shipped\n * sections. The loader is the only seam; the empty-vs-populated rule and the\n * baseline fallback are the lifted algebra.\n */\nexport function makeEvolvableSection(input: EvolvableSectionInput): profile.AgentProfileSection {\n const loaded = input.load()\n const body = stripComments(loaded) ? loaded.trim() : input.baseline\n return { id: input.id, title: input.title, body, evolvable: true }\n}\n\nexport {\n assertSkillDeliveryDisjoint,\n composeShellResources,\n composeSkills,\n corpusSkills,\n loadMarkdownCorpus,\n mergeComposedSkills,\n parseCorpusSkills,\n parseSkillFrontmatter,\n registrySkills,\n renderInlineSkills,\n renderSkillIndex,\n skillEntryFromMarkdown,\n skillMountPath,\n skillRefs,\n} from '../skills/index'\nexport type {\n ComposedSkills,\n ComposeShellResourcesInput,\n CorpusEntry,\n CorpusLoadResult,\n GlobModules,\n LoadCorpusOptions,\n ParsedSkill,\n SkillDeliveryMode,\n SkillEntry,\n SkillFrontmatter,\n} from '../skills/index'\nexport {\n diffProfileFingerprints,\n fingerprintAgentProfile,\n formatProfileDrift,\n} from './fingerprint'\nexport type {\n ProfileDrift,\n ProfileDriftEntry,\n ProfileFingerprint,\n ProfileFingerprintContext,\n} from './fingerprint'\n"],"mappings":"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","names":["profile"]}
|
package/dist/sandbox/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { a as ToolHeaderNames } from '../auth-_FU8w01b.js';
|
|
|
4
4
|
import { f as AppToolName, c as AppToolContext } from '../types-CBRyqijY.js';
|
|
5
5
|
import { Harness } from '../harness/index.js';
|
|
6
6
|
import { f as TangleExecutionEnvironment } from '../model-DmdkIteM.js';
|
|
7
|
+
import { b as ProfileFingerprint } from '../fingerprint-DbmOgy0n.js';
|
|
7
8
|
import '@tangle-network/agent-interface';
|
|
8
9
|
|
|
9
10
|
/** Represent success or failure of an operation with corresponding value or error information */
|
|
@@ -686,6 +687,7 @@ interface StreamSandboxPromptOptions {
|
|
|
686
687
|
plan?: boolean;
|
|
687
688
|
};
|
|
688
689
|
detach?: boolean;
|
|
690
|
+
onProfileResolved?: (fingerprint: ProfileFingerprint) => void;
|
|
689
691
|
}
|
|
690
692
|
/** Resolve and stream AI-generated responses from a sandboxed environment based on input messages and options */
|
|
691
693
|
declare function streamSandboxPrompt(shell: SandboxRuntimeConfig, box: SandboxInstance, message: string | PromptInputPart[], options?: StreamSandboxPromptOptions): AsyncGenerator<unknown>;
|
package/dist/sandbox/index.js
CHANGED
|
@@ -58,7 +58,8 @@ import {
|
|
|
58
58
|
verifySandboxTerminalToken,
|
|
59
59
|
verifyTerminalProxyToken,
|
|
60
60
|
writeProfileFilesToBox
|
|
61
|
-
} from "../chunk-
|
|
61
|
+
} from "../chunk-NWYIACBB.js";
|
|
62
|
+
import "../chunk-IVUN7FL7.js";
|
|
62
63
|
import "../chunk-CQZSAR77.js";
|
|
63
64
|
import "../chunk-WL7XHLDK.js";
|
|
64
65
|
import "../chunk-3EJ6SFJI.js";
|
package/dist/stream/index.d.ts
CHANGED
|
@@ -17,6 +17,13 @@ interface ResolvedChatTurn {
|
|
|
17
17
|
shouldInsertUserMessage: boolean;
|
|
18
18
|
priorMessages: PersistedChatMessageForTurn[];
|
|
19
19
|
userParts: JsonRecord[];
|
|
20
|
+
/** The id of the user row this turn REUSES (retry dedup), when one was
|
|
21
|
+
* found. Absent on the insert path, where no row exists yet.
|
|
22
|
+
*
|
|
23
|
+
* The reused row is deliberately EXCLUDED from `priorMessages` (it is this
|
|
24
|
+
* turn's own user message, not prior context), so this field is the only
|
|
25
|
+
* way a caller can name it. */
|
|
26
|
+
reusedUserMessageId?: string;
|
|
20
27
|
}
|
|
21
28
|
/** Normalize and validate a client turn ID string ensuring it meets format and length requirements */
|
|
22
29
|
declare function normalizeClientTurnId(value: unknown): string | undefined;
|
package/dist/stream/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-app",
|
|
3
|
-
"version": "0.43.
|
|
3
|
+
"version": "0.43.72",
|
|
4
4
|
"packageManager": "pnpm@10.33.4",
|
|
5
5
|
"description": "Application-shell framework for Tangle agent products: a bounded tool loop, the structured agent→app tool side channel, integration-hub client, per-workspace billing, and crypto — composed over the Tangle agent substrate through typed seams.",
|
|
6
6
|
"keywords": [
|