mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-09-14 13:59:18 +08:00
51ad751e0b
## Thinking Path > - Paperclip is the open source control plane for teams of AI agents. > - Agent runs currently use direct adapters and their established finalization paths. > - The new runner package needs one production integration before it can execute a real provider through the server. > - That integration must not change direct adapters or expose unsupported providers. > - The rollout must also preserve native runs that were already recorded when the feature flag changes. > - This pull request adds a default-off, Codex-only native execution path and its authority boundary. > - The benefit is a recoverable production vertical slice with explicit compatibility guards. ## Linked Issues or Issue Description **Subsystem affected** Cross-cutting server orchestration and adapter selection. **Problem or motivation** The runner package exists, but the server cannot yet start and recover a governed Codex run through it. A careless integration could also route existing direct adapters into the native runtime or lose cancellation and finalization state. **Proposed solution** Add a hidden `paperclip_runner` adapter for Codex. Keep it behind the default-off instance flag. Bind native execution, resume, cancellation, semantic tool authority, and finalization to the recorded company, issue, run, and coordinator identities. Leave every direct adapter on its existing path. **Alternatives considered** A multi-provider launch was rejected because only Codex has the complete production bridge in this series. Replacing direct adapter execution was rejected because the runner remains experimental. **Roadmap alignment** This work supports governed tool access, action attribution, and self-healing runs. It keeps the integration narrow and default-off. ## What Changed - Add the Codex-only native session executor and persisted resumption path. - Add run-scoped semantic tool projection, authorization, receipts, and idempotency. - Add audited native cancellation with durable issue and coordinator binding. - Add result fencing so a recorded result cannot reacquire the provider and run twice. - Reject fresh runner starts when the rollout flag is off while preserving recorded native recovery. - Keep direct adapters outside native status, cancellation, record creation, and finalization. - Add focused conformance, recovery, cancellation, status, portability, and compatibility coverage. ## Verification - GitHub Actions is the authoritative test environment for this large stack. - The PR policy and lightweight stack checks run while this is a middle PR. - The full required suite runs when this PR becomes the lowest unmerged or top PR. - Greptile will review this exact delta after the branch is pushed. ## Risks - The main risk is routing a legacy adapter into native execution. Runtime selection and heartbeat tests cover that boundary. - The next risk is stale or cross-company cancellation. Durable binding checks and transactional audit persistence cover it. - The adapter remains hidden and default-off. Only Codex is admitted. - There are no database migration, lockfile, or GitHub workflow changes in this PR. ## Stack 1. [Runner package, SDK, and developer tools](https://github.com/paperclipai/paperclip/pull/12608) 2. This PR: Codex production server integration 3. [Provider-neutral task-thread UI](https://github.com/paperclipai/paperclip/pull/12617) ## Model Used OpenAI Codex with GPT-5, extended reasoning, repository tools, and parallel review agents. ## 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 - [ ] 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 - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge
68 lines
2.2 KiB
JavaScript
68 lines
2.2 KiB
JavaScript
import { readdirSync, statSync } from "node:fs";
|
|
import path from "node:path";
|
|
|
|
export function resolveNativeRunnerRequirement({ exitCode, stdout }) {
|
|
if (exitCode !== 0) {
|
|
return { nativeRunnerRequired: true, valid: false };
|
|
}
|
|
|
|
const jsonLines = stdout
|
|
.split(/\r?\n/)
|
|
.map((line) => line.trim())
|
|
.filter((line) => line.startsWith("{"));
|
|
for (let index = jsonLines.length - 1; index >= 0; index -= 1) {
|
|
try {
|
|
const payload = JSON.parse(jsonLines[index]);
|
|
if (typeof payload?.nativeRunnerRequired === "boolean") {
|
|
return { nativeRunnerRequired: payload.nativeRunnerRequired, valid: true };
|
|
}
|
|
} catch {
|
|
// Keep scanning earlier JSON-looking output from pnpm and runtime logging.
|
|
}
|
|
}
|
|
|
|
// An unknown state must not strand a persisted native run without runnerd.
|
|
return { nativeRunnerRequired: true, valid: false };
|
|
}
|
|
|
|
function newestMtimeMs(target) {
|
|
const stat = statSync(target, { throwIfNoEntry: false });
|
|
if (!stat) return 0;
|
|
if (!stat.isDirectory()) return stat.mtimeMs;
|
|
|
|
let newest = stat.mtimeMs;
|
|
for (const entry of readdirSync(target)) {
|
|
const childNewest = newestMtimeMs(path.join(target, entry));
|
|
if (childNewest > newest) newest = childNewest;
|
|
}
|
|
return newest;
|
|
}
|
|
|
|
export function paperclipRunnerBinaryNeedsBuild({
|
|
repoRoot,
|
|
nativeRunnerRequired,
|
|
configuredBinary,
|
|
platform = process.platform,
|
|
}) {
|
|
if (!nativeRunnerRequired) return false;
|
|
if (configuredBinary?.trim()) return false;
|
|
|
|
const executable = platform === "win32" ? "paperclip-runnerd.exe" : "paperclip-runnerd";
|
|
const packageRoot = path.join(repoRoot, "packages", "paperclip-runner");
|
|
const stagedBinary = path.join(packageRoot, "dist", "bin", executable);
|
|
const binaryStat = statSync(stagedBinary, { throwIfNoEntry: false });
|
|
if (!binaryStat?.isFile()) return true;
|
|
|
|
const runnerRoot = path.join(packageRoot, "runner");
|
|
const buildInputs = [
|
|
path.join(runnerRoot, "Cargo.toml"),
|
|
path.join(runnerRoot, "Cargo.lock"),
|
|
path.join(runnerRoot, ".cargo"),
|
|
path.join(runnerRoot, "rust-toolchain"),
|
|
path.join(runnerRoot, "rust-toolchain.toml"),
|
|
path.join(runnerRoot, "crates"),
|
|
];
|
|
|
|
return buildInputs.some((input) => newestMtimeMs(input) > binaryStat.mtimeMs);
|
|
}
|