@finalai/final-agent-provider 0.6.4 → 0.8.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 (106) hide show
  1. package/HOSTING.md +4 -4
  2. package/README.md +113 -2
  3. package/dist/modules/authoring-final-channel.mjs +2 -2
  4. package/dist/modules/authoring-final-channel.mjs.map +2 -2
  5. package/dist/modules/channel-factories.d.mts +5 -7
  6. package/dist/modules/channel-factories.mjs +4 -50
  7. package/dist/modules/channel-factories.mjs.map +2 -2
  8. package/dist/modules/channels/channel-utils.d.mts +132 -0
  9. package/dist/modules/channels/channel-utils.mjs +445 -0
  10. package/dist/modules/channels/channel-utils.mjs.map +7 -0
  11. package/dist/modules/channels/delivery.d.mts +5 -5
  12. package/dist/modules/channels/delivery.mjs.map +1 -1
  13. package/dist/modules/channels/direct-messaging.d.mts +2 -2
  14. package/dist/modules/channels/direct-messaging.mjs.map +2 -2
  15. package/dist/modules/channels/discord-dispatch.d.mts +17 -0
  16. package/dist/modules/channels/discord-dispatch.mjs +139 -0
  17. package/dist/modules/channels/discord-dispatch.mjs.map +7 -0
  18. package/dist/modules/channels/discord-gateway.d.mts +19 -0
  19. package/dist/modules/channels/discord-gateway.mjs +233 -0
  20. package/dist/modules/channels/discord-gateway.mjs.map +7 -0
  21. package/dist/modules/channels/discord.d.mts +9 -0
  22. package/dist/modules/channels/discord.mjs +904 -0
  23. package/dist/modules/channels/discord.mjs.map +7 -0
  24. package/dist/modules/channels/fake-platform.d.mts +2 -7
  25. package/dist/modules/channels/fake-platform.mjs +2 -38
  26. package/dist/modules/channels/fake-platform.mjs.map +2 -2
  27. package/dist/modules/channels/github.d.mts +5 -0
  28. package/dist/modules/channels/github.mjs +720 -0
  29. package/dist/modules/channels/github.mjs.map +7 -0
  30. package/dist/modules/channels/group-messaging.d.mts +2 -2
  31. package/dist/modules/channels/group-messaging.mjs.map +2 -2
  32. package/dist/modules/channels/index.d.mts +7 -3
  33. package/dist/modules/channels/index.mjs +7 -3
  34. package/dist/modules/channels/index.mjs.map +2 -2
  35. package/dist/modules/channels/linear.d.mts +4 -0
  36. package/dist/modules/channels/linear.mjs +621 -0
  37. package/dist/modules/channels/linear.mjs.map +7 -0
  38. package/dist/modules/channels/messaging-channel.d.mts +3 -3
  39. package/dist/modules/channels/messaging-channel.mjs +2 -2
  40. package/dist/modules/channels/messaging-channel.mjs.map +2 -2
  41. package/dist/modules/channels/phone.d.mts +8 -0
  42. package/dist/modules/channels/phone.mjs +382 -0
  43. package/dist/modules/channels/phone.mjs.map +7 -0
  44. package/dist/modules/channels/projection.mjs.map +1 -1
  45. package/dist/modules/channels/reply-receipts.d.mts +31 -0
  46. package/dist/modules/channels/reply-receipts.mjs +63 -0
  47. package/dist/modules/channels/reply-receipts.mjs.map +7 -0
  48. package/dist/modules/channels/routing.d.mts +1 -0
  49. package/dist/modules/channels/routing.mjs +5 -1
  50. package/dist/modules/channels/routing.mjs.map +2 -2
  51. package/dist/modules/channels/slack.d.mts +7 -0
  52. package/dist/modules/channels/slack.mjs +1134 -0
  53. package/dist/modules/channels/slack.mjs.map +7 -0
  54. package/dist/modules/channels/voice-recording.d.mts +7 -0
  55. package/dist/modules/channels/voice-recording.mjs +49 -0
  56. package/dist/modules/channels/voice-recording.mjs.map +7 -0
  57. package/dist/modules/channels/voice.d.mts +33 -0
  58. package/dist/modules/channels/voice.mjs +851 -0
  59. package/dist/modules/channels/voice.mjs.map +7 -0
  60. package/dist/modules/channels/webhook.d.mts +5 -0
  61. package/dist/modules/channels/webhook.mjs +76 -0
  62. package/dist/modules/channels/webhook.mjs.map +7 -0
  63. package/dist/modules/harnesses/acp-describe.mjs +1 -1
  64. package/dist/modules/harnesses/acp-describe.mjs.map +2 -2
  65. package/dist/modules/harnesses/fidelity.mjs +17 -17
  66. package/dist/modules/harnesses/fidelity.mjs.map +2 -2
  67. package/dist/modules/harnesses/remote-url.mjs.map +1 -1
  68. package/dist/modules/runtime/remote-host.mjs.map +1 -1
  69. package/dist/modules/sandboxes/build-scripts.mjs.map +1 -1
  70. package/dist/modules/sandboxes/bytes.d.mts +1 -0
  71. package/dist/modules/sandboxes/bytes.mjs +7 -1
  72. package/dist/modules/sandboxes/bytes.mjs.map +2 -2
  73. package/dist/modules/sandboxes/computer.mjs +5 -1
  74. package/dist/modules/sandboxes/computer.mjs.map +2 -2
  75. package/dist/modules/sandboxes/computers-api-transport.d.mts +1 -1
  76. package/dist/modules/sandboxes/computers-api-transport.mjs +8 -0
  77. package/dist/modules/sandboxes/computers-api-transport.mjs.map +2 -2
  78. package/dist/modules/sandboxes/device-api.mjs +8 -0
  79. package/dist/modules/sandboxes/device-api.mjs.map +2 -2
  80. package/dist/modules/sandboxes/device.mjs +6 -0
  81. package/dist/modules/sandboxes/device.mjs.map +2 -2
  82. package/dist/modules/sandboxes/paths.mjs.map +1 -1
  83. package/dist/modules/sandboxes/session.d.mts +1 -0
  84. package/dist/modules/sandboxes/session.mjs +106 -36
  85. package/dist/modules/sandboxes/session.mjs.map +3 -3
  86. package/dist/modules/sandboxes/testing/conformance.mjs +3 -0
  87. package/dist/modules/sandboxes/testing/conformance.mjs.map +2 -2
  88. package/dist/modules/sandboxes/testing/fake-machine.mjs +11 -0
  89. package/dist/modules/sandboxes/testing/fake-machine.mjs.map +2 -2
  90. package/dist/modules/sandboxes/transport.d.mts +1 -0
  91. package/dist/modules/sandboxes/transport.mjs.map +2 -2
  92. package/dist/modules/testing/harness.mjs +2 -2
  93. package/dist/modules/testing/harness.mjs.map +1 -1
  94. package/package.json +45 -19
  95. package/dist/modules/channels/address-context.d.mts +0 -7
  96. package/dist/modules/channels/address-context.mjs +0 -80
  97. package/dist/modules/channels/address-context.mjs.map +0 -7
  98. package/dist/modules/channels/agent-events.d.mts +0 -103
  99. package/dist/modules/channels/agent-events.mjs +0 -341
  100. package/dist/modules/channels/agent-events.mjs.map +0 -7
  101. package/dist/modules/channels/agent-schedules.d.mts +0 -25
  102. package/dist/modules/channels/agent-schedules.mjs +0 -96
  103. package/dist/modules/channels/agent-schedules.mjs.map +0 -7
  104. package/dist/modules/channels/service-input.d.mts +0 -22
  105. package/dist/modules/channels/service-input.mjs +0 -32
  106. package/dist/modules/channels/service-input.mjs.map +0 -7
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/channels/voice.ts"],
4
+ "sourcesContent": ["import {\n defineChannel,\n packageRef,\n principalRefSchema,\n type ChannelReceiveContextIface,\n type ChannelSocketIface,\n type JsonObject,\n type JsonValue,\n type PlaceRef,\n type PrincipalRef,\n} from '@finalai/agent-provider';\nimport * as z from 'zod';\nimport { bytesToBase64 } from '@finalai/agent-provider/sandbox/bytes';\nimport { recordingRawLimit, recordingWav } from \"./voice-recording.mjs\";\nimport {\n abortable,\n channelBaseUrl,\n confirmOAuth,\n createQueue,\n decisionChoices,\n decisionSummary,\n decisionAnswer,\n textOf,\n turnStatus,\n type DecisionChoiceIface,\n parseJson,\n personFor,\n responseJson,\n startOAuth,\n takeOAuth,\n} from \"./channel-utils.mjs\";\n\nconst configSchema = z.object({\n baseUrl: z.string().url().optional(),\n oauthAuthorizeUrl: z.string().url().optional(),\n oauthTokenUrl: z.string().url().optional(),\n oauthUserInfoUrl: z.string().url().optional(),\n oauthClientId: z.string().url().optional(),\n oauthResource: z.string().url().optional(),\n voice: z.string().optional(),\n});\nexport type VoiceConfig = z.infer<typeof configSchema>;\nconst userInfoSchema = z\n .object({ sub: z.string(), organizationId: z.string(), displayName: z.string() })\n .transform((value) => ({\n id: value.sub,\n tenant: value.organizationId,\n displayName: value.displayName,\n }));\nconst startSchema = z.object({\n type: z.literal('start'),\n encoding: z.literal('linear16'),\n sampleRate: z.literal(16000),\n principal: principalRefSchema,\n recording: z.boolean().default(false),\n});\nexport interface VoiceConnectionOptionsIface<Config> {\n ctx: ChannelReceiveContextIface<Config>;\n socket: ChannelSocketIface;\n principal: PrincipalRef;\n place: PlaceRef;\n encoding: string;\n sampleRate: number;\n voice?: string;\n recording?: boolean;\n}\n// A single channel owns transcription, posting, observation and synthesis on every transport.\nexport const runVoiceConnection = async <Config>(\n options: VoiceConnectionOptionsIface<Config>,\n): Promise<void> => {\n const { ctx, socket } = options;\n const failed = new AbortController();\n const signal = AbortSignal.any([socket.signal, failed.signal]);\n const send = (data: string | Uint8Array): void => {\n if (!signal.aborted) socket.send(data);\n };\n const connectionKey = crypto.randomUUID();\n const trace = (\n stage: string,\n detail: { utterance?: string; turnId?: string; at?: number } = {},\n ) => {\n const metadata = { connectionId: connectionKey, at: Date.now(), ...detail };\n send(JSON.stringify({ type: 'trace', stage, ...metadata }));\n ctx.log(`voice.${stage}`, metadata);\n };\n let activeSpeech: AbortController | null = null;\n let activeRefusal: AbortController | null = null;\n let refusalPlayback: Promise<void> | null = null;\n let playbackGeneration = 0;\n const speakRefusal = async (message: string, refusalSignal: AbortSignal = signal) => {\n const preceding = refusalPlayback;\n const finished = Promise.withResolvers<void>();\n refusalPlayback = finished.promise;\n const speech = new AbortController();\n activeRefusal?.abort();\n activeRefusal = speech;\n activeSpeech?.abort();\n const playbackVersion = ++playbackGeneration;\n send(JSON.stringify({ type: 'speech.start' }));\n const outputSignal = AbortSignal.any([refusalSignal, speech.signal]);\n try {\n await preceding;\n const text = async function* () {\n yield message;\n };\n for await (const frame of abortable(\n ctx.media.synthesize(text(), {\n voice: options.voice,\n encoding: options.encoding,\n sampleRate: options.sampleRate,\n signal: outputSignal,\n }),\n outputSignal,\n )) {\n if (playbackVersion !== playbackGeneration || outputSignal.aborted) break;\n send(frame);\n }\n } finally {\n speech.abort();\n if (activeRefusal === speech) activeRefusal = null;\n finished.resolve();\n if (refusalPlayback === finished.promise) refusalPlayback = null;\n }\n };\n const person = await personFor(ctx, options.principal);\n const route = await ctx.places.resolve(options.place);\n if (\n typeof person !== 'string' &&\n (person.refused !== 'unlinked' ||\n route.kind !== 'connected' ||\n route.policy.audience !== 'anyone')\n ) {\n send(JSON.stringify({ type: 'linked', linkUrl: person.linkUrl, message: person.message }));\n await speakRefusal(person.message);\n socket.close(1008, 'Account link required');\n return;\n }\n const from = typeof person === 'string' ? person : undefined;\n const conversation = await (async () => {\n try {\n return route.kind === 'connected'\n ? route.conversation\n : route.defaults == null || from == null\n ? null\n : await ctx.places.connect(options.place, { by: from, create: route.defaults.create });\n } catch {\n return null;\n }\n })();\n if (conversation == null) {\n const message = 'This call could not connect to a chat. Check your access in Final.';\n send(JSON.stringify({ type: 'notice', message }));\n await speakRefusal(message, AbortSignal.any([signal, AbortSignal.timeout(10_000)]));\n send(JSON.stringify({ type: 'speech.end', final: true }));\n socket.close(1000);\n return;\n }\n const recordingEnabled = options.recording !== false;\n const recordingLimit = recordingEnabled ? recordingRawLimit(options.encoding) : null;\n if (recordingEnabled)\n send(\n JSON.stringify({\n type: 'recording.notice',\n message: 'Your transcript and recording are saved to this chat.',\n }),\n );\n const audio = createQueue<Uint8Array>();\n const transcriptionStopped = new AbortController();\n const transcriptionSignal = AbortSignal.any([signal, transcriptionStopped.signal]);\n let terminalRefusal = false;\n let microphoneInputClosed = false;\n const closeAudio = () => {\n microphoneInputClosed = true;\n audio.close();\n };\n const microphoneEnded = new AbortController();\n const microphoneLifecycle: { onEnd: (() => void) | null; stopObservation: (() => void) | null } =\n { onEnd: null, stopObservation: null };\n const utterances = createQueue<{ text: string; key: string; finalAt: number }>();\n let queuedAudioBytes = 0;\n const transcriptionAudio = async function* (): AsyncGenerator<Uint8Array> {\n for await (const frame of audio.stream) {\n queuedAudioBytes -= frame.byteLength;\n yield frame;\n }\n };\n let recording: Uint8Array[] = [];\n let recordingSize = 0;\n let segment = 0;\n let generation = 0;\n let activeTurn = false;\n const turnLifecycle: { accepting: { interrupted: boolean } | null; activeId: string | null } = {\n accepting: null,\n activeId: null,\n };\n let audioStarted = false;\n let userSpeaking = false;\n let utteranceNumber = 0;\n let decision: { id: string; choices: DecisionChoiceIface[] } | null = null;\n const pendingDecisions = new Map<string, { id: string; choices: DecisionChoiceIface[] }>();\n const clearDecisions = () => {\n pendingDecisions.clear();\n decision = null;\n };\n const fail = (cause: unknown): void => {\n if (signal.aborted) return;\n ctx.log('voice connection failed', {\n error: cause instanceof Error ? cause.message : 'stream failed',\n });\n send(\n JSON.stringify({ type: 'error', message: 'The voice connection failed. Please reconnect.' }),\n );\n socket.close(1011, 'Voice connection failed');\n failed.abort();\n closeAudio();\n utterances.close();\n };\n interface RecordingSegmentIface {\n chunks: Uint8Array[];\n bytes: number;\n index: number;\n encoded: string | null;\n }\n let upload: {\n segment: RecordingSegmentIface;\n result: Promise<{ ok: true } | { error: unknown }>;\n } | null = null;\n const attachRecording = async (\n part: RecordingSegmentIface,\n deadline?: AbortSignal,\n ): Promise<void> => {\n deadline?.throwIfAborted();\n if (part.encoded == null) {\n part.encoded = bytesToBase64(\n recordingWav({\n chunks: part.chunks,\n bytes: part.bytes,\n encoding: options.encoding,\n sampleRate: options.sampleRate,\n }),\n );\n // Retries reuse the same payload without retaining its raw copy.\n part.chunks = [];\n }\n const result = await ctx.conversation(conversation).attach({\n from,\n label: `Voice recording, part ${part.index}`,\n origin: { place: options.place },\n idempotencyKey: `${options.place.id}:${connectionKey}:recording:${part.index}`,\n content: [{ type: 'audio', data: part.encoded, mimeType: 'audio/wav' }],\n });\n if ('refused' in result) throw new Error(result.message);\n };\n const beginRecordingUpload = (deadline?: AbortSignal): void => {\n if (recordingSize === 0) return;\n const part: RecordingSegmentIface = {\n chunks: recording,\n bytes: recordingSize,\n index: ++segment,\n encoded: null,\n };\n recording = [];\n recordingSize = 0;\n upload = {\n segment: part,\n result: attachRecording(part, deadline).then(\n () => ({ ok: true }),\n (error: unknown) => ({ error }),\n ),\n };\n };\n const settleRecordingUpload = async (retry: boolean, deadline?: AbortSignal): Promise<void> => {\n const pending = upload;\n if (pending == null) return;\n const stop = deadline ?? signal;\n const interrupted = Promise.withResolvers<never>();\n const abortUpload = () =>\n interrupted.reject(\n stop.reason instanceof Error ? stop.reason : new Error('Recording upload interrupted.'),\n );\n stop.addEventListener('abort', abortUpload, { once: true });\n if (stop.aborted) abortUpload();\n let result: { ok: true } | { error: unknown };\n try {\n result = await Promise.race([pending.result, interrupted.promise]);\n if ('error' in result) {\n if (!retry) throw result.error;\n await Promise.race([attachRecording(pending.segment, deadline), interrupted.promise]);\n }\n } finally {\n stop.removeEventListener('abort', abortUpload);\n }\n if (upload === pending) upload = null;\n };\n const rotateRecording = async (): Promise<void> => {\n // At most one segment uploads while the next accumulates; only a second full segment waits.\n await settleRecordingUpload(true);\n signal.throwIfAborted();\n beginRecordingUpload();\n };\n const finishRecording = async (): Promise<void> => {\n const deadline = new AbortController();\n const expired = Promise.withResolvers<never>();\n const timer = setTimeout(() => {\n const cause = new Error('Recording cleanup timed out.');\n deadline.abort(cause);\n expired.reject(cause);\n }, 10_000);\n const finish = async () => {\n let failure: Error | null = null;\n try {\n await settleRecordingUpload(true, deadline.signal);\n } catch (cause) {\n failure = cause instanceof Error ? cause : new Error('Recording attachment failed.');\n upload = null;\n }\n if (recordingSize > 0 && !deadline.signal.aborted) {\n try {\n beginRecordingUpload(deadline.signal);\n await settleRecordingUpload(true, deadline.signal);\n } catch (cause) {\n failure ??= cause instanceof Error ? cause : new Error('Recording attachment failed.');\n }\n }\n if (failure != null) throw failure;\n };\n try {\n await Promise.race([finish(), expired.promise]);\n } finally {\n clearTimeout(timer);\n deadline.abort();\n }\n };\n const reportRefusal = async (\n message: string,\n options: { recoverable?: boolean } = {},\n ): Promise<void> => {\n if (terminalRefusal) return;\n const recoverable =\n options.recoverable !== false &&\n (from == null ||\n (await ctx.people.identify(from).then(\n (principal) => principal != null,\n () => false,\n )));\n if (terminalRefusal) return;\n if (recoverable) {\n send(JSON.stringify({ type: 'error', message, recoverable: true }));\n await speakRefusal(message);\n return;\n }\n terminalRefusal = true;\n closeAudio();\n transcriptionStopped.abort();\n utterances.close();\n activeSpeech?.abort();\n microphoneLifecycle.stopObservation?.();\n send(JSON.stringify({ type: 'notice', message }));\n await speakRefusal(message, AbortSignal.any([signal, AbortSignal.timeout(10_000)]));\n };\n const requestCancellation = async (turnId: string): Promise<void> => {\n try {\n const result = await ctx.turns.cancel({ conversation, by: from, turnId });\n if ('refused' in result) await reportRefusal(result.message);\n else if (!result.accepted)\n await reportRefusal(\n 'No running turn could be interrupted from this connection. Check Final if work is still running.',\n );\n } catch {\n await reportRefusal('The turn could not be interrupted. Try stopping it in Final.');\n }\n };\n const cancel = async (withdrawDecision = false) => {\n playbackGeneration += 1;\n activeSpeech?.abort();\n send(JSON.stringify({ type: 'speech.start' }));\n if (turnLifecycle.accepting != null) {\n turnLifecycle.accepting.interrupted = true;\n generation += 1;\n return;\n }\n if (activeTurn && (decision == null || withdrawDecision)) {\n clearDecisions();\n generation += 1;\n microphoneLifecycle.stopObservation?.();\n const target = turnLifecycle.activeId;\n if (target != null) await requestCancellation(target);\n }\n };\n const answerDecision = async (text: string): Promise<boolean> => {\n if (decision == null) return false;\n const normalized = text\n .trim()\n .toLowerCase()\n .replace(/[.!?]$/g, '');\n const isDecisionAnswer = ['yes', 'no', 'approve', 'deny'].includes(normalized);\n if (pendingDecisions.size > 1) {\n if (\n isDecisionAnswer ||\n [...pendingDecisions.values()].some(\n (pending) => decisionAnswer(pending.choices, normalized) != null,\n )\n ) {\n await speakRefusal('Multiple actions need approval. Answer them in Final.');\n return true;\n }\n return false;\n }\n\n if (from == null) {\n if (!isDecisionAnswer) return false;\n await speakRefusal('This action needs approval in Final.');\n return true;\n }\n const choice = decisionAnswer(decision.choices, normalized);\n if (choice == null) {\n if (!['yes', 'no', 'approve', 'deny'].includes(normalized)) return false;\n await speakRefusal('Choose the exact option in Final.');\n return true;\n }\n const decisionId = decision.id;\n try {\n const result = await ctx.decisions.resolve({\n decisionId,\n outcome: choice.outcome,\n by: from,\n });\n if ('refused' in result) await reportRefusal(result.message);\n else {\n pendingDecisions.delete(decisionId);\n decision = pendingDecisions.values().next().value ?? null;\n if (!result.resolved) await reportRefusal('This decision is no longer waiting in Final.');\n }\n } catch {\n await reportRefusal(\n 'The approval could not be answered. Please try again or answer in Final.',\n );\n }\n return true;\n };\n const speak = async (\n text: AsyncIterable<string>,\n version: number,\n correlation: { utterance: string; turnId: string },\n ) => {\n let firstAudio = true;\n for await (const sentence of abortable(text, signal)) {\n while (refusalPlayback != null) await refusalPlayback;\n if (version !== generation || signal.aborted) break;\n const playbackVersion = playbackGeneration;\n const single = async function* () {\n yield sentence;\n };\n const speech = new AbortController();\n activeSpeech = speech;\n const speechSignal = AbortSignal.any([signal, speech.signal]);\n try {\n for await (const frame of abortable(\n ctx.media.synthesize(single(), {\n voice: options.voice,\n encoding: options.encoding,\n sampleRate: options.sampleRate,\n signal: speechSignal,\n }),\n speechSignal,\n )) {\n if (version !== generation || playbackVersion !== playbackGeneration) break;\n if (firstAudio) {\n firstAudio = false;\n trace('first.audio.out', correlation);\n }\n send(frame);\n }\n } catch (cause) {\n if (!speechSignal.aborted) throw cause;\n } finally {\n speech.abort();\n if (activeSpeech === speech) activeSpeech = null;\n }\n }\n };\n const work = (async () => {\n for await (const utterance of abortable(utterances.stream, signal)) {\n if (terminalRefusal) break;\n const version = ++generation;\n const { text, key, finalAt } = utterance;\n const origin = {\n place: options.place,\n ref: {\n externalKey: key,\n connectionId: connectionKey,\n utterance: key,\n utteranceFinalAt: finalAt,\n },\n };\n const accepting = { interrupted: false };\n turnLifecycle.accepting = accepting;\n const posted =\n from == null\n ? await ctx.conversation(conversation).input({\n content: [{ type: 'text', text }],\n origin,\n idempotencyKey: key,\n waitForAcceptance: true,\n principal: options.principal,\n label: `From ${options.principal.displayName ?? options.principal.id} in ${options.place.title ?? options.place.kind}`,\n })\n : await ctx.conversation(conversation).post({\n from,\n content: [{ type: 'text', text }],\n origin,\n idempotencyKey: key,\n waitForAcceptance: true,\n });\n if (turnLifecycle.accepting === accepting) turnLifecycle.accepting = null;\n if (signal.aborted || terminalRefusal) return;\n if (accepting.interrupted || version !== generation) {\n if (!('refused' in posted) && posted.turnId != null) {\n trace('post.accepted', { utterance: key, turnId: posted.turnId });\n await requestCancellation(posted.turnId);\n }\n continue;\n }\n if ('refused' in posted) {\n await reportRefusal(posted.message, { recoverable: false });\n continue;\n }\n if (posted.turnId == null) {\n const message = \"I couldn't start a voice reply for this request. Check the chat in Final.\";\n await reportRefusal(message);\n continue;\n }\n trace('post.accepted', { utterance: key, turnId: posted.turnId });\n activeTurn = true;\n turnLifecycle.activeId = posted.turnId;\n const sentences = createQueue<string>();\n const output = speak(sentences.stream, version, {\n utterance: key,\n turnId: posted.turnId,\n }).catch(fail);\n const observation = new AbortController();\n const observationSignal = AbortSignal.any([signal, observation.signal]);\n const stopObservation = () => observation.abort();\n microphoneLifecycle.stopObservation = stopObservation;\n const microphoneEnd = () => {\n if (decision == null) return;\n sentences.push('Continue this approval in Final.');\n observation.abort();\n };\n microphoneLifecycle.onEnd = microphoneEnd;\n let hasReply = false;\n let buffer = '';\n let first = true;\n const streamed = new Set<string>();\n const completed = new Set<string>();\n try {\n for await (const event of abortable(\n ctx.observe(conversation, { turnId: posted.turnId, signal: observationSignal }),\n observationSignal,\n )) {\n if (version !== generation) break;\n if (event.type === 'message.delta') {\n if (first) {\n first = false;\n trace('first.delta', { utterance: key, turnId: posted.turnId });\n }\n if (event.data.delta.type !== 'text' || typeof event.data.delta.text !== 'string')\n continue;\n if (event.data.delta.text.length > 0) streamed.add(event.data.messageId);\n if (event.data.delta.text.trim().length > 0) hasReply = true;\n buffer += event.data.delta.text;\n let boundary: RegExpExecArray | null;\n while ((boundary = /[!?](?:\\s|$)|(?<!\\d)\\.(?:\\s|$)|\\.\\s/.exec(buffer)) != null) {\n const end = boundary.index + boundary[0].length;\n sentences.push(buffer.slice(0, end));\n buffer = buffer.slice(end);\n }\n } else if (event.type === 'message.completed') {\n const id = event.data.message.id;\n if (!completed.has(id)) {\n if (!streamed.has(id)) buffer += textOf(event.data.message.content);\n if (buffer.trim().length > 0) {\n hasReply = true;\n sentences.push(buffer);\n }\n buffer = '';\n completed.add(id);\n }\n } else if (event.type === 'decision.created') {\n const choices =\n from == null || microphoneEnded.signal.aborted\n ? []\n : decisionChoices(event.data.request);\n decision = { id: event.data.decisionId, choices };\n pendingDecisions.set(decision.id, decision);\n sentences.push(\n `${decisionSummary(event.data.request)} ${\n pendingDecisions.size > 1\n ? 'Multiple actions need approval. Answer them in Final.'\n : from == null\n ? 'This action needs approval in Final.'\n : choices.length === 0\n ? \"I've sent that to you in Final.\"\n : choices.filter((choice) => choice.answer === 'approve').length === 1 &&\n choices.filter((choice) => choice.answer === 'deny').length <= 1\n ? `Your options are ${choices.map((choice) => choice.label).join(' or ')}. Do you approve this action? Say yes or no.`\n : 'Choose an option in Final.'\n }`,\n );\n if (microphoneEnded.signal.aborted) break;\n } else if (event.type === 'decision.resolved') {\n pendingDecisions.delete(event.data.decisionId);\n decision = pendingDecisions.values().next().value ?? null;\n } else if (event.type === 'turn.completed') {\n turnLifecycle.activeId = null;\n for (const message of event.data.messages ?? []) {\n if (!streamed.has(message.id) && !completed.has(message.id)) {\n const text = textOf(message.content);\n if (text.trim().length > 0) {\n hasReply = true;\n sentences.push(text);\n }\n completed.add(message.id);\n }\n }\n if (streamed.size === 0 && completed.size === 0 && event.data.content != null) {\n const text = textOf(event.data.content);\n if (text.trim().length > 0) {\n hasReply = true;\n sentences.push(text);\n }\n }\n if (buffer.trim().length > 0) {\n sentences.push(buffer);\n buffer = '';\n }\n const status = turnStatus(event.data);\n if (status != null) sentences.push(status);\n else if (!hasReply)\n sentences.push('The turn finished without a spoken reply. Open Final for details.');\n clearDecisions();\n break;\n }\n }\n if (version === generation && buffer.trim().length > 0) sentences.push(buffer);\n } finally {\n clearDecisions();\n observation.abort();\n if (microphoneLifecycle.stopObservation === stopObservation)\n microphoneLifecycle.stopObservation = null;\n if (microphoneLifecycle.onEnd === microphoneEnd) microphoneLifecycle.onEnd = null;\n sentences.close();\n await output;\n activeTurn = false;\n if (turnLifecycle.activeId === posted.turnId) turnLifecycle.activeId = null;\n send(JSON.stringify({ type: 'speech.end', final: false }));\n }\n }\n })().catch(fail);\n const transcription = (async () => {\n try {\n for await (const transcript of abortable(\n ctx.media.transcribe(transcriptionAudio(), {\n encoding: options.encoding,\n sampleRate: options.sampleRate,\n endpointing: true,\n signal: transcriptionSignal,\n }),\n transcriptionSignal,\n )) {\n send(JSON.stringify({ type: 'transcript', ...transcript }));\n if (!transcript.final && transcript.text.trim().length > 0 && !userSpeaking) {\n userSpeaking = true;\n await cancel();\n }\n if (transcript.final && transcript.text.trim().length === 0) userSpeaking = false;\n if (transcript.final && transcript.text.trim().length > 0) {\n const key = `${options.place.id}:${connectionKey}:${++utteranceNumber}`;\n const finalAt = Date.now();\n trace('utterance.final', { utterance: key, at: finalAt });\n if (!userSpeaking && (activeTurn || turnLifecycle.accepting != null) && decision == null)\n await cancel();\n userSpeaking = false;\n if (!(await answerDecision(transcript.text)) && !terminalRefusal) {\n if (decision != null) await cancel(true);\n utterances.push({ text: transcript.text, key, finalAt });\n }\n }\n }\n } finally {\n utterances.close();\n if (!microphoneInputClosed && !signal.aborted)\n fail(new Error('The transcription stream ended unexpectedly.'));\n microphoneEnded.abort();\n microphoneLifecycle.onEnd?.();\n }\n })().catch(fail);\n const aborted = Promise.withResolvers<void>();\n const abort = () => {\n aborted.resolve();\n closeAudio();\n utterances.close();\n generation += 1;\n };\n signal.addEventListener('abort', abort, { once: true });\n if (signal.aborted) abort();\n let cleanupFailure: Error | null = null;\n try {\n for await (const frame of abortable(\n socket.messages,\n AbortSignal.any([signal, microphoneEnded.signal]),\n )) {\n if (terminalRefusal) break;\n if (frame instanceof Uint8Array) {\n if (queuedAudioBytes + frame.byteLength > 2 * 1024 * 1024) {\n fail(new Error('Microphone audio exceeded the pending transcription limit.'));\n break;\n }\n if (recordingLimit != null) {\n for (let offset = 0; offset < frame.byteLength; ) {\n const length = Math.min(frame.byteLength - offset, recordingLimit - recordingSize);\n recording.push(frame.slice(offset, offset + length));\n recordingSize += length;\n offset += length;\n if (recordingSize === recordingLimit) await rotateRecording();\n }\n }\n if (!audioStarted) {\n audioStarted = true;\n trace('audio.in');\n }\n queuedAudioBytes += frame.byteLength;\n audio.push(frame);\n continue;\n }\n const control = z.object({ type: z.string() }).safeParse(parseJson(frame));\n if (control.success && control.data.type === 'cancel') await cancel(true);\n if (control.success && control.data.type === 'stop') {\n closeAudio();\n break;\n }\n }\n closeAudio();\n await Promise.race([transcription, aborted.promise]);\n await Promise.race([work, aborted.promise]);\n } catch (cause) {\n fail(cause);\n } finally {\n signal.removeEventListener('abort', abort);\n abort();\n if (signal.aborted) {\n await Promise.race([Promise.all([transcription, work]), aborted.promise]);\n } else await Promise.all([transcription, work]);\n try {\n await finishRecording();\n } catch (cause) {\n if (signal.aborted)\n cleanupFailure = cause instanceof Error ? cause : new Error('Recording attachment failed.');\n else fail(cause);\n }\n if (!signal.aborted) {\n send(JSON.stringify({ type: 'speech.end', final: true }));\n socket.close(1000);\n }\n }\n if (cleanupFailure != null) throw cleanupFailure;\n};\n\nconst oauthClient = async (\n ctx: ChannelReceiveContextIface<VoiceConfig>,\n config: VoiceConfig,\n): Promise<string> => {\n if (config.oauthClientId != null) return config.oauthClientId;\n const saved = z\n .string()\n .url()\n .safeParse(await ctx.store.get('voice-oauth-client'));\n if (saved.success) return saved.data;\n const endpoint = new URL(\n '/oauth2/register',\n config.oauthAuthorizeUrl ?? 'https://api.final.ai/oauth2/authorize',\n );\n const result = z.object({ client_id: z.string().url() }).parse(\n await responseJson(\n await ctx.credentials.fetch(endpoint, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n client_name: 'Final Voice Channel',\n redirect_uris: [`${channelBaseUrl(config)}/b/${ctx.binding.id}/oauth/callback`],\n grant_types: ['authorization_code', 'refresh_token'],\n response_types: ['code'],\n token_endpoint_auth_method: 'none',\n }),\n }),\n ),\n );\n await ctx.store.set('voice-oauth-client', result.client_id);\n return result.client_id;\n};\nexport const voiceChannel = (options: JsonObject = {}) => {\n const config = configSchema.parse(options);\n return defineChannel<VoiceConfig, JsonValue, JsonValue>({\n name: 'voice',\n config,\n installed: packageRef('@finalai/final-agent-provider/channels/voice'),\n credentials: { app: null },\n identity: {\n kind: 'redirect',\n start: async (args, ctx) =>\n startOAuth(\n {\n ...args,\n authorizeUrl: config.oauthAuthorizeUrl ?? 'https://api.final.ai/oauth2/authorize',\n clientId: await oauthClient(ctx, config),\n redirectUri: `${channelBaseUrl(config)}/b/${ctx.binding.id}/oauth/callback`,\n scope: 'offline_access',\n pkce: true,\n extra: { resource: config.oauthResource ?? 'https://api.final.ai/v1/connectors/mcp' },\n },\n ctx,\n ),\n },\n places: {\n kinds: ['live'],\n defaults: () => ({ create: 'direct-with-agent', replies: 'origin', audience: 'members' }),\n },\n receive: async () => ({ ignored: true, reason: 'voice-uses-stream' }),\n routes: [\n {\n method: 'GET',\n path: '/oauth/callback',\n handler: async (ctx) => {\n const saved = await takeOAuth(ctx.request, ctx);\n if (saved == null)\n return new Response('Invalid or expired link request', { status: 400 });\n const token = z.object({ access_token: z.string() }).parse(\n await responseJson(\n await ctx.credentials.fetch(\n config.oauthTokenUrl ?? 'https://api.final.ai/oauth2/token',\n {\n method: 'POST',\n body: new URLSearchParams({\n client_id: await oauthClient(ctx, config),\n resource: config.oauthResource ?? 'https://api.final.ai/v1/connectors/mcp',\n grant_type: 'authorization_code',\n code: saved.code,\n redirect_uri: saved.redirectUri,\n ...(saved.verifier == null ? {} : { code_verifier: saved.verifier }),\n }),\n },\n ),\n ),\n );\n const principal = userInfoSchema.parse(\n await responseJson(\n await ctx.credentials.fetch(\n config.oauthUserInfoUrl ?? 'https://api.final.ai/oauth2/userinfo',\n {\n headers: { authorization: `Bearer ${token.access_token}` },\n },\n ),\n ),\n );\n return confirmOAuth(\n { ticket: saved.ticket, expected: saved.principal, actual: principal },\n ctx,\n );\n },\n },\n {\n method: 'WS',\n path: '/stream',\n handler: async (ctx) => {\n const token =\n ctx.request.headers.get('authorization')?.replace(/^Bearer /, '') ??\n new URL(ctx.request.url).searchParams.get('access_token');\n if (token == null && ctx.authenticatedPrincipal == null)\n return new Response('A Final OAuth token is required.', { status: 401 });\n let authenticated = ctx.authenticatedPrincipal;\n if (authenticated == null && token != null) {\n try {\n authenticated = userInfoSchema.parse(\n await responseJson(\n await ctx.credentials.fetch(\n config.oauthUserInfoUrl ?? 'https://api.final.ai/oauth2/userinfo',\n {\n headers: { authorization: `Bearer ${token}` },\n },\n ),\n ),\n );\n } catch {\n return new Response('Invalid Final identity', { status: 401 });\n }\n }\n const principal = principalRefSchema.safeParse(authenticated);\n if (!principal.success) return new Response('Invalid Final identity', { status: 401 });\n ctx.socket.accept();\n const iterator = ctx.socket.messages[Symbol.asyncIterator]();\n const initial = await iterator.next();\n const start = startSchema.safeParse(\n typeof initial.value === 'string' ? parseJson(initial.value) : null,\n );\n if (\n !start.success ||\n start.data.principal.id !== principal.data.id ||\n start.data.principal.tenant !== principal.data.tenant\n ) {\n ctx.socket.close(1008, 'The speaker does not match the authenticated account.');\n return;\n }\n const remaining: AsyncIterable<string | Uint8Array> = {\n [Symbol.asyncIterator]: () => iterator,\n };\n await runVoiceConnection({\n ctx,\n socket: {\n ...ctx.socket,\n signal: ctx.socket.signal,\n messages: remaining,\n accept: (args) => ctx.socket.accept(args),\n send: (data) => ctx.socket.send(data),\n close: (code, reason) => ctx.socket.close(code, reason),\n },\n principal: principal.data,\n place: {\n kind: 'live',\n id: crypto.randomUUID(),\n ...(principal.data.tenant == null ? {} : { tenant: principal.data.tenant }),\n parent: {\n kind: 'person',\n id: principal.data.id,\n ...(principal.data.tenant == null ? {} : { tenant: principal.data.tenant }),\n },\n },\n encoding: start.data.encoding,\n sampleRate: start.data.sampleRate,\n voice: config.voice,\n recording: start.data.recording,\n });\n },\n },\n ],\n });\n};\n"],
5
+ "mappings": "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",
6
+ "names": ["options", "text"]
7
+ }
@@ -0,0 +1,5 @@
1
+ import { type JsonObject, type JsonValue } from '@finalai/agent-provider';
2
+ export declare const webhookChannel: (options?: JsonObject) => import("@finalai/agent-provider").ChannelDefinitionIface<{
3
+ secret: string;
4
+ signatureHeader: string;
5
+ }, JsonValue, JsonValue>;
@@ -0,0 +1,76 @@
1
+ import {
2
+ defineChannel,
3
+ jsonObjectSchema,
4
+ jsonValueSchema,
5
+ packageRef,
6
+ placeRefSchema
7
+ } from "@finalai/agent-provider";
8
+ import * as z from "zod";
9
+ import { contentBlocksOf } from "./content.mjs";
10
+ import { parseJson, receipt, verifyHmac } from "./channel-utils.mjs";
11
+ const configSchema = z.object({
12
+ secret: z.string().min(1).default("webhook-signing-secret"),
13
+ signatureHeader: z.string().default("x-final-signature")
14
+ });
15
+ const inputSchema = z.object({
16
+ place: placeRefSchema,
17
+ content: z.array(jsonObjectSchema).min(1),
18
+ label: z.string().min(1),
19
+ idempotencyKey: z.string().min(1),
20
+ context: z.array(z.string()).optional()
21
+ });
22
+ const webhookChannel = (options = {}) => {
23
+ const config = configSchema.parse(options);
24
+ return defineChannel({
25
+ name: "webhook",
26
+ config,
27
+ installed: packageRef("@finalai/final-agent-provider/channels/webhook"),
28
+ credentials: { app: null, secrets: [config.secret] },
29
+ identity: { kind: "none" },
30
+ places: { kinds: ["webhook"] },
31
+ routes: [
32
+ {
33
+ method: "POST",
34
+ path: "/webhook",
35
+ handler: async (ctx) => {
36
+ const body = await ctx.request.text();
37
+ const secret = await ctx.credentials.secret(config.secret);
38
+ const signature = ctx.request.headers.get(config.signatureHeader);
39
+ if (secret == null || signature == null || !await verifyHmac({ secret, body, signature: signature.replace(/^sha256=/, "") }))
40
+ return new Response(null, { status: 401 });
41
+ const parsed = inputSchema.safeParse(parseJson(body));
42
+ if (!parsed.success) return new Response("Invalid webhook input", { status: 400 });
43
+ const result = await ctx.receive(jsonValueSchema.parse(parsed.data));
44
+ return Response.json(result, {
45
+ status: "accepted" in result && !result.accepted ? 403 : 202
46
+ });
47
+ }
48
+ }
49
+ ],
50
+ receive: async (input, ctx) => {
51
+ const parsed = inputSchema.safeParse(input);
52
+ if (!parsed.success) return { ignored: true, reason: "invalid-input" };
53
+ const route = await ctx.places.resolve(parsed.data.place);
54
+ if (route.kind !== "connected")
55
+ return {
56
+ accepted: false,
57
+ refusal: {
58
+ refused: "unconnected",
59
+ connectUrl: route.connectUrl,
60
+ message: "Connect this webhook to a chat in Final."
61
+ }
62
+ };
63
+ return receipt(
64
+ await ctx.conversation(route.conversation).input({
65
+ ...parsed.data,
66
+ content: contentBlocksOf(parsed.data.content),
67
+ origin: { place: parsed.data.place }
68
+ })
69
+ );
70
+ }
71
+ });
72
+ };
73
+ export {
74
+ webhookChannel
75
+ };
76
+ //# sourceMappingURL=webhook.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/channels/webhook.ts"],
4
+ "sourcesContent": ["import {\n defineChannel,\n jsonObjectSchema,\n jsonValueSchema,\n packageRef,\n placeRefSchema,\n type JsonObject,\n type JsonValue,\n} from '@finalai/agent-provider';\nimport * as z from 'zod';\nimport { contentBlocksOf } from \"./content.mjs\";\nimport { parseJson, receipt, verifyHmac } from \"./channel-utils.mjs\";\n\nconst configSchema = z.object({\n secret: z.string().min(1).default('webhook-signing-secret'),\n signatureHeader: z.string().default('x-final-signature'),\n});\nconst inputSchema = z.object({\n place: placeRefSchema,\n content: z.array(jsonObjectSchema).min(1),\n label: z.string().min(1),\n idempotencyKey: z.string().min(1),\n context: z.array(z.string()).optional(),\n});\nexport const webhookChannel = (options: JsonObject = {}) => {\n const config = configSchema.parse(options);\n return defineChannel({\n name: 'webhook',\n config,\n installed: packageRef('@finalai/final-agent-provider/channels/webhook'),\n credentials: { app: null, secrets: [config.secret] },\n identity: { kind: 'none' },\n places: { kinds: ['webhook'] },\n routes: [\n {\n method: 'POST',\n path: '/webhook',\n handler: async (ctx) => {\n const body = await ctx.request.text();\n const secret = await ctx.credentials.secret(config.secret);\n const signature = ctx.request.headers.get(config.signatureHeader);\n if (\n secret == null ||\n signature == null ||\n !(await verifyHmac({ secret, body, signature: signature.replace(/^sha256=/, '') }))\n )\n return new Response(null, { status: 401 });\n const parsed = inputSchema.safeParse(parseJson(body));\n if (!parsed.success) return new Response('Invalid webhook input', { status: 400 });\n const result = await ctx.receive(jsonValueSchema.parse(parsed.data));\n return Response.json(result, {\n status: 'accepted' in result && !result.accepted ? 403 : 202,\n });\n },\n },\n ],\n receive: async (input: JsonValue, ctx) => {\n const parsed = inputSchema.safeParse(input);\n if (!parsed.success) return { ignored: true, reason: 'invalid-input' };\n const route = await ctx.places.resolve(parsed.data.place);\n if (route.kind !== 'connected')\n return {\n accepted: false,\n refusal: {\n refused: 'unconnected',\n connectUrl: route.connectUrl,\n message: 'Connect this webhook to a chat in Final.',\n },\n };\n return receipt(\n await ctx.conversation(route.conversation).input({\n ...parsed.data,\n content: contentBlocksOf(parsed.data.content),\n origin: { place: parsed.data.place },\n }),\n );\n },\n });\n};\n"],
5
+ "mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,YAAY,OAAO;AACnB,SAAS,uBAAuB;AAChC,SAAS,WAAW,SAAS,kBAAkB;AAE/C,MAAM,eAAe,EAAE,OAAO;AAAA,EAC5B,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,QAAQ,wBAAwB;AAAA,EAC1D,iBAAiB,EAAE,OAAO,EAAE,QAAQ,mBAAmB;AACzD,CAAC;AACD,MAAM,cAAc,EAAE,OAAO;AAAA,EAC3B,OAAO;AAAA,EACP,SAAS,EAAE,MAAM,gBAAgB,EAAE,IAAI,CAAC;AAAA,EACxC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACvB,gBAAgB,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAChC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AACxC,CAAC;AACM,MAAM,iBAAiB,CAAC,UAAsB,CAAC,MAAM;AAC1D,QAAM,SAAS,aAAa,MAAM,OAAO;AACzC,SAAO,cAAc;AAAA,IACnB,MAAM;AAAA,IACN;AAAA,IACA,WAAW,WAAW,gDAAgD;AAAA,IACtE,aAAa,EAAE,KAAK,MAAM,SAAS,CAAC,OAAO,MAAM,EAAE;AAAA,IACnD,UAAU,EAAE,MAAM,OAAO;AAAA,IACzB,QAAQ,EAAE,OAAO,CAAC,SAAS,EAAE;AAAA,IAC7B,QAAQ;AAAA,MACN;AAAA,QACE,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,SAAS,OAAO,QAAQ;AACtB,gBAAM,OAAO,MAAM,IAAI,QAAQ,KAAK;AACpC,gBAAM,SAAS,MAAM,IAAI,YAAY,OAAO,OAAO,MAAM;AACzD,gBAAM,YAAY,IAAI,QAAQ,QAAQ,IAAI,OAAO,eAAe;AAChE,cACE,UAAU,QACV,aAAa,QACb,CAAE,MAAM,WAAW,EAAE,QAAQ,MAAM,WAAW,UAAU,QAAQ,YAAY,EAAE,EAAE,CAAC;AAEjF,mBAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;AAC3C,gBAAM,SAAS,YAAY,UAAU,UAAU,IAAI,CAAC;AACpD,cAAI,CAAC,OAAO,QAAS,QAAO,IAAI,SAAS,yBAAyB,EAAE,QAAQ,IAAI,CAAC;AACjF,gBAAM,SAAS,MAAM,IAAI,QAAQ,gBAAgB,MAAM,OAAO,IAAI,CAAC;AACnE,iBAAO,SAAS,KAAK,QAAQ;AAAA,YAC3B,QAAQ,cAAc,UAAU,CAAC,OAAO,WAAW,MAAM;AAAA,UAC3D,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,IACA,SAAS,OAAO,OAAkB,QAAQ;AACxC,YAAM,SAAS,YAAY,UAAU,KAAK;AAC1C,UAAI,CAAC,OAAO,QAAS,QAAO,EAAE,SAAS,MAAM,QAAQ,gBAAgB;AACrE,YAAM,QAAQ,MAAM,IAAI,OAAO,QAAQ,OAAO,KAAK,KAAK;AACxD,UAAI,MAAM,SAAS;AACjB,eAAO;AAAA,UACL,UAAU;AAAA,UACV,SAAS;AAAA,YACP,SAAS;AAAA,YACT,YAAY,MAAM;AAAA,YAClB,SAAS;AAAA,UACX;AAAA,QACF;AACF,aAAO;AAAA,QACL,MAAM,IAAI,aAAa,MAAM,YAAY,EAAE,MAAM;AAAA,UAC/C,GAAG,OAAO;AAAA,UACV,SAAS,gBAAgB,OAAO,KAAK,OAAO;AAAA,UAC5C,QAAQ,EAAE,OAAO,OAAO,KAAK,MAAM;AAAA,QACrC,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF,CAAC;AACH;",
6
+ "names": []
7
+ }
@@ -21,7 +21,7 @@ const sandboxWireOf = {
21
21
  [AdvertisedSandboxWire.HandleRpc]: SandboxWire.HandleRpc,
22
22
  [AdvertisedSandboxWire.Mcp]: SandboxWire.Mcp
23
23
  };
24
- const FINAL_CLIENT_INFO = { name: "final-agent-session-service", version: "1.0.0" };
24
+ const FINAL_CLIENT_INFO = { name: "final-agent-sdk", version: "1.0.0" };
25
25
  const DEFAULT_CONNECT_TIMEOUT_MS = 1e4;
26
26
  const describeFromInitialize = (args) => {
27
27
  const session = args.response.capabilities?.session;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/harnesses/acp-describe.ts"],
4
- "sourcesContent": ["import {\n client,\n methods,\n PROTOCOL_VERSION,\n type InitializeResponse,\n type Stream,\n} from '@agentclientprotocol/sdk/experimental/v2';\nimport {\n advertisedFinalExtensions,\n AdvertisedSandboxWire,\n requireProtocolV2,\n watchInitializeVersion,\n} from '@finalai/acp-session';\nimport {\n CredentialDelivery,\n DEFAULT_PROMPT_WHILE_RUNNING,\n DEFAULT_SESSION_SURVIVES_DISCONNECT,\n InferenceCredentialKind,\n McpTransportKind,\n SandboxWire,\n type AcpTransport,\n type HarnessDescriptionIface,\n type JsonSchema,\n} from '@finalai/agent-provider';\n\n// Describes an arbitrary ACP v2 agent from its `initialize` response: MCP\n// transports from `capabilities.session.mcp`, fork from\n// `capabilities.session.fork`, and Final's optional extensions from\n// `_meta[\"final.ai\"].supports`. A plain agent with no Final extension still\n// gets a complete description (\"a harness that implements no Final\n// extension works completely\"). The sandbox wires are `_meta[\"final.ai\"].consumes`\n// when the agent declares them, else the MCP projection whenever it takes MCP\n// servers.\n\nconst sandboxWireOf = {\n [AdvertisedSandboxWire.HandleRpc]: SandboxWire.HandleRpc,\n [AdvertisedSandboxWire.Mcp]: SandboxWire.Mcp,\n} satisfies Record<AdvertisedSandboxWire, SandboxWire>;\n\nexport const FINAL_CLIENT_INFO = { name: 'final-agent-session-service', version: '1.0.0' };\nexport const DEFAULT_CONNECT_TIMEOUT_MS = 10_000;\n\nexport const describeFromInitialize = (args: {\n response: InitializeResponse;\n transports: AcpTransport[];\n configSchema: JsonSchema;\n}): HarnessDescriptionIface => {\n const session = args.response.capabilities?.session;\n const mcpCapabilities = session?.mcp;\n const mcp: McpTransportKind[] = [];\n if (mcpCapabilities?.acp != null) mcp.push(McpTransportKind.Acp);\n if (mcpCapabilities?.http != null) mcp.push(McpTransportKind.Http);\n if (mcpCapabilities?.stdio != null) mcp.push(McpTransportKind.Stdio);\n const supports = advertisedFinalExtensions(args.response);\n return {\n configSchema: args.configSchema,\n acp: { transports: args.transports, mcp },\n // Native tools of an unknown program are its own business; bound targets\n // reach it only as the sandbox MCP projection.\n baseTools: {},\n consumes:\n supports.consumes == null\n ? mcp.length > 0\n ? [SandboxWire.Mcp]\n : []\n : // The projection is an MCP server: it needs an MCP transport to arrive.\n [...new Set(supports.consumes.map((wire) => sandboxWireOf[wire]))].filter(\n (wire) => wire !== SandboxWire.Mcp || mcp.length > 0,\n ),\n inference: {\n vendors: [],\n kinds: [InferenceCredentialKind.HarnessOwned],\n delivery: CredentialDelivery.AtOpen,\n },\n supports: {\n observe: supports.observe,\n // session/resume with replayFrom is base ACP v2; an agent can opt out through _meta.\n resumeReplay: supports.resumeReplay ?? true,\n fork: session?.fork != null,\n authorMeta: supports.authorMeta,\n },\n // Nothing in a plain ACP v2 `initialize` declares these; assume the\n // conservative profile.\n promptWhileRunning: DEFAULT_PROMPT_WHILE_RUNNING,\n sessionSurvivesDisconnect: DEFAULT_SESSION_SURVIVES_DISCONNECT,\n };\n};\n\n// Static description used before the remote has been reached (Build's config\n// form): nothing about the remote is known yet.\nexport const unprobedDescription = (args: {\n transports: AcpTransport[];\n configSchema: JsonSchema;\n}): HarnessDescriptionIface => ({\n configSchema: args.configSchema,\n acp: { transports: args.transports, mcp: [] },\n baseTools: {},\n consumes: [],\n inference: {\n vendors: [],\n kinds: [InferenceCredentialKind.HarnessOwned],\n delivery: CredentialDelivery.AtOpen,\n },\n supports: { observe: false, resumeReplay: false, fork: false, authorMeta: false },\n promptWhileRunning: DEFAULT_PROMPT_WHILE_RUNNING,\n sessionSurvivesDisconnect: DEFAULT_SESSION_SURVIVES_DISCONNECT,\n});\n\n// Runs `initialize` over a fresh stream and closes the connection. Any reply\n// but ACP v2 is `unsupported_protocol_version`: nothing in the plane\n// negotiates or converts v1.\nexport const initializeOver = async (args: {\n stream: Stream;\n timeoutMs: number;\n}): Promise<InitializeResponse> => {\n const watch = watchInitializeVersion(args.stream);\n const connection = client().connect(watch.stream);\n const timeout = Promise.withResolvers<never>();\n const timer = setTimeout(\n () => timeout.reject(new Error(`initialize did not complete within ${args.timeoutMs} ms`)),\n args.timeoutMs,\n );\n try {\n return await Promise.race([\n requireProtocolV2({\n watch,\n initialize: connection.agent.request(methods.agent.initialize, {\n protocolVersion: PROTOCOL_VERSION,\n info: FINAL_CLIENT_INFO,\n capabilities: {},\n }),\n }),\n timeout.promise,\n ]);\n } finally {\n clearTimeout(timer);\n connection.close();\n }\n};\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import {\n client,\n methods,\n PROTOCOL_VERSION,\n type InitializeResponse,\n type Stream,\n} from '@agentclientprotocol/sdk/experimental/v2';\nimport {\n advertisedFinalExtensions,\n AdvertisedSandboxWire,\n requireProtocolV2,\n watchInitializeVersion,\n} from '@finalai/acp-session';\nimport {\n CredentialDelivery,\n DEFAULT_PROMPT_WHILE_RUNNING,\n DEFAULT_SESSION_SURVIVES_DISCONNECT,\n InferenceCredentialKind,\n McpTransportKind,\n SandboxWire,\n type AcpTransport,\n type HarnessDescriptionIface,\n type JsonSchema,\n} from '@finalai/agent-provider';\n\n// Describes an arbitrary ACP v2 agent from its `initialize` response: MCP\n// transports from `capabilities.session.mcp`, fork from\n// `capabilities.session.fork`, and Final's optional extensions from\n// `_meta[\"final.ai\"].supports`. A plain agent with no Final extension still\n// gets a complete description (\"a harness that implements no Final\n// extension works completely\"). The sandbox wires are `_meta[\"final.ai\"].consumes`\n// when the agent declares them, else the MCP projection whenever it takes MCP\n// servers.\n\nconst sandboxWireOf = {\n [AdvertisedSandboxWire.HandleRpc]: SandboxWire.HandleRpc,\n [AdvertisedSandboxWire.Mcp]: SandboxWire.Mcp,\n} satisfies Record<AdvertisedSandboxWire, SandboxWire>;\n\nexport const FINAL_CLIENT_INFO = { name: 'final-agent-sdk', version: '1.0.0' };\nexport const DEFAULT_CONNECT_TIMEOUT_MS = 10_000;\n\nexport const describeFromInitialize = (args: {\n response: InitializeResponse;\n transports: AcpTransport[];\n configSchema: JsonSchema;\n}): HarnessDescriptionIface => {\n const session = args.response.capabilities?.session;\n const mcpCapabilities = session?.mcp;\n const mcp: McpTransportKind[] = [];\n if (mcpCapabilities?.acp != null) mcp.push(McpTransportKind.Acp);\n if (mcpCapabilities?.http != null) mcp.push(McpTransportKind.Http);\n if (mcpCapabilities?.stdio != null) mcp.push(McpTransportKind.Stdio);\n const supports = advertisedFinalExtensions(args.response);\n return {\n configSchema: args.configSchema,\n acp: { transports: args.transports, mcp },\n // Native tools of an unknown program are its own business; bound targets\n // reach it only as the sandbox MCP projection.\n baseTools: {},\n consumes:\n supports.consumes == null\n ? mcp.length > 0\n ? [SandboxWire.Mcp]\n : []\n : // The projection is an MCP server: it needs an MCP transport to arrive.\n [...new Set(supports.consumes.map((wire) => sandboxWireOf[wire]))].filter(\n (wire) => wire !== SandboxWire.Mcp || mcp.length > 0,\n ),\n inference: {\n vendors: [],\n kinds: [InferenceCredentialKind.HarnessOwned],\n delivery: CredentialDelivery.AtOpen,\n },\n supports: {\n observe: supports.observe,\n // session/resume with replayFrom is base ACP v2; an agent can opt out through _meta.\n resumeReplay: supports.resumeReplay ?? true,\n fork: session?.fork != null,\n authorMeta: supports.authorMeta,\n },\n // Nothing in a plain ACP v2 `initialize` declares these; assume the\n // conservative profile.\n promptWhileRunning: DEFAULT_PROMPT_WHILE_RUNNING,\n sessionSurvivesDisconnect: DEFAULT_SESSION_SURVIVES_DISCONNECT,\n };\n};\n\n// Static description used before the remote has been reached (Build's config\n// form): nothing about the remote is known yet.\nexport const unprobedDescription = (args: {\n transports: AcpTransport[];\n configSchema: JsonSchema;\n}): HarnessDescriptionIface => ({\n configSchema: args.configSchema,\n acp: { transports: args.transports, mcp: [] },\n baseTools: {},\n consumes: [],\n inference: {\n vendors: [],\n kinds: [InferenceCredentialKind.HarnessOwned],\n delivery: CredentialDelivery.AtOpen,\n },\n supports: { observe: false, resumeReplay: false, fork: false, authorMeta: false },\n promptWhileRunning: DEFAULT_PROMPT_WHILE_RUNNING,\n sessionSurvivesDisconnect: DEFAULT_SESSION_SURVIVES_DISCONNECT,\n});\n\n// Runs `initialize` over a fresh stream and closes the connection. Any reply\n// but ACP v2 is `unsupported_protocol_version`: nothing in the plane\n// negotiates or converts v1.\nexport const initializeOver = async (args: {\n stream: Stream;\n timeoutMs: number;\n}): Promise<InitializeResponse> => {\n const watch = watchInitializeVersion(args.stream);\n const connection = client().connect(watch.stream);\n const timeout = Promise.withResolvers<never>();\n const timer = setTimeout(\n () => timeout.reject(new Error(`initialize did not complete within ${args.timeoutMs} ms`)),\n args.timeoutMs,\n );\n try {\n return await Promise.race([\n requireProtocolV2({\n watch,\n initialize: connection.agent.request(methods.agent.initialize, {\n protocolVersion: PROTOCOL_VERSION,\n info: FINAL_CLIENT_INFO,\n capabilities: {},\n }),\n }),\n timeout.promise,\n ]);\n } finally {\n clearTimeout(timer);\n connection.close();\n }\n};\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -6,12 +6,12 @@ var NanocodeEngine = /* @__PURE__ */ ((NanocodeEngine2) => {
6
6
  const MCP_HTTP_REASON = "Serverless harnesses reach MCP servers over HTTP; stdio servers need a host.";
7
7
  const BACKGROUND_REASON = "Background commands stop when the session goes idle and parks, or when it is cancelled.";
8
8
  const SUBAGENT_REASON = "Serverless sessions run one agent; there is no subagent runtime.";
9
- const IMAGE_REASON = "Images attached to a message are not sent to the serverless engines yet; image tool results are.";
9
+ const IMAGE_REASON = "The engines take JPEG, PNG, GIF and WebP.";
10
10
  const NANOCODE_FIDELITY = {
11
11
  ["codex" /* Codex */]: [
12
12
  {
13
13
  feature: "Plan mode",
14
- nanocode: "`update_plan` is always offered and shown as the session plan.",
14
+ nanocode: "`update_plan` is always offered and shown as the session plan, whose steps are the steps of a Goal when the session is its working chat.",
15
15
  remoteOnly: "Codex's own plan and collaboration modes, as your CLI configures them.",
16
16
  gone: {
17
17
  what: "Plan mode (read-only planning, plan-mode questions).",
@@ -82,7 +82,7 @@ const NANOCODE_FIDELITY = {
82
82
  },
83
83
  {
84
84
  feature: "Approval policies",
85
- nanocode: "When the definition's tool policy is silent, `untrusted` asks before `exec_command` and `apply_patch`; `on-request`, `on-failure` and `never` run without asking. With `approvalsReviewer: 'auto-review'`, a reviewer model (`gpt-6-luna`, falling back to the session's model) answers what `untrusted` would ask: low and medium risk run, a rejected action goes to the person with the reviewer's reason, and a critical one is denied.",
85
+ nanocode: "When the definition's tool policy is silent, `untrusted` asks before `exec_command` and `apply_patch`; `on-request`, `on-failure` and `never` run without asking. With `approvalsReviewer: 'auto-review'`, a reviewer model (`codex-auto-review` on a ChatGPT plan, `gpt-6-luna` otherwise, falling back to the session's model) answers what `untrusted` would ask: low and medium risk run, a rejected action goes to the person with the reviewer's reason, and a critical one is denied with codex-rs's rejection. The reviewer sees the person's messages, earlier tool calls, the definition's instructions and AGENTS.md, and `git status` before a command that could discard work. After 3 denials in a row or 10 of the last 50, the session asks the person until they allow a call, where Codex ends the turn.",
86
86
  remoteOnly: "The app-server's own policies together with `sandboxMode`.",
87
87
  gone: {
88
88
  what: "Escalation on failure and sandbox modes.",
@@ -92,10 +92,10 @@ const NANOCODE_FIDELITY = {
92
92
  },
93
93
  {
94
94
  feature: "Images in prompts",
95
- nanocode: "Not supported (`prompt.image` is absent); `view_image` and MCP image results reach the model.",
95
+ nanocode: "A message's JPEG, PNG, GIF and WebP images go to Codex as image input, a message sent while a turn runs joining that turn as Codex's does, as do `view_image` and MCP image results.",
96
96
  remoteOnly: "Prompt images, passed to Codex as image input.",
97
- gone: { what: "Images attached to a message.", reason: IMAGE_REASON },
98
- evidence: 'nanocode codex.test.ts "fidelity" (initialize capabilities)'
97
+ gone: { what: "Other image types.", reason: IMAGE_REASON },
98
+ evidence: 'nanocode codex.test.ts "fidelity" (advertises prompt images and sends them; moves an image prompt that arrives during a turn into it)'
99
99
  },
100
100
  {
101
101
  feature: "Compaction",
@@ -109,8 +109,8 @@ const NANOCODE_FIDELITY = {
109
109
  },
110
110
  {
111
111
  feature: "Native tool set",
112
- nanocode: "`exec_command`, `write_stdin` (shell), `apply_patch`, `view_image` (files) and `update_plan`, on the bound sandbox.",
113
- remoteOnly: "Codex's full tool set on your host (code mode, web search, its MCP and plugins).",
112
+ nanocode: "`exec_command`, `write_stdin` (shell), `apply_patch`, `view_image` (files) and `update_plan`, on the bound sandbox; `web__run`, OpenAI's web search, paid by the session's credential; and the workspace's AGENTS.md files as project instructions, from `$CODEX_HOME` and the repository root down to the working directory.",
113
+ remoteOnly: "Codex's full tool set on your host (code mode, image generation, its MCP and plugins).",
114
114
  gone: {
115
115
  what: "Every other Codex tool.",
116
116
  reason: "Serverless Codex has a fixed tool set that runs on the session's sandbox."
@@ -195,7 +195,7 @@ const NANOCODE_FIDELITY = {
195
195
  },
196
196
  {
197
197
  feature: "Permission modes",
198
- nanocode: "When the definition's tool policy is silent, `default` and `plan` ask before `Write` and `Bash`; `accept-edits` asks before `Bash` only; `bypass` never asks; `auto` has a reviewer model (`claude-sonnet-5`, falling back to the session's model) judge `Write` and `Bash`: low and medium risk run, a rejected action goes to the person with the reviewer's reason, and a critical one is denied.",
198
+ nanocode: "When the definition's tool policy is silent, `default` and `plan` ask before `Write` and `Bash`; `accept-edits` asks before `Bash` only; `bypass` never asks; `auto` has a reviewer model (`claude-sonnet-5-5`, falling back to the session's model) judge every `Write` and `Bash`: low and medium risk run, a rejected action goes to the person with the reviewer's reason, and a critical one is denied with its reason. The reviewer sees the person's messages, earlier tool calls, the definition's instructions and CLAUDE.md, and `git status` before a command that could discard work. After 3 denials in a row or 20 in all, the session asks the person until they allow a call.",
199
199
  remoteOnly: "Claude Code's permission modes and the allow and deny rules in your settings.",
200
200
  gone: {
201
201
  what: "Allow and deny rules from Claude Code settings.",
@@ -205,10 +205,10 @@ const NANOCODE_FIDELITY = {
205
205
  },
206
206
  {
207
207
  feature: "Images in prompts",
208
- nanocode: "Not supported (`prompt.image` is absent); MCP image results reach the model.",
208
+ nanocode: "A message's JPEG, PNG, GIF and WebP images go to Claude, as do `Read` of an image and MCP image results.",
209
209
  remoteOnly: "Prompt images, passed to Claude Code.",
210
- gone: { what: "Images attached to a message.", reason: IMAGE_REASON },
211
- evidence: 'nanocode claude.test.ts "fidelity" (initialize capabilities)'
210
+ gone: { what: "Other image types.", reason: IMAGE_REASON },
211
+ evidence: 'nanocode claude.test.ts "fidelity" (advertises prompt images and sends them)'
212
212
  },
213
213
  {
214
214
  feature: "Compaction",
@@ -222,13 +222,13 @@ const NANOCODE_FIDELITY = {
222
222
  },
223
223
  {
224
224
  feature: "Native tool set",
225
- nanocode: "`Bash`, `BashOutput` (shell), `Read`, `Write` (files), on the bound sandbox.",
226
- remoteOnly: "Claude Code's full tool set on your host (`Edit`, `Glob`, `Grep`, `Task`, `WebFetch`, `WebSearch`, \u2026).",
225
+ nanocode: "`Bash`, `BashOutput` (shell); Claude Code's own `Read`, `Edit` and `Write` (files), with its rule that `Edit` and an overwriting `Write` need the file read first; Claude Code's own `TaskCreate`, `TaskGet`, `TaskList` and `TaskUpdate`, a task list kept across invocations and shown as the session plan, whose steps are the steps of a Goal when the session is its working chat; `WebSearch`, Anthropic's, paid by the session's credential; and CLAUDE.md files as project instructions, from `~/.claude` and each directory down to the working directory. Like Claude Code since 2.1.283, it searches through `Bash`: no `Glob` or `Grep`.",
226
+ remoteOnly: "Claude Code's full tool set on your host (`TaskStop`, `WebFetch`, `NotebookEdit`, Bash's persistent working directory and background tasks, \u2026).",
227
227
  gone: {
228
- what: "`Edit`, `Glob`, `Grep`, `Task`, `TodoWrite`, `WebFetch`, `WebSearch`, `NotebookEdit`.",
229
- reason: "Serverless Claude has a minimal tool set; it searches and edits through the shell."
228
+ what: "`TaskStop`, `WebFetch`, `NotebookEdit`; Bash keeping its working directory between calls.",
229
+ reason: "Serverless Claude keeps Claude Code's core tools: it fetches pages through the shell, and each Bash command starts in the workspace."
230
230
  },
231
- evidence: 'nanocode claude.test.ts "runs Bash on the shell capability and asks under default" (exact list)'
231
+ evidence: 'nanocode claude.test.ts "runs Bash on the shell capability and asks under default" (exact list), "Read, Edit and Write", "keeps the task list as the session plan"'
232
232
  }
233
233
  ]
234
234
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/harnesses/fidelity.ts"],
4
- "sourcesContent": ["// Harness fidelity for the serverless nanocode harnesses per engine, what nanocode\n// keeps, what only the matching remote-* harness keeps (the user's own CLI through\n// @finalai/harness-runtime), and what is gone, with a one-line reason a user can read.\n// Measured from the code and tests named in `evidence`; FIDELITY.md renders these tables, and\n// the legacy import writes the matching one into each moved agent's review file.\n\nexport enum NanocodeEngine {\n Codex = 'codex',\n Claude = 'claude',\n}\n\nexport interface FidelityRowIface {\n feature: string;\n nanocode: string;\n remoteOnly: string;\n // Null when nothing is gone.\n gone: { what: string; reason: string } | null;\n // Where the behavior is proved (test names), or why it is read from source only.\n evidence: string;\n}\n\nconst MCP_HTTP_REASON =\n 'Serverless harnesses reach MCP servers over HTTP; stdio servers need a host.';\nconst BACKGROUND_REASON =\n 'Background commands stop when the session goes idle and parks, or when it is cancelled.';\nconst SUBAGENT_REASON = 'Serverless sessions run one agent; there is no subagent runtime.';\nconst IMAGE_REASON =\n 'Images attached to a message are not sent to the serverless engines yet; image tool results are.';\n\nexport const NANOCODE_FIDELITY = {\n [NanocodeEngine.Codex]: [\n {\n feature: 'Plan mode',\n nanocode: '`update_plan` is always offered and shown as the session plan.',\n remoteOnly: \"Codex's own plan and collaboration modes, as your CLI configures them.\",\n gone: {\n what: 'Plan mode (read-only planning, plan-mode questions).',\n reason: 'Serverless Codex has no plan mode; it shares its plan as a checklist instead.',\n },\n evidence: 'nanocode codex.test.ts \"applies a patch \u2026 and projects update_plan\", \"fidelity\"',\n },\n {\n feature: 'Hooks',\n nanocode: 'None (the engine compaction hook is refused by the host).',\n remoteOnly: 'Hooks and `notify` from your `CODEX_HOME` config.',\n gone: {\n what: 'Hooks.',\n reason: 'Hooks run commands on your machine; a serverless session has none of yours.',\n },\n evidence: 'source: nanocode host-bridge.ts beforeCompaction; runtime codex config.ts',\n },\n {\n feature: 'Slash commands',\n nanocode: 'None: a leading `/` reaches the model as text.',\n remoteOnly: 'None either: the app-server has no slash-command surface.',\n gone: {\n what: 'Slash commands.',\n reason: 'Slash commands belong to the Codex terminal; ask for the action in words.',\n },\n evidence: 'source: nanocode session.ts prompt path',\n },\n {\n feature: 'Custom subagents',\n nanocode: 'None (the host refuses engine subagent routes).',\n remoteOnly: \"Codex's multi-agent tools from your CLI.\",\n gone: { what: 'Subagents.', reason: SUBAGENT_REASON },\n evidence: 'nanocode codex.test.ts \"fidelity\" (no spawn_agent offered); host-bridge.ts',\n },\n {\n feature: 'Background tasks',\n nanocode:\n '`exec_command` returns a session id after its yield time and `write_stdin` polls it, with streamed output, while the session is live.',\n remoteOnly: 'Processes on your host, kept by the Codex app-server.',\n gone: {\n what: 'Processes outliving an idle (parked) or cancelled session.',\n reason: BACKGROUND_REASON,\n },\n evidence:\n 'nanocode codex.test.ts \"runs exec_command \u2026 under never\" (streaming); source only: tools/exec.ts (yield and polling)',\n },\n {\n feature: 'Codex rollouts and `thread/resume`',\n nanocode:\n 'The conversation persists in the Final session and continues after a reconnect; accepted turns are resubmitted.',\n remoteOnly: 'Rollout files in your `CODEX_HOME` and `thread/resume` of them.',\n gone: {\n what: 'Rollout files, and continuing an existing CLI thread.',\n reason: 'History lives in the Final session, not in Codex rollout files.',\n },\n evidence:\n 'nanocode codex.test.ts \"continues the conversation \u2026 after a takeover\", \"submits a turn \u2026 again after a takeover\"',\n },\n {\n feature: 'Session fork',\n nanocode: 'None (`supports.fork` is false).',\n remoteOnly: 'None (remote-codex does not fork either).',\n gone: { what: 'Forking a session.', reason: 'Codex sessions cannot be forked on Final.' },\n evidence: 'descriptions.test.ts; runtime codex session.ts refuses fork',\n },\n {\n feature: 'MCP servers',\n nanocode:\n \"The definition's HTTP and ACP connections as `mcp__<server>__<tool>` tools, with elicitation; tools only.\",\n remoteOnly: 'Your own `config.toml` servers, stdio included, beside the definition servers.',\n gone: { what: 'stdio servers, MCP resources and prompts.', reason: MCP_HTTP_REASON },\n evidence: 'nanocode conformance.test.ts (mcp over acp/http); codex.test.ts list_changed',\n },\n {\n feature: 'Approval policies',\n nanocode:\n \"When the definition's tool policy is silent, `untrusted` asks before `exec_command` and `apply_patch`; `on-request`, `on-failure` and `never` run without asking. With `approvalsReviewer: 'auto-review'`, a reviewer model (`gpt-6-luna`, falling back to the session's model) answers what `untrusted` would ask: low and medium risk run, a rejected action goes to the person with the reviewer's reason, and a critical one is denied.\",\n remoteOnly: \"The app-server's own policies together with `sandboxMode`.\",\n gone: {\n what: 'Escalation on failure and sandbox modes.',\n reason:\n \"Commands already run in the session's remote sandbox, so there is nothing to escalate to.\",\n },\n evidence:\n 'nanocode codex.test.ts \"asks before exec_command under untrusted\u2026\", \"fidelity\" (on-request, on-failure), \"\u2026under never\"; source only: tools/approvals.ts (untrusted apply_patch)',\n },\n {\n feature: 'Images in prompts',\n nanocode:\n 'Not supported (`prompt.image` is absent); `view_image` and MCP image results reach the model.',\n remoteOnly: 'Prompt images, passed to Codex as image input.',\n gone: { what: 'Images attached to a message.', reason: IMAGE_REASON },\n evidence: 'nanocode codex.test.ts \"fidelity\" (initialize capabilities)',\n },\n {\n feature: 'Compaction',\n nanocode:\n 'Automatic, managed by the engine near the context window and shown as a compaction update.',\n remoteOnly: \"Codex's own automatic compaction.\",\n gone: {\n what: 'Compacting on request.',\n reason: 'Context is compacted automatically; there is no manual compact.',\n },\n evidence:\n 'source only: the engine compaction policy; the scripted-provider tests do not reach it',\n },\n {\n feature: 'Native tool set',\n nanocode:\n '`exec_command`, `write_stdin` (shell), `apply_patch`, `view_image` (files) and `update_plan`, on the bound sandbox.',\n remoteOnly:\n \"Codex's full tool set on your host (code mode, web search, its MCP and plugins).\",\n gone: {\n what: 'Every other Codex tool.',\n reason: \"Serverless Codex has a fixed tool set that runs on the session's sandbox.\",\n },\n evidence: 'nanocode codex.test.ts \"fidelity\" (tool list); descriptions.test.ts',\n },\n ],\n [NanocodeEngine.Claude]: [\n {\n feature: 'Plan mode',\n nanocode:\n '`plan` adds a plan-mode section to the system prompt and asks before `Write` and `Bash`, as `default` does.',\n remoteOnly: \"Claude Code's enforced plan mode with `ExitPlanMode`.\",\n gone: {\n what: 'Enforced read-only planning.',\n reason: 'Plan mode on serverless Claude is guidance; edits still ask for approval.',\n },\n evidence: 'nanocode claude.test.ts \"fidelity\" (plan asks for Write and Bash; the write runs)',\n },\n {\n feature: 'Hooks',\n nanocode: 'None.',\n remoteOnly: 'Your hooks, through `settingSources: user, project, local`.',\n gone: {\n what: 'Hooks.',\n reason:\n 'Hooks come from your own Claude Code settings; run Claude Code on your host to keep them.',\n },\n evidence: 'runtime claude-code.test.ts (settings sources); nanocode has no hook host',\n },\n {\n feature: 'Slash commands',\n nanocode: 'None: a leading `/` reaches the model as text.',\n remoteOnly: 'Your user and project commands, through the same settings sources.',\n gone: {\n what: 'Slash commands.',\n reason:\n 'Slash commands are a Claude Code feature; run Claude Code on your host to keep them.',\n },\n evidence: 'source: nanocode session.ts prompt path',\n },\n {\n feature: 'Custom subagents',\n nanocode: 'None (no Task tool; the host refuses engine subagent routes).',\n remoteOnly: 'Your subagents, through the settings sources.',\n gone: { what: 'Subagents.', reason: SUBAGENT_REASON },\n evidence: 'nanocode claude.test.ts \"runs Bash \u2026 asks under default\" (exact tool list)',\n },\n {\n feature: 'Background tasks',\n nanocode:\n '`Bash` returns a session id for a running command and `BashOutput` polls it, while the session is live.',\n remoteOnly: 'Background shells on your host, kept by Claude Code.',\n gone: {\n what: 'Processes outliving an idle (parked) or cancelled session.',\n reason: BACKGROUND_REASON,\n },\n evidence: 'source: nanocode tools/claude.ts; polling is not exercised by a test',\n },\n {\n feature: 'Session resume',\n nanocode:\n 'The conversation persists in the Final session; accepted queued prompts resume when processing continues.',\n remoteOnly: 'Claude Code sessions in your `~/.claude`, resumed by id.',\n gone: {\n what: 'Claude Code session files and `--resume` of them.',\n reason: 'History lives in the Final session, not in Claude Code session files.',\n },\n evidence:\n 'nanocode claude.test.ts \"resumes an accepted queued prompt after the invocation stops\"',\n },\n {\n feature: 'Claude Code session fork',\n nanocode: 'None: `session/fork` is refused.',\n remoteOnly: 'Forking into a new Claude Code session (`forkSession`).',\n gone: {\n what: 'Forking a session.',\n reason: 'Forking needs Claude Code itself; use remote-claude-code to keep it.',\n },\n evidence:\n 'nanocode claude.test.ts \"fidelity\" (no session/fork); runtime claude-code.test.ts \"forks into a new Claude Code session id\"',\n },\n {\n feature: 'MCP servers',\n nanocode:\n \"The definition's HTTP and ACP connections as `mcp__<server>__<tool>` tools, with elicitation; tools only.\",\n remoteOnly:\n 'Your own servers, stdio included, with the definition servers added as `final.<name>`.',\n gone: { what: 'stdio servers, MCP resources and prompts.', reason: MCP_HTTP_REASON },\n evidence: 'nanocode conformance.test.ts (mcp over acp/http)',\n },\n {\n feature: 'Permission modes',\n nanocode:\n \"When the definition's tool policy is silent, `default` and `plan` ask before `Write` and `Bash`; `accept-edits` asks before `Bash` only; `bypass` never asks; `auto` has a reviewer model (`claude-sonnet-5`, falling back to the session's model) judge `Write` and `Bash`: low and medium risk run, a rejected action goes to the person with the reviewer's reason, and a critical one is denied.\",\n remoteOnly: \"Claude Code's permission modes and the allow and deny rules in your settings.\",\n gone: {\n what: 'Allow and deny rules from Claude Code settings.',\n reason:\n \"Claude Code settings are not imported; define allow, ask and deny rules in the agent definition's tool policy.\",\n },\n evidence: 'nanocode claude.test.ts \"fidelity\" (all four modes)',\n },\n {\n feature: 'Images in prompts',\n nanocode: 'Not supported (`prompt.image` is absent); MCP image results reach the model.',\n remoteOnly: 'Prompt images, passed to Claude Code.',\n gone: { what: 'Images attached to a message.', reason: IMAGE_REASON },\n evidence: 'nanocode claude.test.ts \"fidelity\" (initialize capabilities)',\n },\n {\n feature: 'Compaction',\n nanocode:\n 'Automatic in the engine; client updates are emitted when the engine reports compaction.',\n remoteOnly: \"Claude Code's automatic and manual (`/compact`) compaction.\",\n gone: {\n what: 'Compacting on request.',\n reason: 'The host exposes no manual compact action.',\n },\n evidence: 'source only: the engine compaction threshold; not reached by tests',\n },\n {\n feature: 'Native tool set',\n nanocode: '`Bash`, `BashOutput` (shell), `Read`, `Write` (files), on the bound sandbox.',\n remoteOnly:\n \"Claude Code's full tool set on your host (`Edit`, `Glob`, `Grep`, `Task`, `WebFetch`, `WebSearch`, \u2026).\",\n gone: {\n what: '`Edit`, `Glob`, `Grep`, `Task`, `TodoWrite`, `WebFetch`, `WebSearch`, `NotebookEdit`.',\n reason:\n 'Serverless Claude has a minimal tool set; it searches and edits through the shell.',\n },\n evidence:\n 'nanocode claude.test.ts \"runs Bash on the shell capability and asks under default\" (exact list)',\n },\n ],\n} satisfies Record<NanocodeEngine, FidelityRowIface[]>;\n\nexport const NANOCODE_HARNESS_OF = {\n [NanocodeEngine.Codex]: 'serverless-nanocode-codex',\n [NanocodeEngine.Claude]: 'serverless-nanocode-claude',\n} satisfies Record<NanocodeEngine, string>;\n\nexport const REMOTE_HARNESS_OF = {\n [NanocodeEngine.Codex]: 'remote-codex',\n [NanocodeEngine.Claude]: 'remote-claude-code',\n} satisfies Record<NanocodeEngine, string>;\n\nconst cell = (text: string): string => text.replaceAll('|', '\\\\|').replaceAll('\\n', ' ');\n\n// One engine's table as Markdown (FIDELITY.md and the import review use it as is).\nexport const renderFidelityTable = (engine: NanocodeEngine): string =>\n [\n `| | \\`${NANOCODE_HARNESS_OF[engine]}\\` keeps | only \\`${REMOTE_HARNESS_OF[engine]}\\` keeps | gone, and why |`,\n '|---|---|---|---|',\n ...NANOCODE_FIDELITY[engine].map(\n (row) =>\n `| ${cell(row.feature)} | ${cell(row.nanocode)} | ${cell(row.remoteOnly)} | ${\n row.gone == null ? 'nothing' : cell(`${row.gone.what} ${row.gone.reason}`)\n } |`,\n ),\n ].join('\\n');\n\n// Where each row is proved.\nexport const renderFidelityEvidence = (engine: NanocodeEngine): string =>\n NANOCODE_FIDELITY[engine].map((row) => `- ${row.feature}: ${row.evidence}`).join('\\n');\n\n// The generated part of FIDELITY.md for one engine.\nexport const fidelitySection = (engine: NanocodeEngine): string =>\n `${renderFidelityTable(engine)}\\n\\nEvidence:\\n\\n${renderFidelityEvidence(engine)}`;\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["// Harness fidelity for the serverless nanocode harnesses per engine, what nanocode\n// keeps, what requires the user's own remote CLI, and what is unavailable, with\n// a one-line reason a user can read.\n// Measured from the code and tests named in `evidence`; FIDELITY.md renders these tables, and\n// the legacy import writes the matching one into each moved agent's review file.\n\nexport enum NanocodeEngine {\n Codex = 'codex',\n Claude = 'claude',\n}\n\nexport interface FidelityRowIface {\n feature: string;\n nanocode: string;\n remoteOnly: string;\n // Null when nothing is gone.\n gone: { what: string; reason: string } | null;\n // Where the behavior is proved (test names), or why it is read from source only.\n evidence: string;\n}\n\nconst MCP_HTTP_REASON =\n 'Serverless harnesses reach MCP servers over HTTP; stdio servers need a host.';\nconst BACKGROUND_REASON =\n 'Background commands stop when the session goes idle and parks, or when it is cancelled.';\nconst SUBAGENT_REASON = 'Serverless sessions run one agent; there is no subagent runtime.';\nconst IMAGE_REASON = 'The engines take JPEG, PNG, GIF and WebP.';\n\nexport const NANOCODE_FIDELITY = {\n [NanocodeEngine.Codex]: [\n {\n feature: 'Plan mode',\n nanocode:\n '`update_plan` is always offered and shown as the session plan, whose steps are the steps of a Goal when the session is its working chat.',\n remoteOnly: \"Codex's own plan and collaboration modes, as your CLI configures them.\",\n gone: {\n what: 'Plan mode (read-only planning, plan-mode questions).',\n reason: 'Serverless Codex has no plan mode; it shares its plan as a checklist instead.',\n },\n evidence: 'nanocode codex.test.ts \"applies a patch \u2026 and projects update_plan\", \"fidelity\"',\n },\n {\n feature: 'Hooks',\n nanocode: 'None (the engine compaction hook is refused by the host).',\n remoteOnly: 'Hooks and `notify` from your `CODEX_HOME` config.',\n gone: {\n what: 'Hooks.',\n reason: 'Hooks run commands on your machine; a serverless session has none of yours.',\n },\n evidence: 'source: nanocode host-bridge.ts beforeCompaction; runtime codex config.ts',\n },\n {\n feature: 'Slash commands',\n nanocode: 'None: a leading `/` reaches the model as text.',\n remoteOnly: 'None either: the app-server has no slash-command surface.',\n gone: {\n what: 'Slash commands.',\n reason: 'Slash commands belong to the Codex terminal; ask for the action in words.',\n },\n evidence: 'source: nanocode session.ts prompt path',\n },\n {\n feature: 'Custom subagents',\n nanocode: 'None (the host refuses engine subagent routes).',\n remoteOnly: \"Codex's multi-agent tools from your CLI.\",\n gone: { what: 'Subagents.', reason: SUBAGENT_REASON },\n evidence: 'nanocode codex.test.ts \"fidelity\" (no spawn_agent offered); host-bridge.ts',\n },\n {\n feature: 'Background tasks',\n nanocode:\n '`exec_command` returns a session id after its yield time and `write_stdin` polls it, with streamed output, while the session is live.',\n remoteOnly: 'Processes on your host, kept by the Codex app-server.',\n gone: {\n what: 'Processes outliving an idle (parked) or cancelled session.',\n reason: BACKGROUND_REASON,\n },\n evidence:\n 'nanocode codex.test.ts \"runs exec_command \u2026 under never\" (streaming); source only: tools/exec.ts (yield and polling)',\n },\n {\n feature: 'Codex rollouts and `thread/resume`',\n nanocode:\n 'The conversation persists in the Final session and continues after a reconnect; accepted turns are resubmitted.',\n remoteOnly: 'Rollout files in your `CODEX_HOME` and `thread/resume` of them.',\n gone: {\n what: 'Rollout files, and continuing an existing CLI thread.',\n reason: 'History lives in the Final session, not in Codex rollout files.',\n },\n evidence:\n 'nanocode codex.test.ts \"continues the conversation \u2026 after a takeover\", \"submits a turn \u2026 again after a takeover\"',\n },\n {\n feature: 'Session fork',\n nanocode: 'None (`supports.fork` is false).',\n remoteOnly: 'None (remote-codex does not fork either).',\n gone: { what: 'Forking a session.', reason: 'Codex sessions cannot be forked on Final.' },\n evidence: 'descriptions.test.ts; runtime codex session.ts refuses fork',\n },\n {\n feature: 'MCP servers',\n nanocode:\n \"The definition's HTTP and ACP connections as `mcp__<server>__<tool>` tools, with elicitation; tools only.\",\n remoteOnly: 'Your own `config.toml` servers, stdio included, beside the definition servers.',\n gone: { what: 'stdio servers, MCP resources and prompts.', reason: MCP_HTTP_REASON },\n evidence: 'nanocode conformance.test.ts (mcp over acp/http); codex.test.ts list_changed',\n },\n {\n feature: 'Approval policies',\n nanocode:\n \"When the definition's tool policy is silent, `untrusted` asks before `exec_command` and `apply_patch`; `on-request`, `on-failure` and `never` run without asking. With `approvalsReviewer: 'auto-review'`, a reviewer model (`codex-auto-review` on a ChatGPT plan, `gpt-6-luna` otherwise, falling back to the session's model) answers what `untrusted` would ask: low and medium risk run, a rejected action goes to the person with the reviewer's reason, and a critical one is denied with codex-rs's rejection. The reviewer sees the person's messages, earlier tool calls, the definition's instructions and AGENTS.md, and `git status` before a command that could discard work. After 3 denials in a row or 10 of the last 50, the session asks the person until they allow a call, where Codex ends the turn.\",\n remoteOnly: \"The app-server's own policies together with `sandboxMode`.\",\n gone: {\n what: 'Escalation on failure and sandbox modes.',\n reason:\n \"Commands already run in the session's remote sandbox, so there is nothing to escalate to.\",\n },\n evidence:\n 'nanocode codex.test.ts \"asks before exec_command under untrusted\u2026\", \"fidelity\" (on-request, on-failure), \"\u2026under never\"; source only: tools/approvals.ts (untrusted apply_patch)',\n },\n {\n feature: 'Images in prompts',\n nanocode:\n \"A message's JPEG, PNG, GIF and WebP images go to Codex as image input, a message sent while a turn runs joining that turn as Codex's does, as do `view_image` and MCP image results.\",\n remoteOnly: 'Prompt images, passed to Codex as image input.',\n gone: { what: 'Other image types.', reason: IMAGE_REASON },\n evidence:\n 'nanocode codex.test.ts \"fidelity\" (advertises prompt images and sends them; moves an image prompt that arrives during a turn into it)',\n },\n {\n feature: 'Compaction',\n nanocode:\n 'Automatic, managed by the engine near the context window and shown as a compaction update.',\n remoteOnly: \"Codex's own automatic compaction.\",\n gone: {\n what: 'Compacting on request.',\n reason: 'Context is compacted automatically; there is no manual compact.',\n },\n evidence:\n 'source only: the engine compaction policy; the scripted-provider tests do not reach it',\n },\n {\n feature: 'Native tool set',\n nanocode:\n \"`exec_command`, `write_stdin` (shell), `apply_patch`, `view_image` (files) and `update_plan`, on the bound sandbox; `web__run`, OpenAI's web search, paid by the session's credential; and the workspace's AGENTS.md files as project instructions, from `$CODEX_HOME` and the repository root down to the working directory.\",\n remoteOnly:\n \"Codex's full tool set on your host (code mode, image generation, its MCP and plugins).\",\n gone: {\n what: 'Every other Codex tool.',\n reason: \"Serverless Codex has a fixed tool set that runs on the session's sandbox.\",\n },\n evidence: 'nanocode codex.test.ts \"fidelity\" (tool list); descriptions.test.ts',\n },\n ],\n [NanocodeEngine.Claude]: [\n {\n feature: 'Plan mode',\n nanocode:\n '`plan` adds a plan-mode section to the system prompt and asks before `Write` and `Bash`, as `default` does.',\n remoteOnly: \"Claude Code's enforced plan mode with `ExitPlanMode`.\",\n gone: {\n what: 'Enforced read-only planning.',\n reason: 'Plan mode on serverless Claude is guidance; edits still ask for approval.',\n },\n evidence: 'nanocode claude.test.ts \"fidelity\" (plan asks for Write and Bash; the write runs)',\n },\n {\n feature: 'Hooks',\n nanocode: 'None.',\n remoteOnly: 'Your hooks, through `settingSources: user, project, local`.',\n gone: {\n what: 'Hooks.',\n reason:\n 'Hooks come from your own Claude Code settings; run Claude Code on your host to keep them.',\n },\n evidence: 'runtime claude-code.test.ts (settings sources); nanocode has no hook host',\n },\n {\n feature: 'Slash commands',\n nanocode: 'None: a leading `/` reaches the model as text.',\n remoteOnly: 'Your user and project commands, through the same settings sources.',\n gone: {\n what: 'Slash commands.',\n reason:\n 'Slash commands are a Claude Code feature; run Claude Code on your host to keep them.',\n },\n evidence: 'source: nanocode session.ts prompt path',\n },\n {\n feature: 'Custom subagents',\n nanocode: 'None (no Task tool; the host refuses engine subagent routes).',\n remoteOnly: 'Your subagents, through the settings sources.',\n gone: { what: 'Subagents.', reason: SUBAGENT_REASON },\n evidence: 'nanocode claude.test.ts \"runs Bash \u2026 asks under default\" (exact tool list)',\n },\n {\n feature: 'Background tasks',\n nanocode:\n '`Bash` returns a session id for a running command and `BashOutput` polls it, while the session is live.',\n remoteOnly: 'Background shells on your host, kept by Claude Code.',\n gone: {\n what: 'Processes outliving an idle (parked) or cancelled session.',\n reason: BACKGROUND_REASON,\n },\n evidence: 'source: nanocode tools/claude.ts; polling is not exercised by a test',\n },\n {\n feature: 'Session resume',\n nanocode:\n 'The conversation persists in the Final session; accepted queued prompts resume when processing continues.',\n remoteOnly: 'Claude Code sessions in your `~/.claude`, resumed by id.',\n gone: {\n what: 'Claude Code session files and `--resume` of them.',\n reason: 'History lives in the Final session, not in Claude Code session files.',\n },\n evidence:\n 'nanocode claude.test.ts \"resumes an accepted queued prompt after the invocation stops\"',\n },\n {\n feature: 'Claude Code session fork',\n nanocode: 'None: `session/fork` is refused.',\n remoteOnly: 'Forking into a new Claude Code session (`forkSession`).',\n gone: {\n what: 'Forking a session.',\n reason: 'Forking needs Claude Code itself; use remote-claude-code to keep it.',\n },\n evidence:\n 'nanocode claude.test.ts \"fidelity\" (no session/fork); runtime claude-code.test.ts \"forks into a new Claude Code session id\"',\n },\n {\n feature: 'MCP servers',\n nanocode:\n \"The definition's HTTP and ACP connections as `mcp__<server>__<tool>` tools, with elicitation; tools only.\",\n remoteOnly:\n 'Your own servers, stdio included, with the definition servers added as `final.<name>`.',\n gone: { what: 'stdio servers, MCP resources and prompts.', reason: MCP_HTTP_REASON },\n evidence: 'nanocode conformance.test.ts (mcp over acp/http)',\n },\n {\n feature: 'Permission modes',\n nanocode:\n \"When the definition's tool policy is silent, `default` and `plan` ask before `Write` and `Bash`; `accept-edits` asks before `Bash` only; `bypass` never asks; `auto` has a reviewer model (`claude-sonnet-5-5`, falling back to the session's model) judge every `Write` and `Bash`: low and medium risk run, a rejected action goes to the person with the reviewer's reason, and a critical one is denied with its reason. The reviewer sees the person's messages, earlier tool calls, the definition's instructions and CLAUDE.md, and `git status` before a command that could discard work. After 3 denials in a row or 20 in all, the session asks the person until they allow a call.\",\n remoteOnly: \"Claude Code's permission modes and the allow and deny rules in your settings.\",\n gone: {\n what: 'Allow and deny rules from Claude Code settings.',\n reason:\n \"Claude Code settings are not imported; define allow, ask and deny rules in the agent definition's tool policy.\",\n },\n evidence: 'nanocode claude.test.ts \"fidelity\" (all four modes)',\n },\n {\n feature: 'Images in prompts',\n nanocode:\n \"A message's JPEG, PNG, GIF and WebP images go to Claude, as do `Read` of an image and MCP image results.\",\n remoteOnly: 'Prompt images, passed to Claude Code.',\n gone: { what: 'Other image types.', reason: IMAGE_REASON },\n evidence: 'nanocode claude.test.ts \"fidelity\" (advertises prompt images and sends them)',\n },\n {\n feature: 'Compaction',\n nanocode:\n 'Automatic in the engine; client updates are emitted when the engine reports compaction.',\n remoteOnly: \"Claude Code's automatic and manual (`/compact`) compaction.\",\n gone: {\n what: 'Compacting on request.',\n reason: 'The host exposes no manual compact action.',\n },\n evidence: 'source only: the engine compaction threshold; not reached by tests',\n },\n {\n feature: 'Native tool set',\n nanocode:\n \"`Bash`, `BashOutput` (shell); Claude Code's own `Read`, `Edit` and `Write` (files), with its rule that `Edit` and an overwriting `Write` need the file read first; Claude Code's own `TaskCreate`, `TaskGet`, `TaskList` and `TaskUpdate`, a task list kept across invocations and shown as the session plan, whose steps are the steps of a Goal when the session is its working chat; `WebSearch`, Anthropic's, paid by the session's credential; and CLAUDE.md files as project instructions, from `~/.claude` and each directory down to the working directory. Like Claude Code since 2.1.283, it searches through `Bash`: no `Glob` or `Grep`.\",\n remoteOnly:\n \"Claude Code's full tool set on your host (`TaskStop`, `WebFetch`, `NotebookEdit`, Bash's persistent working directory and background tasks, \u2026).\",\n gone: {\n what: '`TaskStop`, `WebFetch`, `NotebookEdit`; Bash keeping its working directory between calls.',\n reason:\n \"Serverless Claude keeps Claude Code's core tools: it fetches pages through the shell, and each Bash command starts in the workspace.\",\n },\n evidence:\n 'nanocode claude.test.ts \"runs Bash on the shell capability and asks under default\" (exact list), \"Read, Edit and Write\", \"keeps the task list as the session plan\"',\n },\n ],\n} satisfies Record<NanocodeEngine, FidelityRowIface[]>;\n\nexport const NANOCODE_HARNESS_OF = {\n [NanocodeEngine.Codex]: 'serverless-nanocode-codex',\n [NanocodeEngine.Claude]: 'serverless-nanocode-claude',\n} satisfies Record<NanocodeEngine, string>;\n\nexport const REMOTE_HARNESS_OF = {\n [NanocodeEngine.Codex]: 'remote-codex',\n [NanocodeEngine.Claude]: 'remote-claude-code',\n} satisfies Record<NanocodeEngine, string>;\n\nconst cell = (text: string): string => text.replaceAll('|', '\\\\|').replaceAll('\\n', ' ');\n\n// One engine's table as Markdown (FIDELITY.md and the import review use it as is).\nexport const renderFidelityTable = (engine: NanocodeEngine): string =>\n [\n `| | \\`${NANOCODE_HARNESS_OF[engine]}\\` keeps | only \\`${REMOTE_HARNESS_OF[engine]}\\` keeps | gone, and why |`,\n '|---|---|---|---|',\n ...NANOCODE_FIDELITY[engine].map(\n (row) =>\n `| ${cell(row.feature)} | ${cell(row.nanocode)} | ${cell(row.remoteOnly)} | ${\n row.gone == null ? 'nothing' : cell(`${row.gone.what} ${row.gone.reason}`)\n } |`,\n ),\n ].join('\\n');\n\n// Where each row is proved.\nexport const renderFidelityEvidence = (engine: NanocodeEngine): string =>\n NANOCODE_FIDELITY[engine].map((row) => `- ${row.feature}: ${row.evidence}`).join('\\n');\n\n// The generated part of FIDELITY.md for one engine.\nexport const fidelitySection = (engine: NanocodeEngine): string =>\n `${renderFidelityTable(engine)}\\n\\nEvidence:\\n\\n${renderFidelityEvidence(engine)}`;\n"],
5
+ "mappings": "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",
6
6
  "names": ["NanocodeEngine"]
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/harnesses/remote-url.ts"],
4
- "sourcesContent": ["import { urlJson, urlZod } from \"./config-fields.mjs\";\n\n// Shared by remote-claude-code, remote-codex and remote-pi: the Final harness\n// runtime (`final-harness serve --program <...>`, @finalai/harness-runtime) on\n// a host you run, dialed over WebSocket or Streamable HTTP with a hop secret.\n//\n// The program's configuration (instructions, skills, model, base-tool bindings,\n// targets, credentials) is provider-private: open() and resume()\n// register it with the runtime themselves (`PUT /v1/program-configs/<id>`) and\n// return an endpoint whose headers name the registration. The session service\n// dials that endpoint and speaks plain ACP, exactly as for any other harness.\n\nexport const REMOTE_URL_SCHEMES = ['ws', 'wss', 'http', 'https'] as const;\n\nexport const remoteUrlZod = () =>\n urlZod(REMOTE_URL_SCHEMES).refine(\n (value) => {\n const url = new URL(value);\n return (\n ['https:', 'wss:'].includes(url.protocol) ||\n ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname)\n );\n },\n { message: 'Remote harness URLs require TLS except on loopback' },\n );\n\nexport const remoteUrlJson = (title: string) => ({\n ...urlJson(title, REMOTE_URL_SCHEMES),\n pattern: '^((wss|https)://|(ws|http)://(localhost|127\\\\.0\\\\.0\\\\.1|\\\\[::1\\\\])(:|/|$))',\n});\n"],
4
+ "sourcesContent": ["import { urlJson, urlZod } from \"./config-fields.mjs\";\n\n// Shared by remote-claude-code, remote-codex and remote-pi: the Final harness\n// runtime (`final-harness serve --program <...>`) on\n// a host you run, dialed over WebSocket or Streamable HTTP with a hop secret.\n//\n// The program's configuration (instructions, skills, model, base-tool bindings,\n// targets, credentials) is provider-private: open() and resume()\n// register it with the runtime themselves (`PUT /v1/program-configs/<id>`) and\n// return an endpoint whose headers name the registration. The session service\n// dials that endpoint and speaks plain ACP, exactly as for any other harness.\n\nexport const REMOTE_URL_SCHEMES = ['ws', 'wss', 'http', 'https'] as const;\n\nexport const remoteUrlZod = () =>\n urlZod(REMOTE_URL_SCHEMES).refine(\n (value) => {\n const url = new URL(value);\n return (\n ['https:', 'wss:'].includes(url.protocol) ||\n ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname)\n );\n },\n { message: 'Remote harness URLs require TLS except on loopback' },\n );\n\nexport const remoteUrlJson = (title: string) => ({\n ...urlJson(title, REMOTE_URL_SCHEMES),\n pattern: '^((wss|https)://|(ws|http)://(localhost|127\\\\.0\\\\.0\\\\.1|\\\\[::1\\\\])(:|/|$))',\n});\n"],
5
5
  "mappings": "AAAA,SAAS,SAAS,cAAc;AAYzB,MAAM,qBAAqB,CAAC,MAAM,OAAO,QAAQ,OAAO;AAExD,MAAM,eAAe,MAC1B,OAAO,kBAAkB,EAAE;AAAA,EACzB,CAAC,UAAU;AACT,UAAM,MAAM,IAAI,IAAI,KAAK;AACzB,WACE,CAAC,UAAU,MAAM,EAAE,SAAS,IAAI,QAAQ,KACxC,CAAC,aAAa,aAAa,OAAO,EAAE,SAAS,IAAI,QAAQ;AAAA,EAE7D;AAAA,EACA,EAAE,SAAS,qDAAqD;AAClE;AAEK,MAAM,gBAAgB,CAAC,WAAmB;AAAA,EAC/C,GAAG,QAAQ,OAAO,kBAAkB;AAAA,EACpC,SAAS;AACX;",
6
6
  "names": []
7
7
  }