mirror of
https://github.com/paperclipai/paperclip
synced 2026-04-25 17:25:15 +02:00
Merge pull request #3383 from paperclipai/pap-1347-codex-fast-mode
feat(codex-local): add fast mode support
This commit is contained in:
@@ -20,6 +20,7 @@ The `codex_local` adapter runs OpenAI's Codex CLI locally. It supports session p
|
||||
| `env` | object | No | Environment variables (supports secret refs) |
|
||||
| `timeoutSec` | number | No | Process timeout (0 = no timeout) |
|
||||
| `graceSec` | number | No | Grace period before force-kill |
|
||||
| `fastMode` | boolean | No | Enables Codex Fast mode. Currently supported on `gpt-5.4` only and burns credits faster |
|
||||
| `dangerouslyBypassApprovalsAndSandbox` | boolean | No | Skip safety checks (dev only) |
|
||||
|
||||
## Session Persistence
|
||||
@@ -30,8 +31,22 @@ Codex uses `previous_response_id` for session continuity. The adapter serializes
|
||||
|
||||
The adapter symlinks Paperclip skills into the global Codex skills directory (`~/.codex/skills`). Existing user skills are not overwritten.
|
||||
|
||||
## Fast Mode
|
||||
|
||||
When `fastMode` is enabled, Paperclip adds Codex config overrides equivalent to:
|
||||
|
||||
```sh
|
||||
-c 'service_tier="fast"' -c 'features.fast_mode=true'
|
||||
```
|
||||
|
||||
Paperclip currently applies that only when the selected model is `gpt-5.4`. On other models, the toggle is preserved in config but ignored at execution time to avoid unsupported runs.
|
||||
|
||||
## Managed `CODEX_HOME`
|
||||
|
||||
When Paperclip is running inside a managed worktree instance (`PAPERCLIP_IN_WORKTREE=true`), the adapter instead uses a worktree-isolated `CODEX_HOME` under the Paperclip instance so Codex skills, sessions, logs, and other runtime state do not leak across checkouts. It seeds that isolated home from the user's main Codex home for shared auth/config continuity.
|
||||
|
||||
## Manual Local CLI
|
||||
|
||||
For manual local CLI usage outside heartbeat runs (for example running as `codexcoder` directly), use:
|
||||
|
||||
```sh
|
||||
|
||||
@@ -372,6 +372,7 @@ export interface CreateConfigValues {
|
||||
chrome: boolean;
|
||||
dangerouslySkipPermissions: boolean;
|
||||
search: boolean;
|
||||
fastMode: boolean;
|
||||
dangerouslyBypassSandbox: boolean;
|
||||
command: string;
|
||||
args: string;
|
||||
|
||||
@@ -2,6 +2,14 @@ export const type = "codex_local";
|
||||
export const label = "Codex (local)";
|
||||
export const DEFAULT_CODEX_LOCAL_MODEL = "gpt-5.3-codex";
|
||||
export const DEFAULT_CODEX_LOCAL_BYPASS_APPROVALS_AND_SANDBOX = true;
|
||||
export const CODEX_LOCAL_FAST_MODE_SUPPORTED_MODELS = ["gpt-5.4"] as const;
|
||||
|
||||
export function isCodexLocalFastModeSupported(model: string | null | undefined): boolean {
|
||||
const normalizedModel = typeof model === "string" ? model.trim() : "";
|
||||
return CODEX_LOCAL_FAST_MODE_SUPPORTED_MODELS.includes(
|
||||
normalizedModel as (typeof CODEX_LOCAL_FAST_MODE_SUPPORTED_MODELS)[number],
|
||||
);
|
||||
}
|
||||
|
||||
export const models = [
|
||||
{ id: "gpt-5.4", label: "gpt-5.4" },
|
||||
@@ -27,6 +35,7 @@ Core fields:
|
||||
- modelReasoningEffort (string, optional): reasoning effort override (minimal|low|medium|high|xhigh) passed via -c model_reasoning_effort=...
|
||||
- promptTemplate (string, optional): run prompt template
|
||||
- search (boolean, optional): run codex with --search
|
||||
- fastMode (boolean, optional): enable Codex Fast mode; currently supported on GPT-5.4 only and consumes credits faster
|
||||
- dangerouslyBypassApprovalsAndSandbox (boolean, optional): run with bypass flag
|
||||
- command (string, optional): defaults to "codex"
|
||||
- extraArgs (string[], optional): additional CLI args
|
||||
@@ -45,5 +54,6 @@ Notes:
|
||||
- Paperclip injects desired local skills into the effective CODEX_HOME/skills/ directory at execution time so Codex can discover "$paperclip" and related skills without polluting the project working directory. In managed-home mode (the default) this is ~/.paperclip/instances/<id>/companies/<companyId>/codex-home/skills/; when CODEX_HOME is explicitly overridden in adapter config, that override is used instead.
|
||||
- Unless explicitly overridden in adapter config, Paperclip runs Codex with a per-company managed CODEX_HOME under the active Paperclip instance and seeds auth/config from the shared Codex home (the CODEX_HOME env var, when set, or ~/.codex).
|
||||
- Some model/tool combinations reject certain effort levels (for example minimal with web search enabled).
|
||||
- Fast mode is currently supported on GPT-5.4 only. When enabled, Paperclip applies \`service_tier="fast"\` and \`features.fast_mode=true\`.
|
||||
- When Paperclip realizes a workspace/runtime for a run, it injects PAPERCLIP_WORKSPACE_* and PAPERCLIP_RUNTIME_* env vars for agent-side tooling.
|
||||
`;
|
||||
|
||||
46
packages/adapters/codex-local/src/server/codex-args.test.ts
Normal file
46
packages/adapters/codex-local/src/server/codex-args.test.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildCodexExecArgs } from "./codex-args.js";
|
||||
|
||||
describe("buildCodexExecArgs", () => {
|
||||
it("enables Codex fast mode overrides for GPT-5.4", () => {
|
||||
const result = buildCodexExecArgs({
|
||||
model: "gpt-5.4",
|
||||
search: true,
|
||||
fastMode: true,
|
||||
});
|
||||
|
||||
expect(result.fastModeRequested).toBe(true);
|
||||
expect(result.fastModeApplied).toBe(true);
|
||||
expect(result.fastModeIgnoredReason).toBeNull();
|
||||
expect(result.args).toEqual([
|
||||
"--search",
|
||||
"exec",
|
||||
"--json",
|
||||
"--model",
|
||||
"gpt-5.4",
|
||||
"-c",
|
||||
'service_tier="fast"',
|
||||
"-c",
|
||||
"features.fast_mode=true",
|
||||
"-",
|
||||
]);
|
||||
});
|
||||
|
||||
it("ignores fast mode for unsupported models", () => {
|
||||
const result = buildCodexExecArgs({
|
||||
model: "gpt-5.3-codex",
|
||||
fastMode: true,
|
||||
});
|
||||
|
||||
expect(result.fastModeRequested).toBe(true);
|
||||
expect(result.fastModeApplied).toBe(false);
|
||||
expect(result.fastModeIgnoredReason).toContain("currently only supported on gpt-5.4");
|
||||
expect(result.args).toEqual([
|
||||
"exec",
|
||||
"--json",
|
||||
"--model",
|
||||
"gpt-5.3-codex",
|
||||
"-",
|
||||
]);
|
||||
});
|
||||
});
|
||||
74
packages/adapters/codex-local/src/server/codex-args.ts
Normal file
74
packages/adapters/codex-local/src/server/codex-args.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { asBoolean, asString, asStringArray } from "@paperclipai/adapter-utils/server-utils";
|
||||
import {
|
||||
CODEX_LOCAL_FAST_MODE_SUPPORTED_MODELS,
|
||||
isCodexLocalFastModeSupported,
|
||||
} from "../index.js";
|
||||
|
||||
export type BuildCodexExecArgsResult = {
|
||||
args: string[];
|
||||
model: string;
|
||||
fastModeRequested: boolean;
|
||||
fastModeApplied: boolean;
|
||||
fastModeIgnoredReason: string | null;
|
||||
};
|
||||
|
||||
function readExtraArgs(config: unknown): string[] {
|
||||
const fromExtraArgs = asStringArray(asRecord(config).extraArgs);
|
||||
if (fromExtraArgs.length > 0) return fromExtraArgs;
|
||||
return asStringArray(asRecord(config).args);
|
||||
}
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: {};
|
||||
}
|
||||
|
||||
function formatFastModeSupportedModels(): string {
|
||||
return CODEX_LOCAL_FAST_MODE_SUPPORTED_MODELS.join(", ");
|
||||
}
|
||||
|
||||
export function buildCodexExecArgs(
|
||||
config: unknown,
|
||||
options: { resumeSessionId?: string | null } = {},
|
||||
): BuildCodexExecArgsResult {
|
||||
const record = asRecord(config);
|
||||
const model = asString(record.model, "").trim();
|
||||
const modelReasoningEffort = asString(
|
||||
record.modelReasoningEffort,
|
||||
asString(record.reasoningEffort, ""),
|
||||
).trim();
|
||||
const search = asBoolean(record.search, false);
|
||||
const fastModeRequested = asBoolean(record.fastMode, false);
|
||||
const fastModeApplied = fastModeRequested && isCodexLocalFastModeSupported(model);
|
||||
const bypass = asBoolean(
|
||||
record.dangerouslyBypassApprovalsAndSandbox,
|
||||
asBoolean(record.dangerouslyBypassSandbox, false),
|
||||
);
|
||||
const extraArgs = readExtraArgs(record);
|
||||
|
||||
const args = ["exec", "--json"];
|
||||
if (search) args.unshift("--search");
|
||||
if (bypass) args.push("--dangerously-bypass-approvals-and-sandbox");
|
||||
if (model) args.push("--model", model);
|
||||
if (modelReasoningEffort) {
|
||||
args.push("-c", `model_reasoning_effort=${JSON.stringify(modelReasoningEffort)}`);
|
||||
}
|
||||
if (fastModeApplied) {
|
||||
args.push("-c", 'service_tier="fast"', "-c", "features.fast_mode=true");
|
||||
}
|
||||
if (extraArgs.length > 0) args.push(...extraArgs);
|
||||
if (options.resumeSessionId) args.push("resume", options.resumeSessionId, "-");
|
||||
else args.push("-");
|
||||
|
||||
return {
|
||||
args,
|
||||
model,
|
||||
fastModeRequested,
|
||||
fastModeApplied,
|
||||
fastModeIgnoredReason:
|
||||
fastModeRequested && !fastModeApplied
|
||||
? `Configured fast mode is currently only supported on ${formatFastModeSupportedModels()}; Paperclip will ignore it for model ${model || "(default)"}.`
|
||||
: null,
|
||||
};
|
||||
}
|
||||
@@ -5,8 +5,6 @@ import { inferOpenAiCompatibleBiller, type AdapterExecutionContext, type Adapter
|
||||
import {
|
||||
asString,
|
||||
asNumber,
|
||||
asBoolean,
|
||||
asStringArray,
|
||||
parseObject,
|
||||
buildPaperclipEnv,
|
||||
buildInvocationEnvForLogs,
|
||||
@@ -26,6 +24,7 @@ import {
|
||||
import { parseCodexJsonl, isCodexUnknownSessionError } from "./parse.js";
|
||||
import { pathExists, prepareManagedCodexHome, resolveManagedCodexHomeDir, resolveSharedCodexHomeDir } from "./codex-home.js";
|
||||
import { resolveCodexDesiredSkillNames } from "./skills.js";
|
||||
import { buildCodexExecArgs } from "./codex-args.js";
|
||||
|
||||
const __moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const CODEX_ROLLOUT_NOISE_RE =
|
||||
@@ -223,15 +222,6 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
|
||||
);
|
||||
const command = asString(config.command, "codex");
|
||||
const model = asString(config.model, "");
|
||||
const modelReasoningEffort = asString(
|
||||
config.modelReasoningEffort,
|
||||
asString(config.reasoningEffort, ""),
|
||||
);
|
||||
const search = asBoolean(config.search, false);
|
||||
const bypass = asBoolean(
|
||||
config.dangerouslyBypassApprovalsAndSandbox,
|
||||
asBoolean(config.dangerouslyBypassSandbox, false),
|
||||
);
|
||||
|
||||
const workspaceContext = parseObject(context.paperclipWorkspace);
|
||||
const workspaceCwd = asString(workspaceContext.cwd, "");
|
||||
@@ -399,11 +389,6 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
|
||||
|
||||
const timeoutSec = asNumber(config.timeoutSec, 0);
|
||||
const graceSec = asNumber(config.graceSec, 20);
|
||||
const extraArgs = (() => {
|
||||
const fromExtraArgs = asStringArray(config.extraArgs);
|
||||
if (fromExtraArgs.length > 0) return fromExtraArgs;
|
||||
return asStringArray(config.args);
|
||||
})();
|
||||
|
||||
const runtimeSessionParams = parseObject(runtime.sessionParams);
|
||||
const runtimeSessionId = asString(runtimeSessionParams.sessionId, runtime.sessionId ?? "");
|
||||
@@ -499,26 +484,19 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
|
||||
heartbeatPromptChars: renderedPrompt.length,
|
||||
};
|
||||
|
||||
const buildArgs = (resumeSessionId: string | null) => {
|
||||
const args = ["exec", "--json"];
|
||||
if (search) args.unshift("--search");
|
||||
if (bypass) args.push("--dangerously-bypass-approvals-and-sandbox");
|
||||
if (model) args.push("--model", model);
|
||||
if (modelReasoningEffort) args.push("-c", `model_reasoning_effort=${JSON.stringify(modelReasoningEffort)}`);
|
||||
if (extraArgs.length > 0) args.push(...extraArgs);
|
||||
if (resumeSessionId) args.push("resume", resumeSessionId, "-");
|
||||
else args.push("-");
|
||||
return args;
|
||||
};
|
||||
|
||||
const runAttempt = async (resumeSessionId: string | null) => {
|
||||
const args = buildArgs(resumeSessionId);
|
||||
const execArgs = buildCodexExecArgs(config, { resumeSessionId });
|
||||
const args = execArgs.args;
|
||||
const commandNotesWithFastMode =
|
||||
execArgs.fastModeIgnoredReason == null
|
||||
? commandNotes
|
||||
: [...commandNotes, execArgs.fastModeIgnoredReason];
|
||||
if (onMeta) {
|
||||
await onMeta({
|
||||
adapterType: "codex_local",
|
||||
command: resolvedCommand,
|
||||
cwd,
|
||||
commandNotes,
|
||||
commandNotes: commandNotesWithFastMode,
|
||||
commandArgs: args.map((value, idx) => {
|
||||
if (idx === args.length - 1 && value !== "-") return `<prompt ${prompt.length} chars>`;
|
||||
return value;
|
||||
|
||||
@@ -5,8 +5,6 @@ import type {
|
||||
} from "@paperclipai/adapter-utils";
|
||||
import {
|
||||
asString,
|
||||
asBoolean,
|
||||
asStringArray,
|
||||
parseObject,
|
||||
ensureAbsoluteDirectory,
|
||||
ensureCommandResolvable,
|
||||
@@ -16,6 +14,7 @@ import {
|
||||
import path from "node:path";
|
||||
import { parseCodexJsonl } from "./parse.js";
|
||||
import { codexHomeDir, readCodexAuthInfo } from "./quota.js";
|
||||
import { buildCodexExecArgs } from "./codex-args.js";
|
||||
|
||||
function summarizeStatus(checks: AdapterEnvironmentCheck[]): AdapterEnvironmentTestResult["status"] {
|
||||
if (checks.some((check) => check.level === "error")) return "fail";
|
||||
@@ -140,31 +139,16 @@ export async function testEnvironment(
|
||||
hint: "Use the `codex` CLI command to run the automatic login and installation probe.",
|
||||
});
|
||||
} else {
|
||||
const model = asString(config.model, "").trim();
|
||||
const modelReasoningEffort = asString(
|
||||
config.modelReasoningEffort,
|
||||
asString(config.reasoningEffort, ""),
|
||||
).trim();
|
||||
const search = asBoolean(config.search, false);
|
||||
const bypass = asBoolean(
|
||||
config.dangerouslyBypassApprovalsAndSandbox,
|
||||
asBoolean(config.dangerouslyBypassSandbox, false),
|
||||
);
|
||||
const extraArgs = (() => {
|
||||
const fromExtraArgs = asStringArray(config.extraArgs);
|
||||
if (fromExtraArgs.length > 0) return fromExtraArgs;
|
||||
return asStringArray(config.args);
|
||||
})();
|
||||
|
||||
const args = ["exec", "--json"];
|
||||
if (search) args.unshift("--search");
|
||||
if (bypass) args.push("--dangerously-bypass-approvals-and-sandbox");
|
||||
if (model) args.push("--model", model);
|
||||
if (modelReasoningEffort) {
|
||||
args.push("-c", `model_reasoning_effort=${JSON.stringify(modelReasoningEffort)}`);
|
||||
const execArgs = buildCodexExecArgs({ ...config, fastMode: false });
|
||||
const args = execArgs.args;
|
||||
if (execArgs.fastModeIgnoredReason) {
|
||||
checks.push({
|
||||
code: "codex_fast_mode_unsupported_model",
|
||||
level: "warn",
|
||||
message: execArgs.fastModeIgnoredReason,
|
||||
hint: "Switch the agent model to GPT-5.4 to enable Codex Fast mode.",
|
||||
});
|
||||
}
|
||||
if (extraArgs.length > 0) args.push(...extraArgs);
|
||||
args.push("-");
|
||||
|
||||
const probe = await runChildProcess(
|
||||
`codex-envtest-${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
||||
|
||||
54
packages/adapters/codex-local/src/ui/build-config.test.ts
Normal file
54
packages/adapters/codex-local/src/ui/build-config.test.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildCodexLocalConfig } from "./build-config.js";
|
||||
import type { CreateConfigValues } from "@paperclipai/adapter-utils";
|
||||
|
||||
function makeValues(overrides: Partial<CreateConfigValues> = {}): CreateConfigValues {
|
||||
return {
|
||||
adapterType: "codex_local",
|
||||
cwd: "",
|
||||
instructionsFilePath: "",
|
||||
promptTemplate: "",
|
||||
model: "gpt-5.4",
|
||||
thinkingEffort: "",
|
||||
chrome: false,
|
||||
dangerouslySkipPermissions: true,
|
||||
search: false,
|
||||
fastMode: false,
|
||||
dangerouslyBypassSandbox: true,
|
||||
command: "",
|
||||
args: "",
|
||||
extraArgs: "",
|
||||
envVars: "",
|
||||
envBindings: {},
|
||||
url: "",
|
||||
bootstrapPrompt: "",
|
||||
payloadTemplateJson: "",
|
||||
workspaceStrategyType: "project_primary",
|
||||
workspaceBaseRef: "",
|
||||
workspaceBranchTemplate: "",
|
||||
worktreeParentDir: "",
|
||||
runtimeServicesJson: "",
|
||||
maxTurnsPerRun: 1000,
|
||||
heartbeatEnabled: false,
|
||||
intervalSec: 300,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("buildCodexLocalConfig", () => {
|
||||
it("persists the fastMode toggle into adapter config", () => {
|
||||
const config = buildCodexLocalConfig(
|
||||
makeValues({
|
||||
search: true,
|
||||
fastMode: true,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(config).toMatchObject({
|
||||
model: "gpt-5.4",
|
||||
search: true,
|
||||
fastMode: true,
|
||||
dangerouslyBypassApprovalsAndSandbox: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -85,6 +85,7 @@ export function buildCodexLocalConfig(v: CreateConfigValues): Record<string, unk
|
||||
}
|
||||
if (Object.keys(env).length > 0) ac.env = env;
|
||||
ac.search = v.search;
|
||||
ac.fastMode = v.fastMode;
|
||||
ac.dangerouslyBypassApprovalsAndSandbox =
|
||||
typeof v.dangerouslyBypassSandbox === "boolean"
|
||||
? v.dangerouslyBypassSandbox
|
||||
|
||||
@@ -7,6 +7,10 @@ import {
|
||||
} from "../../components/agent-config-primitives";
|
||||
import { ChoosePathButton } from "../../components/PathInstructionsModal";
|
||||
import { LocalWorkspaceRuntimeFields } from "../local-workspace-runtime-fields";
|
||||
import {
|
||||
CODEX_LOCAL_FAST_MODE_SUPPORTED_MODELS,
|
||||
isCodexLocalFastModeSupported,
|
||||
} from "@paperclipai/adapter-codex-local";
|
||||
|
||||
const inputClass =
|
||||
"w-full rounded-md border border-border px-2.5 py-1.5 bg-transparent outline-none text-sm font-mono placeholder:text-muted-foreground/40";
|
||||
@@ -27,6 +31,14 @@ export function CodexLocalConfigFields({
|
||||
}: AdapterConfigFieldsProps) {
|
||||
const bypassEnabled =
|
||||
config.dangerouslyBypassApprovalsAndSandbox === true || config.dangerouslyBypassSandbox === true;
|
||||
const fastModeEnabled = isCreate
|
||||
? Boolean(values!.fastMode)
|
||||
: eff("adapterConfig", "fastMode", Boolean(config.fastMode));
|
||||
const currentModel = isCreate
|
||||
? String(values!.model ?? "")
|
||||
: eff("adapterConfig", "model", String(config.model ?? ""));
|
||||
const fastModeSupported = isCodexLocalFastModeSupported(currentModel);
|
||||
const supportedModelsLabel = CODEX_LOCAL_FAST_MODE_SUPPORTED_MODELS.join(", ");
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -88,6 +100,23 @@ export function CodexLocalConfigFields({
|
||||
: mark("adapterConfig", "search", v)
|
||||
}
|
||||
/>
|
||||
<ToggleField
|
||||
label="Fast mode"
|
||||
hint={help.fastMode}
|
||||
checked={fastModeEnabled}
|
||||
onChange={(v) =>
|
||||
isCreate
|
||||
? set!({ fastMode: v })
|
||||
: mark("adapterConfig", "fastMode", v)
|
||||
}
|
||||
/>
|
||||
{fastModeEnabled && (
|
||||
<div className="rounded-md border border-amber-300/70 bg-amber-50/80 px-3 py-2 text-sm text-amber-900 dark:border-amber-500/40 dark:bg-amber-500/10 dark:text-amber-100">
|
||||
{fastModeSupported
|
||||
? "Fast mode consumes credits/tokens much faster than standard Codex runs."
|
||||
: `Fast mode currently only works on ${supportedModelsLabel}. Paperclip will ignore this toggle until the model is switched.`}
|
||||
</div>
|
||||
)}
|
||||
<LocalWorkspaceRuntimeFields
|
||||
isCreate={isCreate}
|
||||
values={values}
|
||||
|
||||
@@ -10,6 +10,7 @@ export const defaultCreateValues: CreateConfigValues = {
|
||||
chrome: false,
|
||||
dangerouslySkipPermissions: true,
|
||||
search: false,
|
||||
fastMode: false,
|
||||
dangerouslyBypassSandbox: false,
|
||||
command: "",
|
||||
args: "",
|
||||
|
||||
@@ -34,6 +34,7 @@ export const help: Record<string, string> = {
|
||||
dangerouslySkipPermissions: "Run unattended by auto-approving adapter permission prompts when supported.",
|
||||
dangerouslyBypassSandbox: "Run Codex without sandbox restrictions. Required for filesystem/network access.",
|
||||
search: "Enable Codex web search capability during runs.",
|
||||
fastMode: "Enable Codex Fast mode. This burns credits/tokens much faster and is currently supported on GPT-5.4 only.",
|
||||
workspaceStrategy: "How Paperclip should realize an execution workspace for this agent. Keep project_primary for normal cwd execution, or use git_worktree for issue-scoped isolated checkouts.",
|
||||
workspaceBaseRef: "Base git ref used when creating a worktree branch. Leave blank to use the resolved workspace ref or HEAD.",
|
||||
workspaceBranchTemplate: "Template for naming derived branches. Supports {{issue.identifier}}, {{issue.title}}, {{agent.name}}, {{project.id}}, {{workspace.repoRef}}, and {{slug}}.",
|
||||
|
||||
Reference in New Issue
Block a user