Files
PaperClipAI/ui/src/lib/agent-onboarding-prompt.ts
T
DottaandPaperclip c5f7713dd4 fix: clarify native tool use for external agents (#15734)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - External agents keep their own tools and app connections.
> - The Paperclip assignment catalog controls the Paperclip bridge.
> - Dot treated a qualification test restriction as a rule for later
work.
> - Existing prompts did not clearly separate these two tool sources.
> - This pull request tells agents to use their own capabilities and
return results to Paperclip.
> - The benefit is that a missing Paperclip app does not block an
available native capability.

## Linked Issues or Issue Description

Related work: #15722 and #15691. I searched open PRs for Dot tool
guidance and found no duplicate.

**What happened?**
After a qualification test, Dot refused a Slack request because the test
Paperclip agent had no assigned Slack app. Dot already had a working
Slack connection in ChatGPT. It carried the test's plugin restriction
into a later task message.

**Expected behavior**
An external agent uses its own available tools, apps, plugins, skills,
and capabilities to complete the current request. It then returns the
result through Paperclip's task reporting and completion tools. The
assignment catalog limits Paperclip calls. It does not disable the
agent's native tools.

**Steps to reproduce**
1. Connect a Dot that has a native Slack connection.
2. Complete a Paperclip qualification task with a restriction scoped to
that test.
3. Send a later task message that requires Slack.
4. Observe that Dot can mistake the Paperclip catalog for its full tool
inventory.

**Deployment mode**
Observed on Cloud staging with the Dot Runner.

## What Changed

- Add shared tool-use guidance for external-agent onboarding and Dot
setup.
- Include it in the server onboarding document, pairing response, event
instructions, and capability response.
- Clarify the tool descriptions on both Dot bridge paths.
- Include the same guidance in each Dot Runner assignment without adding
a shared-package dependency to the standalone Runner.
- Document native tool use and the scope of test restrictions.

## Verification

- The 49 relevant onboarding, Dot invitation, and Runner context tests
passed. The final UI run passed 29 tests. The updated plain-text
renderer run passed all 4 tests.
- Full workspace typecheck, full workspace build, and design token gates
passed. All current-head CI gates passed, including full tests, Runner
verification, and browser E2E. Greptile is 5/5 on the exact final
commit, and the one review finding is resolved.
- The real Dot received the standing correction and confirmed that it
will use its relevant tools and connections, return results through
Paperclip, and stop carrying the test restriction forward.
- Extended the existing plain-text renderer test to check native Slack
access, qualification-test scope, and the Paperclip denial boundary.
This covers agents that receive only the onboarding.txt URL.
- The full Vitest suite passed in CI. The earlier local Cloud
qualification run encountered failures in unchanged suites; this
follow-up uses focused local tests for the instruction changes.

## Risks

This changes instructions, not access checks. Native tools retain their
own permissions and approval requirements. Paperclip operations remain
scoped to the authorized company, agent, and task. Agents must not use
another connection or identity to bypass a denial. There is no database
migration or new permission grant.

## Model Used

OpenAI GPT-6 through Codex. Used reasoning, repository tools, and
browser control. The runtime does not expose a more specific model ID or
context window size.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-09 16:20:38 -05:00

127 lines
6.6 KiB
TypeScript

import { EXTERNAL_AGENT_TOOL_GUIDANCE } from "@paperclipai/shared/external-agent-guidance";
export type AgentOnboardingPromptInput = {
onboardingTextUrl: string;
connectionCandidates?: string[] | null;
testResolutionUrl?: string | null;
};
export function buildAgentOnboardingPrompt(input: AgentOnboardingPromptInput) {
const candidateUrls = buildCandidateOnboardingUrls(input);
const resolutionTestUrl = buildResolutionTestUrl(input);
const candidateList =
candidateUrls.length > 0
? candidateUrls.map((url) => `- ${url}`).join("\n")
: "- (No candidate URLs are available yet.)";
const connectivityBlock =
candidateUrls.length === 0
? `No candidate URLs are available. Ask the operator to configure a reachable Paperclip hostname, then retry.
Suggested steps for the operator:
- choose a hostname that resolves to the Paperclip host from your runtime
- run: npx paperclipai allowed-hostname <host>
- restart Paperclip
- verify with: curl -fsS http://<host>:3100/api/health
- regenerate this agent onboarding prompt`
: `If none are reachable, ask the operator to add a reachable Paperclip hostname, restart, and retry.
Suggested command for the operator:
- npx paperclipai allowed-hostname <host>
Then verify with: curl -fsS <base-url>/api/health`;
const resolutionLine = resolutionTestUrl
? `\nIf your runtime exposes a callback or gateway URL, test Paperclip-to-agent reachability with: ${resolutionTestUrl}?url=<urlencoded-agent-url>.`
: "";
return `You're invited to join a Paperclip company as an agent.
First, respond to your user that you understand the request and are going to onboard into Paperclip. Then work through the steps below.
Paperclip onboarding documents to try:
${candidateList}
Connectivity guidance:
Paperclip must be reachable from your runtime. Verify a base URL with: GET <base-url>/api/health
${connectivityBlock}${resolutionLine}
Join flow:
1. Read the onboarding.txt document from the first reachable URL above.
2. Submit an agent join request to the invite registration endpoint.
3. Use your own agent name for \`agentName\`.
4. Include a concise \`capabilities\` summary so the board knows what work to assign you.
5. Set \`adapterType\` to the Paperclip adapter that matches your runtime when one exists.
6. Put runtime-specific settings in \`agentDefaultsPayload\`.
7. Wait for board approval before claiming the API key.
8. Claim the Paperclip API key once through the standard claim endpoint, store it securely, and use it for future Paperclip API calls. Store the parsed \`token\` field from the raw HTTP JSON response before printing or summarizing it; do not copy token values from chat, transcript, or tool-output previews. A token value containing literal \`...\` or \`[redacted]\` is a masked display preview, not a valid key. Do not rotate or invent a Paperclip key manually.
OpenClaw Gateway note:
If you are an OpenClaw Gateway agent, use \`adapterType: "openclaw_gateway"\`, set \`agentDefaultsPayload.url\` to your \`ws://\` or \`wss://\` gateway URL, and include \`agentDefaultsPayload.headers["x-openclaw-token"]\` with your gateway token. Do not use \`/v1/responses\` or \`/hooks/*\` in that join flow.
Hermes Gateway note:
If you are a Hermes Gateway agent, use \`adapterType: "hermes_gateway"\`.
- For a clean Hermes install, generate a fresh random gateway key, start Hermes with \`API_SERVER_ENABLED=true\` and \`API_SERVER_KEY=<random-gateway-key>\`, then run \`hermes gateway run --replace --accept-hooks\`. The default Hermes API server port is \`8642\`.
- Set \`agentDefaultsPayload.apiBaseUrl\` to the Hermes gateway URL Paperclip can reach, \`agentDefaultsPayload.apiKey\` to the exact same value as \`API_SERVER_KEY\`, and \`agentDefaultsPayload.paperclipApiUrl\` to the Paperclip base URL Hermes can reach. \`agentDefaultsPayload.apiKey\` is the Hermes gateway key, not the Paperclip API key.
- \`apiBaseUrl\` examples: local loopback API \`http://127.0.0.1:8642\`; local dashboard root \`http://127.0.0.1:9119\` (Paperclip maps it to \`/api\`); LAN/private network \`http://<private-ip>:8642\`; private overlay \`http://<tailnet-host>:8642\`; Docker \`http://host.docker.internal:8642\`; reverse proxy/TLS \`https://hermes-gateway.example\`.
- If you only have the default Hermes dashboard or chat URL, \`http://127.0.0.1:9119\` and \`http://127.0.0.1:9119/chat\` are accepted and map to \`/api\` automatically.
- Watch out: \`/chat\` and the dashboard root are browser UI routes. Paperclip tests \`/api/health\` and starts runs with \`/api/v1/runs\` after mapping them to the API base.
- Three distinct uses to keep apart: \`hermes_local\` runs Hermes on the Paperclip host; \`hermes_gateway\` calls an already-running Hermes API server via \`apiBaseUrl\`; Hermes-originated Paperclip API calls use the claimed \`PAPERCLIP_API_KEY\` and \`PAPERCLIP_API_URL\`, not \`agentDefaultsPayload.apiBaseUrl\`.
- When claiming the Paperclip API key from Hermes, write the raw response \`token\` directly to private storage and verify it with a Paperclip API call. Hermes/tool displays may redact or truncate secrets, so never persist a displayed preview that contains \`...\` as the actual key.
After you have connected to Paperclip, review and follow the full onboarding instructions in onboarding.txt.
Working through Paperclip:
${EXTERNAL_AGENT_TOOL_GUIDANCE}
`;
}
function buildCandidateOnboardingUrls(input: AgentOnboardingPromptInput): string[] {
const candidates = (input.connectionCandidates ?? [])
.map((candidate) => candidate.trim())
.filter(Boolean);
const urls = new Set<string>();
let onboardingUrl: URL | null = null;
try {
onboardingUrl = new URL(input.onboardingTextUrl);
urls.add(onboardingUrl.toString());
} catch {
const trimmed = input.onboardingTextUrl.trim();
if (trimmed) urls.add(trimmed);
}
if (!onboardingUrl) {
for (const candidate of candidates) {
urls.add(candidate);
}
return Array.from(urls);
}
const onboardingPath = `${onboardingUrl.pathname}${onboardingUrl.search}`;
for (const candidate of candidates) {
try {
const base = new URL(candidate);
urls.add(`${base.origin}${onboardingPath}`);
} catch {
urls.add(candidate);
}
}
return Array.from(urls);
}
function buildResolutionTestUrl(input: AgentOnboardingPromptInput): string | null {
const explicit = input.testResolutionUrl?.trim();
if (explicit) return explicit;
try {
const onboardingUrl = new URL(input.onboardingTextUrl);
const testPath = onboardingUrl.pathname.replace(
/\/onboarding\.txt$/,
"/test-resolution",
);
return `${onboardingUrl.origin}${testPath}`;
} catch {
return null;
}
}