mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-22 16:51:28 +02:00
## Thinking Path > - Paperclip manages AI agents and their work. > - The native Runner keeps a live provider process between task turns. > - Managed GitHub access used a token tied to one run. > - A new run forced Paperclip to replace that process to replace its token. > - This PR gives the session a stable credential transport and binds each operation to the active run. > - The agent can keep its process while Paperclip checks current identity and grants. ## Linked Issues or Issue Description Follow-up to #13738. Related credential-rotation work: #11770 and #8208 use process replacement for other adapter credentials; this change applies to managed GitHub access in the native Runner. **What happened?** A configured GitHub connection forced a warm native provider process to close at each new run. The saved conversation survived, but the live process did not. **Expected behavior** Keep the warm provider process. Resolve GitHub access for the current run when each command starts. Deny access while idle or after the run ends. **Steps to reproduce** 1. Configure managed GitHub access for a native Runner agent with a warm session. 2. Complete a turn, then send another message to the same task. 3. Observe the provider process close with the reason `warm native session configuration changed`. **Paperclip version or commit** Reproduced on master `8326e33ad`. Rebased onto `e3d8fb087` before submission. **Deployment mode** Local and remote native execution, including the sandbox callback bridge. ## What Changed - Move configured native GitHub transport and launcher ownership from the run to the provider session. - Bind the broker only after the executor acquires session ownership. Clear that binding when the run exits. - Keep the shared live-run, identity, grant, and trust-policy checks for each credential request. - Reject wrong scopes, idle requests, and credential responses that arrive after their run binding changes. - Retire transport and launcher files with the provider session. Keep anonymous commands available if bridge startup fails. - Add red/green executor tests, real subprocess and callback-bridge tests, and database checks. Update the runtime documentation. ## Verification - Before the fix, both new local and remote warm-session reuse tests failed. - After the fix, 435 targeted tests passed across the executor, broker, launcher, token, and database suites. - A real long-lived test process kept the same PID and original environment across two runs, including through the production callback bridge on local test processes. - Server typecheck and TypeScript compilation passed. - Full workspace typecheck and build passed. Server typecheck passed again after the review fix. - The fallback-logging regression failed before the fix; all 9 broker tests pass afterward. - The exact chat sidebar browser scenario passed locally. The initial CI timeout showed failed Vite module downloads; all eight browser shards pass on the latest commit. - All 53 latest-head checks passed, including the full CI test matrix and security checks (two unrelated conditional checks skipped). - The duplicate full local test run was stopped after CI passed; it is not claimed as a completed local pass. Targeted local tests, workspace typecheck/build, and the browser scenario passed. - Greptile reviewed the latest commit at 5/5 with no unresolved findings. - No fresh paid provider or Daytona campaign has run for this change. ## Risks - The broker now lives as long as the provider session. Tests cover idle denial, late cleanup, late responses, shutdown, and failed startup. - Its in-memory authority does not survive a controller restart. Existing checkpoint and process-recovery rules still apply. - Raw GitHub credentials remain confined to individual command processes. The session transport token cannot select a different task, agent, company, or run. - No database migration or public API change. ## Model Used OpenAI Codex, GPT-6, with reasoning, terminal tools, and code execution. The exact serving model ID and context-window size are not exposed in this session. ## 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>
53 lines
2.3 KiB
TypeScript
53 lines
2.3 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
import { githubBrokerEnvironment } from "@paperclipai/adapter-utils/github-launcher";
|
|
import { cleanupGitHubOperationLaunchers, prepareGitHubOperationLaunchers } from "@paperclipai/adapter-utils/execution-target";
|
|
|
|
type LauncherInput = Parameters<typeof prepareGitHubOperationLaunchers>[0];
|
|
|
|
export async function prepareHeartbeatGitHubLaunchers(
|
|
input: LauncherInput & {
|
|
native: boolean;
|
|
githubConfigured: boolean;
|
|
agentId: string;
|
|
brokerUrl: string;
|
|
createBrokerToken: () => string;
|
|
},
|
|
prepareLaunchers = prepareGitHubOperationLaunchers,
|
|
cleanupLaunchers = cleanupGitHubOperationLaunchers,
|
|
) {
|
|
// Native configured access is owned by the provider session supervisor.
|
|
// Defer staging until it has acquired that session; never mutate a live
|
|
// process's authorization from heartbeat preparation.
|
|
if (input.native && input.githubConfigured) {
|
|
return { env: githubBrokerEnvironment(input.env, { url: "", token: "" }), cleanupLocation: null };
|
|
}
|
|
// An unconfigured sandbox has no managed GitHub identity to broker. Its
|
|
// token-free wrappers still isolate image credentials, but may live as long
|
|
// as the workspace so a warm provider never inherits a deleted run path.
|
|
// Other adapter paths retain their run-scoped capability/retirement rules.
|
|
const anonymous = input.native && !input.githubConfigured &&
|
|
input.target?.kind === "remote" && input.target.transport === "sandbox";
|
|
const location = {
|
|
runId: anonymous
|
|
? `anonymous-${input.agentId}-${createHash("sha256").update(input.env.PATH ?? "").digest("hex").slice(0, 16)}`
|
|
: input.runId,
|
|
target: input.target,
|
|
};
|
|
const env = githubBrokerEnvironment(input.env, {
|
|
url: anonymous ? "" : input.brokerUrl,
|
|
token: anonymous ? "" : input.createBrokerToken(),
|
|
});
|
|
try {
|
|
return {
|
|
env: await prepareLaunchers({ ...location, cwd: input.cwd, env }),
|
|
cleanupLocation: anonymous ? null : location,
|
|
};
|
|
} catch (error) {
|
|
// The caller cannot retain a cleanup location until staging returns. Clean
|
|
// partial run-specific files here, preserving the original staging failure.
|
|
// Anonymous files may still belong to a live warm provider; never delete them.
|
|
if (!anonymous) await cleanupLaunchers(location).catch(() => undefined);
|
|
throw error;
|
|
}
|
|
}
|