@ai-ecoverse/slicc-agent 3.47.3 → 3.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +18 -7
  2. package/dist/attachments.js +168 -0
  3. package/dist/attachments.js.map +7 -0
  4. package/dist/changes/git.js +2 -2
  5. package/dist/changes/git.js.map +1 -1
  6. package/dist/control.js +30 -10
  7. package/dist/control.js.map +2 -2
  8. package/dist/host.js +26 -9
  9. package/dist/host.js.map +3 -3
  10. package/dist/images/fit.js +50 -0
  11. package/dist/images/fit.js.map +7 -0
  12. package/dist/images/index.js +7 -47
  13. package/dist/images/index.js.map +3 -3
  14. package/dist/index.js +15 -0
  15. package/dist/index.js.map +2 -2
  16. package/dist/main.js +11 -2
  17. package/dist/main.js.map +2 -2
  18. package/dist/mcp/runtime.js +1 -1
  19. package/dist/mcp/runtime.js.map +2 -2
  20. package/dist/net.js +10 -1
  21. package/dist/net.js.map +2 -2
  22. package/dist/readonly.js +7 -1
  23. package/dist/readonly.js.map +2 -2
  24. package/dist/remote/client.js +9 -4
  25. package/dist/remote/client.js.map +2 -2
  26. package/dist/remote/host.js +1 -0
  27. package/dist/remote/host.js.map +2 -2
  28. package/dist/scoops/identity.js +4 -1
  29. package/dist/scoops/identity.js.map +2 -2
  30. package/dist/services.js.map +2 -2
  31. package/dist/spectrum/agent-port.js +17 -2
  32. package/dist/spectrum/agent-port.js.map +2 -2
  33. package/dist/spectrum/messages.js +7 -2
  34. package/dist/spectrum/messages.js.map +2 -2
  35. package/dist/spectrum/outgoing.js +73 -0
  36. package/dist/spectrum/outgoing.js.map +7 -0
  37. package/dist/system.js +11 -6
  38. package/dist/system.js.map +2 -2
  39. package/dist/thumbs.js +105 -0
  40. package/dist/thumbs.js.map +7 -0
  41. package/dist/types/attachments.d.ts +58 -0
  42. package/dist/types/changes/git.d.ts +2 -2
  43. package/dist/types/control.d.ts +2 -1
  44. package/dist/types/host.d.ts +4 -0
  45. package/dist/types/images/fit.d.ts +10 -0
  46. package/dist/types/images/index.d.ts +2 -10
  47. package/dist/types/index.d.ts +2 -0
  48. package/dist/types/main.d.ts +2 -0
  49. package/dist/types/net.d.ts +8 -0
  50. package/dist/types/remote/client.d.ts +2 -0
  51. package/dist/types/remote/host.d.ts +2 -0
  52. package/dist/types/services.d.ts +16 -0
  53. package/dist/types/spectrum/outgoing.d.ts +8 -0
  54. package/dist/types/system.d.ts +1 -1
  55. package/dist/types/thumbs.d.ts +19 -0
  56. package/dist/types/uploads.d.ts +14 -0
  57. package/dist/types/views.d.ts +2 -1
  58. package/dist/uploads.js +43 -0
  59. package/dist/uploads.js.map +7 -0
  60. package/dist/views.js +6 -2
  61. package/dist/views.js.map +2 -2
  62. package/package.json +3 -3
  63. package/packages/vfs-root/skills/browser/SKILL.md +2 -2
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/mcp/runtime.ts"],
4
- "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport type { CredentialStore } from '@earendil-works/pi-ai';\nimport type { Registry } from '@earendil-works/pi-durable';\nimport type { ExecutionEnv, FileWatcher } from '@earendil-works/pi-durable/env';\nimport { StreamableHttpTransport } from '@earendil-works/pi-mcp';\nimport type { LickEvent, Licks } from '../licks/licks.ts';\nimport { type McpAccounts, mcpAccounts } from './accounts.ts';\nimport { McpCatalog } from './catalog.ts';\nimport {\n type McpConfigProblem,\n type McpServerEntry,\n parseMcpConfig,\n sameConfig,\n usesOAuth,\n} from './config.ts';\nimport { McpServerConnection } from './connection.ts';\nimport { resolveHeaders, scrub, secretNames } from './credentials.ts';\nimport { authProvider as oauthAuth, StoredOAuth } from './oauth.ts';\nimport { patchSse } from './sse.ts';\n\nexport const MCP_LOG_MAX_BYTES = 5 * 1024 * 1024;\n\nexport function configPath(home: string): string {\n return `${home}/.pi/agent/mcp.json`;\n}\n\nexport function logPath(home: string): string {\n return `${home}/.pi/agent/mcp.log`;\n}\n\nexport interface McpAttach {\n licks: Licks;\n env: ExecutionEnv;\n home: string;\n credentials: CredentialStore;\n fetch: typeof fetch;\n version: string;\n reloadMs: number;\n codemode: boolean;\n cors: boolean;\n redirect: string;\n}\n\nexport interface ServerStatus {\n name: string;\n state: string;\n tools: number;\n error?: string;\n}\n\nexport type { McpAccounts } from './accounts.ts';\n\nexport interface McpRuntime extends McpAccounts {\n status(): ServerStatus[];\n secrets(): string[];\n reload(context: Context): Promise<void>;\n close(context: Context): Promise<void>;\n}\n\nexport function problemLick(problem: McpConfigProblem, home: string): LickEvent {\n const file = `~${configPath(home).slice(home.length)}`;\n const where = problem.server ? `server \"${problem.server}\"` : file;\n const field = problem.field ? `, field ${problem.field}` : '';\n return {\n channel: 'fswatch',\n source: `mcp:${file}`,\n title: `MCP config problem: ${where}${field}`,\n text: problem.message,\n body: `${file}: the entry is skipped until it is fixed. The format is in the slicc-agent README.`,\n target: 'cone',\n severity: 'warn',\n eventId: `${problem.server ?? ''}\\n${problem.field ?? ''}\\n${problem.message}`,\n };\n}\n\nconst CORS_ERROR = /failed to fetch|networkerror|load failed|cors/i;\n\nexport function stateLick(connection: McpServerConnection, cors: boolean): LickEvent | undefined {\n const name = connection.name;\n const base = { channel: 'fswatch' as const, source: `mcp:${name}`, target: 'cone' as const };\n if (connection.state === 'needs-secret')\n return {\n ...base,\n title: `MCP server \"${name}\" needs a token`,\n text: `Its headers use ${connection.missing.map((item) => `\\${${item}}`).join(', ')}. Add the value in Settings \u203A MCP servers.`,\n severity: 'warn',\n eventId: `${name}\\nsecret\\n${connection.missing.join(',')}`,\n };\n if (connection.state === 'needs-auth')\n return {\n ...base,\n title: secretNames(connection.entry.config).length\n ? `MCP server \"${name}\" rejected its token`\n : `MCP server \"${name}\" needs sign-in`,\n text: connection.signInMessage(),\n severity: 'warn',\n eventId: `${name}\\nauth`,\n };\n if (connection.state !== 'failed') return undefined;\n const error = scrub(connection.error ?? 'unknown error', connection.secrets);\n const blocked = cors && CORS_ERROR.test(error);\n return {\n ...base,\n title: `MCP server \"${name}\" can't connect`,\n text: blocked\n ? \"Seven reaches it with the page's own fetch, and the server sends no CORS headers. Run slicc-node, slicc-swift or the SLICC extension, which fetch without CORS.\"\n : error,\n severity: 'warn',\n eventId: `${name}\\nfailed\\n${blocked ? 'cors' : error}`,\n };\n}\n\nexport function connectedLick(connection: McpServerConnection): LickEvent {\n const count = connection.tools.length;\n return {\n channel: 'fswatch',\n source: `mcp:${connection.name}`,\n title: `MCP server \"${connection.name}\" connected`,\n text: `${connection.entry.config.url} offers ${count} tool${count === 1 ? '' : 's'}.`,\n target: 'cone',\n eventId: `${connection.name}\\nconnected\\n${connection.entry.config.url}`,\n };\n}\n\nexport function logLine(\n server: string,\n params: unknown,\n secrets: readonly string[],\n at: Date\n): string {\n const { level, logger, data } = (params ?? {}) as {\n level?: string;\n logger?: string;\n data?: unknown;\n };\n const text = scrub(typeof data === 'string' ? data : (JSON.stringify(data) ?? ''), secrets);\n return `${at.toISOString()} [${server}] ${level ?? 'info'}${logger ? ` ${logger}` : ''}: ${text}\\n`;\n}\n\nfunction serverLog(options: McpAttach, context: Context) {\n let logging = Promise.resolve();\n const path = logPath(options.home);\n const { env } = options;\n return {\n write(connection: McpServerConnection, params: unknown) {\n const line = logLine(connection.name, params, connection.secrets, new Date());\n logging = logging.then(async () => {\n const info = await env.fileInfo(path, context);\n if (info.ok && info.value.size > MCP_LOG_MAX_BYTES)\n await env.renameFile(path, `${path}.1`, context);\n await env.appendFile(path, line, context);\n });\n },\n settled: () => logging,\n };\n}\n\nexport async function attachMcp(\n catalog: McpCatalog,\n options: McpAttach,\n context: Context\n): Promise<McpRuntime> {\n const { env, home } = options;\n catalog.codemodeOn = options.codemode;\n const reported = new Set<string>();\n const report = async (lick: LickEvent | undefined, using: Context) => {\n if (!lick?.eventId || reported.has(lick.eventId)) return;\n reported.add(lick.eventId);\n await options.licks.deliver(lick, using).catch(() => undefined);\n };\n const listeners = new Set<() => void>();\n const notify = () => {\n for (const listener of listeners) listener();\n };\n let initial: Set<string> = new Set();\n let first = true;\n const log = serverLog(options, context);\n const fetcher = (input: string | URL, init?: RequestInit) => options.fetch(input, init);\n const oauth = (entry: McpServerEntry) =>\n new StoredOAuth(entry, options.credentials, options.redirect);\n const transport = async (entry: McpServerEntry) => {\n const resolved = await resolveHeaders(entry.name, entry.config, options.credentials);\n if (!resolved.ok) return { missing: resolved.missing };\n const authProvider = usesOAuth(entry.config) ? oauthAuth(oauth(entry), entry) : undefined;\n return {\n transport: new StreamableHttpTransport({\n url: entry.config.url,\n headers: resolved.headers,\n fetch: fetcher,\n ...(authProvider ? { authProvider } : {}),\n }),\n secrets: resolved.secrets,\n };\n };\n const open = (entry: McpServerEntry) => {\n const connection = new McpServerConnection({\n entry,\n version: options.version,\n transport,\n onTools: () => catalog.rebuild(),\n onLog: log.write,\n onChange: (changed) => {\n if (catalog.connections.get(changed.name) !== changed) return;\n if (changed.state === 'connected' && !initial.has(changed.name))\n void report(connectedLick(changed), context);\n void report(stateLick(changed, options.cors), context);\n if (changed.state !== 'connected') catalog.rebuild();\n notify();\n },\n });\n catalog.connections.set(entry.name, connection);\n void connection.getClient().catch(() => undefined);\n };\n const reload = async (using: Context) => {\n const read = await env.readTextFile(configPath(home), using);\n const parsed = parseMcpConfig(read.ok ? read.value : undefined);\n for (const problem of parsed.problems) await report(problemLick(problem, home), using);\n const wanted = new Map(\n parsed.servers\n .filter((entry) => entry.config.enabled !== false)\n .map((entry) => [entry.name, entry])\n );\n const closing: Promise<void>[] = [];\n for (const [name, connection] of [...catalog.connections]) {\n const next = wanted.get(name);\n if (next && sameConfig(next.config, connection.entry.config)) continue;\n catalog.connections.delete(name);\n closing.push(connection.close());\n }\n if (first) initial = new Set(wanted.keys());\n first = false;\n for (const [name, entry] of wanted) if (!catalog.connections.has(name)) open(entry);\n catalog.rebuild();\n notify();\n await Promise.all(closing);\n };\n await reload(context);\n let timer: ReturnType<typeof setTimeout> | undefined;\n const schedule = () => {\n clearTimeout(timer);\n timer = setTimeout(() => void reload(context).catch(() => undefined), options.reloadMs);\n };\n const watched = await env.watch([{ path: configPath(home) }], schedule, context);\n const watcher: FileWatcher | undefined = watched.ok ? watched.value : undefined;\n const restart = (connection: McpServerConnection) => {\n open(connection.entry);\n void connection.close();\n catalog.rebuild();\n };\n return {\n ...mcpAccounts(\n catalog,\n { credentials: options.credentials, oauth, fetch: fetcher },\n restart,\n listeners\n ),\n status: () =>\n [...catalog.connections.values()].map((connection) => ({\n name: connection.name,\n state: connection.state,\n tools: connection.tools.length,\n ...(connection.error ? { error: scrub(connection.error, connection.secrets) } : {}),\n })),\n secrets: () => [...catalog.connections.values()].flatMap((connection) => connection.secrets),\n reload,\n async close(using) {\n clearTimeout(timer);\n await watcher?.close(using);\n const all = [...catalog.connections.values()];\n catalog.connections.clear();\n await Promise.all(all.map((connection) => connection.close()));\n catalog.rebuild();\n await log.settled();\n },\n };\n}\n\nexport async function startMcp(\n registry: Registry,\n options: McpAttach,\n context: Context\n): Promise<{ catalog: McpCatalog; runtime: McpRuntime }> {\n patchSse();\n const catalog = new McpCatalog(registry);\n return { catalog, runtime: await attachMcp(catalog, options, context) };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport type { CredentialStore } from '@earendil-works/pi-ai';\nimport type { Registry } from '@earendil-works/pi-durable';\nimport type { ExecutionEnv, FileWatcher } from '@earendil-works/pi-durable/env';\nimport { StreamableHttpTransport } from '@earendil-works/pi-mcp';\nimport type { LickEvent, Licks } from '../licks/licks.ts';\nimport { type McpAccounts, mcpAccounts } from './accounts.ts';\nimport { McpCatalog } from './catalog.ts';\nimport {\n type McpConfigProblem,\n type McpServerEntry,\n parseMcpConfig,\n sameConfig,\n usesOAuth,\n} from './config.ts';\nimport { McpServerConnection } from './connection.ts';\nimport { resolveHeaders, scrub, secretNames } from './credentials.ts';\nimport { authProvider as oauthAuth, StoredOAuth } from './oauth.ts';\nimport { patchSse } from './sse.ts';\n\nexport const MCP_LOG_MAX_BYTES = 5 * 1024 * 1024;\n\nexport function configPath(home: string): string {\n return `${home}/.pi/agent/mcp.json`;\n}\n\nexport function logPath(home: string): string {\n return `${home}/.pi/agent/mcp.log`;\n}\n\nexport interface McpAttach {\n licks: Licks;\n env: ExecutionEnv;\n home: string;\n credentials: CredentialStore;\n fetch: typeof fetch;\n version: string;\n reloadMs: number;\n codemode: boolean;\n cors: boolean;\n redirect: string;\n}\n\nexport interface ServerStatus {\n name: string;\n state: string;\n tools: number;\n error?: string;\n}\n\nexport type { McpAccounts } from './accounts.ts';\n\nexport interface McpRuntime extends McpAccounts {\n status(): ServerStatus[];\n secrets(): string[];\n reload(context: Context): Promise<void>;\n close(context: Context): Promise<void>;\n}\n\nexport function problemLick(problem: McpConfigProblem, home: string): LickEvent {\n const file = `~${configPath(home).slice(home.length)}`;\n const where = problem.server ? `server \"${problem.server}\"` : file;\n const field = problem.field ? `, field ${problem.field}` : '';\n return {\n channel: 'fswatch',\n source: `mcp:${file}`,\n title: `MCP config problem: ${where}${field}`,\n text: problem.message,\n body: `${file}: the entry is skipped until it is fixed. The format is in the slicc-agent README.`,\n target: 'cone',\n severity: 'warn',\n eventId: `${problem.server ?? ''}\\n${problem.field ?? ''}\\n${problem.message}`,\n };\n}\n\nconst CORS_ERROR = /failed to fetch|networkerror|load failed|cors/i;\n\nexport function stateLick(connection: McpServerConnection, cors: boolean): LickEvent | undefined {\n const name = connection.name;\n const base = { channel: 'fswatch' as const, source: `mcp:${name}`, target: 'cone' as const };\n if (connection.state === 'needs-secret')\n return {\n ...base,\n title: `MCP server \"${name}\" needs a token`,\n text: `Its headers use ${connection.missing.map((item) => `\\${${item}}`).join(', ')}. Add the value in Settings \u203A MCP servers.`,\n severity: 'warn',\n eventId: `${name}\\nsecret\\n${connection.missing.join(',')}`,\n };\n if (connection.state === 'needs-auth')\n return {\n ...base,\n title: secretNames(connection.entry.config).length\n ? `MCP server \"${name}\" rejected its token`\n : `MCP server \"${name}\" needs sign-in`,\n text: connection.signInMessage(),\n severity: 'warn',\n eventId: `${name}\\nauth`,\n };\n if (connection.state !== 'failed') return undefined;\n const error = scrub(connection.error ?? 'unknown error', connection.secrets);\n const blocked = cors && CORS_ERROR.test(error);\n return {\n ...base,\n title: `MCP server \"${name}\" can't connect`,\n text: blocked\n ? \"Seven reaches it with the page's own fetch, and the server sends no CORS headers. Link the `slicc` CLI or connect the SLICC extension; either fetches without CORS.\"\n : error,\n severity: 'warn',\n eventId: `${name}\\nfailed\\n${blocked ? 'cors' : error}`,\n };\n}\n\nexport function connectedLick(connection: McpServerConnection): LickEvent {\n const count = connection.tools.length;\n return {\n channel: 'fswatch',\n source: `mcp:${connection.name}`,\n title: `MCP server \"${connection.name}\" connected`,\n text: `${connection.entry.config.url} offers ${count} tool${count === 1 ? '' : 's'}.`,\n target: 'cone',\n eventId: `${connection.name}\\nconnected\\n${connection.entry.config.url}`,\n };\n}\n\nexport function logLine(\n server: string,\n params: unknown,\n secrets: readonly string[],\n at: Date\n): string {\n const { level, logger, data } = (params ?? {}) as {\n level?: string;\n logger?: string;\n data?: unknown;\n };\n const text = scrub(typeof data === 'string' ? data : (JSON.stringify(data) ?? ''), secrets);\n return `${at.toISOString()} [${server}] ${level ?? 'info'}${logger ? ` ${logger}` : ''}: ${text}\\n`;\n}\n\nfunction serverLog(options: McpAttach, context: Context) {\n let logging = Promise.resolve();\n const path = logPath(options.home);\n const { env } = options;\n return {\n write(connection: McpServerConnection, params: unknown) {\n const line = logLine(connection.name, params, connection.secrets, new Date());\n logging = logging.then(async () => {\n const info = await env.fileInfo(path, context);\n if (info.ok && info.value.size > MCP_LOG_MAX_BYTES)\n await env.renameFile(path, `${path}.1`, context);\n await env.appendFile(path, line, context);\n });\n },\n settled: () => logging,\n };\n}\n\nexport async function attachMcp(\n catalog: McpCatalog,\n options: McpAttach,\n context: Context\n): Promise<McpRuntime> {\n const { env, home } = options;\n catalog.codemodeOn = options.codemode;\n const reported = new Set<string>();\n const report = async (lick: LickEvent | undefined, using: Context) => {\n if (!lick?.eventId || reported.has(lick.eventId)) return;\n reported.add(lick.eventId);\n await options.licks.deliver(lick, using).catch(() => undefined);\n };\n const listeners = new Set<() => void>();\n const notify = () => {\n for (const listener of listeners) listener();\n };\n let initial: Set<string> = new Set();\n let first = true;\n const log = serverLog(options, context);\n const fetcher = (input: string | URL, init?: RequestInit) => options.fetch(input, init);\n const oauth = (entry: McpServerEntry) =>\n new StoredOAuth(entry, options.credentials, options.redirect);\n const transport = async (entry: McpServerEntry) => {\n const resolved = await resolveHeaders(entry.name, entry.config, options.credentials);\n if (!resolved.ok) return { missing: resolved.missing };\n const authProvider = usesOAuth(entry.config) ? oauthAuth(oauth(entry), entry) : undefined;\n return {\n transport: new StreamableHttpTransport({\n url: entry.config.url,\n headers: resolved.headers,\n fetch: fetcher,\n ...(authProvider ? { authProvider } : {}),\n }),\n secrets: resolved.secrets,\n };\n };\n const open = (entry: McpServerEntry) => {\n const connection = new McpServerConnection({\n entry,\n version: options.version,\n transport,\n onTools: () => catalog.rebuild(),\n onLog: log.write,\n onChange: (changed) => {\n if (catalog.connections.get(changed.name) !== changed) return;\n if (changed.state === 'connected' && !initial.has(changed.name))\n void report(connectedLick(changed), context);\n void report(stateLick(changed, options.cors), context);\n if (changed.state !== 'connected') catalog.rebuild();\n notify();\n },\n });\n catalog.connections.set(entry.name, connection);\n void connection.getClient().catch(() => undefined);\n };\n const reload = async (using: Context) => {\n const read = await env.readTextFile(configPath(home), using);\n const parsed = parseMcpConfig(read.ok ? read.value : undefined);\n for (const problem of parsed.problems) await report(problemLick(problem, home), using);\n const wanted = new Map(\n parsed.servers\n .filter((entry) => entry.config.enabled !== false)\n .map((entry) => [entry.name, entry])\n );\n const closing: Promise<void>[] = [];\n for (const [name, connection] of [...catalog.connections]) {\n const next = wanted.get(name);\n if (next && sameConfig(next.config, connection.entry.config)) continue;\n catalog.connections.delete(name);\n closing.push(connection.close());\n }\n if (first) initial = new Set(wanted.keys());\n first = false;\n for (const [name, entry] of wanted) if (!catalog.connections.has(name)) open(entry);\n catalog.rebuild();\n notify();\n await Promise.all(closing);\n };\n await reload(context);\n let timer: ReturnType<typeof setTimeout> | undefined;\n const schedule = () => {\n clearTimeout(timer);\n timer = setTimeout(() => void reload(context).catch(() => undefined), options.reloadMs);\n };\n const watched = await env.watch([{ path: configPath(home) }], schedule, context);\n const watcher: FileWatcher | undefined = watched.ok ? watched.value : undefined;\n const restart = (connection: McpServerConnection) => {\n open(connection.entry);\n void connection.close();\n catalog.rebuild();\n };\n return {\n ...mcpAccounts(\n catalog,\n { credentials: options.credentials, oauth, fetch: fetcher },\n restart,\n listeners\n ),\n status: () =>\n [...catalog.connections.values()].map((connection) => ({\n name: connection.name,\n state: connection.state,\n tools: connection.tools.length,\n ...(connection.error ? { error: scrub(connection.error, connection.secrets) } : {}),\n })),\n secrets: () => [...catalog.connections.values()].flatMap((connection) => connection.secrets),\n reload,\n async close(using) {\n clearTimeout(timer);\n await watcher?.close(using);\n const all = [...catalog.connections.values()];\n catalog.connections.clear();\n await Promise.all(all.map((connection) => connection.close()));\n catalog.rebuild();\n await log.settled();\n },\n };\n}\n\nexport async function startMcp(\n registry: Registry,\n options: McpAttach,\n context: Context\n): Promise<{ catalog: McpCatalog; runtime: McpRuntime }> {\n patchSse();\n const catalog = new McpCatalog(registry);\n return { catalog, runtime: await attachMcp(catalog, options, context) };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/dist/net.js CHANGED
@@ -1,4 +1,11 @@
1
1
  import { FACADE_PORT, kernelHost, ownKernelServer } from "./kernel/loopback.js";
2
+ function linkedExit(transport) {
3
+ const exit = transport?.traits?.exit;
4
+ return typeof exit?.kind === "string" ? exit : void 0;
5
+ }
6
+ function pageOnly(transport) {
7
+ return transport?.traits?.crossOrigin === "cors" && !linkedExit(transport);
8
+ }
2
9
  const empty = /* @__PURE__ */ new Set([101, 103, 204, 205, 304]);
3
10
  function kernelPort(url) {
4
11
  const host = new URL(url).hostname.replace(/\.+$/, "");
@@ -27,7 +34,7 @@ function transportFetch(transport, origin, native = globalThis.fetch.bind(global
27
34
  const request = new Request(input, init);
28
35
  const port = kernelPort(request.url);
29
36
  if (loopback && port) return loopback(request, { port, signal: request.signal });
30
- if (new URL(request.url).origin === origin || transport.traits?.crossOrigin === "cors") {
37
+ if (new URL(request.url).origin === origin || transport.traits?.unavailable || request.credentials === "include") {
31
38
  return native(request);
32
39
  }
33
40
  const body = ["GET", "HEAD"].includes(request.method) ? void 0 : new Uint8Array(await request.arrayBuffer());
@@ -47,6 +54,8 @@ function transportFetch(transport, origin, native = globalThis.fetch.bind(global
47
54
  }
48
55
  export {
49
56
  kernelPort,
57
+ linkedExit,
58
+ pageOnly,
50
59
  transportFetch
51
60
  };
52
61
  //# sourceMappingURL=net.js.map
package/dist/net.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/net.ts"],
4
- "sourcesContent": ["import { FACADE_PORT, kernelHost, ownKernelServer } from './kernel/loopback.ts';\n\nexport interface TransportResponse {\n status: number;\n statusText?: string;\n headers: HeadersInit;\n body: AsyncIterable<Uint8Array>;\n}\n\nexport interface Transport {\n traits?: { crossOrigin?: 'cors' | 'any'; manualRedirects?: boolean };\n fetch(request: {\n url: string;\n method: string;\n headers: [string, string][];\n body?: Uint8Array;\n signal?: AbortSignal;\n }): Promise<TransportResponse>;\n}\n\nexport type Loopback = (\n request: Request,\n options: { port: number; signal?: AbortSignal }\n) => Promise<Response>;\n\nconst empty = new Set([101, 103, 204, 205, 304]);\n\nexport function kernelPort(url: string): number | undefined {\n const host = new URL(url).hostname.replace(/\\.+$/, '');\n if (!kernelHost(host)) return undefined;\n if (!ownKernelServer(host))\n throw new TypeError(\n `${host} isn't a kernel server SLICC may reach: use <port>.kernel.localhost with a port from 1 to 65535 other than ${FACADE_PORT}, the CDP facade`\n );\n return Number(host.split('.')[0]);\n}\n\nfunction stream(body: AsyncIterable<Uint8Array>): ReadableStream<Uint8Array> {\n const iterator = body[Symbol.asyncIterator]();\n return new ReadableStream({\n async pull(controller) {\n const next = await iterator.next();\n if (next.done) controller.close();\n else controller.enqueue(next.value);\n },\n async cancel() {\n await iterator.return?.();\n },\n });\n}\n\nexport function transportFetch(\n transport: Transport,\n origin: string,\n native: typeof fetch = globalThis.fetch.bind(globalThis),\n loopback?: Loopback\n): typeof fetch {\n return async (input, init) => {\n const request = new Request(input, init);\n const port = kernelPort(request.url);\n if (loopback && port) return loopback(request, { port, signal: request.signal });\n if (new URL(request.url).origin === origin || transport.traits?.crossOrigin === 'cors') {\n return native(request);\n }\n const body = ['GET', 'HEAD'].includes(request.method)\n ? undefined\n : new Uint8Array(await request.arrayBuffer());\n const response = await transport.fetch({\n url: request.url,\n method: request.method,\n headers: [...request.headers],\n ...(body ? { body } : {}),\n signal: request.signal,\n });\n return new Response(empty.has(response.status) ? null : stream(response.body), {\n status: response.status,\n statusText: response.statusText ?? '',\n headers: response.headers,\n });\n };\n}\n"],
5
- "mappings": "AAAA,SAAS,aAAa,YAAY,uBAAuB;AAyBzD,MAAM,QAAQ,oBAAI,IAAI,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG,CAAC;AAExC,SAAS,WAAW,KAAiC;AAC1D,QAAM,OAAO,IAAI,IAAI,GAAG,EAAE,SAAS,QAAQ,QAAQ,EAAE;AACrD,MAAI,CAAC,WAAW,IAAI,EAAG,QAAO;AAC9B,MAAI,CAAC,gBAAgB,IAAI;AACvB,UAAM,IAAI;AAAA,MACR,GAAG,IAAI,8GAA8G,WAAW;AAAA,IAClI;AACF,SAAO,OAAO,KAAK,MAAM,GAAG,EAAE,CAAC,CAAC;AAClC;AAEA,SAAS,OAAO,MAA6D;AAC3E,QAAM,WAAW,KAAK,OAAO,aAAa,EAAE;AAC5C,SAAO,IAAI,eAAe;AAAA,IACxB,MAAM,KAAK,YAAY;AACrB,YAAM,OAAO,MAAM,SAAS,KAAK;AACjC,UAAI,KAAK,KAAM,YAAW,MAAM;AAAA,UAC3B,YAAW,QAAQ,KAAK,KAAK;AAAA,IACpC;AAAA,IACA,MAAM,SAAS;AACb,YAAM,SAAS,SAAS;AAAA,IAC1B;AAAA,EACF,CAAC;AACH;AAEO,SAAS,eACd,WACA,QACA,SAAuB,WAAW,MAAM,KAAK,UAAU,GACvD,UACc;AACd,SAAO,OAAO,OAAO,SAAS;AAC5B,UAAM,UAAU,IAAI,QAAQ,OAAO,IAAI;AACvC,UAAM,OAAO,WAAW,QAAQ,GAAG;AACnC,QAAI,YAAY,KAAM,QAAO,SAAS,SAAS,EAAE,MAAM,QAAQ,QAAQ,OAAO,CAAC;AAC/E,QAAI,IAAI,IAAI,QAAQ,GAAG,EAAE,WAAW,UAAU,UAAU,QAAQ,gBAAgB,QAAQ;AACtF,aAAO,OAAO,OAAO;AAAA,IACvB;AACA,UAAM,OAAO,CAAC,OAAO,MAAM,EAAE,SAAS,QAAQ,MAAM,IAChD,SACA,IAAI,WAAW,MAAM,QAAQ,YAAY,CAAC;AAC9C,UAAM,WAAW,MAAM,UAAU,MAAM;AAAA,MACrC,KAAK,QAAQ;AAAA,MACb,QAAQ,QAAQ;AAAA,MAChB,SAAS,CAAC,GAAG,QAAQ,OAAO;AAAA,MAC5B,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;AAAA,MACvB,QAAQ,QAAQ;AAAA,IAClB,CAAC;AACD,WAAO,IAAI,SAAS,MAAM,IAAI,SAAS,MAAM,IAAI,OAAO,OAAO,SAAS,IAAI,GAAG;AAAA,MAC7E,QAAQ,SAAS;AAAA,MACjB,YAAY,SAAS,cAAc;AAAA,MACnC,SAAS,SAAS;AAAA,IACpB,CAAC;AAAA,EACH;AACF;",
4
+ "sourcesContent": ["import { FACADE_PORT, kernelHost, ownKernelServer } from './kernel/loopback.ts';\n\nexport interface TransportResponse {\n status: number;\n statusText?: string;\n headers: HeadersInit;\n body: AsyncIterable<Uint8Array>;\n}\n\nexport interface Transport {\n traits?: {\n crossOrigin?: 'cors' | 'any';\n manualRedirects?: boolean;\n unavailable?: boolean;\n exit?: TransportExit;\n };\n fetch(request: {\n url: string;\n method: string;\n headers: [string, string][];\n body?: Uint8Array;\n signal?: AbortSignal;\n }): Promise<TransportResponse>;\n}\n\nexport interface TransportExit {\n kind: string;\n caps: string[];\n}\n\nexport function linkedExit(transport: Transport | undefined): TransportExit | undefined {\n const exit = transport?.traits?.exit;\n return typeof exit?.kind === 'string' ? exit : undefined;\n}\n\nexport function pageOnly(transport: Transport | undefined): boolean {\n return transport?.traits?.crossOrigin === 'cors' && !linkedExit(transport);\n}\n\nexport type Loopback = (\n request: Request,\n options: { port: number; signal?: AbortSignal }\n) => Promise<Response>;\n\nconst empty = new Set([101, 103, 204, 205, 304]);\n\nexport function kernelPort(url: string): number | undefined {\n const host = new URL(url).hostname.replace(/\\.+$/, '');\n if (!kernelHost(host)) return undefined;\n if (!ownKernelServer(host))\n throw new TypeError(\n `${host} isn't a kernel server SLICC may reach: use <port>.kernel.localhost with a port from 1 to 65535 other than ${FACADE_PORT}, the CDP facade`\n );\n return Number(host.split('.')[0]);\n}\n\nfunction stream(body: AsyncIterable<Uint8Array>): ReadableStream<Uint8Array> {\n const iterator = body[Symbol.asyncIterator]();\n return new ReadableStream({\n async pull(controller) {\n const next = await iterator.next();\n if (next.done) controller.close();\n else controller.enqueue(next.value);\n },\n async cancel() {\n await iterator.return?.();\n },\n });\n}\n\nexport function transportFetch(\n transport: Transport,\n origin: string,\n native: typeof fetch = globalThis.fetch.bind(globalThis),\n loopback?: Loopback\n): typeof fetch {\n return async (input, init) => {\n const request = new Request(input, init);\n const port = kernelPort(request.url);\n if (loopback && port) return loopback(request, { port, signal: request.signal });\n if (\n new URL(request.url).origin === origin ||\n transport.traits?.unavailable ||\n request.credentials === 'include'\n ) {\n return native(request);\n }\n const body = ['GET', 'HEAD'].includes(request.method)\n ? undefined\n : new Uint8Array(await request.arrayBuffer());\n const response = await transport.fetch({\n url: request.url,\n method: request.method,\n headers: [...request.headers],\n ...(body ? { body } : {}),\n signal: request.signal,\n });\n return new Response(empty.has(response.status) ? null : stream(response.body), {\n status: response.status,\n statusText: response.statusText ?? '',\n headers: response.headers,\n });\n };\n}\n"],
5
+ "mappings": "AAAA,SAAS,aAAa,YAAY,uBAAuB;AA8BlD,SAAS,WAAW,WAA6D;AACtF,QAAM,OAAO,WAAW,QAAQ;AAChC,SAAO,OAAO,MAAM,SAAS,WAAW,OAAO;AACjD;AAEO,SAAS,SAAS,WAA2C;AAClE,SAAO,WAAW,QAAQ,gBAAgB,UAAU,CAAC,WAAW,SAAS;AAC3E;AAOA,MAAM,QAAQ,oBAAI,IAAI,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG,CAAC;AAExC,SAAS,WAAW,KAAiC;AAC1D,QAAM,OAAO,IAAI,IAAI,GAAG,EAAE,SAAS,QAAQ,QAAQ,EAAE;AACrD,MAAI,CAAC,WAAW,IAAI,EAAG,QAAO;AAC9B,MAAI,CAAC,gBAAgB,IAAI;AACvB,UAAM,IAAI;AAAA,MACR,GAAG,IAAI,8GAA8G,WAAW;AAAA,IAClI;AACF,SAAO,OAAO,KAAK,MAAM,GAAG,EAAE,CAAC,CAAC;AAClC;AAEA,SAAS,OAAO,MAA6D;AAC3E,QAAM,WAAW,KAAK,OAAO,aAAa,EAAE;AAC5C,SAAO,IAAI,eAAe;AAAA,IACxB,MAAM,KAAK,YAAY;AACrB,YAAM,OAAO,MAAM,SAAS,KAAK;AACjC,UAAI,KAAK,KAAM,YAAW,MAAM;AAAA,UAC3B,YAAW,QAAQ,KAAK,KAAK;AAAA,IACpC;AAAA,IACA,MAAM,SAAS;AACb,YAAM,SAAS,SAAS;AAAA,IAC1B;AAAA,EACF,CAAC;AACH;AAEO,SAAS,eACd,WACA,QACA,SAAuB,WAAW,MAAM,KAAK,UAAU,GACvD,UACc;AACd,SAAO,OAAO,OAAO,SAAS;AAC5B,UAAM,UAAU,IAAI,QAAQ,OAAO,IAAI;AACvC,UAAM,OAAO,WAAW,QAAQ,GAAG;AACnC,QAAI,YAAY,KAAM,QAAO,SAAS,SAAS,EAAE,MAAM,QAAQ,QAAQ,OAAO,CAAC;AAC/E,QACE,IAAI,IAAI,QAAQ,GAAG,EAAE,WAAW,UAChC,UAAU,QAAQ,eAClB,QAAQ,gBAAgB,WACxB;AACA,aAAO,OAAO,OAAO;AAAA,IACvB;AACA,UAAM,OAAO,CAAC,OAAO,MAAM,EAAE,SAAS,QAAQ,MAAM,IAChD,SACA,IAAI,WAAW,MAAM,QAAQ,YAAY,CAAC;AAC9C,UAAM,WAAW,MAAM,UAAU,MAAM;AAAA,MACrC,KAAK,QAAQ;AAAA,MACb,QAAQ,QAAQ;AAAA,MAChB,SAAS,CAAC,GAAG,QAAQ,OAAO;AAAA,MAC5B,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;AAAA,MACvB,QAAQ,QAAQ;AAAA,IAClB,CAAC;AACD,WAAO,IAAI,SAAS,MAAM,IAAI,SAAS,MAAM,IAAI,OAAO,OAAO,SAAS,IAAI,GAAG;AAAA,MAC7E,QAAQ,SAAS;AAAA,MACjB,YAAY,SAAS,cAAc;AAAA,MACnC,SAAS,SAAS;AAAA,IACpB,CAAC;AAAA,EACH;AACF;",
6
6
  "names": []
7
7
  }
package/dist/readonly.js CHANGED
@@ -1,6 +1,12 @@
1
1
  import { ServerError } from "@earendil-works/pi-server";
2
2
  import { VIEW_ONLY } from "./view-only.js";
3
- const READS = /* @__PURE__ */ new Set(["wait", "changesOpen", "monitorOpen", "monitorClose"]);
3
+ const READS = /* @__PURE__ */ new Set([
4
+ "wait",
5
+ "changesOpen",
6
+ "monitorOpen",
7
+ "monitorClose",
8
+ "image"
9
+ ]);
4
10
  function refuse() {
5
11
  return Promise.reject(new ServerError("EACCES", VIEW_ONLY));
6
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/readonly.ts"],
4
- "sourcesContent": ["import { ServerError } from '@earendil-works/pi-server';\nimport type { AgentRemote } from './remote/host.ts';\nimport type { AgentPages } from './remote/pages.ts';\nimport type { AgentControl, AgentSettings } from './services.ts';\nimport { VIEW_ONLY } from './view-only.ts';\n\nexport { VIEW_ONLY };\n\nconst READS = new Set<keyof AgentControl>(['wait', 'changesOpen', 'monitorOpen', 'monitorClose']);\n\nfunction refuse(): Promise<never> {\n return Promise.reject(new ServerError('EACCES' as never, VIEW_ONLY));\n}\n\nconst refused = async () => ({\n accepted: false as const,\n submissionId: null,\n error: { code: 'EACCES', message: VIEW_ONLY },\n});\n\nconst ANSWERS: Partial<Record<keyof AgentControl, () => Promise<unknown>>> = {\n send: refused,\n compact: refused,\n compactAgent: refused,\n};\n\nexport function viewControl(control: AgentControl): AgentControl {\n const view: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(control))\n view[key] =\n typeof value !== 'function' || READS.has(key as keyof AgentControl)\n ? value\n : (ANSWERS[key as keyof AgentControl] ?? refuse);\n return view as unknown as AgentControl;\n}\n\nexport function viewSettings(settings: AgentSettings): AgentSettings {\n return { state: settings.state, connect: refuse, disconnect: refuse, signIn: refuse };\n}\n\nexport function viewPages(): AgentPages {\n return {\n open: async () => ({ error: VIEW_ONLY }),\n report: async () => {},\n next: async () => ({ requests: [], done: true }),\n answer: async () => {},\n close: async () => {},\n };\n}\n\nexport function viewRemote(remote: AgentRemote): AgentRemote {\n const mine = new Set<string>();\n return {\n prompt: async () => ({ ok: false, code: 'EACCES', message: VIEW_ONLY }),\n async watch(request, context) {\n const opened = await remote.watch(request, context);\n if (opened.ok) mine.add(opened.stream);\n return opened;\n },\n async next(stream, waitMs, context) {\n if (!mine.has(stream)) return { events: [], done: true };\n const batch = await remote.next(stream, waitMs, context);\n if (batch.done) mine.delete(stream);\n return batch;\n },\n async close(stream, context) {\n if (!mine.delete(stream)) return;\n await remote.close(stream, context);\n },\n abort: refuse,\n };\n}\n"],
5
- "mappings": "AAAA,SAAS,mBAAmB;AAI5B,SAAS,iBAAiB;AAI1B,MAAM,QAAQ,oBAAI,IAAwB,CAAC,QAAQ,eAAe,eAAe,cAAc,CAAC;AAEhG,SAAS,SAAyB;AAChC,SAAO,QAAQ,OAAO,IAAI,YAAY,UAAmB,SAAS,CAAC;AACrE;AAEA,MAAM,UAAU,aAAa;AAAA,EAC3B,UAAU;AAAA,EACV,cAAc;AAAA,EACd,OAAO,EAAE,MAAM,UAAU,SAAS,UAAU;AAC9C;AAEA,MAAM,UAAuE;AAAA,EAC3E,MAAM;AAAA,EACN,SAAS;AAAA,EACT,cAAc;AAChB;AAEO,SAAS,YAAY,SAAqC;AAC/D,QAAM,OAAgC,CAAC;AACvC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO;AAC/C,SAAK,GAAG,IACN,OAAO,UAAU,cAAc,MAAM,IAAI,GAAyB,IAC9D,QACC,QAAQ,GAAyB,KAAK;AAC/C,SAAO;AACT;AAEO,SAAS,aAAa,UAAwC;AACnE,SAAO,EAAE,OAAO,SAAS,OAAO,SAAS,QAAQ,YAAY,QAAQ,QAAQ,OAAO;AACtF;AAEO,SAAS,YAAwB;AACtC,SAAO;AAAA,IACL,MAAM,aAAa,EAAE,OAAO,UAAU;AAAA,IACtC,QAAQ,YAAY;AAAA,IAAC;AAAA,IACrB,MAAM,aAAa,EAAE,UAAU,CAAC,GAAG,MAAM,KAAK;AAAA,IAC9C,QAAQ,YAAY;AAAA,IAAC;AAAA,IACrB,OAAO,YAAY;AAAA,IAAC;AAAA,EACtB;AACF;AAEO,SAAS,WAAW,QAAkC;AAC3D,QAAM,OAAO,oBAAI,IAAY;AAC7B,SAAO;AAAA,IACL,QAAQ,aAAa,EAAE,IAAI,OAAO,MAAM,UAAU,SAAS,UAAU;AAAA,IACrE,MAAM,MAAM,SAAS,SAAS;AAC5B,YAAM,SAAS,MAAM,OAAO,MAAM,SAAS,OAAO;AAClD,UAAI,OAAO,GAAI,MAAK,IAAI,OAAO,MAAM;AACrC,aAAO;AAAA,IACT;AAAA,IACA,MAAM,KAAK,QAAQ,QAAQ,SAAS;AAClC,UAAI,CAAC,KAAK,IAAI,MAAM,EAAG,QAAO,EAAE,QAAQ,CAAC,GAAG,MAAM,KAAK;AACvD,YAAM,QAAQ,MAAM,OAAO,KAAK,QAAQ,QAAQ,OAAO;AACvD,UAAI,MAAM,KAAM,MAAK,OAAO,MAAM;AAClC,aAAO;AAAA,IACT;AAAA,IACA,MAAM,MAAM,QAAQ,SAAS;AAC3B,UAAI,CAAC,KAAK,OAAO,MAAM,EAAG;AAC1B,YAAM,OAAO,MAAM,QAAQ,OAAO;AAAA,IACpC;AAAA,IACA,OAAO;AAAA,EACT;AACF;",
4
+ "sourcesContent": ["import { ServerError } from '@earendil-works/pi-server';\nimport type { AgentRemote } from './remote/host.ts';\nimport type { AgentPages } from './remote/pages.ts';\nimport type { AgentControl, AgentSettings } from './services.ts';\nimport { VIEW_ONLY } from './view-only.ts';\n\nexport { VIEW_ONLY };\n\nconst READS = new Set<keyof AgentControl>([\n 'wait',\n 'changesOpen',\n 'monitorOpen',\n 'monitorClose',\n 'image',\n]);\n\nfunction refuse(): Promise<never> {\n return Promise.reject(new ServerError('EACCES' as never, VIEW_ONLY));\n}\n\nconst refused = async () => ({\n accepted: false as const,\n submissionId: null,\n error: { code: 'EACCES', message: VIEW_ONLY },\n});\n\nconst ANSWERS: Partial<Record<keyof AgentControl, () => Promise<unknown>>> = {\n send: refused,\n compact: refused,\n compactAgent: refused,\n};\n\nexport function viewControl(control: AgentControl): AgentControl {\n const view: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(control))\n view[key] =\n typeof value !== 'function' || READS.has(key as keyof AgentControl)\n ? value\n : (ANSWERS[key as keyof AgentControl] ?? refuse);\n return view as unknown as AgentControl;\n}\n\nexport function viewSettings(settings: AgentSettings): AgentSettings {\n return { state: settings.state, connect: refuse, disconnect: refuse, signIn: refuse };\n}\n\nexport function viewPages(): AgentPages {\n return {\n open: async () => ({ error: VIEW_ONLY }),\n report: async () => {},\n next: async () => ({ requests: [], done: true }),\n answer: async () => {},\n close: async () => {},\n };\n}\n\nexport function viewRemote(remote: AgentRemote): AgentRemote {\n const mine = new Set<string>();\n return {\n prompt: async () => ({ ok: false, code: 'EACCES', message: VIEW_ONLY }),\n async watch(request, context) {\n const opened = await remote.watch(request, context);\n if (opened.ok) mine.add(opened.stream);\n return opened;\n },\n async next(stream, waitMs, context) {\n if (!mine.has(stream)) return { events: [], done: true };\n const batch = await remote.next(stream, waitMs, context);\n if (batch.done) mine.delete(stream);\n return batch;\n },\n async close(stream, context) {\n if (!mine.delete(stream)) return;\n await remote.close(stream, context);\n },\n abort: refuse,\n };\n}\n"],
5
+ "mappings": "AAAA,SAAS,mBAAmB;AAI5B,SAAS,iBAAiB;AAI1B,MAAM,QAAQ,oBAAI,IAAwB;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,SAAS,SAAyB;AAChC,SAAO,QAAQ,OAAO,IAAI,YAAY,UAAmB,SAAS,CAAC;AACrE;AAEA,MAAM,UAAU,aAAa;AAAA,EAC3B,UAAU;AAAA,EACV,cAAc;AAAA,EACd,OAAO,EAAE,MAAM,UAAU,SAAS,UAAU;AAC9C;AAEA,MAAM,UAAuE;AAAA,EAC3E,MAAM;AAAA,EACN,SAAS;AAAA,EACT,cAAc;AAChB;AAEO,SAAS,YAAY,SAAqC;AAC/D,QAAM,OAAgC,CAAC;AACvC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO;AAC/C,SAAK,GAAG,IACN,OAAO,UAAU,cAAc,MAAM,IAAI,GAAyB,IAC9D,QACC,QAAQ,GAAyB,KAAK;AAC/C,SAAO;AACT;AAEO,SAAS,aAAa,UAAwC;AACnE,SAAO,EAAE,OAAO,SAAS,OAAO,SAAS,QAAQ,YAAY,QAAQ,QAAQ,OAAO;AACtF;AAEO,SAAS,YAAwB;AACtC,SAAO;AAAA,IACL,MAAM,aAAa,EAAE,OAAO,UAAU;AAAA,IACtC,QAAQ,YAAY;AAAA,IAAC;AAAA,IACrB,MAAM,aAAa,EAAE,UAAU,CAAC,GAAG,MAAM,KAAK;AAAA,IAC9C,QAAQ,YAAY;AAAA,IAAC;AAAA,IACrB,OAAO,YAAY;AAAA,IAAC;AAAA,EACtB;AACF;AAEO,SAAS,WAAW,QAAkC;AAC3D,QAAM,OAAO,oBAAI,IAAY;AAC7B,SAAO;AAAA,IACL,QAAQ,aAAa,EAAE,IAAI,OAAO,MAAM,UAAU,SAAS,UAAU;AAAA,IACrE,MAAM,MAAM,SAAS,SAAS;AAC5B,YAAM,SAAS,MAAM,OAAO,MAAM,SAAS,OAAO;AAClD,UAAI,OAAO,GAAI,MAAK,IAAI,OAAO,MAAM;AACrC,aAAO;AAAA,IACT;AAAA,IACA,MAAM,KAAK,QAAQ,QAAQ,SAAS;AAClC,UAAI,CAAC,KAAK,IAAI,MAAM,EAAG,QAAO,EAAE,QAAQ,CAAC,GAAG,MAAM,KAAK;AACvD,YAAM,QAAQ,MAAM,OAAO,KAAK,QAAQ,QAAQ,OAAO;AACvD,UAAI,MAAM,KAAM,MAAK,OAAO,MAAM;AAClC,aAAO;AAAA,IACT;AAAA,IACA,MAAM,MAAM,QAAQ,SAAS;AAC3B,UAAI,CAAC,KAAK,OAAO,MAAM,EAAG;AAC1B,YAAM,OAAO,MAAM,QAAQ,OAAO;AAAA,IACpC;AAAA,IACA,OAAO;AAAA,EACT;AACF;",
6
6
  "names": []
7
7
  }
@@ -46,11 +46,16 @@ function remoteControl(connection, options = {}) {
46
46
  return remote;
47
47
  };
48
48
  return {
49
- async prompt({ text, whenBusy = "followUp", thinking = false }) {
49
+ async prompt({ text, whenBusy = "followUp", thinking = false, attachments }) {
50
50
  const service = need();
51
- const result = opened(
52
- await service.prompt({ agentId: agent(), text, whenBusy, thinking }, BACKGROUND_CONTEXT)
53
- );
51
+ const request = {
52
+ agentId: agent(),
53
+ text,
54
+ whenBusy,
55
+ thinking,
56
+ ...attachments?.length ? { attachments } : {}
57
+ };
58
+ const result = opened(await service.prompt(request, BACKGROUND_CONTEXT));
54
59
  return {
55
60
  delivered: result.delivered,
56
61
  events: events(service, result.stream)
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/remote/client.ts"],
4
- "sourcesContent": ["import { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { AgentConnection } from '../client.ts';\nimport type { Delivered, SendMode } from '../services.ts';\nimport type { RemoteEvent } from './events.ts';\nimport type { AgentRemote, RemoteOpened } from './host.ts';\n\nexport type { RemoteEvent } from './events.ts';\n\nexport const POLL_MS = 25000;\n\nexport interface RemoteOptions {\n agent?: () => string | null;\n}\n\nexport interface RemoteControl {\n prompt(request: {\n text: string;\n whenBusy?: SendMode;\n thinking?: boolean;\n }): Promise<{ delivered: Delivered; events: AsyncIterable<RemoteEvent> }>;\n watch(request?: {\n since?: string | null;\n thinking?: boolean;\n }): Promise<{ head: string; gap?: true; events: AsyncIterable<RemoteEvent> }>;\n abort(): Promise<{ aborted: boolean }>;\n}\n\nfunction failure(code: string, message: string): Error {\n return Object.assign(new Error(`${code}: ${message}`), { code });\n}\n\nfunction opened(result: RemoteOpened): Extract<RemoteOpened, { ok: true }> {\n if (!result.ok) throw failure(result.code, result.message);\n return result;\n}\n\nfunction events(remote: AgentRemote, stream: string): AsyncIterable<RemoteEvent> {\n return {\n [Symbol.asyncIterator]() {\n let buffer: RemoteEvent[] = [];\n let done = false;\n let closed = false;\n const close = async () => {\n if (closed) return;\n closed = true;\n await remote.close(stream, BACKGROUND_CONTEXT).catch(() => undefined);\n };\n return {\n async next(): Promise<IteratorResult<RemoteEvent>> {\n while (!buffer.length && !done && !closed) {\n const batch = await remote.next(stream, POLL_MS, BACKGROUND_CONTEXT);\n buffer = batch.events;\n done = batch.done;\n }\n const value = buffer.shift();\n if (value) return { value, done: false };\n await close();\n return { value: undefined, done: true };\n },\n async return(): Promise<IteratorResult<RemoteEvent>> {\n await close();\n return { value: undefined, done: true };\n },\n };\n },\n };\n}\n\nexport function remoteControl(\n connection: Pick<AgentConnection, 'remote'>,\n options: RemoteOptions = {}\n): RemoteControl {\n const remote = connection.remote;\n const agent = () => options.agent?.() ?? null;\n const need = (): AgentRemote => {\n if (!remote) throw failure('ENOSYS', 'this agent has no remote control');\n return remote;\n };\n return {\n async prompt({ text, whenBusy = 'followUp', thinking = false }) {\n const service = need();\n const result = opened(\n await service.prompt({ agentId: agent(), text, whenBusy, thinking }, BACKGROUND_CONTEXT)\n );\n return {\n delivered: result.delivered as Delivered,\n events: events(service, result.stream),\n };\n },\n async watch({ since = null, thinking = false } = {}) {\n const service = need();\n const result = opened(\n await service.watch({ agentId: agent(), since, thinking }, BACKGROUND_CONTEXT)\n );\n return {\n head: result.head ?? '',\n ...(result.gap ? { gap: true as const } : {}),\n events: events(service, result.stream),\n };\n },\n async abort() {\n return need().abort(agent(), BACKGROUND_CONTEXT);\n },\n };\n}\n"],
5
- "mappings": "AAAA,SAAS,0BAA0B;AAQ5B,MAAM,UAAU;AAmBvB,SAAS,QAAQ,MAAc,SAAwB;AACrD,SAAO,OAAO,OAAO,IAAI,MAAM,GAAG,IAAI,KAAK,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC;AACjE;AAEA,SAAS,OAAO,QAA2D;AACzE,MAAI,CAAC,OAAO,GAAI,OAAM,QAAQ,OAAO,MAAM,OAAO,OAAO;AACzD,SAAO;AACT;AAEA,SAAS,OAAO,QAAqB,QAA4C;AAC/E,SAAO;AAAA,IACL,CAAC,OAAO,aAAa,IAAI;AACvB,UAAI,SAAwB,CAAC;AAC7B,UAAI,OAAO;AACX,UAAI,SAAS;AACb,YAAM,QAAQ,YAAY;AACxB,YAAI,OAAQ;AACZ,iBAAS;AACT,cAAM,OAAO,MAAM,QAAQ,kBAAkB,EAAE,MAAM,MAAM,MAAS;AAAA,MACtE;AACA,aAAO;AAAA,QACL,MAAM,OAA6C;AACjD,iBAAO,CAAC,OAAO,UAAU,CAAC,QAAQ,CAAC,QAAQ;AACzC,kBAAM,QAAQ,MAAM,OAAO,KAAK,QAAQ,SAAS,kBAAkB;AACnE,qBAAS,MAAM;AACf,mBAAO,MAAM;AAAA,UACf;AACA,gBAAM,QAAQ,OAAO,MAAM;AAC3B,cAAI,MAAO,QAAO,EAAE,OAAO,MAAM,MAAM;AACvC,gBAAM,MAAM;AACZ,iBAAO,EAAE,OAAO,QAAW,MAAM,KAAK;AAAA,QACxC;AAAA,QACA,MAAM,SAA+C;AACnD,gBAAM,MAAM;AACZ,iBAAO,EAAE,OAAO,QAAW,MAAM,KAAK;AAAA,QACxC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEO,SAAS,cACd,YACA,UAAyB,CAAC,GACX;AACf,QAAM,SAAS,WAAW;AAC1B,QAAM,QAAQ,MAAM,QAAQ,QAAQ,KAAK;AACzC,QAAM,OAAO,MAAmB;AAC9B,QAAI,CAAC,OAAQ,OAAM,QAAQ,UAAU,kCAAkC;AACvE,WAAO;AAAA,EACT;AACA,SAAO;AAAA,IACL,MAAM,OAAO,EAAE,MAAM,WAAW,YAAY,WAAW,MAAM,GAAG;AAC9D,YAAM,UAAU,KAAK;AACrB,YAAM,SAAS;AAAA,QACb,MAAM,QAAQ,OAAO,EAAE,SAAS,MAAM,GAAG,MAAM,UAAU,SAAS,GAAG,kBAAkB;AAAA,MACzF;AACA,aAAO;AAAA,QACL,WAAW,OAAO;AAAA,QAClB,QAAQ,OAAO,SAAS,OAAO,MAAM;AAAA,MACvC;AAAA,IACF;AAAA,IACA,MAAM,MAAM,EAAE,QAAQ,MAAM,WAAW,MAAM,IAAI,CAAC,GAAG;AACnD,YAAM,UAAU,KAAK;AACrB,YAAM,SAAS;AAAA,QACb,MAAM,QAAQ,MAAM,EAAE,SAAS,MAAM,GAAG,OAAO,SAAS,GAAG,kBAAkB;AAAA,MAC/E;AACA,aAAO;AAAA,QACL,MAAM,OAAO,QAAQ;AAAA,QACrB,GAAI,OAAO,MAAM,EAAE,KAAK,KAAc,IAAI,CAAC;AAAA,QAC3C,QAAQ,OAAO,SAAS,OAAO,MAAM;AAAA,MACvC;AAAA,IACF;AAAA,IACA,MAAM,QAAQ;AACZ,aAAO,KAAK,EAAE,MAAM,MAAM,GAAG,kBAAkB;AAAA,IACjD;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { SendAttachment } from '../attachments.ts';\nimport type { AgentConnection } from '../client.ts';\nimport type { Delivered, SendMode } from '../services.ts';\nimport type { RemoteEvent } from './events.ts';\nimport type { AgentRemote, RemoteOpened } from './host.ts';\n\nexport type { RemoteEvent } from './events.ts';\n\nexport const POLL_MS = 25000;\n\nexport interface RemoteOptions {\n agent?: () => string | null;\n}\n\nexport interface RemoteControl {\n prompt(request: {\n text: string;\n whenBusy?: SendMode;\n thinking?: boolean;\n attachments?: SendAttachment[];\n }): Promise<{ delivered: Delivered; events: AsyncIterable<RemoteEvent> }>;\n watch(request?: {\n since?: string | null;\n thinking?: boolean;\n }): Promise<{ head: string; gap?: true; events: AsyncIterable<RemoteEvent> }>;\n abort(): Promise<{ aborted: boolean }>;\n}\n\nfunction failure(code: string, message: string): Error {\n return Object.assign(new Error(`${code}: ${message}`), { code });\n}\n\nfunction opened(result: RemoteOpened): Extract<RemoteOpened, { ok: true }> {\n if (!result.ok) throw failure(result.code, result.message);\n return result;\n}\n\nfunction events(remote: AgentRemote, stream: string): AsyncIterable<RemoteEvent> {\n return {\n [Symbol.asyncIterator]() {\n let buffer: RemoteEvent[] = [];\n let done = false;\n let closed = false;\n const close = async () => {\n if (closed) return;\n closed = true;\n await remote.close(stream, BACKGROUND_CONTEXT).catch(() => undefined);\n };\n return {\n async next(): Promise<IteratorResult<RemoteEvent>> {\n while (!buffer.length && !done && !closed) {\n const batch = await remote.next(stream, POLL_MS, BACKGROUND_CONTEXT);\n buffer = batch.events;\n done = batch.done;\n }\n const value = buffer.shift();\n if (value) return { value, done: false };\n await close();\n return { value: undefined, done: true };\n },\n async return(): Promise<IteratorResult<RemoteEvent>> {\n await close();\n return { value: undefined, done: true };\n },\n };\n },\n };\n}\n\nexport function remoteControl(\n connection: Pick<AgentConnection, 'remote'>,\n options: RemoteOptions = {}\n): RemoteControl {\n const remote = connection.remote;\n const agent = () => options.agent?.() ?? null;\n const need = (): AgentRemote => {\n if (!remote) throw failure('ENOSYS', 'this agent has no remote control');\n return remote;\n };\n return {\n async prompt({ text, whenBusy = 'followUp', thinking = false, attachments }) {\n const service = need();\n const request = {\n agentId: agent(),\n text,\n whenBusy,\n thinking,\n ...(attachments?.length ? { attachments } : {}),\n };\n const result = opened(await service.prompt(request, BACKGROUND_CONTEXT));\n return {\n delivered: result.delivered as Delivered,\n events: events(service, result.stream),\n };\n },\n async watch({ since = null, thinking = false } = {}) {\n const service = need();\n const result = opened(\n await service.watch({ agentId: agent(), since, thinking }, BACKGROUND_CONTEXT)\n );\n return {\n head: result.head ?? '',\n ...(result.gap ? { gap: true as const } : {}),\n events: events(service, result.stream),\n };\n },\n async abort() {\n return need().abort(agent(), BACKGROUND_CONTEXT);\n },\n };\n}\n"],
5
+ "mappings": "AAAA,SAAS,0BAA0B;AAS5B,MAAM,UAAU;AAoBvB,SAAS,QAAQ,MAAc,SAAwB;AACrD,SAAO,OAAO,OAAO,IAAI,MAAM,GAAG,IAAI,KAAK,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC;AACjE;AAEA,SAAS,OAAO,QAA2D;AACzE,MAAI,CAAC,OAAO,GAAI,OAAM,QAAQ,OAAO,MAAM,OAAO,OAAO;AACzD,SAAO;AACT;AAEA,SAAS,OAAO,QAAqB,QAA4C;AAC/E,SAAO;AAAA,IACL,CAAC,OAAO,aAAa,IAAI;AACvB,UAAI,SAAwB,CAAC;AAC7B,UAAI,OAAO;AACX,UAAI,SAAS;AACb,YAAM,QAAQ,YAAY;AACxB,YAAI,OAAQ;AACZ,iBAAS;AACT,cAAM,OAAO,MAAM,QAAQ,kBAAkB,EAAE,MAAM,MAAM,MAAS;AAAA,MACtE;AACA,aAAO;AAAA,QACL,MAAM,OAA6C;AACjD,iBAAO,CAAC,OAAO,UAAU,CAAC,QAAQ,CAAC,QAAQ;AACzC,kBAAM,QAAQ,MAAM,OAAO,KAAK,QAAQ,SAAS,kBAAkB;AACnE,qBAAS,MAAM;AACf,mBAAO,MAAM;AAAA,UACf;AACA,gBAAM,QAAQ,OAAO,MAAM;AAC3B,cAAI,MAAO,QAAO,EAAE,OAAO,MAAM,MAAM;AACvC,gBAAM,MAAM;AACZ,iBAAO,EAAE,OAAO,QAAW,MAAM,KAAK;AAAA,QACxC;AAAA,QACA,MAAM,SAA+C;AACnD,gBAAM,MAAM;AACZ,iBAAO,EAAE,OAAO,QAAW,MAAM,KAAK;AAAA,QACxC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEO,SAAS,cACd,YACA,UAAyB,CAAC,GACX;AACf,QAAM,SAAS,WAAW;AAC1B,QAAM,QAAQ,MAAM,QAAQ,QAAQ,KAAK;AACzC,QAAM,OAAO,MAAmB;AAC9B,QAAI,CAAC,OAAQ,OAAM,QAAQ,UAAU,kCAAkC;AACvE,WAAO;AAAA,EACT;AACA,SAAO;AAAA,IACL,MAAM,OAAO,EAAE,MAAM,WAAW,YAAY,WAAW,OAAO,YAAY,GAAG;AAC3E,YAAM,UAAU,KAAK;AACrB,YAAM,UAAU;AAAA,QACd,SAAS,MAAM;AAAA,QACf;AAAA,QACA;AAAA,QACA;AAAA,QACA,GAAI,aAAa,SAAS,EAAE,YAAY,IAAI,CAAC;AAAA,MAC/C;AACA,YAAM,SAAS,OAAO,MAAM,QAAQ,OAAO,SAAS,kBAAkB,CAAC;AACvE,aAAO;AAAA,QACL,WAAW,OAAO;AAAA,QAClB,QAAQ,OAAO,SAAS,OAAO,MAAM;AAAA,MACvC;AAAA,IACF;AAAA,IACA,MAAM,MAAM,EAAE,QAAQ,MAAM,WAAW,MAAM,IAAI,CAAC,GAAG;AACnD,YAAM,UAAU,KAAK;AACrB,YAAM,SAAS;AAAA,QACb,MAAM,QAAQ,MAAM,EAAE,SAAS,MAAM,GAAG,OAAO,SAAS,GAAG,kBAAkB;AAAA,MAC/E;AACA,aAAO;AAAA,QACL,MAAM,OAAO,QAAQ;AAAA,QACrB,GAAI,OAAO,MAAM,EAAE,KAAK,KAAc,IAAI,CAAC;AAAA,QAC3C,QAAQ,OAAO,SAAS,OAAO,MAAM;AAAA,MACvC;AAAA,IACF;AAAA,IACA,MAAM,QAAQ;AACZ,aAAO,KAAK,EAAE,MAAM,MAAM,GAAG,kBAAkB;AAAA,IACjD;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -199,6 +199,7 @@ function createRemote(host) {
199
199
  text: request.text,
200
200
  whenBusy: request.whenBusy,
201
201
  requestId: null,
202
+ ...request.attachments?.length ? { attachments: request.attachments } : {},
202
203
  ...request.agentId ? { agentId: request.agentId } : {}
203
204
  },
204
205
  context
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/remote/host.ts"],
4
- "sourcesContent": ["import type { Context, ReplicatedState } from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT, withCancel } from '@earendil-works/chord/context';\nimport {\n type AgentEvent,\n type AgentEventStream,\n type Conversation,\n type EntryRecord,\n type Harness,\n type SnapshotEvent,\n type SubmissionId,\n watchEvents,\n} from '@earendil-works/pi-durable';\nimport { holdText, type VaultState } from '../secrets/vault.ts';\nimport type { Delivered, SendMode, SendRequest, SendResponse } from '../services.ts';\nimport { type EndReason, idOf, Mapper, type RemoteEvent, replayOf } from './events.ts';\n\nexport const VAULT_BACK = 'the vault is answering again';\n\nexport function vaultNotices(\n vault: ReplicatedState<VaultState> | undefined,\n push: (events: RemoteEvent[]) => void\n): () => void {\n if (!vault) return () => undefined;\n let shown = vault.value ? holdText(vault.value) : undefined;\n let serial = 0;\n const notice = (text: string) => push([{ t: 'notice', id: `vault-${++serial}`, text }]);\n if (shown) notice(shown);\n return vault.subscribe((value) => {\n const next = holdText(value);\n if (next === shown) return;\n shown = next;\n notice(next ?? VAULT_BACK);\n });\n}\n\nexport const MAX_QUEUED = 10000;\nexport const IDLE_MS = 120000;\n\nexport type RemoteOpened =\n | { ok: true; stream: string; delivered: Delivered | null; head: string | null; gap: boolean }\n | { ok: false; code: string; message: string };\n\nexport interface RemoteBatch {\n events: RemoteEvent[];\n done: boolean;\n}\n\nexport interface AgentRemote {\n prompt(\n request: { agentId: string | null; text: string; whenBusy: SendMode; thinking: boolean },\n context: Context\n ): Promise<RemoteOpened>;\n watch(\n request: { agentId: string | null; since: string | null; thinking: boolean },\n context: Context\n ): Promise<RemoteOpened>;\n next(stream: string, waitMs: number, context: Context): Promise<RemoteBatch>;\n close(stream: string, context: Context): Promise<void>;\n abort(agentId: string | null, context: Context): Promise<{ aborted: boolean }>;\n}\n\nexport interface RemoteHost {\n harness: Harness;\n conversation(agentId: string | null, context: Context): Promise<Conversation>;\n send(request: SendRequest, context: Context): Promise<SendResponse>;\n vault?: ReplicatedState<VaultState>;\n now?: () => number;\n limit?: number;\n sweepMs?: number;\n}\n\nexport class Stream<T = RemoteEvent> {\n readonly queue: T[] = [];\n done = false;\n touched: number;\n #wake: (() => void) | undefined;\n readonly #stop: () => Promise<unknown>;\n readonly #limit: number;\n readonly #overflow: readonly T[];\n\n constructor(stop: () => Promise<unknown>, now: number, limit: number, overflow: readonly T[]) {\n this.#stop = stop;\n this.touched = now;\n this.#limit = limit;\n this.#overflow = overflow;\n }\n\n push(events: readonly T[]): void {\n if (this.done || !events.length) return;\n this.queue.push(...events);\n if (this.queue.length > this.#limit) {\n this.queue.length = 0;\n this.queue.push(...this.#overflow);\n void this.finish();\n }\n this.#wake?.();\n }\n\n async finish(): Promise<void> {\n if (this.done) return;\n this.done = true;\n this.#wake?.();\n await this.#stop().catch(() => undefined);\n }\n\n wait(ms: number, signal?: AbortSignal): Promise<void> {\n if (this.queue.length || this.done) return Promise.resolve();\n return new Promise((resolve) => {\n const timer = setTimeout(done, ms);\n signal?.addEventListener('abort', done, { once: true });\n function done() {\n clearTimeout(timer);\n signal?.removeEventListener('abort', done);\n resolve();\n }\n this.#wake = () => {\n this.#wake = undefined;\n done();\n };\n });\n }\n}\n\nfunction deliveredOf(\n status: { status: string } | undefined,\n whenBusy: SendMode,\n busy: boolean\n): Delivered {\n if (status?.status === 'queued') return whenBusy === 'followUp' ? 'follow-up' : 'steer';\n return busy ? 'steer' : 'run';\n}\n\nfunction headOf(events: readonly RemoteEvent[]): string | null {\n for (let at = events.length - 1; at >= 0; at--) {\n const id = idOf(events[at] as RemoteEvent);\n if (id) return id;\n }\n return null;\n}\n\nexport function followed(mapper: Mapper, event: AgentEvent): RemoteEvent[] {\n return event.type === 'snapshot' ? resync(mapper, event) : mapper.event(event);\n}\n\nexport function resync(mapper: Mapper, snapshot: SnapshotEvent): RemoteEvent[] {\n const last = mapper.last();\n return [\n ...snapshot.entries.filter((entry) => entry.id > last).flatMap((entry) => mapper.entry(entry)),\n ...mapper.partial(snapshot.generation?.message as never),\n ];\n}\n\nfunction placedEntry(value: unknown): number | undefined {\n const entry = (value as { entry?: unknown } | undefined)?.entry;\n return typeof entry === 'number' ? entry : undefined;\n}\n\nexport class Follower {\n readonly #mapper: Mapper;\n readonly #entries: Map<number, EntryRecord>;\n readonly #submission: SubmissionId;\n readonly #delivered: Delivered;\n readonly #out: RemoteEvent[] = [];\n #user: number | undefined;\n #started = false;\n\n constructor(\n mapper: Mapper,\n entries: readonly EntryRecord[],\n submission: SubmissionId,\n delivered: Delivered,\n record: unknown\n ) {\n this.#mapper = mapper;\n this.#entries = new Map(entries.map((entry) => [entry.id, entry]));\n this.#submission = submission;\n this.#delivered = delivered;\n this.#user = placedEntry(record);\n }\n\n drain(): RemoteEvent[] {\n return this.#out.splice(0);\n }\n\n #begin(): void {\n const entry = this.#user === undefined ? undefined : this.#entries.get(this.#user);\n if (!entry) return;\n this.#started = true;\n this.#out.push(...this.#mapper.entry(entry, this.#delivered));\n }\n\n #end(reason: EndReason, message?: string): true {\n this.#out.push({ t: 'end', reason, ...(message ? { message } : {}) });\n return true;\n }\n\n #ours(event: AgentEvent): boolean {\n if (event.type !== 'submission' || event.record.id !== this.#submission) return false;\n this.#user = placedEntry(event.record) ?? this.#user;\n return this.#user === undefined && !['queued', 'placed'].includes(event.record.status);\n }\n\n take(batch: readonly AgentEvent[]): boolean {\n for (const event of batch) {\n if (event.type === 'message_end') this.#entries.set(event.entry.id, event.entry);\n if (this.#ours(event)) return this.#end('aborted');\n if (!this.#started) {\n this.#begin();\n continue;\n }\n const mapped = this.#mapper.event(event).filter((item) => item.t !== 'user');\n this.#out.push(...mapped);\n if (mapped.some((item) => item.t === 'run' && item.state === 'end'))\n return this.#end(this.#mapper.reason, this.#mapper.error);\n }\n return false;\n }\n}\n\nexport function createRemote(host: RemoteHost): AgentRemote & { dispose(): Promise<void> } {\n const now = host.now ?? Date.now;\n const streams = new Map<string, Stream>();\n let serial = 0;\n let timer: ReturnType<typeof setInterval> | undefined;\n const sweep = () => {\n for (const [id, stream] of streams)\n if (now() - stream.touched > IDLE_MS) {\n streams.delete(id);\n void stream.finish();\n }\n if (!streams.size && timer !== undefined) {\n clearInterval(timer);\n timer = undefined;\n }\n };\n const track = (id: string, stream: Stream) => {\n streams.set(id, stream);\n timer ??= setInterval(sweep, host.sweepMs ?? IDLE_MS / 4);\n };\n const watching = async (agentId: string | null, context: Context) => {\n const conversation = await host.conversation(agentId, context);\n const cancel = withCancel(BACKGROUND_CONTEXT);\n const events: AgentEventStream = await watchEvents(\n host.harness,\n conversation.id,\n cancel.context\n );\n const open = (onEvents: (batch: readonly AgentEvent[]) => void) => {\n let off = () => undefined as void;\n const stream = new Stream<RemoteEvent>(\n async () => {\n off();\n await events.stop();\n cancel.cancel();\n },\n now(),\n host.limit ?? MAX_QUEUED,\n [{ t: 'end', reason: 'error', message: 'the stream fell too far behind' }]\n );\n const id = `s${++serial}-${crypto.randomUUID()}`;\n track(id, stream);\n off = vaultNotices(host.vault, (notices) => stream.push(notices));\n events.start(async (batch) => onEvents(batch));\n return { id, stream };\n };\n return { conversation, snapshot: events.snapshot, open, stop: () => events.stop() };\n };\n return {\n async prompt(request, context) {\n sweep();\n const watched = await watching(request.agentId, context);\n const busy = Boolean(watched.snapshot.run);\n const response = await host.send(\n {\n text: request.text,\n whenBusy: request.whenBusy,\n requestId: null,\n ...(request.agentId ? { agentId: request.agentId } : {}),\n },\n context\n );\n if (!response.accepted) {\n await watched.stop();\n return {\n ok: false,\n code: response.error.code === 'busy' ? 'EBUSY' : 'EIO',\n message: response.error.message,\n };\n }\n const submission = Number(response.submissionId) as SubmissionId;\n const record = await (await host.harness.submission(submission, context))?.status(context);\n const delivered = deliveredOf(record, request.whenBusy, busy);\n const follower = new Follower(\n new Mapper(request.thinking),\n watched.snapshot.entries,\n submission,\n delivered,\n record\n );\n const { id, stream } = watched.open((batch) => {\n const ended = follower.take(batch);\n stream.push(follower.drain());\n if (ended) void stream.finish();\n });\n return { ok: true, stream: id, delivered, head: null, gap: false };\n },\n async watch(request, context) {\n sweep();\n const watched = await watching(request.agentId, context);\n const partial = watched.snapshot.generation?.message as never;\n const all = replayOf(watched.snapshot.entries, partial, request.thinking);\n const head = headOf(all);\n const at = request.since === null ? -1 : all.findIndex((e) => idOf(e) === request.since);\n const gap = request.since !== null && at < 0;\n const mapper = new Mapper(request.thinking);\n for (const entry of watched.snapshot.entries) mapper.entry(entry);\n mapper.partial(partial);\n const { id, stream } = watched.open((batch) =>\n stream.push(batch.flatMap((event) => followed(mapper, event)))\n );\n if (at >= 0) {\n const own = all[at] as RemoteEvent;\n const sinceId = idOf(own);\n let last = at;\n while (last + 1 < all.length && idOf(all[last + 1] as RemoteEvent) === sinceId) last++;\n const replay = own.t === 'text' || own.t === 'thinking' ? [own] : [];\n stream.push([...replay, ...all.slice(last + 1)]);\n }\n return { ok: true, stream: id, delivered: null, head, gap };\n },\n async next(id, waitMs, context) {\n const stream = streams.get(id);\n if (!stream) return { events: [], done: true };\n stream.touched = now();\n await stream.wait(Math.max(0, Math.min(waitMs, 30000)));\n context.abortSignal?.throwIfAborted();\n const events = stream.queue.splice(0);\n const done = stream.done && stream.queue.length === 0;\n if (done) streams.delete(id);\n return { events, done };\n },\n async close(id) {\n const stream = streams.get(id);\n streams.delete(id);\n await stream?.finish();\n },\n async abort(agentId, context) {\n const conversation = await host.conversation(agentId, context);\n const watched = await watchEvents(host.harness, conversation.id, context);\n const busy = Boolean(watched.snapshot.run);\n await watched.stop();\n if (busy) await conversation.abort(context);\n return { aborted: busy };\n },\n async dispose() {\n clearInterval(timer);\n timer = undefined;\n const all = [...streams.values()];\n streams.clear();\n await Promise.all(all.map((stream) => stream.finish()));\n },\n };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { Context, ReplicatedState } from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT, withCancel } from '@earendil-works/chord/context';\nimport {\n type AgentEvent,\n type AgentEventStream,\n type Conversation,\n type EntryRecord,\n type Harness,\n type SnapshotEvent,\n type SubmissionId,\n watchEvents,\n} from '@earendil-works/pi-durable';\nimport type { SendAttachment } from '../attachments.ts';\nimport { holdText, type VaultState } from '../secrets/vault.ts';\nimport type { Delivered, SendMode, SendRequest, SendResponse } from '../services.ts';\nimport { type EndReason, idOf, Mapper, type RemoteEvent, replayOf } from './events.ts';\n\nexport const VAULT_BACK = 'the vault is answering again';\n\nexport function vaultNotices(\n vault: ReplicatedState<VaultState> | undefined,\n push: (events: RemoteEvent[]) => void\n): () => void {\n if (!vault) return () => undefined;\n let shown = vault.value ? holdText(vault.value) : undefined;\n let serial = 0;\n const notice = (text: string) => push([{ t: 'notice', id: `vault-${++serial}`, text }]);\n if (shown) notice(shown);\n return vault.subscribe((value) => {\n const next = holdText(value);\n if (next === shown) return;\n shown = next;\n notice(next ?? VAULT_BACK);\n });\n}\n\nexport const MAX_QUEUED = 10000;\nexport const IDLE_MS = 120000;\n\nexport type RemoteOpened =\n | { ok: true; stream: string; delivered: Delivered | null; head: string | null; gap: boolean }\n | { ok: false; code: string; message: string };\n\nexport interface RemoteBatch {\n events: RemoteEvent[];\n done: boolean;\n}\n\nexport interface AgentRemote {\n prompt(\n request: {\n agentId: string | null;\n text: string;\n whenBusy: SendMode;\n thinking: boolean;\n attachments?: SendAttachment[];\n },\n context: Context\n ): Promise<RemoteOpened>;\n watch(\n request: { agentId: string | null; since: string | null; thinking: boolean },\n context: Context\n ): Promise<RemoteOpened>;\n next(stream: string, waitMs: number, context: Context): Promise<RemoteBatch>;\n close(stream: string, context: Context): Promise<void>;\n abort(agentId: string | null, context: Context): Promise<{ aborted: boolean }>;\n}\n\nexport interface RemoteHost {\n harness: Harness;\n conversation(agentId: string | null, context: Context): Promise<Conversation>;\n send(request: SendRequest, context: Context): Promise<SendResponse>;\n vault?: ReplicatedState<VaultState>;\n now?: () => number;\n limit?: number;\n sweepMs?: number;\n}\n\nexport class Stream<T = RemoteEvent> {\n readonly queue: T[] = [];\n done = false;\n touched: number;\n #wake: (() => void) | undefined;\n readonly #stop: () => Promise<unknown>;\n readonly #limit: number;\n readonly #overflow: readonly T[];\n\n constructor(stop: () => Promise<unknown>, now: number, limit: number, overflow: readonly T[]) {\n this.#stop = stop;\n this.touched = now;\n this.#limit = limit;\n this.#overflow = overflow;\n }\n\n push(events: readonly T[]): void {\n if (this.done || !events.length) return;\n this.queue.push(...events);\n if (this.queue.length > this.#limit) {\n this.queue.length = 0;\n this.queue.push(...this.#overflow);\n void this.finish();\n }\n this.#wake?.();\n }\n\n async finish(): Promise<void> {\n if (this.done) return;\n this.done = true;\n this.#wake?.();\n await this.#stop().catch(() => undefined);\n }\n\n wait(ms: number, signal?: AbortSignal): Promise<void> {\n if (this.queue.length || this.done) return Promise.resolve();\n return new Promise((resolve) => {\n const timer = setTimeout(done, ms);\n signal?.addEventListener('abort', done, { once: true });\n function done() {\n clearTimeout(timer);\n signal?.removeEventListener('abort', done);\n resolve();\n }\n this.#wake = () => {\n this.#wake = undefined;\n done();\n };\n });\n }\n}\n\nfunction deliveredOf(\n status: { status: string } | undefined,\n whenBusy: SendMode,\n busy: boolean\n): Delivered {\n if (status?.status === 'queued') return whenBusy === 'followUp' ? 'follow-up' : 'steer';\n return busy ? 'steer' : 'run';\n}\n\nfunction headOf(events: readonly RemoteEvent[]): string | null {\n for (let at = events.length - 1; at >= 0; at--) {\n const id = idOf(events[at] as RemoteEvent);\n if (id) return id;\n }\n return null;\n}\n\nexport function followed(mapper: Mapper, event: AgentEvent): RemoteEvent[] {\n return event.type === 'snapshot' ? resync(mapper, event) : mapper.event(event);\n}\n\nexport function resync(mapper: Mapper, snapshot: SnapshotEvent): RemoteEvent[] {\n const last = mapper.last();\n return [\n ...snapshot.entries.filter((entry) => entry.id > last).flatMap((entry) => mapper.entry(entry)),\n ...mapper.partial(snapshot.generation?.message as never),\n ];\n}\n\nfunction placedEntry(value: unknown): number | undefined {\n const entry = (value as { entry?: unknown } | undefined)?.entry;\n return typeof entry === 'number' ? entry : undefined;\n}\n\nexport class Follower {\n readonly #mapper: Mapper;\n readonly #entries: Map<number, EntryRecord>;\n readonly #submission: SubmissionId;\n readonly #delivered: Delivered;\n readonly #out: RemoteEvent[] = [];\n #user: number | undefined;\n #started = false;\n\n constructor(\n mapper: Mapper,\n entries: readonly EntryRecord[],\n submission: SubmissionId,\n delivered: Delivered,\n record: unknown\n ) {\n this.#mapper = mapper;\n this.#entries = new Map(entries.map((entry) => [entry.id, entry]));\n this.#submission = submission;\n this.#delivered = delivered;\n this.#user = placedEntry(record);\n }\n\n drain(): RemoteEvent[] {\n return this.#out.splice(0);\n }\n\n #begin(): void {\n const entry = this.#user === undefined ? undefined : this.#entries.get(this.#user);\n if (!entry) return;\n this.#started = true;\n this.#out.push(...this.#mapper.entry(entry, this.#delivered));\n }\n\n #end(reason: EndReason, message?: string): true {\n this.#out.push({ t: 'end', reason, ...(message ? { message } : {}) });\n return true;\n }\n\n #ours(event: AgentEvent): boolean {\n if (event.type !== 'submission' || event.record.id !== this.#submission) return false;\n this.#user = placedEntry(event.record) ?? this.#user;\n return this.#user === undefined && !['queued', 'placed'].includes(event.record.status);\n }\n\n take(batch: readonly AgentEvent[]): boolean {\n for (const event of batch) {\n if (event.type === 'message_end') this.#entries.set(event.entry.id, event.entry);\n if (this.#ours(event)) return this.#end('aborted');\n if (!this.#started) {\n this.#begin();\n continue;\n }\n const mapped = this.#mapper.event(event).filter((item) => item.t !== 'user');\n this.#out.push(...mapped);\n if (mapped.some((item) => item.t === 'run' && item.state === 'end'))\n return this.#end(this.#mapper.reason, this.#mapper.error);\n }\n return false;\n }\n}\n\nexport function createRemote(host: RemoteHost): AgentRemote & { dispose(): Promise<void> } {\n const now = host.now ?? Date.now;\n const streams = new Map<string, Stream>();\n let serial = 0;\n let timer: ReturnType<typeof setInterval> | undefined;\n const sweep = () => {\n for (const [id, stream] of streams)\n if (now() - stream.touched > IDLE_MS) {\n streams.delete(id);\n void stream.finish();\n }\n if (!streams.size && timer !== undefined) {\n clearInterval(timer);\n timer = undefined;\n }\n };\n const track = (id: string, stream: Stream) => {\n streams.set(id, stream);\n timer ??= setInterval(sweep, host.sweepMs ?? IDLE_MS / 4);\n };\n const watching = async (agentId: string | null, context: Context) => {\n const conversation = await host.conversation(agentId, context);\n const cancel = withCancel(BACKGROUND_CONTEXT);\n const events: AgentEventStream = await watchEvents(\n host.harness,\n conversation.id,\n cancel.context\n );\n const open = (onEvents: (batch: readonly AgentEvent[]) => void) => {\n let off = () => undefined as void;\n const stream = new Stream<RemoteEvent>(\n async () => {\n off();\n await events.stop();\n cancel.cancel();\n },\n now(),\n host.limit ?? MAX_QUEUED,\n [{ t: 'end', reason: 'error', message: 'the stream fell too far behind' }]\n );\n const id = `s${++serial}-${crypto.randomUUID()}`;\n track(id, stream);\n off = vaultNotices(host.vault, (notices) => stream.push(notices));\n events.start(async (batch) => onEvents(batch));\n return { id, stream };\n };\n return { conversation, snapshot: events.snapshot, open, stop: () => events.stop() };\n };\n return {\n async prompt(request, context) {\n sweep();\n const watched = await watching(request.agentId, context);\n const busy = Boolean(watched.snapshot.run);\n const response = await host.send(\n {\n text: request.text,\n whenBusy: request.whenBusy,\n requestId: null,\n ...(request.attachments?.length ? { attachments: request.attachments } : {}),\n ...(request.agentId ? { agentId: request.agentId } : {}),\n },\n context\n );\n if (!response.accepted) {\n await watched.stop();\n return {\n ok: false,\n code: response.error.code === 'busy' ? 'EBUSY' : 'EIO',\n message: response.error.message,\n };\n }\n const submission = Number(response.submissionId) as SubmissionId;\n const record = await (await host.harness.submission(submission, context))?.status(context);\n const delivered = deliveredOf(record, request.whenBusy, busy);\n const follower = new Follower(\n new Mapper(request.thinking),\n watched.snapshot.entries,\n submission,\n delivered,\n record\n );\n const { id, stream } = watched.open((batch) => {\n const ended = follower.take(batch);\n stream.push(follower.drain());\n if (ended) void stream.finish();\n });\n return { ok: true, stream: id, delivered, head: null, gap: false };\n },\n async watch(request, context) {\n sweep();\n const watched = await watching(request.agentId, context);\n const partial = watched.snapshot.generation?.message as never;\n const all = replayOf(watched.snapshot.entries, partial, request.thinking);\n const head = headOf(all);\n const at = request.since === null ? -1 : all.findIndex((e) => idOf(e) === request.since);\n const gap = request.since !== null && at < 0;\n const mapper = new Mapper(request.thinking);\n for (const entry of watched.snapshot.entries) mapper.entry(entry);\n mapper.partial(partial);\n const { id, stream } = watched.open((batch) =>\n stream.push(batch.flatMap((event) => followed(mapper, event)))\n );\n if (at >= 0) {\n const own = all[at] as RemoteEvent;\n const sinceId = idOf(own);\n let last = at;\n while (last + 1 < all.length && idOf(all[last + 1] as RemoteEvent) === sinceId) last++;\n const replay = own.t === 'text' || own.t === 'thinking' ? [own] : [];\n stream.push([...replay, ...all.slice(last + 1)]);\n }\n return { ok: true, stream: id, delivered: null, head, gap };\n },\n async next(id, waitMs, context) {\n const stream = streams.get(id);\n if (!stream) return { events: [], done: true };\n stream.touched = now();\n await stream.wait(Math.max(0, Math.min(waitMs, 30000)));\n context.abortSignal?.throwIfAborted();\n const events = stream.queue.splice(0);\n const done = stream.done && stream.queue.length === 0;\n if (done) streams.delete(id);\n return { events, done };\n },\n async close(id) {\n const stream = streams.get(id);\n streams.delete(id);\n await stream?.finish();\n },\n async abort(agentId, context) {\n const conversation = await host.conversation(agentId, context);\n const watched = await watchEvents(host.harness, conversation.id, context);\n const busy = Boolean(watched.snapshot.run);\n await watched.stop();\n if (busy) await conversation.abort(context);\n return { aborted: busy };\n },\n async dispose() {\n clearInterval(timer);\n timer = undefined;\n const all = [...streams.values()];\n streams.clear();\n await Promise.all(all.map((stream) => stream.finish()));\n },\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -16,7 +16,10 @@ function identity(agents, fallback) {
16
16
  return async (target, context) => {
17
17
  const out = {};
18
18
  const caller = callerOf(agents(), target.conversationId);
19
- if (caller) out.SLICC_AGENT = caller;
19
+ if (caller) {
20
+ out.SLICC_AGENT = caller;
21
+ out.PLAYWRIGHT_CLI_SESSION = caller;
22
+ }
20
23
  const read = target.read;
21
24
  const stored = read && target.conversationId !== void 0 ? await read.snapshot(AgentDoc, target.conversationId, context) : void 0;
22
25
  const model = stored?.model ?? fallback;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/scoops/identity.ts"],
4
- "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport {\n AgentDoc,\n type ConversationId,\n type DocumentReader,\n type ModelRef,\n} from '@earendil-works/pi-durable';\nimport type { Agents } from '../agents.ts';\nimport type { EnvTargetLike } from '../kernel/env.ts';\n\nexport function callerOf(\n agents: Agents | undefined,\n conversation: number | undefined\n): string | undefined {\n if (!agents || conversation === undefined) return undefined;\n const state = agents.state();\n const cone = Object.entries(state.cones).find(\n ([, record]) => record.conversation === conversation\n )?.[0];\n if (cone) return `cone:${cone}`;\n return Object.entries(state.scoops).find(\n ([, record]) => record.conversation === conversation\n )?.[0];\n}\n\nexport function identity(agents: () => Agents | undefined, fallback: ModelRef) {\n return async (target: EnvTargetLike, context: Context): Promise<Record<string, string>> => {\n const out: Record<string, string> = {};\n const caller = callerOf(agents(), target.conversationId);\n if (caller) out.SLICC_AGENT = caller;\n const read = target.read as DocumentReader | undefined;\n const stored =\n read && target.conversationId !== undefined\n ? await read.snapshot(AgentDoc, target.conversationId as ConversationId, context)\n : undefined;\n const model = stored?.model ?? fallback;\n out.PI_PROVIDER = model.provider;\n out.PI_MODEL = model.modelId;\n out.PI_REASONING_LEVEL = stored?.thinkingLevel ?? 'off';\n return out;\n };\n}\n"],
5
- "mappings": "AACA;AAAA,EACE;AAAA,OAIK;AAIA,SAAS,SACd,QACA,cACoB;AACpB,MAAI,CAAC,UAAU,iBAAiB,OAAW,QAAO;AAClD,QAAM,QAAQ,OAAO,MAAM;AAC3B,QAAM,OAAO,OAAO,QAAQ,MAAM,KAAK,EAAE;AAAA,IACvC,CAAC,CAAC,EAAE,MAAM,MAAM,OAAO,iBAAiB;AAAA,EAC1C,IAAI,CAAC;AACL,MAAI,KAAM,QAAO,QAAQ,IAAI;AAC7B,SAAO,OAAO,QAAQ,MAAM,MAAM,EAAE;AAAA,IAClC,CAAC,CAAC,EAAE,MAAM,MAAM,OAAO,iBAAiB;AAAA,EAC1C,IAAI,CAAC;AACP;AAEO,SAAS,SAAS,QAAkC,UAAoB;AAC7E,SAAO,OAAO,QAAuB,YAAsD;AACzF,UAAM,MAA8B,CAAC;AACrC,UAAM,SAAS,SAAS,OAAO,GAAG,OAAO,cAAc;AACvD,QAAI,OAAQ,KAAI,cAAc;AAC9B,UAAM,OAAO,OAAO;AACpB,UAAM,SACJ,QAAQ,OAAO,mBAAmB,SAC9B,MAAM,KAAK,SAAS,UAAU,OAAO,gBAAkC,OAAO,IAC9E;AACN,UAAM,QAAQ,QAAQ,SAAS;AAC/B,QAAI,cAAc,MAAM;AACxB,QAAI,WAAW,MAAM;AACrB,QAAI,qBAAqB,QAAQ,iBAAiB;AAClD,WAAO;AAAA,EACT;AACF;",
4
+ "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport {\n AgentDoc,\n type ConversationId,\n type DocumentReader,\n type ModelRef,\n} from '@earendil-works/pi-durable';\nimport type { Agents } from '../agents.ts';\nimport type { EnvTargetLike } from '../kernel/env.ts';\n\nexport function callerOf(\n agents: Agents | undefined,\n conversation: number | undefined\n): string | undefined {\n if (!agents || conversation === undefined) return undefined;\n const state = agents.state();\n const cone = Object.entries(state.cones).find(\n ([, record]) => record.conversation === conversation\n )?.[0];\n if (cone) return `cone:${cone}`;\n return Object.entries(state.scoops).find(\n ([, record]) => record.conversation === conversation\n )?.[0];\n}\n\nexport function identity(agents: () => Agents | undefined, fallback: ModelRef) {\n return async (target: EnvTargetLike, context: Context): Promise<Record<string, string>> => {\n const out: Record<string, string> = {};\n const caller = callerOf(agents(), target.conversationId);\n if (caller) {\n out.SLICC_AGENT = caller;\n out.PLAYWRIGHT_CLI_SESSION = caller;\n }\n const read = target.read as DocumentReader | undefined;\n const stored =\n read && target.conversationId !== undefined\n ? await read.snapshot(AgentDoc, target.conversationId as ConversationId, context)\n : undefined;\n const model = stored?.model ?? fallback;\n out.PI_PROVIDER = model.provider;\n out.PI_MODEL = model.modelId;\n out.PI_REASONING_LEVEL = stored?.thinkingLevel ?? 'off';\n return out;\n };\n}\n"],
5
+ "mappings": "AACA;AAAA,EACE;AAAA,OAIK;AAIA,SAAS,SACd,QACA,cACoB;AACpB,MAAI,CAAC,UAAU,iBAAiB,OAAW,QAAO;AAClD,QAAM,QAAQ,OAAO,MAAM;AAC3B,QAAM,OAAO,OAAO,QAAQ,MAAM,KAAK,EAAE;AAAA,IACvC,CAAC,CAAC,EAAE,MAAM,MAAM,OAAO,iBAAiB;AAAA,EAC1C,IAAI,CAAC;AACL,MAAI,KAAM,QAAO,QAAQ,IAAI;AAC7B,SAAO,OAAO,QAAQ,MAAM,MAAM,EAAE;AAAA,IAClC,CAAC,CAAC,EAAE,MAAM,MAAM,OAAO,iBAAiB;AAAA,EAC1C,IAAI,CAAC;AACP;AAEO,SAAS,SAAS,QAAkC,UAAoB;AAC7E,SAAO,OAAO,QAAuB,YAAsD;AACzF,UAAM,MAA8B,CAAC;AACrC,UAAM,SAAS,SAAS,OAAO,GAAG,OAAO,cAAc;AACvD,QAAI,QAAQ;AACV,UAAI,cAAc;AAClB,UAAI,yBAAyB;AAAA,IAC/B;AACA,UAAM,OAAO,OAAO;AACpB,UAAM,SACJ,QAAQ,OAAO,mBAAmB,SAC9B,MAAM,KAAK,SAAS,UAAU,OAAO,gBAAkC,OAAO,IAC9E;AACN,UAAM,QAAQ,QAAQ,SAAS;AAC/B,QAAI,cAAc,MAAM;AACxB,QAAI,WAAW,MAAM;AACrB,QAAI,qBAAqB,QAAQ,iBAAiB;AAClD,WAAO;AAAA,EACT;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/services.ts"],
4
- "sourcesContent": ["import {\n type Context,\n defineService,\n type JsonValue,\n type ReplicatedState,\n} from '@earendil-works/chord';\nimport type { ConversationView } from '@earendil-works/pi-durable';\nimport type { ChangesView } from './changes/git.ts';\nimport type { FrozenCone } from './freezer/index.ts';\nimport type { MemoryEntry, MemoryScope, MemoryTag } from './memory/format.ts';\nimport type { AgentRemote } from './remote/host.ts';\nimport type { AgentPages } from './remote/pages.ts';\nimport type { VaultState } from './secrets/vault.ts';\nimport type { Sprinkle } from './sprinkles/kind.ts';\n\nexport type SendMode = 'steer' | 'followUp' | 'reject';\n\nexport interface SendRequest {\n text: string;\n whenBusy: SendMode;\n requestId: string | null;\n agentId?: string | null;\n}\n\nexport interface OperationError {\n code: string;\n message: string;\n}\n\nexport type SendResponse =\n | { accepted: true; submissionId: string; error: null }\n | { accepted: false; submissionId: null; error: OperationError };\n\nexport type PromptResult =\n | { status: 'done'; text: string; reason: null }\n | { status: 'unanswered'; text: null; reason: string };\n\nexport interface AgentControl {\n send(request: SendRequest, context: Context): Promise<SendResponse>;\n wait(submissionId: string, context: Context): Promise<PromptResult>;\n withdraw(\n submissionId: string,\n context: Context\n ): Promise<{ outcome: 'withdrawn' | 'already_placed' | 'not_found' }>;\n abort(context: Context): Promise<void>;\n compact(instructions: string | null, context: Context): Promise<SendResponse>;\n compactAgent(\n agentId: string,\n instructions: string | null,\n context: Context\n ): Promise<SendResponse>;\n reset(handoff: string | null, context: Context): Promise<void>;\n configure(change: AgentSettingsChange, context: Context): Promise<void>;\n rewind(messageId: string | null, context: Context): Promise<Rewound>;\n rewindAgent(agentId: string, messageId: string | null, context: Context): Promise<Rewound>;\n resolveLick(\n lickId: string,\n state: 'confirmed' | 'dismissed',\n context: Context\n ): Promise<LickResolved>;\n resolveAgentLick(\n agentId: string,\n lickId: string,\n state: 'confirmed' | 'dismissed',\n context: Context\n ): Promise<LickResolved>;\n webhook(\n name: string,\n delivery: WebhookRequest,\n context: Context\n ): Promise<{ delivered: boolean }>;\n stopAgent(agentId: string, context: Context): Promise<void>;\n selectCone(agentId: string, context: Context): Promise<void>;\n createCone(name: string, context: Context): Promise<Created>;\n createScoop(parentId: string, name: string, context: Context): Promise<Created>;\n drop(agentId: string, context: Context): Promise<Created>;\n unqueue(\n agentId: string | null,\n submissionId: string,\n context: Context\n ): Promise<{ outcome: 'withdrawn' | 'already_placed' | 'not_found' }>;\n sprinkleSend(\n sprinkleId: string,\n payload: { action: string; data: JsonValue | null; target: string | null },\n context: Context\n ): Promise<{ delivered: boolean }>;\n sprinkleCall(\n sprinkleId: string,\n method: string,\n args: JsonValue[],\n context: Context\n ): Promise<JsonValue>;\n memorySave(draft: MemoryDraft, context: Context): Promise<MemoryEntry>;\n memoryRemove(id: string, context: Context): Promise<{ removed: boolean }>;\n freeze(agentId: string, context: Context): Promise<Created>;\n newChat(agentId: string, context: Context): Promise<Created>;\n thaw(frozenId: string, context: Context): Promise<Created>;\n discard(frozenId: string, context: Context): Promise<Created>;\n changesOpen(context: Context): Promise<void>;\n changeAccept(path: string, context: Context): Promise<Created>;\n changeRevert(path: string, context: Context): Promise<Created>;\n monitorOpen(lease: string | null, resync: boolean, context: Context): Promise<MonitorLease>;\n monitorClose(lease: string, context: Context): Promise<void>;\n}\n\nexport interface MonitorLease {\n lease: string;\n ttlMs: number;\n}\n\nexport interface MonitorAgent {\n id: string;\n name: string;\n kind: 'cone' | 'scoop';\n parentId: string | null;\n model: string | null;\n running: boolean;\n queued: number;\n tool: string | null;\n retry: { attempt: number; since: number; error: string } | null;\n failed: string | null;\n context: number | null;\n cpu?: number | null;\n}\n\nexport interface MonitorTask {\n id: string;\n kind: string;\n detail: string | null;\n agentId: string | null;\n owner: string | null;\n status: 'pending' | 'running' | 'waiting' | 'completing';\n phase: string;\n background: boolean;\n aborting: number | null;\n since: number;\n waits: number;\n}\n\nexport interface MonitorProcess {\n pid: number;\n ppid: number;\n pgid: number;\n user: string | null;\n agentId: string | null;\n command: string;\n state: 'S' | 'Z';\n started: number;\n zombie: number | null;\n tty: boolean;\n rss: number | null;\n utime?: number;\n stime?: number;\n cpu?: number | null;\n}\n\nexport interface MonitorProblem {\n id: string;\n at: number;\n agentId: string | null;\n kind: 'task' | 'retry' | 'turn';\n message: string;\n}\n\nexport interface MonitorFeed {\n open: boolean;\n at: number;\n usage: { tokens: number; cost: number; free: boolean } | null;\n agents: MonitorAgent[];\n tasks: MonitorTask[];\n processes: MonitorProcess[] | null;\n kernel: 'checking' | 'ok' | 'unreachable';\n problems: MonitorProblem[];\n paused: boolean;\n}\n\nexport interface MemoryDraft {\n id: string | null;\n scope: string;\n section: string;\n title: string;\n body: string;\n tag: MemoryTag | null;\n}\n\nexport type Created = { id: string; error: null } | { id: null; error: string };\n\nexport interface AgentSummary {\n id: string;\n name: string;\n kind: 'cone' | 'scoop';\n parentId: string | null;\n role: string | null;\n}\n\nexport interface AgentsSummary {\n active: string;\n agents: AgentSummary[];\n}\n\nexport type LickResolved =\n | { done: true; text: string; error: null }\n | { done: false; text: null; error: string };\n\nexport interface WebhookRequest {\n id: string | null;\n headers: Record<string, string>;\n body: string;\n}\n\nexport type Rewound =\n | { done: true; text: string; reason: null }\n | { done: false; text: null; reason: 'busy' | 'no-turn' };\n\nexport interface AgentSettingsChange {\n model: { provider: string; modelId: string } | null;\n thinkingLevel: 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max' | null;\n agentId?: string | null;\n}\n\nexport const AgentControl = defineService<AgentControl>('slicc.agent.control');\n\nexport type Delivered = 'run' | 'steer' | 'follow-up';\n\nexport interface AgentTranscript {\n readonly state: ReplicatedState<ConversationView>;\n readonly deliveries: ReplicatedState<Record<string, Delivered>>;\n readonly agents: ReplicatedState<AgentsSummary>;\n readonly views: ReplicatedState<Record<string, ConversationView>>;\n readonly commands: ReplicatedState<Command[]>;\n readonly sprinkles: ReplicatedState<Sprinkle[]>;\n readonly memories: ReplicatedState<MemoryEntry[]>;\n readonly memoryScopes: ReplicatedState<MemoryScope[]>;\n readonly frozen: ReplicatedState<FrozenCone[]>;\n readonly changes: ReplicatedState<ChangesView>;\n readonly monitor: ReplicatedState<MonitorFeed>;\n readonly vault: ReplicatedState<VaultState>;\n}\n\nexport interface Command {\n name: string;\n description: string;\n kind: 'prompt' | 'skill' | 'action';\n}\n\nexport const AgentTranscript = defineService<AgentTranscript>('slicc.agent.transcript');\n\nexport interface AgentSessions {\n attach(sessionId: string, context: Context): Promise<void>;\n detach(context: Context): Promise<void>;\n}\n\nexport const AgentSessions = defineService<AgentSessions>('slicc.agent.sessions');\n\nexport const AgentRemoteService = defineService<AgentRemote>('slicc.agent.remote');\n\nexport const AgentPagesService = defineService<AgentPages>('slicc.agent.pages');\n\nexport const AGENT_SESSION = 'agent';\n\nexport interface ModelChoice {\n id: string;\n label: string;\n provider: string;\n kind: 'chat' | 'classifier';\n reasoning: boolean;\n images?: boolean;\n contextWindow: number;\n}\n\nexport interface AccountState {\n id: string;\n provider: string;\n identity: string;\n status: 'connected' | 'disconnected' | 'expired';\n auth: 'api-key' | 'oauth';\n needs: 'cors-free-transport' | null;\n group?: 'providers' | 'mcp';\n secret?: 'api-key' | 'token';\n}\n\nexport interface BudgetState {\n provider: string;\n percent: number;\n window: 'weekly';\n resets: string;\n}\n\nexport interface SignIn {\n clientId: string;\n scopes: string;\n imsEnvironment: string;\n}\n\nexport interface McpSignIn {\n url: string;\n state: string;\n}\n\nexport interface SettingsState {\n models: ModelChoice[];\n accounts: AccountState[];\n budget?: BudgetState | null;\n}\n\nexport interface AgentSettings {\n readonly state: ReplicatedState<SettingsState>;\n connect(\n providerId: string,\n secret: string,\n region: string | null,\n context: Context\n ): Promise<void>;\n disconnect(providerId: string, context: Context): Promise<void>;\n signIn(providerId: string, context: Context): Promise<SignIn | McpSignIn | null>;\n}\n\nexport const AgentSettings = defineService<AgentSettings>('slicc.agent.settings');\n"],
5
- "mappings": "AAAA;AAAA,EAEE;AAAA,OAGK;AAuNA,MAAM,eAAe,cAA4B,qBAAqB;AAyBtE,MAAM,kBAAkB,cAA+B,wBAAwB;AAO/E,MAAM,gBAAgB,cAA6B,sBAAsB;AAEzE,MAAM,qBAAqB,cAA2B,oBAAoB;AAE1E,MAAM,oBAAoB,cAA0B,mBAAmB;AAEvE,MAAM,gBAAgB;AA2DtB,MAAM,gBAAgB,cAA6B,sBAAsB;",
4
+ "sourcesContent": ["import {\n type Context,\n defineService,\n type JsonValue,\n type ReplicatedState,\n} from '@earendil-works/chord';\nimport type { ConversationView } from '@earendil-works/pi-durable';\nimport type { SendAttachment, ShownAttachment } from './attachments.ts';\nimport type { ChangesView } from './changes/git.ts';\nimport type { FrozenCone } from './freezer/index.ts';\nimport type { MemoryEntry, MemoryScope, MemoryTag } from './memory/format.ts';\nimport type { AgentRemote } from './remote/host.ts';\nimport type { AgentPages } from './remote/pages.ts';\nimport type { VaultState } from './secrets/vault.ts';\nimport type { Sprinkle } from './sprinkles/kind.ts';\n\nexport type SendMode = 'steer' | 'followUp' | 'reject';\n\nexport interface SendRequest {\n text: string;\n whenBusy: SendMode;\n requestId: string | null;\n agentId?: string | null;\n attachments?: SendAttachment[];\n}\n\nexport interface OperationError {\n code: string;\n message: string;\n}\n\nexport type SendResponse =\n | { accepted: true; submissionId: string; error: null }\n | { accepted: false; submissionId: null; error: OperationError };\n\nexport type PromptResult =\n | { status: 'done'; text: string; reason: null }\n | { status: 'unanswered'; text: null; reason: string };\n\nexport interface AgentControl {\n send(request: SendRequest, context: Context): Promise<SendResponse>;\n wait(submissionId: string, context: Context): Promise<PromptResult>;\n withdraw(\n submissionId: string,\n context: Context\n ): Promise<{ outcome: 'withdrawn' | 'already_placed' | 'not_found' }>;\n abort(context: Context): Promise<void>;\n compact(instructions: string | null, context: Context): Promise<SendResponse>;\n compactAgent(\n agentId: string,\n instructions: string | null,\n context: Context\n ): Promise<SendResponse>;\n reset(handoff: string | null, context: Context): Promise<void>;\n configure(change: AgentSettingsChange, context: Context): Promise<void>;\n rewind(messageId: string | null, context: Context): Promise<Rewound>;\n rewindAgent(agentId: string, messageId: string | null, context: Context): Promise<Rewound>;\n resolveLick(\n lickId: string,\n state: 'confirmed' | 'dismissed',\n context: Context\n ): Promise<LickResolved>;\n resolveAgentLick(\n agentId: string,\n lickId: string,\n state: 'confirmed' | 'dismissed',\n context: Context\n ): Promise<LickResolved>;\n webhook(\n name: string,\n delivery: WebhookRequest,\n context: Context\n ): Promise<{ delivered: boolean }>;\n stopAgent(agentId: string, context: Context): Promise<void>;\n selectCone(agentId: string, context: Context): Promise<void>;\n createCone(name: string, context: Context): Promise<Created>;\n createScoop(parentId: string, name: string, context: Context): Promise<Created>;\n drop(agentId: string, context: Context): Promise<Created>;\n unqueue(\n agentId: string | null,\n submissionId: string,\n context: Context\n ): Promise<{ outcome: 'withdrawn' | 'already_placed' | 'not_found' }>;\n sprinkleSend(\n sprinkleId: string,\n payload: { action: string; data: JsonValue | null; target: string | null },\n context: Context\n ): Promise<{ delivered: boolean }>;\n sprinkleCall(\n sprinkleId: string,\n method: string,\n args: JsonValue[],\n context: Context\n ): Promise<JsonValue>;\n memorySave(draft: MemoryDraft, context: Context): Promise<MemoryEntry>;\n memoryRemove(id: string, context: Context): Promise<{ removed: boolean }>;\n freeze(agentId: string, context: Context): Promise<Created>;\n newChat(agentId: string, context: Context): Promise<Created>;\n thaw(frozenId: string, context: Context): Promise<Created>;\n discard(frozenId: string, context: Context): Promise<Created>;\n changesOpen(context: Context): Promise<void>;\n changeAccept(path: string, context: Context): Promise<Created>;\n changeRevert(path: string, context: Context): Promise<Created>;\n monitorOpen(lease: string | null, resync: boolean, context: Context): Promise<MonitorLease>;\n monitorClose(lease: string, context: Context): Promise<void>;\n image(ref: ImageRef, context: Context): Promise<StoredImage>;\n}\n\nexport interface ImageRef {\n entry: number;\n message: number;\n part: number;\n}\n\nexport type StoredImage = { data: string; mimeType: string } | { data: null; mimeType: null };\n\nexport interface MonitorLease {\n lease: string;\n ttlMs: number;\n}\n\nexport interface MonitorAgent {\n id: string;\n name: string;\n kind: 'cone' | 'scoop';\n parentId: string | null;\n model: string | null;\n running: boolean;\n queued: number;\n tool: string | null;\n retry: { attempt: number; since: number; error: string } | null;\n failed: string | null;\n context: number | null;\n cpu?: number | null;\n}\n\nexport interface MonitorTask {\n id: string;\n kind: string;\n detail: string | null;\n agentId: string | null;\n owner: string | null;\n status: 'pending' | 'running' | 'waiting' | 'completing';\n phase: string;\n background: boolean;\n aborting: number | null;\n since: number;\n waits: number;\n}\n\nexport interface MonitorProcess {\n pid: number;\n ppid: number;\n pgid: number;\n user: string | null;\n agentId: string | null;\n command: string;\n state: 'S' | 'Z';\n started: number;\n zombie: number | null;\n tty: boolean;\n rss: number | null;\n utime?: number;\n stime?: number;\n cpu?: number | null;\n}\n\nexport interface MonitorProblem {\n id: string;\n at: number;\n agentId: string | null;\n kind: 'task' | 'retry' | 'turn';\n message: string;\n}\n\nexport interface MonitorFeed {\n open: boolean;\n at: number;\n usage: { tokens: number; cost: number; free: boolean } | null;\n agents: MonitorAgent[];\n tasks: MonitorTask[];\n processes: MonitorProcess[] | null;\n kernel: 'checking' | 'ok' | 'unreachable';\n problems: MonitorProblem[];\n paused: boolean;\n}\n\nexport interface MemoryDraft {\n id: string | null;\n scope: string;\n section: string;\n title: string;\n body: string;\n tag: MemoryTag | null;\n}\n\nexport type Created = { id: string; error: null } | { id: null; error: string };\n\nexport interface AgentSummary {\n id: string;\n name: string;\n kind: 'cone' | 'scoop';\n parentId: string | null;\n role: string | null;\n}\n\nexport interface AgentsSummary {\n active: string;\n agents: AgentSummary[];\n}\n\nexport type LickResolved =\n | { done: true; text: string; error: null }\n | { done: false; text: null; error: string };\n\nexport interface WebhookRequest {\n id: string | null;\n headers: Record<string, string>;\n body: string;\n}\n\nexport type Rewound =\n | { done: true; text: string; reason: null; attachments?: ShownAttachment[] }\n | { done: false; text: null; reason: 'busy' | 'no-turn' };\n\nexport interface AgentSettingsChange {\n model: { provider: string; modelId: string } | null;\n thinkingLevel: 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max' | null;\n agentId?: string | null;\n}\n\nexport const AgentControl = defineService<AgentControl>('slicc.agent.control');\n\nexport type Delivered = 'run' | 'steer' | 'follow-up';\n\nexport interface AgentTranscript {\n readonly state: ReplicatedState<ConversationView>;\n readonly deliveries: ReplicatedState<Record<string, Delivered>>;\n readonly agents: ReplicatedState<AgentsSummary>;\n readonly views: ReplicatedState<Record<string, ConversationView>>;\n readonly commands: ReplicatedState<Command[]>;\n readonly sprinkles: ReplicatedState<Sprinkle[]>;\n readonly memories: ReplicatedState<MemoryEntry[]>;\n readonly memoryScopes: ReplicatedState<MemoryScope[]>;\n readonly frozen: ReplicatedState<FrozenCone[]>;\n readonly changes: ReplicatedState<ChangesView>;\n readonly monitor: ReplicatedState<MonitorFeed>;\n readonly vault: ReplicatedState<VaultState>;\n}\n\nexport interface Command {\n name: string;\n description: string;\n kind: 'prompt' | 'skill' | 'action';\n}\n\nexport const AgentTranscript = defineService<AgentTranscript>('slicc.agent.transcript');\n\nexport interface AgentSessions {\n attach(sessionId: string, context: Context): Promise<void>;\n detach(context: Context): Promise<void>;\n}\n\nexport const AgentSessions = defineService<AgentSessions>('slicc.agent.sessions');\n\nexport const AgentRemoteService = defineService<AgentRemote>('slicc.agent.remote');\n\nexport const AgentPagesService = defineService<AgentPages>('slicc.agent.pages');\n\nexport const AGENT_SESSION = 'agent';\n\nexport interface ModelChoice {\n id: string;\n label: string;\n provider: string;\n kind: 'chat' | 'classifier';\n reasoning: boolean;\n images?: boolean;\n contextWindow: number;\n}\n\nexport interface AccountState {\n id: string;\n provider: string;\n identity: string;\n status: 'connected' | 'disconnected' | 'expired';\n auth: 'api-key' | 'oauth';\n needs: 'cors-free-transport' | null;\n group?: 'providers' | 'mcp';\n secret?: 'api-key' | 'token';\n}\n\nexport interface BudgetState {\n provider: string;\n percent: number;\n window: 'weekly';\n resets: string;\n}\n\nexport interface SignIn {\n clientId: string;\n scopes: string;\n imsEnvironment: string;\n}\n\nexport interface McpSignIn {\n url: string;\n state: string;\n}\n\nexport interface SettingsState {\n models: ModelChoice[];\n accounts: AccountState[];\n budget?: BudgetState | null;\n}\n\nexport interface AgentSettings {\n readonly state: ReplicatedState<SettingsState>;\n connect(\n providerId: string,\n secret: string,\n region: string | null,\n context: Context\n ): Promise<void>;\n disconnect(providerId: string, context: Context): Promise<void>;\n signIn(providerId: string, context: Context): Promise<SignIn | McpSignIn | null>;\n}\n\nexport const AgentSettings = defineService<AgentSettings>('slicc.agent.settings');\n"],
5
+ "mappings": "AAAA;AAAA,EAEE;AAAA,OAGK;AAkOA,MAAM,eAAe,cAA4B,qBAAqB;AAyBtE,MAAM,kBAAkB,cAA+B,wBAAwB;AAO/E,MAAM,gBAAgB,cAA6B,sBAAsB;AAEzE,MAAM,qBAAqB,cAA2B,oBAAoB;AAE1E,MAAM,oBAAoB,cAA0B,mBAAmB;AAEvE,MAAM,gBAAgB;AA2DtB,MAAM,gBAAgB,cAA6B,sBAAsB;",
6
6
  "names": []
7
7
  }
@@ -1,9 +1,11 @@
1
1
  import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
2
+ import { shrink } from "../images/fit.js";
2
3
  import { entryLick } from "../licks/state.js";
3
4
  import { vaultHold } from "../secrets/view.js";
4
5
  import { attempt } from "./attempt.js";
5
6
  import { Emitter } from "./emitter.js";
6
7
  import { isBusy, queued, toMessages } from "./messages.js";
8
+ import { readUrl, sendAttachments } from "./outgoing.js";
7
9
  const CONE = "cone";
8
10
  const thinking = {
9
11
  off: "off",
@@ -178,6 +180,16 @@ class AgentAdapter extends Emitter {
178
180
  }
179
181
  async send(agentId, input) {
180
182
  const outgoing = typeof input === "string" ? { text: input } : input;
183
+ const attachments = outgoing.attachments?.length ? await sendAttachments(
184
+ outgoing.attachments,
185
+ readUrl,
186
+ shrink,
187
+ (ref) => this.#connection.control.image(ref, BACKGROUND_CONTEXT)
188
+ ).catch((error) => {
189
+ this.notify(error.message, agentId);
190
+ return null;
191
+ }) : [];
192
+ if (!attachments) return;
181
193
  const target = this.#creating.has(agentId) ? await this.#creating.get(agentId) : agentId;
182
194
  if (target === null) throw new Error("The scoop could not be created.");
183
195
  const pending = target && this.#held(target) ? this.#pending(target, outgoing) : void 0;
@@ -188,7 +200,8 @@ class AgentAdapter extends Emitter {
188
200
  text: outgoing.text,
189
201
  whenBusy: outgoing.mode === "queue" ? "followUp" : "steer",
190
202
  requestId: crypto.randomUUID(),
191
- agentId: target || null
203
+ agentId: target || null,
204
+ ...attachments.length ? { attachments } : {}
192
205
  },
193
206
  BACKGROUND_CONTEXT
194
207
  ).finally(() => pending && this.#settle(pending));
@@ -207,6 +220,7 @@ class AgentAdapter extends Emitter {
207
220
  role: "user",
208
221
  text: outgoing.text,
209
222
  createdAt: Date.now(),
223
+ ...outgoing.attachments?.length ? { attachments: [...outgoing.attachments] } : {},
210
224
  queued: true,
211
225
  mode: outgoing.mode === "queue" ? "queue" : "steer"
212
226
  };
@@ -226,7 +240,8 @@ class AgentAdapter extends Emitter {
226
240
  messageId,
227
241
  BACKGROUND_CONTEXT
228
242
  );
229
- return rewound.done ? { text: rewound.text } : null;
243
+ if (!rewound.done) return null;
244
+ return rewound.attachments?.length ? { text: rewound.text, attachments: rewound.attachments } : { text: rewound.text };
230
245
  }
231
246
  stop(agentId = this.active()) {
232
247
  attempt(