@finalai/final-agent-provider 0.6.2 → 0.7.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.
- package/HOSTING.md +4 -4
- package/README.md +94 -2
- package/dist/modules/authoring-final-channel.mjs +2 -2
- package/dist/modules/authoring-final-channel.mjs.map +2 -2
- package/dist/modules/channel-factories.d.mts +5 -7
- package/dist/modules/channel-factories.mjs +4 -50
- package/dist/modules/channel-factories.mjs.map +2 -2
- package/dist/modules/channels/channel-utils.d.mts +132 -0
- package/dist/modules/channels/channel-utils.mjs +445 -0
- package/dist/modules/channels/channel-utils.mjs.map +7 -0
- package/dist/modules/channels/delivery.d.mts +5 -5
- package/dist/modules/channels/delivery.mjs.map +1 -1
- package/dist/modules/channels/direct-messaging.d.mts +2 -2
- package/dist/modules/channels/direct-messaging.mjs.map +2 -2
- package/dist/modules/channels/discord-dispatch.d.mts +17 -0
- package/dist/modules/channels/discord-dispatch.mjs +139 -0
- package/dist/modules/channels/discord-dispatch.mjs.map +7 -0
- package/dist/modules/channels/discord-gateway.d.mts +19 -0
- package/dist/modules/channels/discord-gateway.mjs +233 -0
- package/dist/modules/channels/discord-gateway.mjs.map +7 -0
- package/dist/modules/channels/discord.d.mts +9 -0
- package/dist/modules/channels/discord.mjs +904 -0
- package/dist/modules/channels/discord.mjs.map +7 -0
- package/dist/modules/channels/fake-platform.d.mts +2 -7
- package/dist/modules/channels/fake-platform.mjs +2 -38
- package/dist/modules/channels/fake-platform.mjs.map +2 -2
- package/dist/modules/channels/github.d.mts +5 -0
- package/dist/modules/channels/github.mjs +720 -0
- package/dist/modules/channels/github.mjs.map +7 -0
- package/dist/modules/channels/group-messaging.d.mts +2 -2
- package/dist/modules/channels/group-messaging.mjs.map +2 -2
- package/dist/modules/channels/index.d.mts +7 -3
- package/dist/modules/channels/index.mjs +7 -3
- package/dist/modules/channels/index.mjs.map +2 -2
- package/dist/modules/channels/linear.d.mts +4 -0
- package/dist/modules/channels/linear.mjs +621 -0
- package/dist/modules/channels/linear.mjs.map +7 -0
- package/dist/modules/channels/messaging-channel.d.mts +3 -3
- package/dist/modules/channels/messaging-channel.mjs +2 -2
- package/dist/modules/channels/messaging-channel.mjs.map +2 -2
- package/dist/modules/channels/phone.d.mts +8 -0
- package/dist/modules/channels/phone.mjs +382 -0
- package/dist/modules/channels/phone.mjs.map +7 -0
- package/dist/modules/channels/projection.mjs.map +1 -1
- package/dist/modules/channels/reply-receipts.d.mts +31 -0
- package/dist/modules/channels/reply-receipts.mjs +63 -0
- package/dist/modules/channels/reply-receipts.mjs.map +7 -0
- package/dist/modules/channels/routing.d.mts +1 -0
- package/dist/modules/channels/routing.mjs +5 -1
- package/dist/modules/channels/routing.mjs.map +2 -2
- package/dist/modules/channels/slack.d.mts +7 -0
- package/dist/modules/channels/slack.mjs +1134 -0
- package/dist/modules/channels/slack.mjs.map +7 -0
- package/dist/modules/channels/voice-recording.d.mts +7 -0
- package/dist/modules/channels/voice-recording.mjs +49 -0
- package/dist/modules/channels/voice-recording.mjs.map +7 -0
- package/dist/modules/channels/voice.d.mts +33 -0
- package/dist/modules/channels/voice.mjs +851 -0
- package/dist/modules/channels/voice.mjs.map +7 -0
- package/dist/modules/channels/webhook.d.mts +5 -0
- package/dist/modules/channels/webhook.mjs +76 -0
- package/dist/modules/channels/webhook.mjs.map +7 -0
- package/dist/modules/harnesses/acp-describe.mjs +1 -1
- package/dist/modules/harnesses/acp-describe.mjs.map +2 -2
- package/dist/modules/harnesses/config-fields.d.mts +6 -1
- package/dist/modules/harnesses/config-fields.mjs +7 -0
- package/dist/modules/harnesses/config-fields.mjs.map +3 -3
- package/dist/modules/harnesses/fidelity.mjs +17 -17
- package/dist/modules/harnesses/fidelity.mjs.map +2 -2
- package/dist/modules/harnesses/remote-claude-code.d.mts +14 -2
- package/dist/modules/harnesses/remote-claude-code.mjs +5 -3
- package/dist/modules/harnesses/remote-claude-code.mjs.map +2 -2
- package/dist/modules/harnesses/remote-url.mjs.map +1 -1
- package/dist/modules/harnesses/serverless-nanocode-codex.d.mts +3 -1
- package/dist/modules/harnesses/serverless-nanocode-codex.mjs +6 -0
- package/dist/modules/harnesses/serverless-nanocode-codex.mjs.map +2 -2
- package/dist/modules/runtime/remote-host.mjs.map +1 -1
- package/dist/modules/sandboxes/build-scripts.mjs.map +1 -1
- package/dist/modules/sandboxes/paths.mjs.map +1 -1
- package/dist/modules/testing/harness.mjs +2 -2
- package/dist/modules/testing/harness.mjs.map +1 -1
- package/package.json +45 -19
- package/dist/modules/channels/address-context.d.mts +0 -7
- package/dist/modules/channels/address-context.mjs +0 -80
- package/dist/modules/channels/address-context.mjs.map +0 -7
- package/dist/modules/channels/agent-events.d.mts +0 -103
- package/dist/modules/channels/agent-events.mjs +0 -341
- package/dist/modules/channels/agent-events.mjs.map +0 -7
- package/dist/modules/channels/agent-schedules.d.mts +0 -25
- package/dist/modules/channels/agent-schedules.mjs +0 -96
- package/dist/modules/channels/agent-schedules.mjs.map +0 -7
- package/dist/modules/channels/service-input.d.mts +0 -22
- package/dist/modules/channels/service-input.mjs +0 -32
- package/dist/modules/channels/service-input.mjs.map +0 -7
|
@@ -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.\",\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.\",\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": "AAMO,IAAK,iBAAL,kBAAKA,oBAAL;AACL,EAAAA,gBAAA,WAAQ;AACR,EAAAA,gBAAA,YAAS;AAFC,SAAAA;AAAA,GAAA;AAeZ,MAAM,kBACJ;AACF,MAAM,oBACJ;AACF,MAAM,kBAAkB;AACxB,MAAM,
|
|
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
|
}
|
|
@@ -3,6 +3,13 @@ import * as z from 'zod';
|
|
|
3
3
|
import { ClaudePermissionMode, type CredentialsInput, type HarnessBindingOutputIface, type InstructionInput, type SecretRefIface } from "./config-fields.mjs";
|
|
4
4
|
import { type RemoteClientTlsInputIface } from "./remote-client-tls.mjs";
|
|
5
5
|
export declare const SPECIFIER = "@finalai/final-agent-provider/harnesses/remote-claude-code";
|
|
6
|
+
export declare const remotePermissionModeSchema: z.ZodEnum<{
|
|
7
|
+
readonly Default: ClaudePermissionMode.Default;
|
|
8
|
+
readonly Plan: ClaudePermissionMode.Plan;
|
|
9
|
+
readonly AcceptEdits: ClaudePermissionMode.AcceptEdits;
|
|
10
|
+
readonly Bypass: ClaudePermissionMode.Bypass;
|
|
11
|
+
}>;
|
|
12
|
+
export type RemoteClaudePermissionMode = z.output<typeof remotePermissionModeSchema>;
|
|
6
13
|
export declare const remoteClaudeCodeConfigSchema: z.ZodObject<{
|
|
7
14
|
url: z.ZodString;
|
|
8
15
|
tls: z.ZodOptional<z.ZodObject<{
|
|
@@ -50,7 +57,12 @@ export declare const remoteClaudeCodeConfigSchema: z.ZodObject<{
|
|
|
50
57
|
events: z.ZodDefault<z.ZodArray<z.ZodEnum<typeof import("@finalai/agent-provider").DynamicEvent>>>;
|
|
51
58
|
}, z.core.$strip>]>>;
|
|
52
59
|
}, z.core.$strip>;
|
|
53
|
-
permissionMode: z.ZodOptional<z.ZodEnum<
|
|
60
|
+
permissionMode: z.ZodOptional<z.ZodEnum<{
|
|
61
|
+
readonly Default: ClaudePermissionMode.Default;
|
|
62
|
+
readonly Plan: ClaudePermissionMode.Plan;
|
|
63
|
+
readonly AcceptEdits: ClaudePermissionMode.AcceptEdits;
|
|
64
|
+
readonly Bypass: ClaudePermissionMode.Bypass;
|
|
65
|
+
}>>;
|
|
54
66
|
instructions: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
55
67
|
role: z.ZodEnum<typeof import("@finalai/agent-provider").InstructionRole>;
|
|
56
68
|
content: z.ZodString;
|
|
@@ -69,7 +81,7 @@ export interface RemoteClaudeCodeConfigInputIface {
|
|
|
69
81
|
executable?: string;
|
|
70
82
|
model?: string;
|
|
71
83
|
credentials: CredentialsInput;
|
|
72
|
-
permissionMode?:
|
|
84
|
+
permissionMode?: RemoteClaudePermissionMode | `${RemoteClaudePermissionMode}`;
|
|
73
85
|
instructions?: InstructionInput[];
|
|
74
86
|
allowedTools?: string[];
|
|
75
87
|
}
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
} from "./remote-client-tls.mjs";
|
|
31
31
|
import { remoteUrlJson, remoteUrlZod } from "./remote-url.mjs";
|
|
32
32
|
const SPECIFIER = "@finalai/final-agent-provider/harnesses/remote-claude-code";
|
|
33
|
+
const remotePermissionModeSchema = z.nativeEnum(ClaudePermissionMode).exclude(["Auto"]);
|
|
33
34
|
const remoteClaudeCodeConfigSchema = z.strictObject({
|
|
34
35
|
url: remoteUrlZod(),
|
|
35
36
|
tls: remoteClientTlsSchema.optional(),
|
|
@@ -38,7 +39,7 @@ const remoteClaudeCodeConfigSchema = z.strictObject({
|
|
|
38
39
|
executable: z.string().min(1).optional(),
|
|
39
40
|
model: z.string().min(1).optional(),
|
|
40
41
|
credentials: credentialsZod(),
|
|
41
|
-
permissionMode:
|
|
42
|
+
permissionMode: remotePermissionModeSchema.optional(),
|
|
42
43
|
instructions: instructionsZod(),
|
|
43
44
|
allowedTools: z.array(z.string().min(1)).optional()
|
|
44
45
|
});
|
|
@@ -56,7 +57,7 @@ const REMOTE_CLAUDE_CODE_CONFIG_JSON_SCHEMA = configObjectJson({
|
|
|
56
57
|
...credentialsJson(),
|
|
57
58
|
default: { payer: "agent-owner", tryInOrder: [{ kind: "harness-owned" }] }
|
|
58
59
|
},
|
|
59
|
-
permissionMode: permissionModeJson(Object.values(
|
|
60
|
+
permissionMode: permissionModeJson(Object.values(remotePermissionModeSchema.enum)),
|
|
60
61
|
instructions: instructionsJson(),
|
|
61
62
|
allowedTools: { type: "array", title: "Allowed tools", items: { type: "string" } }
|
|
62
63
|
}
|
|
@@ -106,6 +107,7 @@ export {
|
|
|
106
107
|
REMOTE_CLAUDE_CODE_DESCRIPTION,
|
|
107
108
|
SPECIFIER,
|
|
108
109
|
remoteClaudeCode,
|
|
109
|
-
remoteClaudeCodeConfigSchema
|
|
110
|
+
remoteClaudeCodeConfigSchema,
|
|
111
|
+
remotePermissionModeSchema
|
|
110
112
|
};
|
|
111
113
|
//# sourceMappingURL=remote-claude-code.mjs.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/harnesses/remote-claude-code.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n AcpTransport,\n BaseCapability,\n BaseToolProvision,\n CredentialDelivery,\n InferenceCredentialKind,\n McpTransportKind,\n PromptWhileRunning,\n SandboxWire,\n type HarnessDescriptionIface,\n type JsonSchema,\n} from '@finalai/agent-provider';\nimport * as z from 'zod';\nimport {\n absolutePathJson,\n absolutePathZod,\n ClaudePermissionMode,\n configObjectJson,\n credentialsJson,\n credentialsZod,\n harnessBinding,\n instructionsJson,\n instructionsZod,\n permissionModeJson,\n secretRefJson,\n secretRefZod,\n type CredentialsInput,\n type HarnessBindingOutputIface,\n type InstructionInput,\n type SecretRefIface,\n} from \"./config-fields.mjs\";\nimport { ModelVendor } from \"./model-reference.mjs\";\nimport {\n remoteClientTlsJson,\n remoteClientTlsSchema,\n type RemoteClientTlsInputIface,\n} from \"./remote-client-tls.mjs\";\nimport { remoteUrlJson, remoteUrlZod } from \"./remote-url.mjs\";\n\n// Claude Code (Agent SDK) behind the Final ACP adapter, served by\n// `final-harness serve --program claude-code` on a host you run.\n\nexport const SPECIFIER = '@finalai/final-agent-provider/harnesses/remote-claude-code';\n\nexport const remoteClaudeCodeConfigSchema = z.strictObject({\n url: remoteUrlZod(),\n tls: remoteClientTlsSchema.optional(),\n hop: secretRefZod(),\n cwd: absolutePathZod().optional(),\n executable: z.string().min(1).optional(),\n model: z.string().min(1).optional(),\n credentials: credentialsZod(),\n permissionMode:
|
|
5
|
-
"mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,YAAY,OAAO;AACnB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAKK;AACP,SAAS,mBAAmB;AAC5B;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AACP,SAAS,eAAe,oBAAoB;AAKrC,MAAM,YAAY;
|
|
4
|
+
"sourcesContent": ["import {\n AcpTransport,\n BaseCapability,\n BaseToolProvision,\n CredentialDelivery,\n InferenceCredentialKind,\n McpTransportKind,\n PromptWhileRunning,\n SandboxWire,\n type HarnessDescriptionIface,\n type JsonSchema,\n} from '@finalai/agent-provider';\nimport * as z from 'zod';\nimport {\n absolutePathJson,\n absolutePathZod,\n ClaudePermissionMode,\n configObjectJson,\n credentialsJson,\n credentialsZod,\n harnessBinding,\n instructionsJson,\n instructionsZod,\n permissionModeJson,\n secretRefJson,\n secretRefZod,\n type CredentialsInput,\n type HarnessBindingOutputIface,\n type InstructionInput,\n type SecretRefIface,\n} from \"./config-fields.mjs\";\nimport { ModelVendor } from \"./model-reference.mjs\";\nimport {\n remoteClientTlsJson,\n remoteClientTlsSchema,\n type RemoteClientTlsInputIface,\n} from \"./remote-client-tls.mjs\";\nimport { remoteUrlJson, remoteUrlZod } from \"./remote-url.mjs\";\n\n// Claude Code (Agent SDK) behind the Final ACP adapter, served by\n// `final-harness serve --program claude-code` on a host you run.\n\nexport const SPECIFIER = '@finalai/final-agent-provider/harnesses/remote-claude-code';\n\n// Auto-review is the nanocode Claude engine's; the Claude Code CLI behind the adapter takes\n// the other modes.\nexport const remotePermissionModeSchema = z.nativeEnum(ClaudePermissionMode).exclude(['Auto']);\n\nexport type RemoteClaudePermissionMode = z.output<typeof remotePermissionModeSchema>;\n\nexport const remoteClaudeCodeConfigSchema = z.strictObject({\n url: remoteUrlZod(),\n tls: remoteClientTlsSchema.optional(),\n hop: secretRefZod(),\n cwd: absolutePathZod().optional(),\n executable: z.string().min(1).optional(),\n model: z.string().min(1).optional(),\n credentials: credentialsZod(),\n permissionMode: remotePermissionModeSchema.optional(),\n instructions: instructionsZod(),\n allowedTools: z.array(z.string().min(1)).optional(),\n});\n\nexport type RemoteClaudeCodeConfigIface = z.output<typeof remoteClaudeCodeConfigSchema>;\n\nexport const REMOTE_CLAUDE_CODE_CONFIG_JSON_SCHEMA: JsonSchema = configObjectJson({\n title: 'Claude Code (your host)',\n required: ['credentials', 'url', 'hop'],\n properties: {\n url: remoteUrlJson('Runtime URL'),\n tls: remoteClientTlsJson(),\n hop: secretRefJson(),\n cwd: absolutePathJson('Working directory on your host'),\n executable: { type: 'string', title: 'Installed executable on your host' },\n model: { $ref: 'final://schemas/model-picker' },\n credentials: {\n ...credentialsJson(),\n default: { payer: 'agent-owner', tryInOrder: [{ kind: 'harness-owned' }] },\n },\n permissionMode: permissionModeJson(Object.values(remotePermissionModeSchema.enum)),\n instructions: instructionsJson(),\n allowedTools: { type: 'array', title: 'Allowed tools', items: { type: 'string' } },\n },\n});\n\nexport const REMOTE_CLAUDE_CODE_DESCRIPTION: HarnessDescriptionIface = {\n configSchema: REMOTE_CLAUDE_CODE_CONFIG_JSON_SCHEMA,\n acp: {\n transports: [AcpTransport.Ws, AcpTransport.Http],\n mcp: [McpTransportKind.Acp, McpTransportKind.Http, McpTransportKind.Stdio],\n },\n // Claude Code's own tools on your host, switched through its allow-lists.\n baseTools: {\n [BaseCapability.Shell]: {\n provision: BaseToolProvision.HostDisableable,\n nativeTools: ['Bash', 'BashOutput', 'KillShell'],\n },\n [BaseCapability.Files]: {\n provision: BaseToolProvision.HostDisableable,\n nativeTools: ['Read', 'Write', 'Edit', 'Glob', 'Grep', 'NotebookEdit'],\n },\n },\n // Other bound targets reach it as the sandbox MCP projection.\n consumes: [SandboxWire.Mcp],\n inference: {\n vendors: [ModelVendor.Anthropic],\n kinds: [\n InferenceCredentialKind.HarnessOwned,\n InferenceCredentialKind.Subscription,\n InferenceCredentialKind.ApiKey,\n InferenceCredentialKind.Platform,\n ],\n delivery: CredentialDelivery.AtOpen,\n },\n supports: {\n observe: true,\n resumeReplay: true,\n fork: true,\n authorMeta: true,\n nativeToolPolicy: false,\n },\n promptWhileRunning: PromptWhileRunning.Inject,\n sessionSurvivesDisconnect: true,\n};\n\nexport interface RemoteClaudeCodeConfigInputIface {\n url: string;\n tls?: RemoteClientTlsInputIface;\n hop: SecretRefIface;\n cwd?: string;\n executable?: string;\n model?: string;\n credentials: CredentialsInput;\n permissionMode?: RemoteClaudePermissionMode | `${RemoteClaudePermissionMode}`;\n instructions?: InstructionInput[];\n allowedTools?: string[];\n}\n\nexport const remoteClaudeCode = (\n config: RemoteClaudeCodeConfigInputIface,\n): HarnessBindingOutputIface => harnessBinding(SPECIFIER, config);\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/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
|
|
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
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type HarnessDescriptionIface, type JsonSchema } from '@finalai/agent-provider';
|
|
2
2
|
import * as z from 'zod';
|
|
3
|
-
import { CodexApprovalPolicy, ReasoningLevel, type CredentialsInput, type HarnessBindingOutputIface, type InstructionInput, type SkillInput } from "./config-fields.mjs";
|
|
3
|
+
import { CodexApprovalPolicy, CodexApprovalsReviewer, ReasoningLevel, type CredentialsInput, type HarnessBindingOutputIface, type InstructionInput, type SkillInput } from "./config-fields.mjs";
|
|
4
4
|
export declare const SPECIFIER = "@finalai/final-agent-provider/harnesses/serverless-nanocode-codex";
|
|
5
5
|
export declare const CODEX_REASONING_LEVELS: readonly [ReasoningLevel.Low, ReasoningLevel.Medium, ReasoningLevel.High, ReasoningLevel.Max];
|
|
6
6
|
export declare const CODEX_BASE_TOOLS: HarnessDescriptionIface['baseTools'];
|
|
@@ -36,6 +36,7 @@ export declare const serverlessNanocodeCodexConfigSchema: z.ZodObject<{
|
|
|
36
36
|
}, z.core.$strip>]>>;
|
|
37
37
|
}, z.core.$strip>;
|
|
38
38
|
approvalPolicy: z.ZodDefault<z.ZodEnum<typeof CodexApprovalPolicy>>;
|
|
39
|
+
approvalsReviewer: z.ZodDefault<z.ZodEnum<typeof CodexApprovalsReviewer>>;
|
|
39
40
|
instructions: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
40
41
|
role: z.ZodEnum<typeof import("@finalai/agent-provider").InstructionRole>;
|
|
41
42
|
content: z.ZodString;
|
|
@@ -58,6 +59,7 @@ export interface ServerlessNanocodeCodexConfigInputIface {
|
|
|
58
59
|
reasoning?: (typeof CODEX_REASONING_LEVELS)[number] | `${(typeof CODEX_REASONING_LEVELS)[number]}`;
|
|
59
60
|
credentials: CredentialsInput;
|
|
60
61
|
approvalPolicy?: CodexApprovalPolicy | `${CodexApprovalPolicy}`;
|
|
62
|
+
approvalsReviewer?: CodexApprovalsReviewer | `${CodexApprovalsReviewer}`;
|
|
61
63
|
instructions?: InstructionInput[];
|
|
62
64
|
skills?: SkillInput[];
|
|
63
65
|
}
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import * as z from "zod";
|
|
13
13
|
import {
|
|
14
14
|
CodexApprovalPolicy,
|
|
15
|
+
CodexApprovalsReviewer,
|
|
15
16
|
configObjectJson,
|
|
16
17
|
credentialsJson,
|
|
17
18
|
credentialsZod,
|
|
@@ -50,6 +51,7 @@ const serverlessNanocodeCodexConfigSchema = z.strictObject({
|
|
|
50
51
|
reasoning: reasoningZod(CODEX_REASONING_LEVELS).optional(),
|
|
51
52
|
credentials: credentialsZod(),
|
|
52
53
|
approvalPolicy: z.nativeEnum(CodexApprovalPolicy).default(CodexApprovalPolicy.OnRequest),
|
|
54
|
+
approvalsReviewer: z.nativeEnum(CodexApprovalsReviewer).default(CodexApprovalsReviewer.User),
|
|
53
55
|
instructions: instructionsZod(),
|
|
54
56
|
skills: skillsZod()
|
|
55
57
|
});
|
|
@@ -64,6 +66,10 @@ const SERVERLESS_NANOCODE_CODEX_CONFIG_JSON_SCHEMA = configObjectJson({
|
|
|
64
66
|
Object.values(CodexApprovalPolicy),
|
|
65
67
|
CodexApprovalPolicy.OnRequest
|
|
66
68
|
),
|
|
69
|
+
approvalsReviewer: permissionModeJson(
|
|
70
|
+
Object.values(CodexApprovalsReviewer),
|
|
71
|
+
CodexApprovalsReviewer.User
|
|
72
|
+
),
|
|
67
73
|
instructions: instructionsJson(),
|
|
68
74
|
skills: skillsJson()
|
|
69
75
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/harnesses/serverless-nanocode-codex.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n AcpTransport,\n BaseCapability,\n BaseToolProvision,\n CredentialDelivery,\n HarnessTrustClass,\n InferenceCredentialKind,\n McpTransportKind,\n PromptWhileRunning,\n SandboxWire,\n type HarnessDescriptionIface,\n type JsonSchema,\n} from '@finalai/agent-provider';\nimport * as z from 'zod';\nimport {\n CodexApprovalPolicy,\n configObjectJson,\n credentialsJson,\n credentialsZod,\n harnessBinding,\n instructionsJson,\n instructionsZod,\n modelJson,\n modelZod,\n permissionModeJson,\n reasoningJson,\n ReasoningLevel,\n reasoningZod,\n skillsJson,\n skillsZod,\n type CredentialsInput,\n type HarnessBindingOutputIface,\n type InstructionInput,\n type SkillInput,\n} from \"./config-fields.mjs\";\nimport { ModelVendor } from \"./model-reference.mjs\";\n\n// nanocode's Codex engine (Rust \u2192 wasm) in the nanocode Durable Object script.\n// Responses Lite models only; ChatGPT subscriptions go through the session\n// service relay because chatgpt.com blocks Workers egress.\n\nexport const SPECIFIER = '@finalai/final-agent-provider/harnesses/serverless-nanocode-codex';\n\n// The final://schemas/reasoning fragment has no xhigh/ultra yet; nanocode's\n// policies accept the rest.\nexport const CODEX_REASONING_LEVELS = [\n ReasoningLevel.Low,\n ReasoningLevel.Medium,\n ReasoningLevel.High,\n ReasoningLevel.Max,\n] as const;\n\nexport const CODEX_BASE_TOOLS: HarnessDescriptionIface['baseTools'] = {\n [BaseCapability.Shell]: {\n provision: BaseToolProvision.Bound,\n nativeTools: ['exec_command', 'write_stdin'],\n },\n [BaseCapability.Files]: {\n provision: BaseToolProvision.Bound,\n nativeTools: ['apply_patch', 'view_image'],\n },\n};\n\nexport const serverlessNanocodeCodexConfigSchema = z.strictObject({\n model: modelZod().optional(),\n reasoning: reasoningZod(CODEX_REASONING_LEVELS).optional(),\n credentials: credentialsZod(),\n approvalPolicy: z.nativeEnum(CodexApprovalPolicy).default(CodexApprovalPolicy.OnRequest),\n instructions: instructionsZod(),\n skills: skillsZod(),\n});\n\nexport type ServerlessNanocodeCodexConfigIface = z.output<\n typeof serverlessNanocodeCodexConfigSchema\n>;\n\nexport const SERVERLESS_NANOCODE_CODEX_CONFIG_JSON_SCHEMA: JsonSchema = configObjectJson({\n required: ['credentials'],\n title: 'Codex on nanocode (serverless)',\n properties: {\n model: modelJson(),\n reasoning: reasoningJson(CODEX_REASONING_LEVELS),\n credentials: credentialsJson(),\n approvalPolicy: permissionModeJson(\n Object.values(CodexApprovalPolicy),\n CodexApprovalPolicy.OnRequest,\n ),\n instructions: instructionsJson(),\n skills: skillsJson(),\n },\n});\n\nexport const SERVERLESS_NANOCODE_CODEX_DESCRIPTION: HarnessDescriptionIface = {\n configSchema: SERVERLESS_NANOCODE_CODEX_CONFIG_JSON_SCHEMA,\n acp: {\n transports: [AcpTransport.Relay, AcpTransport.Ws],\n mcp: [McpTransportKind.Acp, McpTransportKind.Http],\n },\n baseTools: CODEX_BASE_TOOLS,\n consumes: [SandboxWire.HandleRpc],\n inference: {\n vendors: [ModelVendor.OpenAI],\n kinds: [\n InferenceCredentialKind.Subscription,\n InferenceCredentialKind.ApiKey,\n InferenceCredentialKind.Platform,\n ],\n delivery: CredentialDelivery.PerRequest,\n },\n // Final runs these on its own Workers.\n trust: HarnessTrustClass.FirstParty,\n supports: { observe: true, resumeReplay: true, fork: false, authorMeta: true },\n // Measured (agent-harness-workers nanocode/codex.test.ts \"steers a prompt that arrives\n // while the turn runs into it\"); open approvals are asked again with new ids after a\n // lost invocation, so no disconnect survival yet.\n promptWhileRunning: PromptWhileRunning.Inject,\n sessionSurvivesDisconnect: false,\n};\n\nexport interface ServerlessNanocodeCodexConfigInputIface {\n model?: string;\n reasoning?:\n | (typeof CODEX_REASONING_LEVELS)[number]\n | `${(typeof CODEX_REASONING_LEVELS)[number]}`;\n credentials: CredentialsInput;\n approvalPolicy?: CodexApprovalPolicy | `${CodexApprovalPolicy}`;\n instructions?: InstructionInput[];\n skills?: SkillInput[];\n}\n\nexport const serverlessNanocodeCodex = (\n config: ServerlessNanocodeCodexConfigInputIface,\n): HarnessBindingOutputIface => harnessBinding(SPECIFIER, config);\n"],
|
|
5
|
-
"mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,YAAY,OAAO;AACnB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAKK;AACP,SAAS,mBAAmB;AAMrB,MAAM,YAAY;AAIlB,MAAM,yBAAyB;AAAA,EACpC,eAAe;AAAA,EACf,eAAe;AAAA,EACf,eAAe;AAAA,EACf,eAAe;AACjB;AAEO,MAAM,mBAAyD;AAAA,EACpE,CAAC,eAAe,KAAK,GAAG;AAAA,IACtB,WAAW,kBAAkB;AAAA,IAC7B,aAAa,CAAC,gBAAgB,aAAa;AAAA,EAC7C;AAAA,EACA,CAAC,eAAe,KAAK,GAAG;AAAA,IACtB,WAAW,kBAAkB;AAAA,IAC7B,aAAa,CAAC,eAAe,YAAY;AAAA,EAC3C;AACF;AAEO,MAAM,sCAAsC,EAAE,aAAa;AAAA,EAChE,OAAO,SAAS,EAAE,SAAS;AAAA,EAC3B,WAAW,aAAa,sBAAsB,EAAE,SAAS;AAAA,EACzD,aAAa,eAAe;AAAA,EAC5B,gBAAgB,EAAE,WAAW,mBAAmB,EAAE,QAAQ,oBAAoB,SAAS;AAAA,EACvF,cAAc,gBAAgB;AAAA,EAC9B,QAAQ,UAAU;AACpB,CAAC;AAMM,MAAM,+CAA2D,iBAAiB;AAAA,EACvF,UAAU,CAAC,aAAa;AAAA,EACxB,OAAO;AAAA,EACP,YAAY;AAAA,IACV,OAAO,UAAU;AAAA,IACjB,WAAW,cAAc,sBAAsB;AAAA,IAC/C,aAAa,gBAAgB;AAAA,IAC7B,gBAAgB;AAAA,MACd,OAAO,OAAO,mBAAmB;AAAA,MACjC,oBAAoB;AAAA,IACtB;AAAA,IACA,cAAc,iBAAiB;AAAA,IAC/B,QAAQ,WAAW;AAAA,EACrB;AACF,CAAC;AAEM,MAAM,wCAAiE;AAAA,EAC5E,cAAc;AAAA,EACd,KAAK;AAAA,IACH,YAAY,CAAC,aAAa,OAAO,aAAa,EAAE;AAAA,IAChD,KAAK,CAAC,iBAAiB,KAAK,iBAAiB,IAAI;AAAA,EACnD;AAAA,EACA,WAAW;AAAA,EACX,UAAU,CAAC,YAAY,SAAS;AAAA,EAChC,WAAW;AAAA,IACT,SAAS,CAAC,YAAY,MAAM;AAAA,IAC5B,OAAO;AAAA,MACL,wBAAwB;AAAA,MACxB,wBAAwB;AAAA,MACxB,wBAAwB;AAAA,IAC1B;AAAA,IACA,UAAU,mBAAmB;AAAA,EAC/B;AAAA;AAAA,EAEA,OAAO,kBAAkB;AAAA,EACzB,UAAU,EAAE,SAAS,MAAM,cAAc,MAAM,MAAM,OAAO,YAAY,KAAK;AAAA;AAAA;AAAA;AAAA,EAI7E,oBAAoB,mBAAmB;AAAA,EACvC,2BAA2B;AAC7B;
|
|
4
|
+
"sourcesContent": ["import {\n AcpTransport,\n BaseCapability,\n BaseToolProvision,\n CredentialDelivery,\n HarnessTrustClass,\n InferenceCredentialKind,\n McpTransportKind,\n PromptWhileRunning,\n SandboxWire,\n type HarnessDescriptionIface,\n type JsonSchema,\n} from '@finalai/agent-provider';\nimport * as z from 'zod';\nimport {\n CodexApprovalPolicy,\n CodexApprovalsReviewer,\n configObjectJson,\n credentialsJson,\n credentialsZod,\n harnessBinding,\n instructionsJson,\n instructionsZod,\n modelJson,\n modelZod,\n permissionModeJson,\n reasoningJson,\n ReasoningLevel,\n reasoningZod,\n skillsJson,\n skillsZod,\n type CredentialsInput,\n type HarnessBindingOutputIface,\n type InstructionInput,\n type SkillInput,\n} from \"./config-fields.mjs\";\nimport { ModelVendor } from \"./model-reference.mjs\";\n\n// nanocode's Codex engine (Rust \u2192 wasm) in the nanocode Durable Object script.\n// Responses Lite models only; ChatGPT subscriptions go through the session\n// service relay because chatgpt.com blocks Workers egress.\n\nexport const SPECIFIER = '@finalai/final-agent-provider/harnesses/serverless-nanocode-codex';\n\n// The final://schemas/reasoning fragment has no xhigh/ultra yet; nanocode's\n// policies accept the rest.\nexport const CODEX_REASONING_LEVELS = [\n ReasoningLevel.Low,\n ReasoningLevel.Medium,\n ReasoningLevel.High,\n ReasoningLevel.Max,\n] as const;\n\nexport const CODEX_BASE_TOOLS: HarnessDescriptionIface['baseTools'] = {\n [BaseCapability.Shell]: {\n provision: BaseToolProvision.Bound,\n nativeTools: ['exec_command', 'write_stdin'],\n },\n [BaseCapability.Files]: {\n provision: BaseToolProvision.Bound,\n nativeTools: ['apply_patch', 'view_image'],\n },\n};\n\nexport const serverlessNanocodeCodexConfigSchema = z.strictObject({\n model: modelZod().optional(),\n reasoning: reasoningZod(CODEX_REASONING_LEVELS).optional(),\n credentials: credentialsZod(),\n approvalPolicy: z.nativeEnum(CodexApprovalPolicy).default(CodexApprovalPolicy.OnRequest),\n approvalsReviewer: z.nativeEnum(CodexApprovalsReviewer).default(CodexApprovalsReviewer.User),\n instructions: instructionsZod(),\n skills: skillsZod(),\n});\n\nexport type ServerlessNanocodeCodexConfigIface = z.output<\n typeof serverlessNanocodeCodexConfigSchema\n>;\n\nexport const SERVERLESS_NANOCODE_CODEX_CONFIG_JSON_SCHEMA: JsonSchema = configObjectJson({\n required: ['credentials'],\n title: 'Codex on nanocode (serverless)',\n properties: {\n model: modelJson(),\n reasoning: reasoningJson(CODEX_REASONING_LEVELS),\n credentials: credentialsJson(),\n approvalPolicy: permissionModeJson(\n Object.values(CodexApprovalPolicy),\n CodexApprovalPolicy.OnRequest,\n ),\n approvalsReviewer: permissionModeJson(\n Object.values(CodexApprovalsReviewer),\n CodexApprovalsReviewer.User,\n ),\n instructions: instructionsJson(),\n skills: skillsJson(),\n },\n});\n\nexport const SERVERLESS_NANOCODE_CODEX_DESCRIPTION: HarnessDescriptionIface = {\n configSchema: SERVERLESS_NANOCODE_CODEX_CONFIG_JSON_SCHEMA,\n acp: {\n transports: [AcpTransport.Relay, AcpTransport.Ws],\n mcp: [McpTransportKind.Acp, McpTransportKind.Http],\n },\n baseTools: CODEX_BASE_TOOLS,\n consumes: [SandboxWire.HandleRpc],\n inference: {\n vendors: [ModelVendor.OpenAI],\n kinds: [\n InferenceCredentialKind.Subscription,\n InferenceCredentialKind.ApiKey,\n InferenceCredentialKind.Platform,\n ],\n delivery: CredentialDelivery.PerRequest,\n },\n // Final runs these on its own Workers.\n trust: HarnessTrustClass.FirstParty,\n supports: { observe: true, resumeReplay: true, fork: false, authorMeta: true },\n // Measured (agent-harness-workers nanocode/codex.test.ts \"steers a prompt that arrives\n // while the turn runs into it\"); open approvals are asked again with new ids after a\n // lost invocation, so no disconnect survival yet.\n promptWhileRunning: PromptWhileRunning.Inject,\n sessionSurvivesDisconnect: false,\n};\n\nexport interface ServerlessNanocodeCodexConfigInputIface {\n model?: string;\n reasoning?:\n | (typeof CODEX_REASONING_LEVELS)[number]\n | `${(typeof CODEX_REASONING_LEVELS)[number]}`;\n credentials: CredentialsInput;\n approvalPolicy?: CodexApprovalPolicy | `${CodexApprovalPolicy}`;\n approvalsReviewer?: CodexApprovalsReviewer | `${CodexApprovalsReviewer}`;\n instructions?: InstructionInput[];\n skills?: SkillInput[];\n}\n\nexport const serverlessNanocodeCodex = (\n config: ServerlessNanocodeCodexConfigInputIface,\n): HarnessBindingOutputIface => harnessBinding(SPECIFIER, config);\n"],
|
|
5
|
+
"mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,YAAY,OAAO;AACnB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAKK;AACP,SAAS,mBAAmB;AAMrB,MAAM,YAAY;AAIlB,MAAM,yBAAyB;AAAA,EACpC,eAAe;AAAA,EACf,eAAe;AAAA,EACf,eAAe;AAAA,EACf,eAAe;AACjB;AAEO,MAAM,mBAAyD;AAAA,EACpE,CAAC,eAAe,KAAK,GAAG;AAAA,IACtB,WAAW,kBAAkB;AAAA,IAC7B,aAAa,CAAC,gBAAgB,aAAa;AAAA,EAC7C;AAAA,EACA,CAAC,eAAe,KAAK,GAAG;AAAA,IACtB,WAAW,kBAAkB;AAAA,IAC7B,aAAa,CAAC,eAAe,YAAY;AAAA,EAC3C;AACF;AAEO,MAAM,sCAAsC,EAAE,aAAa;AAAA,EAChE,OAAO,SAAS,EAAE,SAAS;AAAA,EAC3B,WAAW,aAAa,sBAAsB,EAAE,SAAS;AAAA,EACzD,aAAa,eAAe;AAAA,EAC5B,gBAAgB,EAAE,WAAW,mBAAmB,EAAE,QAAQ,oBAAoB,SAAS;AAAA,EACvF,mBAAmB,EAAE,WAAW,sBAAsB,EAAE,QAAQ,uBAAuB,IAAI;AAAA,EAC3F,cAAc,gBAAgB;AAAA,EAC9B,QAAQ,UAAU;AACpB,CAAC;AAMM,MAAM,+CAA2D,iBAAiB;AAAA,EACvF,UAAU,CAAC,aAAa;AAAA,EACxB,OAAO;AAAA,EACP,YAAY;AAAA,IACV,OAAO,UAAU;AAAA,IACjB,WAAW,cAAc,sBAAsB;AAAA,IAC/C,aAAa,gBAAgB;AAAA,IAC7B,gBAAgB;AAAA,MACd,OAAO,OAAO,mBAAmB;AAAA,MACjC,oBAAoB;AAAA,IACtB;AAAA,IACA,mBAAmB;AAAA,MACjB,OAAO,OAAO,sBAAsB;AAAA,MACpC,uBAAuB;AAAA,IACzB;AAAA,IACA,cAAc,iBAAiB;AAAA,IAC/B,QAAQ,WAAW;AAAA,EACrB;AACF,CAAC;AAEM,MAAM,wCAAiE;AAAA,EAC5E,cAAc;AAAA,EACd,KAAK;AAAA,IACH,YAAY,CAAC,aAAa,OAAO,aAAa,EAAE;AAAA,IAChD,KAAK,CAAC,iBAAiB,KAAK,iBAAiB,IAAI;AAAA,EACnD;AAAA,EACA,WAAW;AAAA,EACX,UAAU,CAAC,YAAY,SAAS;AAAA,EAChC,WAAW;AAAA,IACT,SAAS,CAAC,YAAY,MAAM;AAAA,IAC5B,OAAO;AAAA,MACL,wBAAwB;AAAA,MACxB,wBAAwB;AAAA,MACxB,wBAAwB;AAAA,IAC1B;AAAA,IACA,UAAU,mBAAmB;AAAA,EAC/B;AAAA;AAAA,EAEA,OAAO,kBAAkB;AAAA,EACzB,UAAU,EAAE,SAAS,MAAM,cAAc,MAAM,MAAM,OAAO,YAAY,KAAK;AAAA;AAAA;AAAA;AAAA,EAI7E,oBAAoB,mBAAmB;AAAA,EACvC,2BAA2B;AAC7B;AAcO,MAAM,0BAA0B,CACrC,WAC8B,eAAe,WAAW,MAAM;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/runtime/remote-host.ts"],
|
|
4
|
-
"sourcesContent": ["import { RequestError } from '@agentclientprotocol/sdk/experimental/v2';\n\nimport {\n AcpTransport,\n BaseCapability,\n BaseToolProvision,\n CredentialDelivery,\n CredentialRoute,\n formatProviderRef,\n harnessDescriptionSchema,\n InferenceCredentialKind,\n InstructionRole,\n packageRef,\n SandboxWire,\n type HarnessClientTlsIface,\n type HarnessDescriptionIface,\n type HarnessEndpointIface,\n type HarnessHealthIface,\n type HarnessIface,\n type HarnessSessionRefIface,\n type InstructionFragmentIface,\n type JsonObject,\n type OpenHarnessSessionArgsIface,\n type ResumeHarnessSessionArgsIface,\n type SecretReaderIface,\n type SkillBundleIface,\n type ToolApproval,\n} from '@finalai/agent-provider';\n\nimport { createNodeClientTlsFetch } from '@finalai/websockets/node-client-tls';\n\nimport { createHmac } from 'node:crypto';\n\nimport * as z from 'zod';\n\nimport { toJsonObject, type SecretRefIface } from \"../harnesses/config-fields.mjs\";\n\nimport {\n resolveRemoteClientTls,\n type RemoteClientTlsInputIface,\n} from \"../harnesses/remote-client-tls.mjs\";\n\nconst unavailableSchema = z.object({\n code: z.literal('harness_unavailable'),\n executable: z.string(),\n foundVersion: z.string().nullable(),\n requiredVersion: z.string(),\n detail: z.string(),\n});\n\nconst unavailableOf = (text: string) => {\n try {\n return unavailableSchema.safeParse(JSON.parse(text)).data ?? null;\n } catch {\n return null;\n }\n};\n\nconst DEFAULT_HEALTH_TIMEOUT_MS = 5_000;\n\nconst DEFAULT_REGISTER_TIMEOUT_MS = 10_000;\n\nconst DEFAULT_CLOSE_TIMEOUT_MS = 15_000;\n\n// Mirrors @finalai/harness-runtime's serve.ts and server/auth.ts.\nexport const PROGRAM_CONFIG_HEADER = 'x-final-program-config';\n\nexport const HEALTH_TOKEN_HEADER = 'x-final-health-token';\n\nexport const CLOSE_TOKEN_HEADER = 'x-final-close-token';\n\n// The runtime's registration-scoped health credential (HMAC of the registration id under the\n// hop token): health(ref) has no secrets, so open/resume put it in the ref instead of the hop.\nexport const healthTokenFor = (args: { hop: string; id: string }): string =>\n createHmac('sha256', args.hop).update(`final-harness-health:${args.id}`).digest('base64url');\n\nexport const closeTokenFor = (args: { hop: string; id: string }): string =>\n createHmac('sha256', args.hop).update(`final-harness-close:${args.id}`).digest('base64url');\n\nconst PROGRAM_CONFIGS_PATH = '/v1/program-configs/';\n\nexport enum RemoteProgram {\n ClaudeCode = 'claude-code',\n Codex = 'codex',\n Pi = 'pi',\n}\n\nexport interface RemoteHarnessFactoryConfigIface {\n fetch?: typeof fetch;\n tlsFetch?: (tls: HarnessClientTlsIface) => typeof fetch;\n // Test seams.\n now?: () => number;\n newId?: () => string;\n healthTimeoutMs?: number;\n registerTimeoutMs?: number;\n closeTimeoutMs?: number;\n}\n\nexport interface RemoteSettingsIface {\n url: string;\n tls: RemoteClientTlsInputIface | null;\n hop: SecretRefIface;\n cwd: string | null;\n executable: string | null;\n model: string | null;\n reasoning: string | null;\n permissionMode: string | null;\n approvalPolicy: string | null;\n sandboxMode: string | null;\n allowedTools: string[] | null;\n instructions: InstructionFragmentIface[];\n skills: SkillBundleIface[];\n}\n\nexport interface RemoteProgramSpecIface {\n specifier: string;\n program: RemoteProgram;\n description: HarnessDescriptionIface;\n settings: (config: OpenHarnessSessionArgsIface['config']) => RemoteSettingsIface;\n}\n\n// The provider-private program config this harness registers with the runtime\n// See @finalai/harness-runtime's program-config.ts. Version 1.\n// `credentials`, `broker` and `refresh` carry route grants (and, for at-open\n// harnesses, the resolved model credential); they travel only between this\n// provider and the runtime, over the hop-authenticated registration.\nexport interface RemoteProgramConfigIface {\n version: 1;\n program: RemoteProgram;\n // Absolute path on the runtime host; null = the runtime's own working directory.\n cwd: string | null;\n executable: string | null;\n model: string | null;\n reasoning: string | null;\n permissionMode: string | null;\n approvalPolicy: string | null;\n sandboxMode: string | null;\n allowedTools: string[] | null;\n // System fragments only; user fragments arrive as _final/session/observe.\n instructions: Pick<InstructionFragmentIface, 'role' | 'content'>[];\n skills: SkillBundleIface[];\n // Declared capabilities this harness provides, with the target a bound one\n // routes to; null runs it natively on the host. A host-disableable capability\n // absent here must be disabled through the program's own allow-lists.\n baseTools: Record<string, { provision: BaseToolProvision; target: string | null }>;\n sandboxes: Record<\n string,\n {\n bindingId: string;\n capabilities: string[];\n rpc: { url: string; token: string; expiresAt: string } | null;\n mcp: { url: string; token: string; expiresAt: string } | null;\n }\n >;\n toolPolicy: Record<string, ToolApproval>;\n attachments: { url: string; token: string } | null;\n // Matches @finalai/harness-runtime's program config: the model credential\n // handed over at open (claude-code, codex), or the broker `model` route grant\n // for per-request programs (pi), which post to `<broker.url>/model`.\n credentials: OpenHarnessSessionArgsIface['credentials']['atOpen'];\n // `sandboxToken` is the broker `sandbox` route grant, for renewing sandbox endpoints.\n broker: {\n url: string;\n token: string;\n sandboxToken: string | null;\n harnessSessionId: string;\n } | null;\n // The grant's standard refresh route: the runtime pulls the next generation\n // of `credentials` and `broker` from it before they expire.\n refresh: OpenHarnessSessionArgsIface['credentials']['refresh'];\n}\n\nexport type RemoteOpenArgsIface = OpenHarnessSessionArgsIface;\n\nexport type RemoteHarnessIface = HarnessIface;\n\n// Scoped capabilities survive a session-service restart without retaining the hop or TLS keys.\nexport const formatRemoteRef = (args: {\n id: string;\n url: string;\n healthToken: string | null;\n closeToken?: string | null;\n}): string =>\n args.closeToken != null && args.healthToken != null\n ? `${args.id}|${args.healthToken}|${args.closeToken}|${args.url}`\n : args.healthToken == null\n ? `${args.id}|${args.url}`\n : `${args.id}|${args.healthToken}|${args.url}`;\n\nconst HEALTH_TOKEN_PATTERN = /^[A-Za-z0-9_-]{43}$/u;\n\nexport interface RemoteRefIface {\n id: string;\n url: string | null;\n healthToken: string | null;\n closeToken: string | null;\n}\n\nexport const parseRemoteRef = (ref: string): RemoteRefIface => {\n const bar = ref.indexOf('|');\n if (bar < 0) return { id: ref, url: null, healthToken: null, closeToken: null };\n const id = ref.slice(0, bar);\n const rest = ref.slice(bar + 1);\n const next = rest.indexOf('|');\n const token = next < 0 ? null : rest.slice(0, next);\n if (token == null || !HEALTH_TOKEN_PATTERN.test(token))\n return { id, url: rest, healthToken: null, closeToken: null };\n const remaining = rest.slice(next + 1);\n const separator = remaining.indexOf('|');\n const closeToken = separator < 0 ? null : remaining.slice(0, separator);\n return closeToken != null && HEALTH_TOKEN_PATTERN.test(closeToken)\n ? { id, url: remaining.slice(separator + 1), healthToken: token, closeToken }\n : { id, url: remaining, healthToken: token, closeToken: null };\n};\n\nexport const transportForUrl = (url: string): AcpTransport.Ws | AcpTransport.Http =>\n /^wss?:\\/\\//.test(url) ? AcpTransport.Ws : AcpTransport.Http;\n\nconst httpUrlOf = (url: string): string =>\n url.replace(/^ws(s?):\\/\\//, (_match, secure) => `http${secure}://`);\n\n// Lifecycle routes are siblings of /acp, including a reverse proxy's path prefix.\nconst runtimeUrlFor = (args: { url: string; path: string }): string => {\n const endpoint = new URL(httpUrlOf(args.url));\n if (endpoint.pathname !== '/') endpoint.pathname = endpoint.pathname.replace(/\\/+$/, '');\n return new URL(args.path.replace(/^\\/+/, ''), endpoint).toString();\n};\n\nexport const healthUrlFor = (url: string): string => runtimeUrlFor({ url, path: 'healthz' });\n\nexport const programConfigUrlFor = (args: { url: string; id: string }): string =>\n runtimeUrlFor({ url: args.url, path: `${PROGRAM_CONFIGS_PATH}${encodeURIComponent(args.id)}` });\n\nexport const resolveSecretRef = async (args: {\n specifier: string;\n field: string;\n ref: SecretRefIface;\n secrets: SecretReaderIface;\n}): Promise<string> => {\n const value = await args.secrets.get(args.ref.secretKey);\n if (value == null || value.length === 0) {\n throw new Error(\n `${args.specifier}: secret '${args.ref.secretKey}' for ${args.field} is not bound`,\n );\n }\n return value;\n};\n\nexport const buildRemoteProgramConfig = (args: {\n spec: RemoteProgramSpecIface;\n open: OpenHarnessSessionArgsIface;\n}): RemoteProgramConfigIface => {\n const settings = args.spec.settings(args.open.config);\n const description = args.spec.description;\n // A capability bound to a resolved target routes there. One declared on the\n // harness host (`harnessHost`), or bound to a target a harness that cannot\n // route it ignores (not reboundable), runs natively on the host.\n const routesTargets = description.consumes.includes(SandboxWire.HandleRpc);\n const baseTools: RemoteProgramConfigIface['baseTools'] = {};\n for (const capability of Object.values(BaseCapability)) {\n const endpoint = args.open.baseTools[capability];\n const declaration = description.baseTools[capability];\n if (declaration == null) continue;\n if (endpoint != null) {\n baseTools[capability] = { provision: declaration.provision, target: endpoint.target };\n continue;\n }\n if (declaration.provision === BaseToolProvision.Bound) continue;\n const onHost = args.open.manifest.tools.host?.includes(capability) === true;\n const unroutedTarget = args.open.manifest.tools.base[capability] != null && !routesTargets;\n if (onHost || unroutedTarget) {\n baseTools[capability] = { provision: declaration.provision, target: null };\n }\n }\n const delivery = description.inference.delivery;\n const modelGrant = args.open.credentials.grants.find(\n (grant) => grant.route === CredentialRoute.Model,\n );\n // The broker's sandbox route renews sandbox endpoints; it accepts only this grant.\n const sandboxGrant = args.open.credentials.grants.find(\n (grant) => grant.route === CredentialRoute.Sandbox,\n );\n return {\n attachments: (() => {\n const grant = args.open.credentials.grants.find(\n (entry) => entry.route === CredentialRoute.Mcp,\n );\n return grant == null\n ? null\n : {\n url: `${args.open.credentials.brokerUrl.replace(/\\/+$/, '')}/attachment`,\n token: grant.token,\n };\n })(),\n version: 1,\n program: args.spec.program,\n cwd: settings.cwd,\n executable: settings.executable,\n model: settings.model,\n reasoning: settings.reasoning,\n permissionMode: settings.permissionMode,\n approvalPolicy: settings.approvalPolicy,\n sandboxMode: settings.sandboxMode,\n allowedTools: settings.allowedTools,\n instructions: settings.instructions\n .filter((fragment) => fragment.role === InstructionRole.System)\n .map((fragment) => ({ role: fragment.role, content: fragment.content })),\n skills: settings.skills,\n baseTools,\n sandboxes: Object.fromEntries(\n Object.entries(args.open.sandboxes).map(([target, endpoint]) => [\n target,\n {\n bindingId: endpoint.bindingId,\n capabilities: [...endpoint.capabilities],\n rpc: endpoint.rpc,\n mcp: endpoint.mcp,\n },\n ]),\n ),\n toolPolicy: { ...args.open.manifest.toolPolicy },\n // The harness's own login (`credentials.harness()`) is configured by its\n // user, not by Final: nothing is handed over and the CLI's settings apply.\n credentials:\n delivery === CredentialDelivery.AtOpen &&\n args.open.credentials.atOpen?.kind !== InferenceCredentialKind.HarnessOwned\n ? args.open.credentials.atOpen\n : null,\n broker:\n delivery === CredentialDelivery.PerRequest && modelGrant != null\n ? {\n url: args.open.credentials.brokerUrl,\n token: modelGrant.token,\n sandboxToken: sandboxGrant?.token ?? null,\n harnessSessionId: args.open.harnessSessionId,\n }\n : null,\n refresh: args.open.credentials.refresh,\n };\n};\n\nexport const createRemoteHarness = (\n spec: RemoteProgramSpecIface,\n config: RemoteHarnessFactoryConfigIface = {},\n): RemoteHarnessIface => {\n const fetchImpl = config.fetch ?? fetch;\n const fetchFor = (tls: HarnessClientTlsIface | undefined) =>\n tls == null ? fetchImpl : (config.tlsFetch?.(tls) ?? createNodeClientTlsFetch({ tls }));\n const now = config.now ?? Date.now;\n const healthTimeoutMs = config.healthTimeoutMs ?? DEFAULT_HEALTH_TIMEOUT_MS;\n const registerTimeoutMs = config.registerTimeoutMs ?? DEFAULT_REGISTER_TIMEOUT_MS;\n const clientTls = new Map<string, HarnessClientTlsIface>();\n\n // Registers the program config (fresh credentials included) under the\n // harness session's id; the runtime applies it to session/new and\n // session/resume on connections that name it.\n const register = async (args: {\n url: string;\n hop: string;\n id: string;\n open: OpenHarnessSessionArgsIface;\n tls?: HarnessClientTlsIface;\n }): Promise<void> => {\n const source = args.open.forkSource;\n const forkSource = source == null ? null : parseRemoteRef(source.ref.harnessSessionRef);\n if (\n source != null &&\n (!spec.description.supports.fork ||\n formatProviderRef(source.ref.harness) !== formatProviderRef(packageRef(spec.specifier)))\n )\n throw new Error(`${spec.specifier}: the source does not support this harness's native fork`);\n if (forkSource != null && (forkSource.id === args.id || forkSource.url !== args.url))\n throw new Error(\n `${spec.specifier}: a fork requires a distinct registration on the source host`,\n );\n const target = programConfigUrlFor({ url: args.url, id: args.id });\n const registration: JsonObject = {\n program: toJsonObject(buildRemoteProgramConfig({ spec, open: args.open })),\n };\n if (source != null && forkSource != null)\n registration['forkSource'] = {\n programConfigId: forkSource.id,\n sessionId: source.acpSessionId,\n childSessionId: args.open.session.sessionId,\n };\n let response: Response;\n try {\n response = await fetchFor(args.tls)(target, {\n method: 'PUT',\n headers: { authorization: `Bearer ${args.hop}`, 'content-type': 'application/json' },\n body: JSON.stringify(registration),\n signal: AbortSignal.timeout(registerTimeoutMs),\n });\n } catch (error) {\n throw new Error(\n `${spec.specifier}: registering the program config failed: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n const body = await response.text().catch(() => '');\n if (!response.ok) {\n const unavailable = unavailableOf(body);\n if (unavailable != null) throw RequestError.internalError(unavailable, unavailable.detail);\n throw new Error(\n `${spec.specifier}: the runtime refused the program config (HTTP ${response.status}) ${body.slice(0, 200)}`.trim(),\n );\n }\n };\n\n const endpointFor = async (args: {\n open: OpenHarnessSessionArgsIface;\n ref: HarnessSessionRefIface;\n }): Promise<HarnessEndpointIface> => {\n const settings = spec.settings(args.open.config);\n const hop = await resolveSecretRef({\n specifier: spec.specifier,\n field: 'hop',\n ref: settings.hop,\n secrets: args.open.secrets,\n });\n const { id } = parseRemoteRef(args.ref.harnessSessionRef);\n const tls = await resolveRemoteClientTls({\n config: settings.tls,\n secrets: args.open.secrets,\n url: settings.url,\n });\n await register({ url: settings.url, hop, id, open: args.open, tls });\n if (tls != null) clientTls.set(id, tls);\n else clientTls.delete(id);\n return {\n // Re-minted on every open and resume: a rotated hop or a legacy ref gets a current token.\n ref: {\n ...args.ref,\n harnessSessionRef: formatRemoteRef({\n id,\n url: settings.url,\n healthToken: healthTokenFor({ hop, id }),\n closeToken: closeTokenFor({ hop, id }),\n }),\n },\n transport: transportForUrl(settings.url),\n url: settings.url,\n tls,\n headers: { authorization: `Bearer ${hop}`, [PROGRAM_CONFIG_HEADER]: id },\n };\n };\n\n return {\n describe: async (binding) => {\n if (binding == null) return spec.description;\n const settings = spec.settings(binding.config);\n const hop = await resolveSecretRef({\n specifier: spec.specifier,\n field: 'hop',\n ref: settings.hop,\n secrets: binding.secrets,\n });\n const tls = await resolveRemoteClientTls({\n config: settings.tls,\n secrets: binding.secrets,\n url: settings.url,\n });\n const response = await fetchFor(tls)(\n runtimeUrlFor({ url: settings.url, path: 'v1/describe' }),\n {\n method: 'POST',\n headers: { authorization: `Bearer ${hop}`, 'content-type': 'application/json' },\n body: JSON.stringify({ program: { executable: settings.executable } }),\n signal: AbortSignal.timeout(registerTimeoutMs),\n },\n );\n const text = await response.text();\n if (!response.ok) {\n const unavailable = unavailableOf(text);\n if (unavailable != null) throw RequestError.internalError(unavailable, unavailable.detail);\n throw new Error(\n `${spec.specifier}: describe failed (HTTP ${response.status}) ${text.slice(0, 200)}`,\n );\n }\n const description = harnessDescriptionSchema.parse(JSON.parse(text));\n return {\n ...description,\n configSchema: spec.description.configSchema,\n trust: spec.description.trust,\n inference: spec.description.inference,\n consumes: spec.description.consumes,\n };\n },\n open: async (args) => {\n const ref: HarnessSessionRefIface = {\n harness: packageRef(spec.specifier),\n // endpointFor() completes the ref with the url and health token.\n harnessSessionRef: args.harnessSessionId,\n generation: 1,\n };\n return endpointFor({ open: args, ref });\n },\n // The runtime resumes the program's own session (Claude Code session, Codex\n // rollout); re-registering hands it the fresh grants.\n resume: async (args: ResumeHarnessSessionArgsIface) =>\n endpointFor({ open: args, ref: args.ref }),\n park: async () => undefined,\n close: async (ref, options) => {\n const { id, url: retainedUrl, closeToken } = parseRemoteRef(ref.harnessSessionRef);\n const context = options?.context;\n const settings = context == null ? null : spec.settings(context.config);\n const url = retainedUrl ?? settings?.url ?? null;\n if (url == null) {\n if (options?.force === true)\n throw new Error(`${spec.specifier}: the legacy ref must be resumed before forced close`);\n return;\n }\n const hop =\n context == null || settings == null\n ? null\n : await context.secrets.get(settings.hop.secretKey);\n const scopedToken = hop == null || hop.length === 0 ? closeToken : closeTokenFor({ hop, id });\n if (scopedToken == null) {\n if (options?.force === true)\n throw new Error(\n `${spec.specifier}: the legacy ref needs close context or resume before forced close`,\n );\n return;\n }\n const tls =\n context == null || settings == null\n ? clientTls.get(id)\n : await resolveRemoteClientTls({\n config: settings.tls,\n secrets: context.secrets,\n url,\n });\n const response = await fetchFor(tls)(programConfigUrlFor({ url, id }), {\n method: 'DELETE',\n headers: { [CLOSE_TOKEN_HEADER]: scopedToken, 'content-type': 'application/json' },\n body: JSON.stringify({ force: options?.force === true }),\n signal: AbortSignal.timeout(config.closeTimeoutMs ?? DEFAULT_CLOSE_TIMEOUT_MS),\n });\n const body = await response.text();\n if (!response.ok && response.status !== 404)\n throw new Error(\n `${spec.specifier}: remote close failed (HTTP ${response.status}) ${body.slice(0, 200)}`,\n );\n clientTls.delete(id);\n },\n health: async (ref, context): Promise<HarnessHealthIface> => {\n const { id, url, healthToken } = parseRemoteRef(ref.harnessSessionRef);\n const checkedAt = new Date(now()).toISOString();\n if (url == null)\n return { ok: false, detail: 'harness session ref has no runtime url', checkedAt };\n try {\n const headers = new Headers({ [PROGRAM_CONFIG_HEADER]: id });\n if (healthToken != null) headers.set(HEALTH_TOKEN_HEADER, healthToken);\n const tls =\n context == null\n ? undefined\n : await resolveRemoteClientTls({\n config: spec.settings(context.config).tls,\n secrets: context.secrets,\n url,\n });\n const response = await fetchFor(tls)(healthUrlFor(url), {\n method: 'GET',\n headers,\n signal: AbortSignal.timeout(healthTimeoutMs),\n });\n const body = await response.text();\n const unavailable = response.ok ? null : unavailableOf(body);\n return response.ok\n ? { ok: true, checkedAt }\n : {\n ok: false,\n detail: unavailable?.detail ?? `healthz returned HTTP ${response.status}`,\n checkedAt,\n };\n } catch (error) {\n return {\n ok: false,\n detail: `healthz failed: ${error instanceof Error ? error.message : String(error)}`,\n checkedAt,\n };\n }\n },\n };\n};\nexport { REMOTE_URL_SCHEMES, remoteUrlJson, remoteUrlZod } from \"../harnesses/remote-url.mjs\";\n"],
|
|
4
|
+
"sourcesContent": ["import { RequestError } from '@agentclientprotocol/sdk/experimental/v2';\n\nimport {\n AcpTransport,\n BaseCapability,\n BaseToolProvision,\n CredentialDelivery,\n CredentialRoute,\n formatProviderRef,\n harnessDescriptionSchema,\n InferenceCredentialKind,\n InstructionRole,\n packageRef,\n SandboxWire,\n type HarnessClientTlsIface,\n type HarnessDescriptionIface,\n type HarnessEndpointIface,\n type HarnessHealthIface,\n type HarnessIface,\n type HarnessSessionRefIface,\n type InstructionFragmentIface,\n type JsonObject,\n type OpenHarnessSessionArgsIface,\n type ResumeHarnessSessionArgsIface,\n type SecretReaderIface,\n type SkillBundleIface,\n type ToolApproval,\n} from '@finalai/agent-provider';\n\nimport { createNodeClientTlsFetch } from '@finalai/websockets/node-client-tls';\n\nimport { createHmac } from 'node:crypto';\n\nimport * as z from 'zod';\n\nimport { toJsonObject, type SecretRefIface } from \"../harnesses/config-fields.mjs\";\n\nimport {\n resolveRemoteClientTls,\n type RemoteClientTlsInputIface,\n} from \"../harnesses/remote-client-tls.mjs\";\n\nconst unavailableSchema = z.object({\n code: z.literal('harness_unavailable'),\n executable: z.string(),\n foundVersion: z.string().nullable(),\n requiredVersion: z.string(),\n detail: z.string(),\n});\n\nconst unavailableOf = (text: string) => {\n try {\n return unavailableSchema.safeParse(JSON.parse(text)).data ?? null;\n } catch {\n return null;\n }\n};\n\nconst DEFAULT_HEALTH_TIMEOUT_MS = 5_000;\n\nconst DEFAULT_REGISTER_TIMEOUT_MS = 10_000;\n\nconst DEFAULT_CLOSE_TIMEOUT_MS = 15_000;\n\n// Authentication modes supported by remote harness servers.\nexport const PROGRAM_CONFIG_HEADER = 'x-final-program-config';\n\nexport const HEALTH_TOKEN_HEADER = 'x-final-health-token';\n\nexport const CLOSE_TOKEN_HEADER = 'x-final-close-token';\n\n// The runtime's registration-scoped health credential (HMAC of the registration id under the\n// hop token): health(ref) has no secrets, so open/resume put it in the ref instead of the hop.\nexport const healthTokenFor = (args: { hop: string; id: string }): string =>\n createHmac('sha256', args.hop).update(`final-harness-health:${args.id}`).digest('base64url');\n\nexport const closeTokenFor = (args: { hop: string; id: string }): string =>\n createHmac('sha256', args.hop).update(`final-harness-close:${args.id}`).digest('base64url');\n\nconst PROGRAM_CONFIGS_PATH = '/v1/program-configs/';\n\nexport enum RemoteProgram {\n ClaudeCode = 'claude-code',\n Codex = 'codex',\n Pi = 'pi',\n}\n\nexport interface RemoteHarnessFactoryConfigIface {\n fetch?: typeof fetch;\n tlsFetch?: (tls: HarnessClientTlsIface) => typeof fetch;\n // Test seams.\n now?: () => number;\n newId?: () => string;\n healthTimeoutMs?: number;\n registerTimeoutMs?: number;\n closeTimeoutMs?: number;\n}\n\nexport interface RemoteSettingsIface {\n url: string;\n tls: RemoteClientTlsInputIface | null;\n hop: SecretRefIface;\n cwd: string | null;\n executable: string | null;\n model: string | null;\n reasoning: string | null;\n permissionMode: string | null;\n approvalPolicy: string | null;\n sandboxMode: string | null;\n allowedTools: string[] | null;\n instructions: InstructionFragmentIface[];\n skills: SkillBundleIface[];\n}\n\nexport interface RemoteProgramSpecIface {\n specifier: string;\n program: RemoteProgram;\n description: HarnessDescriptionIface;\n settings: (config: OpenHarnessSessionArgsIface['config']) => RemoteSettingsIface;\n}\n\n// The provider-private program config this harness registers with the runtime\n// Remote harness program configuration, version 1.\n// `credentials`, `broker` and `refresh` carry route grants (and, for at-open\n// harnesses, the resolved model credential); they travel only between this\n// provider and the runtime, over the hop-authenticated registration.\nexport interface RemoteProgramConfigIface {\n version: 1;\n program: RemoteProgram;\n // Absolute path on the runtime host; null = the runtime's own working directory.\n cwd: string | null;\n executable: string | null;\n model: string | null;\n reasoning: string | null;\n permissionMode: string | null;\n approvalPolicy: string | null;\n sandboxMode: string | null;\n allowedTools: string[] | null;\n // System fragments only; user fragments arrive as _final/session/observe.\n instructions: Pick<InstructionFragmentIface, 'role' | 'content'>[];\n skills: SkillBundleIface[];\n // Declared capabilities this harness provides, with the target a bound one\n // routes to; null runs it natively on the host. A host-disableable capability\n // absent here must be disabled through the program's own allow-lists.\n baseTools: Record<string, { provision: BaseToolProvision; target: string | null }>;\n sandboxes: Record<\n string,\n {\n bindingId: string;\n capabilities: string[];\n rpc: { url: string; token: string; expiresAt: string } | null;\n mcp: { url: string; token: string; expiresAt: string } | null;\n }\n >;\n toolPolicy: Record<string, ToolApproval>;\n attachments: { url: string; token: string } | null;\n // In the remote program configuration, the model credential\n // handed over at open (claude-code, codex), or the broker `model` route grant\n // for per-request programs (pi), which post to `<broker.url>/model`.\n credentials: OpenHarnessSessionArgsIface['credentials']['atOpen'];\n // `sandboxToken` is the broker `sandbox` route grant, for renewing sandbox endpoints.\n broker: {\n url: string;\n token: string;\n sandboxToken: string | null;\n harnessSessionId: string;\n } | null;\n // The grant's standard refresh route: the runtime pulls the next generation\n // of `credentials` and `broker` from it before they expire.\n refresh: OpenHarnessSessionArgsIface['credentials']['refresh'];\n}\n\nexport type RemoteOpenArgsIface = OpenHarnessSessionArgsIface;\n\nexport type RemoteHarnessIface = HarnessIface;\n\n// Scoped capabilities survive a session-service restart without retaining the hop or TLS keys.\nexport const formatRemoteRef = (args: {\n id: string;\n url: string;\n healthToken: string | null;\n closeToken?: string | null;\n}): string =>\n args.closeToken != null && args.healthToken != null\n ? `${args.id}|${args.healthToken}|${args.closeToken}|${args.url}`\n : args.healthToken == null\n ? `${args.id}|${args.url}`\n : `${args.id}|${args.healthToken}|${args.url}`;\n\nconst HEALTH_TOKEN_PATTERN = /^[A-Za-z0-9_-]{43}$/u;\n\nexport interface RemoteRefIface {\n id: string;\n url: string | null;\n healthToken: string | null;\n closeToken: string | null;\n}\n\nexport const parseRemoteRef = (ref: string): RemoteRefIface => {\n const bar = ref.indexOf('|');\n if (bar < 0) return { id: ref, url: null, healthToken: null, closeToken: null };\n const id = ref.slice(0, bar);\n const rest = ref.slice(bar + 1);\n const next = rest.indexOf('|');\n const token = next < 0 ? null : rest.slice(0, next);\n if (token == null || !HEALTH_TOKEN_PATTERN.test(token))\n return { id, url: rest, healthToken: null, closeToken: null };\n const remaining = rest.slice(next + 1);\n const separator = remaining.indexOf('|');\n const closeToken = separator < 0 ? null : remaining.slice(0, separator);\n return closeToken != null && HEALTH_TOKEN_PATTERN.test(closeToken)\n ? { id, url: remaining.slice(separator + 1), healthToken: token, closeToken }\n : { id, url: remaining, healthToken: token, closeToken: null };\n};\n\nexport const transportForUrl = (url: string): AcpTransport.Ws | AcpTransport.Http =>\n /^wss?:\\/\\//.test(url) ? AcpTransport.Ws : AcpTransport.Http;\n\nconst httpUrlOf = (url: string): string =>\n url.replace(/^ws(s?):\\/\\//, (_match, secure) => `http${secure}://`);\n\n// Lifecycle routes are siblings of /acp, including a reverse proxy's path prefix.\nconst runtimeUrlFor = (args: { url: string; path: string }): string => {\n const endpoint = new URL(httpUrlOf(args.url));\n if (endpoint.pathname !== '/') endpoint.pathname = endpoint.pathname.replace(/\\/+$/, '');\n return new URL(args.path.replace(/^\\/+/, ''), endpoint).toString();\n};\n\nexport const healthUrlFor = (url: string): string => runtimeUrlFor({ url, path: 'healthz' });\n\nexport const programConfigUrlFor = (args: { url: string; id: string }): string =>\n runtimeUrlFor({ url: args.url, path: `${PROGRAM_CONFIGS_PATH}${encodeURIComponent(args.id)}` });\n\nexport const resolveSecretRef = async (args: {\n specifier: string;\n field: string;\n ref: SecretRefIface;\n secrets: SecretReaderIface;\n}): Promise<string> => {\n const value = await args.secrets.get(args.ref.secretKey);\n if (value == null || value.length === 0) {\n throw new Error(\n `${args.specifier}: secret '${args.ref.secretKey}' for ${args.field} is not bound`,\n );\n }\n return value;\n};\n\nexport const buildRemoteProgramConfig = (args: {\n spec: RemoteProgramSpecIface;\n open: OpenHarnessSessionArgsIface;\n}): RemoteProgramConfigIface => {\n const settings = args.spec.settings(args.open.config);\n const description = args.spec.description;\n // A capability bound to a resolved target routes there. One declared on the\n // harness host (`harnessHost`), or bound to a target a harness that cannot\n // route it ignores (not reboundable), runs natively on the host.\n const routesTargets = description.consumes.includes(SandboxWire.HandleRpc);\n const baseTools: RemoteProgramConfigIface['baseTools'] = {};\n for (const capability of Object.values(BaseCapability)) {\n const endpoint = args.open.baseTools[capability];\n const declaration = description.baseTools[capability];\n if (declaration == null) continue;\n if (endpoint != null) {\n baseTools[capability] = { provision: declaration.provision, target: endpoint.target };\n continue;\n }\n if (declaration.provision === BaseToolProvision.Bound) continue;\n const onHost = args.open.manifest.tools.host?.includes(capability) === true;\n const unroutedTarget = args.open.manifest.tools.base[capability] != null && !routesTargets;\n if (onHost || unroutedTarget) {\n baseTools[capability] = { provision: declaration.provision, target: null };\n }\n }\n const delivery = description.inference.delivery;\n const modelGrant = args.open.credentials.grants.find(\n (grant) => grant.route === CredentialRoute.Model,\n );\n // The broker's sandbox route renews sandbox endpoints; it accepts only this grant.\n const sandboxGrant = args.open.credentials.grants.find(\n (grant) => grant.route === CredentialRoute.Sandbox,\n );\n return {\n attachments: (() => {\n const grant = args.open.credentials.grants.find(\n (entry) => entry.route === CredentialRoute.Mcp,\n );\n return grant == null\n ? null\n : {\n url: `${args.open.credentials.brokerUrl.replace(/\\/+$/, '')}/attachment`,\n token: grant.token,\n };\n })(),\n version: 1,\n program: args.spec.program,\n cwd: settings.cwd,\n executable: settings.executable,\n model: settings.model,\n reasoning: settings.reasoning,\n permissionMode: settings.permissionMode,\n approvalPolicy: settings.approvalPolicy,\n sandboxMode: settings.sandboxMode,\n allowedTools: settings.allowedTools,\n instructions: settings.instructions\n .filter((fragment) => fragment.role === InstructionRole.System)\n .map((fragment) => ({ role: fragment.role, content: fragment.content })),\n skills: settings.skills,\n baseTools,\n sandboxes: Object.fromEntries(\n Object.entries(args.open.sandboxes).map(([target, endpoint]) => [\n target,\n {\n bindingId: endpoint.bindingId,\n capabilities: [...endpoint.capabilities],\n rpc: endpoint.rpc,\n mcp: endpoint.mcp,\n },\n ]),\n ),\n toolPolicy: { ...args.open.manifest.toolPolicy },\n // The harness's own login (`credentials.harness()`) is configured by its\n // user, not by Final: nothing is handed over and the CLI's settings apply.\n credentials:\n delivery === CredentialDelivery.AtOpen &&\n args.open.credentials.atOpen?.kind !== InferenceCredentialKind.HarnessOwned\n ? args.open.credentials.atOpen\n : null,\n broker:\n delivery === CredentialDelivery.PerRequest && modelGrant != null\n ? {\n url: args.open.credentials.brokerUrl,\n token: modelGrant.token,\n sandboxToken: sandboxGrant?.token ?? null,\n harnessSessionId: args.open.harnessSessionId,\n }\n : null,\n refresh: args.open.credentials.refresh,\n };\n};\n\nexport const createRemoteHarness = (\n spec: RemoteProgramSpecIface,\n config: RemoteHarnessFactoryConfigIface = {},\n): RemoteHarnessIface => {\n const fetchImpl = config.fetch ?? fetch;\n const fetchFor = (tls: HarnessClientTlsIface | undefined) =>\n tls == null ? fetchImpl : (config.tlsFetch?.(tls) ?? createNodeClientTlsFetch({ tls }));\n const now = config.now ?? Date.now;\n const healthTimeoutMs = config.healthTimeoutMs ?? DEFAULT_HEALTH_TIMEOUT_MS;\n const registerTimeoutMs = config.registerTimeoutMs ?? DEFAULT_REGISTER_TIMEOUT_MS;\n const clientTls = new Map<string, HarnessClientTlsIface>();\n\n // Registers the program config (fresh credentials included) under the\n // harness session's id; the runtime applies it to session/new and\n // session/resume on connections that name it.\n const register = async (args: {\n url: string;\n hop: string;\n id: string;\n open: OpenHarnessSessionArgsIface;\n tls?: HarnessClientTlsIface;\n }): Promise<void> => {\n const source = args.open.forkSource;\n const forkSource = source == null ? null : parseRemoteRef(source.ref.harnessSessionRef);\n if (\n source != null &&\n (!spec.description.supports.fork ||\n formatProviderRef(source.ref.harness) !== formatProviderRef(packageRef(spec.specifier)))\n )\n throw new Error(`${spec.specifier}: the source does not support this harness's native fork`);\n if (forkSource != null && (forkSource.id === args.id || forkSource.url !== args.url))\n throw new Error(\n `${spec.specifier}: a fork requires a distinct registration on the source host`,\n );\n const target = programConfigUrlFor({ url: args.url, id: args.id });\n const registration: JsonObject = {\n program: toJsonObject(buildRemoteProgramConfig({ spec, open: args.open })),\n };\n if (source != null && forkSource != null)\n registration['forkSource'] = {\n programConfigId: forkSource.id,\n sessionId: source.acpSessionId,\n childSessionId: args.open.session.sessionId,\n };\n let response: Response;\n try {\n response = await fetchFor(args.tls)(target, {\n method: 'PUT',\n headers: { authorization: `Bearer ${args.hop}`, 'content-type': 'application/json' },\n body: JSON.stringify(registration),\n signal: AbortSignal.timeout(registerTimeoutMs),\n });\n } catch (error) {\n throw new Error(\n `${spec.specifier}: registering the program config failed: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n const body = await response.text().catch(() => '');\n if (!response.ok) {\n const unavailable = unavailableOf(body);\n if (unavailable != null) throw RequestError.internalError(unavailable, unavailable.detail);\n throw new Error(\n `${spec.specifier}: the runtime refused the program config (HTTP ${response.status}) ${body.slice(0, 200)}`.trim(),\n );\n }\n };\n\n const endpointFor = async (args: {\n open: OpenHarnessSessionArgsIface;\n ref: HarnessSessionRefIface;\n }): Promise<HarnessEndpointIface> => {\n const settings = spec.settings(args.open.config);\n const hop = await resolveSecretRef({\n specifier: spec.specifier,\n field: 'hop',\n ref: settings.hop,\n secrets: args.open.secrets,\n });\n const { id } = parseRemoteRef(args.ref.harnessSessionRef);\n const tls = await resolveRemoteClientTls({\n config: settings.tls,\n secrets: args.open.secrets,\n url: settings.url,\n });\n await register({ url: settings.url, hop, id, open: args.open, tls });\n if (tls != null) clientTls.set(id, tls);\n else clientTls.delete(id);\n return {\n // Re-minted on every open and resume: a rotated hop or a legacy ref gets a current token.\n ref: {\n ...args.ref,\n harnessSessionRef: formatRemoteRef({\n id,\n url: settings.url,\n healthToken: healthTokenFor({ hop, id }),\n closeToken: closeTokenFor({ hop, id }),\n }),\n },\n transport: transportForUrl(settings.url),\n url: settings.url,\n tls,\n headers: { authorization: `Bearer ${hop}`, [PROGRAM_CONFIG_HEADER]: id },\n };\n };\n\n return {\n describe: async (binding) => {\n if (binding == null) return spec.description;\n const settings = spec.settings(binding.config);\n const hop = await resolveSecretRef({\n specifier: spec.specifier,\n field: 'hop',\n ref: settings.hop,\n secrets: binding.secrets,\n });\n const tls = await resolveRemoteClientTls({\n config: settings.tls,\n secrets: binding.secrets,\n url: settings.url,\n });\n const response = await fetchFor(tls)(\n runtimeUrlFor({ url: settings.url, path: 'v1/describe' }),\n {\n method: 'POST',\n headers: { authorization: `Bearer ${hop}`, 'content-type': 'application/json' },\n body: JSON.stringify({ program: { executable: settings.executable } }),\n signal: AbortSignal.timeout(registerTimeoutMs),\n },\n );\n const text = await response.text();\n if (!response.ok) {\n const unavailable = unavailableOf(text);\n if (unavailable != null) throw RequestError.internalError(unavailable, unavailable.detail);\n throw new Error(\n `${spec.specifier}: describe failed (HTTP ${response.status}) ${text.slice(0, 200)}`,\n );\n }\n const description = harnessDescriptionSchema.parse(JSON.parse(text));\n return {\n ...description,\n configSchema: spec.description.configSchema,\n trust: spec.description.trust,\n inference: spec.description.inference,\n consumes: spec.description.consumes,\n };\n },\n open: async (args) => {\n const ref: HarnessSessionRefIface = {\n harness: packageRef(spec.specifier),\n // endpointFor() completes the ref with the url and health token.\n harnessSessionRef: args.harnessSessionId,\n generation: 1,\n };\n return endpointFor({ open: args, ref });\n },\n // The runtime resumes the program's own session (Claude Code session, Codex\n // rollout); re-registering hands it the fresh grants.\n resume: async (args: ResumeHarnessSessionArgsIface) =>\n endpointFor({ open: args, ref: args.ref }),\n park: async () => undefined,\n close: async (ref, options) => {\n const { id, url: retainedUrl, closeToken } = parseRemoteRef(ref.harnessSessionRef);\n const context = options?.context;\n const settings = context == null ? null : spec.settings(context.config);\n const url = retainedUrl ?? settings?.url ?? null;\n if (url == null) {\n if (options?.force === true)\n throw new Error(`${spec.specifier}: the legacy ref must be resumed before forced close`);\n return;\n }\n const hop =\n context == null || settings == null\n ? null\n : await context.secrets.get(settings.hop.secretKey);\n const scopedToken = hop == null || hop.length === 0 ? closeToken : closeTokenFor({ hop, id });\n if (scopedToken == null) {\n if (options?.force === true)\n throw new Error(\n `${spec.specifier}: the legacy ref needs close context or resume before forced close`,\n );\n return;\n }\n const tls =\n context == null || settings == null\n ? clientTls.get(id)\n : await resolveRemoteClientTls({\n config: settings.tls,\n secrets: context.secrets,\n url,\n });\n const response = await fetchFor(tls)(programConfigUrlFor({ url, id }), {\n method: 'DELETE',\n headers: { [CLOSE_TOKEN_HEADER]: scopedToken, 'content-type': 'application/json' },\n body: JSON.stringify({ force: options?.force === true }),\n signal: AbortSignal.timeout(config.closeTimeoutMs ?? DEFAULT_CLOSE_TIMEOUT_MS),\n });\n const body = await response.text();\n if (!response.ok && response.status !== 404)\n throw new Error(\n `${spec.specifier}: remote close failed (HTTP ${response.status}) ${body.slice(0, 200)}`,\n );\n clientTls.delete(id);\n },\n health: async (ref, context): Promise<HarnessHealthIface> => {\n const { id, url, healthToken } = parseRemoteRef(ref.harnessSessionRef);\n const checkedAt = new Date(now()).toISOString();\n if (url == null)\n return { ok: false, detail: 'harness session ref has no runtime url', checkedAt };\n try {\n const headers = new Headers({ [PROGRAM_CONFIG_HEADER]: id });\n if (healthToken != null) headers.set(HEALTH_TOKEN_HEADER, healthToken);\n const tls =\n context == null\n ? undefined\n : await resolveRemoteClientTls({\n config: spec.settings(context.config).tls,\n secrets: context.secrets,\n url,\n });\n const response = await fetchFor(tls)(healthUrlFor(url), {\n method: 'GET',\n headers,\n signal: AbortSignal.timeout(healthTimeoutMs),\n });\n const body = await response.text();\n const unavailable = response.ok ? null : unavailableOf(body);\n return response.ok\n ? { ok: true, checkedAt }\n : {\n ok: false,\n detail: unavailable?.detail ?? `healthz returned HTTP ${response.status}`,\n checkedAt,\n };\n } catch (error) {\n return {\n ok: false,\n detail: `healthz failed: ${error instanceof Error ? error.message : String(error)}`,\n checkedAt,\n };\n }\n },\n };\n};\nexport { REMOTE_URL_SCHEMES, remoteUrlJson, remoteUrlZod } from \"../harnesses/remote-url.mjs\";\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": ["RemoteProgram"]
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/sandboxes/build-scripts.ts"],
|
|
4
|
-
"sourcesContent": ["import { shellQuote } from \"./paths.mjs\";\n\n// The template builder's check that a guide build leaves nothing under /home or\n// /workspaces: a computer's user volume is mounted over both, so anything a build\n// step put there (~/.cargo, ~/.local, a clone in /workspaces) is hidden at run\n// time. Home content belongs in /usr/share/final-vm/home-skel\n//
|
|
4
|
+
"sourcesContent": ["import { shellQuote } from \"./paths.mjs\";\n\n// The template builder's check that a guide build leaves nothing under /home or\n// /workspaces: a computer's user volume is mounted over both, so anything a build\n// step put there (~/.cargo, ~/.local, a clone in /workspaces) is hidden at run\n// time. Home content belongs in /usr/share/final-vm/home-skel\n// so it remains available when the user volume is mounted.\n\nexport const HOME_SKELETON_ROOT = '/usr/share/final-vm/home-skel';\nexport const USER_VOLUME_ROOTS = ['/home', '/workspaces'];\n// Under /var/tmp, which the pre-clean and the capture scrub both remove.\nexport const HOME_BASELINE_ROOT = '/var/tmp/final-template-build/home-baseline';\nexport const HOME_CONTENT_LIMIT = 50;\nexport const HOME_BASELINE_MISSING_EXIT_CODE = 3;\n\nexport interface HomeContentScriptOptionsIface {\n // Paths the runner stages or the capture scrub removes; skipped with their subtrees.\n excludedPaths: string[];\n roots?: string[];\n homeRoot?: string;\n skeletonRoot?: string;\n baselineRoot?: string;\n}\n\nconst volumePathPrefixes = (roots: string[]): string[] => roots.map((root) => `${root}/`);\n\n// Snapshot of what the platform put under /home and /workspaces before any build\n// step ran, such as the guest agent's home marker.\nexport const homeBaselineScript = (\n args: { roots?: string[]; baselineRoot?: string } = {},\n): string => {\n const roots = args.roots ?? USER_VOLUME_ROOTS;\n const baselineRoot = shellQuote(args.baselineRoot ?? HOME_BASELINE_ROOT);\n return `\nset -eu\nrm -rf ${baselineRoot}\nmkdir -p ${baselineRoot}\nfor root in ${roots.map(shellQuote).join(' ')}; do\n if [ -d \"$root\" ]; then\n mkdir -p ${baselineRoot}\"$(dirname \"$root\")\"\n cp -PRp \"$root\" ${baselineRoot}\"$root\" 2>/dev/null || true\n fi\ndone\n`;\n};\n\n// A VM's user volume is mounted over /home/user and /workspaces, so the layer\n// must leave nothing there. Prints up to 50 entries the build steps created or\n// changed that neither the home skeleton nor the pre-build baseline has. A\n// directory neither has is reported once, when it holds anything but directories.\nexport const homeContentScript = (options: HomeContentScriptOptionsIface): string => {\n const roots = options.roots ?? USER_VOLUME_ROOTS;\n const homeRoot = options.homeRoot ?? '/home/user';\n const skeletonRoot = options.skeletonRoot ?? HOME_SKELETON_ROOT;\n const baselineRoot = options.baselineRoot ?? HOME_BASELINE_ROOT;\n const excluded = options.excludedPaths.filter((path) =>\n volumePathPrefixes(roots).some((prefix) => path.startsWith(prefix)),\n );\n const prune =\n excluded.length === 0\n ? ''\n : `\\\\( ${excluded.map((path) => `-path ${shellQuote(path)}`).join(' -o ')} \\\\) -prune -o`;\n return `\nset -u\nif [ ! -d ${shellQuote(baselineRoot)} ]; then\n echo \"the pre-build home baseline ${baselineRoot} is missing\"\n exit ${HOME_BASELINE_MISSING_EXIT_CODE}\nfi\nsame_entry() {\n if [ -L \"$1\" ]; then\n [ -L \"$2\" ] && [ \"$(readlink \"$1\")\" = \"$(readlink \"$2\")\" ]\n return\n fi\n if [ -L \"$2\" ]; then\n return 1\n fi\n if [ -d \"$1\" ]; then\n [ -d \"$2\" ]\n return\n fi\n [ -f \"$1\" ] && [ -f \"$2\" ] && cmp -s \"$1\" \"$2\"\n}\nhome_root=${shellQuote(homeRoot)}\nskeleton_root=${shellQuote(skeletonRoot)}\nskeleton_entry() {\n case \"$1\" in\n (\"$home_root\") printf '%s\\\\n' \"$skeleton_root\" ;;\n (\"$home_root\"/*) printf '%s/%s\\\\n' \"$skeleton_root\" \"\\${1#\"$home_root\"/}\" ;;\n (*) printf '%s\\\\n' /nonexistent/final-home-skeleton-entry ;;\n esac\n}\nhas_content() {\n if [ ! -r \"$1\" ] || [ ! -x \"$1\" ]; then\n return 0\n fi\n [ -n \"$(find \"$1\" -xdev -mindepth 1 ${prune} ! -type d -print 2>/dev/null | head -n 1)\" ]\n}\nscan() {\n reported=''\n find ${roots.map(shellQuote).join(' ')} -xdev -mindepth 1 ${prune} -print 2>/dev/null | while IFS= read -r path; do\n if [ -n \"$reported\" ]; then\n case \"$path\" in\n (\"$reported\"/*) continue ;;\n esac\n fi\n if same_entry \"$path\" \"$(skeleton_entry \"$path\")\" || same_entry \"$path\" ${shellQuote(baselineRoot)}\"$path\"; then\n continue\n fi\n if [ -d \"$path\" ] && [ ! -L \"$path\" ]; then\n if ! has_content \"$path\"; then\n continue\n fi\n reported=\"$path\"\n fi\n printf '%s\\\\n' \"$path\"\n done\n}\nhidden=\"$(scan | head -n ${HOME_CONTENT_LIMIT})\"\nif [ -n \"$hidden\" ]; then\n printf '%s\\\\n' \"$hidden\"\n exit 1\nfi\n`;\n};\n"],
|
|
5
5
|
"mappings": "AAAA,SAAS,kBAAkB;AAQpB,MAAM,qBAAqB;AAC3B,MAAM,oBAAoB,CAAC,SAAS,aAAa;AAEjD,MAAM,qBAAqB;AAC3B,MAAM,qBAAqB;AAC3B,MAAM,kCAAkC;AAW/C,MAAM,qBAAqB,CAAC,UAA8B,MAAM,IAAI,CAAC,SAAS,GAAG,IAAI,GAAG;AAIjF,MAAM,qBAAqB,CAChC,OAAoD,CAAC,MAC1C;AACX,QAAM,QAAQ,KAAK,SAAS;AAC5B,QAAM,eAAe,WAAW,KAAK,gBAAgB,kBAAkB;AACvE,SAAO;AAAA;AAAA,SAEA,YAAY;AAAA,WACV,YAAY;AAAA,cACT,MAAM,IAAI,UAAU,EAAE,KAAK,GAAG,CAAC;AAAA;AAAA,eAE9B,YAAY;AAAA,sBACL,YAAY;AAAA;AAAA;AAAA;AAIlC;AAMO,MAAM,oBAAoB,CAAC,YAAmD;AACnF,QAAM,QAAQ,QAAQ,SAAS;AAC/B,QAAM,WAAW,QAAQ,YAAY;AACrC,QAAM,eAAe,QAAQ,gBAAgB;AAC7C,QAAM,eAAe,QAAQ,gBAAgB;AAC7C,QAAM,WAAW,QAAQ,cAAc;AAAA,IAAO,CAAC,SAC7C,mBAAmB,KAAK,EAAE,KAAK,CAAC,WAAW,KAAK,WAAW,MAAM,CAAC;AAAA,EACpE;AACA,QAAM,QACJ,SAAS,WAAW,IAChB,KACA,OAAO,SAAS,IAAI,CAAC,SAAS,SAAS,WAAW,IAAI,CAAC,EAAE,EAAE,KAAK,MAAM,CAAC;AAC7E,SAAO;AAAA;AAAA,YAEG,WAAW,YAAY,CAAC;AAAA,sCACE,YAAY;AAAA,SACzC,+BAA+B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAgB5B,WAAW,QAAQ,CAAC;AAAA,gBAChB,WAAW,YAAY,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,wCAYA,KAAK;AAAA;AAAA;AAAA;AAAA,SAIpC,MAAM,IAAI,UAAU,EAAE,KAAK,GAAG,CAAC,sBAAsB,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,8EAMW,WAAW,YAAY,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,2BAY3E,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAM7C;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/sandboxes/paths.ts"],
|
|
4
|
-
"sourcesContent": ["import { quoteShellArgument } from '@finalai/computers/shell';\nimport { posix, win32 } from 'node:path';\n\n// Sandbox paths, by the sandbox's platform: computers and Unix Devices are\n// POSIX; a Windows Device's workspace is a drive path (`C:\\
|
|
4
|
+
"sourcesContent": ["import { quoteShellArgument } from '@finalai/computers/shell';\nimport { posix, win32 } from 'node:path';\n\n// Sandbox paths, by the sandbox's platform: computers and Unix Devices are\n// POSIX; a Windows Device's workspace is a drive path (`C:\\workspaces\\chat`).\n// Resolving with the wrong flavour turns the root into a relative path under this\n// process's cwd, so every operation would land outside the workspace.\n\nexport enum SandboxPlatform {\n Posix = 'posix',\n Windows = 'windows',\n}\n\nexport interface SandboxPathsIface {\n readonly platform: SandboxPlatform;\n readonly separator: string;\n // Absolute, normalized, without a trailing separator (except a root).\n resolve(root: string, path: string): string;\n join(...parts: string[]): string;\n dirname(path: string): string;\n basename(path: string): string;\n // Whether `path` is `root` or below it.\n isWithin(root: string, path: string): boolean;\n}\n\nconst posixPaths: SandboxPathsIface = {\n platform: SandboxPlatform.Posix,\n separator: '/',\n resolve: (root, path) => {\n const resolved = posix.resolve(root, path);\n return resolved === '/' ? resolved : resolved.replace(/\\/+$/, '');\n },\n join: (...parts) => posix.join(...parts),\n dirname: (path) => posix.dirname(path),\n basename: (path) => posix.basename(path),\n isWithin: (root, path) => {\n const relative = posix.relative(root, path);\n return (\n relative === '' ||\n (relative !== '..' && !relative.startsWith('../') && !posix.isAbsolute(relative))\n );\n },\n};\n\nconst DRIVE_ROOT = /^[A-Za-z]:\\\\$/;\n\n// `\\\\server\\share` and `\\\\?\\` / `\\\\.\\` device paths.\nexport const isUncPath = (path: string): boolean => /^[\\\\/]{2}/.test(path);\n\nexport const isWindowsDrivePath = (path: string): boolean => /^[A-Za-z]:[\\\\/]/.test(path);\n\nconst refuseUnc = (path: string): string => {\n if (isUncPath(path))\n throw new Error(`UNC paths are not supported in a Device workspace: ${path}`);\n return path;\n};\n\nconst windowsPaths: SandboxPathsIface = {\n platform: SandboxPlatform.Windows,\n separator: '\\\\',\n resolve: (root, path) => {\n refuseUnc(path);\n const resolved = refuseUnc(win32.resolve(refuseUnc(root), path));\n return DRIVE_ROOT.test(resolved) ? resolved : resolved.replace(/\\\\+$/, '');\n },\n join: (...parts) => win32.join(...parts),\n dirname: (path) => win32.dirname(path),\n basename: (path) => win32.basename(path),\n // win32.relative compares case-insensitively and across separators; a\n // different drive comes back absolute.\n isWithin: (root, path) => {\n if (isUncPath(root) || isUncPath(path)) return false;\n const relative = win32.relative(root, path);\n return (\n relative === '' ||\n (relative !== '..' && !relative.startsWith('..\\\\') && !win32.isAbsolute(relative))\n );\n },\n};\n\nexport const sandboxPathsFor = (platform: SandboxPlatform): SandboxPathsIface =>\n platform === SandboxPlatform.Windows ? windowsPaths : posixPaths;\n\n// A platform a Device did not report, from the shape of its workspace root.\nexport const platformOfRoot = (root: string): SandboxPlatform =>\n isWindowsDrivePath(root) || isUncPath(root) ? SandboxPlatform.Windows : SandboxPlatform.Posix;\n\nexport const POSIX_PATHS = posixPaths;\n\n// Preserve arguments through both POSIX shells and a Unix Device's fish shell.\nexport const shellQuote = quoteShellArgument;\n"],
|
|
5
5
|
"mappings": "AAAA,SAAS,0BAA0B;AACnC,SAAS,OAAO,aAAa;AAOtB,IAAK,kBAAL,kBAAKA,qBAAL;AACL,EAAAA,iBAAA,WAAQ;AACR,EAAAA,iBAAA,aAAU;AAFA,SAAAA;AAAA,GAAA;AAiBZ,MAAM,aAAgC;AAAA,EACpC,UAAU;AAAA,EACV,WAAW;AAAA,EACX,SAAS,CAAC,MAAM,SAAS;AACvB,UAAM,WAAW,MAAM,QAAQ,MAAM,IAAI;AACzC,WAAO,aAAa,MAAM,WAAW,SAAS,QAAQ,QAAQ,EAAE;AAAA,EAClE;AAAA,EACA,MAAM,IAAI,UAAU,MAAM,KAAK,GAAG,KAAK;AAAA,EACvC,SAAS,CAAC,SAAS,MAAM,QAAQ,IAAI;AAAA,EACrC,UAAU,CAAC,SAAS,MAAM,SAAS,IAAI;AAAA,EACvC,UAAU,CAAC,MAAM,SAAS;AACxB,UAAM,WAAW,MAAM,SAAS,MAAM,IAAI;AAC1C,WACE,aAAa,MACZ,aAAa,QAAQ,CAAC,SAAS,WAAW,KAAK,KAAK,CAAC,MAAM,WAAW,QAAQ;AAAA,EAEnF;AACF;AAEA,MAAM,aAAa;AAGZ,MAAM,YAAY,CAAC,SAA0B,YAAY,KAAK,IAAI;AAElE,MAAM,qBAAqB,CAAC,SAA0B,kBAAkB,KAAK,IAAI;AAExF,MAAM,YAAY,CAAC,SAAyB;AAC1C,MAAI,UAAU,IAAI;AAChB,UAAM,IAAI,MAAM,sDAAsD,IAAI,EAAE;AAC9E,SAAO;AACT;AAEA,MAAM,eAAkC;AAAA,EACtC,UAAU;AAAA,EACV,WAAW;AAAA,EACX,SAAS,CAAC,MAAM,SAAS;AACvB,cAAU,IAAI;AACd,UAAM,WAAW,UAAU,MAAM,QAAQ,UAAU,IAAI,GAAG,IAAI,CAAC;AAC/D,WAAO,WAAW,KAAK,QAAQ,IAAI,WAAW,SAAS,QAAQ,QAAQ,EAAE;AAAA,EAC3E;AAAA,EACA,MAAM,IAAI,UAAU,MAAM,KAAK,GAAG,KAAK;AAAA,EACvC,SAAS,CAAC,SAAS,MAAM,QAAQ,IAAI;AAAA,EACrC,UAAU,CAAC,SAAS,MAAM,SAAS,IAAI;AAAA;AAAA;AAAA,EAGvC,UAAU,CAAC,MAAM,SAAS;AACxB,QAAI,UAAU,IAAI,KAAK,UAAU,IAAI,EAAG,QAAO;AAC/C,UAAM,WAAW,MAAM,SAAS,MAAM,IAAI;AAC1C,WACE,aAAa,MACZ,aAAa,QAAQ,CAAC,SAAS,WAAW,MAAM,KAAK,CAAC,MAAM,WAAW,QAAQ;AAAA,EAEpF;AACF;AAEO,MAAM,kBAAkB,CAAC,aAC9B,aAAa,0BAA0B,eAAe;AAGjD,MAAM,iBAAiB,CAAC,SAC7B,mBAAmB,IAAI,KAAK,UAAU,IAAI,IAAI,0BAA0B;AAEnE,MAAM,cAAc;AAGpB,MAAM,aAAa;",
|
|
6
6
|
"names": ["SandboxPlatform"]
|
|
7
7
|
}
|
|
@@ -84,14 +84,14 @@ const openArgs = (config, secrets = {}) => ({
|
|
|
84
84
|
},
|
|
85
85
|
sandboxes: { computer: computerEndpoint, device: deviceEndpoint },
|
|
86
86
|
credentials: {
|
|
87
|
-
brokerUrl: "https://
|
|
87
|
+
brokerUrl: "https://credentials.example.com/provider",
|
|
88
88
|
grants: [
|
|
89
89
|
{ route: CredentialRoute.Model, token: "grant-model", expiresAt: EXPIRES_AT },
|
|
90
90
|
{ route: CredentialRoute.Mcp, token: "grant-mcp", expiresAt: EXPIRES_AT }
|
|
91
91
|
],
|
|
92
92
|
atOpen: null,
|
|
93
93
|
refresh: {
|
|
94
|
-
url: "https://
|
|
94
|
+
url: "https://credentials.example.com/provider/refresh",
|
|
95
95
|
token: "grant-model",
|
|
96
96
|
harnessSessionId: "0192f7a0-0000-7000-8000-0000000000a1",
|
|
97
97
|
expiresAt: EXPIRES_AT
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/testing/harness.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n BaseCapability,\n CredentialRoute,\n credentials,\n jsonObjectSchema,\n MANIFEST_VERSION,\n packageRef,\n ToolApproval,\n type OpenHarnessSessionArgsIface,\n type ResolvedAgentManifestIface,\n type SandboxTargetEndpointIface,\n} from '@finalai/agent-provider';\n\n// Shared fixtures for the harness tests (no module mocks: fakes go in through\n// each factory's fetch/now/newId seams).\n\nexport const SESSION_ID = '0192f7a0-0000-7000-8000-000000000001';\nexport const HARNESS_SESSION_ID = '0192f7a0-0000-7000-8000-0000000000aa';\nexport const EXPIRES_AT = '2026-10-10T00:15:00.000Z';\n\nexport const computerEndpoint: SandboxTargetEndpointIface = {\n target: 'computer',\n bindingId: '0192f7a0-0000-7000-8000-0000000000c1',\n capabilities: ['shell', 'files', 'process'],\n rpc: { url: 'wss://agent-session.test/sandboxes/b/c1', token: 'rpc-c1', expiresAt: EXPIRES_AT },\n mcp: {\n url: 'https://agent-session.test/sandboxes/b/c1/mcp',\n token: 'mcp-c1',\n expiresAt: EXPIRES_AT,\n },\n};\n\nexport const deviceEndpoint: SandboxTargetEndpointIface = {\n target: 'device',\n bindingId: '0192f7a0-0000-7000-8000-0000000000d1',\n capabilities: ['computerUse'],\n rpc: null,\n mcp: {\n url: 'https://agent-session.test/sandboxes/b/d1/mcp',\n token: 'mcp-d1',\n expiresAt: EXPIRES_AT,\n },\n};\n\nexport const resolvedManifest = (): ResolvedAgentManifestIface => ({\n version: MANIFEST_VERSION,\n identity: { name: 'Release Scout', description: null },\n harness: {\n harness: packageRef('@finalai/final-agent-provider/harnesses/serverless-pi'),\n config: {},\n },\n mcpServers: [],\n sandboxes: {},\n tools: {\n base: {\n shell: { target: 'computer' },\n files: { target: 'computer' },\n computerUse: { target: 'device' },\n },\n },\n secrets: [],\n toolPolicy: {\n shell: ToolApproval.Ask,\n 'files.write': ToolApproval.Ask,\n create_issue: ToolApproval.Allow,\n },\n channels: [],\n hooks: [],\n schedules: [],\n bundle: null,\n});\n\nexport const openArgs = (\n config: unknown,\n secrets: Record<string, string> = {},\n): OpenHarnessSessionArgsIface => ({\n harnessSessionId: '0192f7a0-0000-7000-8000-0000000000a1',\n session: {\n sessionId: SESSION_ID,\n organizationId: '0192f7a0-0000-7000-8000-0000000000o1',\n definitionId: '0192f7a0-0000-7000-8000-0000000000e1',\n revisionId: '0192f7a0-0000-7000-8000-0000000000r1',\n sessionServiceUrl: 'https://agent-session.test',\n },\n config: {\n credentials: credentials.chain({\n payer: 'organization',\n tryInOrder: [credentials.apiKey({ owner: 'organization' }), credentials.platform()],\n }),\n ...jsonObjectSchema.parse(config),\n },\n manifest: resolvedManifest(),\n mcpServers: [],\n baseTools: {\n [BaseCapability.Shell]: computerEndpoint,\n [BaseCapability.Files]: computerEndpoint,\n [BaseCapability.ComputerUse]: deviceEndpoint,\n },\n sandboxes: { computer: computerEndpoint, device: deviceEndpoint },\n credentials: {\n brokerUrl: 'https://
|
|
4
|
+
"sourcesContent": ["import {\n BaseCapability,\n CredentialRoute,\n credentials,\n jsonObjectSchema,\n MANIFEST_VERSION,\n packageRef,\n ToolApproval,\n type OpenHarnessSessionArgsIface,\n type ResolvedAgentManifestIface,\n type SandboxTargetEndpointIface,\n} from '@finalai/agent-provider';\n\n// Shared fixtures for the harness tests (no module mocks: fakes go in through\n// each factory's fetch/now/newId seams).\n\nexport const SESSION_ID = '0192f7a0-0000-7000-8000-000000000001';\nexport const HARNESS_SESSION_ID = '0192f7a0-0000-7000-8000-0000000000aa';\nexport const EXPIRES_AT = '2026-10-10T00:15:00.000Z';\n\nexport const computerEndpoint: SandboxTargetEndpointIface = {\n target: 'computer',\n bindingId: '0192f7a0-0000-7000-8000-0000000000c1',\n capabilities: ['shell', 'files', 'process'],\n rpc: { url: 'wss://agent-session.test/sandboxes/b/c1', token: 'rpc-c1', expiresAt: EXPIRES_AT },\n mcp: {\n url: 'https://agent-session.test/sandboxes/b/c1/mcp',\n token: 'mcp-c1',\n expiresAt: EXPIRES_AT,\n },\n};\n\nexport const deviceEndpoint: SandboxTargetEndpointIface = {\n target: 'device',\n bindingId: '0192f7a0-0000-7000-8000-0000000000d1',\n capabilities: ['computerUse'],\n rpc: null,\n mcp: {\n url: 'https://agent-session.test/sandboxes/b/d1/mcp',\n token: 'mcp-d1',\n expiresAt: EXPIRES_AT,\n },\n};\n\nexport const resolvedManifest = (): ResolvedAgentManifestIface => ({\n version: MANIFEST_VERSION,\n identity: { name: 'Release Scout', description: null },\n harness: {\n harness: packageRef('@finalai/final-agent-provider/harnesses/serverless-pi'),\n config: {},\n },\n mcpServers: [],\n sandboxes: {},\n tools: {\n base: {\n shell: { target: 'computer' },\n files: { target: 'computer' },\n computerUse: { target: 'device' },\n },\n },\n secrets: [],\n toolPolicy: {\n shell: ToolApproval.Ask,\n 'files.write': ToolApproval.Ask,\n create_issue: ToolApproval.Allow,\n },\n channels: [],\n hooks: [],\n schedules: [],\n bundle: null,\n});\n\nexport const openArgs = (\n config: unknown,\n secrets: Record<string, string> = {},\n): OpenHarnessSessionArgsIface => ({\n harnessSessionId: '0192f7a0-0000-7000-8000-0000000000a1',\n session: {\n sessionId: SESSION_ID,\n organizationId: '0192f7a0-0000-7000-8000-0000000000o1',\n definitionId: '0192f7a0-0000-7000-8000-0000000000e1',\n revisionId: '0192f7a0-0000-7000-8000-0000000000r1',\n sessionServiceUrl: 'https://agent-session.test',\n },\n config: {\n credentials: credentials.chain({\n payer: 'organization',\n tryInOrder: [credentials.apiKey({ owner: 'organization' }), credentials.platform()],\n }),\n ...jsonObjectSchema.parse(config),\n },\n manifest: resolvedManifest(),\n mcpServers: [],\n baseTools: {\n [BaseCapability.Shell]: computerEndpoint,\n [BaseCapability.Files]: computerEndpoint,\n [BaseCapability.ComputerUse]: deviceEndpoint,\n },\n sandboxes: { computer: computerEndpoint, device: deviceEndpoint },\n credentials: {\n brokerUrl: 'https://credentials.example.com/provider',\n grants: [\n { route: CredentialRoute.Model, token: 'grant-model', expiresAt: EXPIRES_AT },\n { route: CredentialRoute.Mcp, token: 'grant-mcp', expiresAt: EXPIRES_AT },\n ],\n atOpen: null,\n refresh: {\n url: 'https://credentials.example.com/provider/refresh',\n token: 'grant-model',\n harnessSessionId: '0192f7a0-0000-7000-8000-0000000000a1',\n expiresAt: EXPIRES_AT,\n },\n },\n secrets: { get: async (name) => secrets[name] ?? null },\n trace: {\n traceparent: '00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01',\n tracestate: null,\n },\n});\n\n// Monotonic ids so assertions can name them.\nexport const idSequence = (prefix: string): (() => string) => {\n let next = 0;\n return () => {\n next += 1;\n return `${prefix}-7000-8000-${String(next).padStart(12, '0')}`;\n };\n};\n"],
|
|
5
5
|
"mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AAKA,MAAM,aAAa;AACnB,MAAM,qBAAqB;AAC3B,MAAM,aAAa;AAEnB,MAAM,mBAA+C;AAAA,EAC1D,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,cAAc,CAAC,SAAS,SAAS,SAAS;AAAA,EAC1C,KAAK,EAAE,KAAK,2CAA2C,OAAO,UAAU,WAAW,WAAW;AAAA,EAC9F,KAAK;AAAA,IACH,KAAK;AAAA,IACL,OAAO;AAAA,IACP,WAAW;AAAA,EACb;AACF;AAEO,MAAM,iBAA6C;AAAA,EACxD,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,cAAc,CAAC,aAAa;AAAA,EAC5B,KAAK;AAAA,EACL,KAAK;AAAA,IACH,KAAK;AAAA,IACL,OAAO;AAAA,IACP,WAAW;AAAA,EACb;AACF;AAEO,MAAM,mBAAmB,OAAmC;AAAA,EACjE,SAAS;AAAA,EACT,UAAU,EAAE,MAAM,iBAAiB,aAAa,KAAK;AAAA,EACrD,SAAS;AAAA,IACP,SAAS,WAAW,uDAAuD;AAAA,IAC3E,QAAQ,CAAC;AAAA,EACX;AAAA,EACA,YAAY,CAAC;AAAA,EACb,WAAW,CAAC;AAAA,EACZ,OAAO;AAAA,IACL,MAAM;AAAA,MACJ,OAAO,EAAE,QAAQ,WAAW;AAAA,MAC5B,OAAO,EAAE,QAAQ,WAAW;AAAA,MAC5B,aAAa,EAAE,QAAQ,SAAS;AAAA,IAClC;AAAA,EACF;AAAA,EACA,SAAS,CAAC;AAAA,EACV,YAAY;AAAA,IACV,OAAO,aAAa;AAAA,IACpB,eAAe,aAAa;AAAA,IAC5B,cAAc,aAAa;AAAA,EAC7B;AAAA,EACA,UAAU,CAAC;AAAA,EACX,OAAO,CAAC;AAAA,EACR,WAAW,CAAC;AAAA,EACZ,QAAQ;AACV;AAEO,MAAM,WAAW,CACtB,QACA,UAAkC,CAAC,OACF;AAAA,EACjC,kBAAkB;AAAA,EAClB,SAAS;AAAA,IACP,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,cAAc;AAAA,IACd,YAAY;AAAA,IACZ,mBAAmB;AAAA,EACrB;AAAA,EACA,QAAQ;AAAA,IACN,aAAa,YAAY,MAAM;AAAA,MAC7B,OAAO;AAAA,MACP,YAAY,CAAC,YAAY,OAAO,EAAE,OAAO,eAAe,CAAC,GAAG,YAAY,SAAS,CAAC;AAAA,IACpF,CAAC;AAAA,IACD,GAAG,iBAAiB,MAAM,MAAM;AAAA,EAClC;AAAA,EACA,UAAU,iBAAiB;AAAA,EAC3B,YAAY,CAAC;AAAA,EACb,WAAW;AAAA,IACT,CAAC,eAAe,KAAK,GAAG;AAAA,IACxB,CAAC,eAAe,KAAK,GAAG;AAAA,IACxB,CAAC,eAAe,WAAW,GAAG;AAAA,EAChC;AAAA,EACA,WAAW,EAAE,UAAU,kBAAkB,QAAQ,eAAe;AAAA,EAChE,aAAa;AAAA,IACX,WAAW;AAAA,IACX,QAAQ;AAAA,MACN,EAAE,OAAO,gBAAgB,OAAO,OAAO,eAAe,WAAW,WAAW;AAAA,MAC5E,EAAE,OAAO,gBAAgB,KAAK,OAAO,aAAa,WAAW,WAAW;AAAA,IAC1E;AAAA,IACA,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,KAAK;AAAA,MACL,OAAO;AAAA,MACP,kBAAkB;AAAA,MAClB,WAAW;AAAA,IACb;AAAA,EACF;AAAA,EACA,SAAS,EAAE,KAAK,OAAO,SAAS,QAAQ,IAAI,KAAK,KAAK;AAAA,EACtD,OAAO;AAAA,IACL,aAAa;AAAA,IACb,YAAY;AAAA,EACd;AACF;AAGO,MAAM,aAAa,CAAC,WAAmC;AAC5D,MAAI,OAAO;AACX,SAAO,MAAM;AACX,YAAQ;AACR,WAAO,GAAG,MAAM,cAAc,OAAO,IAAI,EAAE,SAAS,IAAI,GAAG,CAAC;AAAA,EAC9D;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|