@rosthq/cli 0.7.112 → 0.7.114
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/dist/forge-workspace.d.ts +3 -1
- package/dist/forge-workspace.d.ts.map +1 -1
- package/dist/index.js +124 -12
- package/dist/index.js.map +3 -3
- package/dist/runner-sandbox.d.ts +51 -1
- package/dist/runner-sandbox.d.ts.map +1 -1
- package/dist/runner-serve.d.ts +1 -0
- package/dist/runner-serve.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type StepLedger } from "./step-ledger.js";
|
|
2
2
|
export declare const MODEL_SPAWN_ENV_ALLOWLIST: readonly ["PATH", "HOME", "USER", "LANG", "LC_ALL", "TMPDIR", "SHELL", "NODE_OPTIONS"];
|
|
3
|
-
export declare function buildModelSpawnEnv(base: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
3
|
+
export declare function buildModelSpawnEnv(base: NodeJS.ProcessEnv, claudeConfigDir?: string): NodeJS.ProcessEnv;
|
|
4
|
+
export declare function resolveSourceClaudeConfigDir(base: NodeJS.ProcessEnv): string;
|
|
5
|
+
export declare function provisionIsolatedClaudeConfigDir(base: NodeJS.ProcessEnv, configDir: string): Promise<string>;
|
|
4
6
|
export type ForgeCredential = {
|
|
5
7
|
token: string;
|
|
6
8
|
expiresAt: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"forge-workspace.d.ts","sourceRoot":"","sources":["../src/forge-workspace.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"forge-workspace.d.ts","sourceRoot":"","sources":["../src/forge-workspace.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -38708,6 +38708,14 @@ function accessLevelForScope(scopeTier) {
|
|
|
38708
38708
|
return "admin";
|
|
38709
38709
|
}
|
|
38710
38710
|
}
|
|
38711
|
+
var RUNNER_WIRED_CONNECTOR_IDS = /* @__PURE__ */ new Set([
|
|
38712
|
+
"gmail.read",
|
|
38713
|
+
"gmail.draft",
|
|
38714
|
+
"gmail.send",
|
|
38715
|
+
"sheets.read",
|
|
38716
|
+
"sheets.write",
|
|
38717
|
+
"slack.post_message"
|
|
38718
|
+
]);
|
|
38711
38719
|
function providerRequirementFor(entry) {
|
|
38712
38720
|
if (!entry.credential_required) {
|
|
38713
38721
|
return entry.provider === "rost" || entry.provider === "web" ? "platform_managed" : "none";
|
|
@@ -38730,6 +38738,12 @@ function trustedRequiredMetadataFor(entry) {
|
|
|
38730
38738
|
"authorization_revoked_at"
|
|
38731
38739
|
];
|
|
38732
38740
|
}
|
|
38741
|
+
function laneSupportFor(entry) {
|
|
38742
|
+
if (providerRequirementFor(entry) === "connection" && !RUNNER_WIRED_CONNECTOR_IDS.has(entry.id)) {
|
|
38743
|
+
return ["cloud"];
|
|
38744
|
+
}
|
|
38745
|
+
return ["cloud", "runner", "mcp"];
|
|
38746
|
+
}
|
|
38733
38747
|
function enrichCatalogEntry(entry) {
|
|
38734
38748
|
const accessLevels = [...new Set(entry.scope_tiers.map(accessLevelForScope))];
|
|
38735
38749
|
return {
|
|
@@ -38738,7 +38752,7 @@ function enrichCatalogEntry(entry) {
|
|
|
38738
38752
|
default_access_level: accessLevelForScope(entry.default_scope_tier),
|
|
38739
38753
|
provider_requirement: providerRequirementFor(entry),
|
|
38740
38754
|
cost_class: entry.id === "script.run" || entry.id === "secret.broker" || entry.id === "baserow.read" || entry.id === "baserow.write" ? "metered_standard" : entry.id === "web.search" || entry.id === "web.fetch" || entry.id === "browser.read" ? "metered_low" : "included",
|
|
38741
|
-
lane_support:
|
|
38755
|
+
lane_support: laneSupportFor(entry),
|
|
38742
38756
|
trusted_required_metadata: trustedRequiredMetadataFor(entry)
|
|
38743
38757
|
};
|
|
38744
38758
|
}
|
|
@@ -38746,6 +38760,7 @@ var catalogEntries = CATALOG_ENTRIES.map(enrichCatalogEntry);
|
|
|
38746
38760
|
var catalogById = new Map(catalogEntries.map((entry) => [entry.id, entry]));
|
|
38747
38761
|
|
|
38748
38762
|
// ../../packages/protocol/src/skills.ts
|
|
38763
|
+
var SKILL_DESCRIPTION_MAX_CHARS = 500;
|
|
38749
38764
|
var skillVisibilitySchema = external_exports.enum(["private", "seat", "tenant"]);
|
|
38750
38765
|
var skillStatusSchema = external_exports.enum(["draft", "pending_review", "published", "archived"]);
|
|
38751
38766
|
var skillSourceTypeSchema = external_exports.enum([
|
|
@@ -38762,12 +38777,14 @@ var skillCatalogTierSchema = external_exports.enum(["free", "pro", "enterprise",
|
|
|
38762
38777
|
var skillLocalClientSchema = external_exports.enum(["claude-code", "codex", "cursor"]);
|
|
38763
38778
|
var skillSlugSchema = external_exports.string().regex(/^[a-z0-9][a-z0-9-]{1,80}$/);
|
|
38764
38779
|
var skillNameSchema = external_exports.string().trim().min(1).max(120);
|
|
38765
|
-
var
|
|
38780
|
+
var skillApplicationShape = {
|
|
38766
38781
|
domain: external_exports.string().trim().min(1).max(80).optional(),
|
|
38767
38782
|
summary: external_exports.string().trim().min(1).max(500),
|
|
38768
38783
|
best_for: external_exports.array(external_exports.string().trim().min(1).max(160)).max(20).default([]),
|
|
38769
38784
|
not_for: external_exports.array(external_exports.string().trim().min(1).max(160)).max(20).default([])
|
|
38770
|
-
}
|
|
38785
|
+
};
|
|
38786
|
+
var skillApplicationSchema = external_exports.object(skillApplicationShape).strict();
|
|
38787
|
+
var skillApplicationInputSchema = external_exports.object(skillApplicationShape);
|
|
38771
38788
|
var skillToolDependencySchema = external_exports.object({
|
|
38772
38789
|
tool: external_exports.string().trim().min(1).max(120),
|
|
38773
38790
|
min_scope_tier: toolScopeTierSchema,
|
|
@@ -38787,7 +38804,7 @@ var skillValidationIssueSchema = external_exports.object({
|
|
|
38787
38804
|
var normalizedSkillPackageSchema = external_exports.object({
|
|
38788
38805
|
slug: skillSlugSchema,
|
|
38789
38806
|
name: skillNameSchema,
|
|
38790
|
-
description: external_exports.string().trim().min(1).max(
|
|
38807
|
+
description: external_exports.string().trim().min(1).max(SKILL_DESCRIPTION_MAX_CHARS),
|
|
38791
38808
|
visibility: skillVisibilitySchema,
|
|
38792
38809
|
application: skillApplicationSchema,
|
|
38793
38810
|
frontmatter: external_exports.record(external_exports.string(), external_exports.unknown()),
|
|
@@ -48678,7 +48695,7 @@ Use the lower-level commands after health names a finding: \`agent.get_run\` for
|
|
|
48678
48695
|
order: 46,
|
|
48679
48696
|
title: "Tool access and vault",
|
|
48680
48697
|
summary: "How to give agents access to tools without exposing raw credentials or expanding authority by accident.",
|
|
48681
|
-
version: "2026-07-
|
|
48698
|
+
version: "2026-07-21.1",
|
|
48682
48699
|
public: true,
|
|
48683
48700
|
audiences: ["human", "cli", "mcp", "in_app_agent"],
|
|
48684
48701
|
stages: ["staffing"],
|
|
@@ -48734,6 +48751,10 @@ The native Gmail handlers run inside the same broker boundary as the REST and Sl
|
|
|
48734
48751
|
|
|
48735
48752
|
Google can be connected from Settings once the workspace OAuth app is configured. The connection flow requests offline access for Gmail read/compose/send and Sheets, stores the returned credential in the vault, and records only account, scope, and capability-availability metadata in the integration row. Partial grants are accepted when at least one connector capability is usable; missing or revoked scopes reduce the affected capability instead of granting more authority. The test action refreshes the vaulted credential and performs a minimal Gmail profile read. Gmail read/draft/send, bounded Sheets read, and approval-held Sheets write handlers run through the guarded broker when Google is connected; connecting Google still does not bypass the signed Charter manifest, tenant policy ceiling, credential vault, or tool-call audit. Sheet grants should use exact signed argument bindings for approved spreadsheet ids and ranges.
|
|
48736
48753
|
|
|
48754
|
+
## Connectors work the same on either lane
|
|
48755
|
+
|
|
48756
|
+
A seat's governed connectors are available whether its agent runs on the cloud lane or the runner lane. {{brand}} projects each connector the seat's signed Charter grants onto the seat's own MCP surface and executes it server-side \u2014 so a runner-lane agent calls the same Gmail, Sheets, and Slack connectors a cloud-lane agent does, with identical governance: the same server guard, the same held-write approval for send/write tiers, and the same tool-call audit row. The OAuth or vaulted credential is resolved and bound only on the server inside the credential closure and never travels to the runner. Because this projection is derived from the Charter manifest and the connected integration, connecting a new integration makes its capabilities available to an already-live runner seat with no charter re-sign or code change. Connectors without a server-side handler yet stay cloud-only and are shown on the seat's Tools tab as not available on the runner lane until one ships.
|
|
48757
|
+
|
|
48737
48758
|
A connected Baserow REST integration (\`integration.connect_rest\`) derives a \`baserow.read\` tenant capability ceiling the same way: a live connection surfaces read, and a disconnected/revoked one collapses to off. The derivation is read-only by design \u2014 \`baserow.write\` is never inferred from the connection and is granted only through an explicit per-seat brokered secret grant, matching the conservative-by-default rule for every connection-backed ceiling.
|
|
48738
48759
|
|
|
48739
48760
|
CLI and MCP can inspect connector readiness without seeing secrets: \`integration.list\` / \`rost_list_integrations\` lists connected providers and health metadata, \`integration.status\` / \`rost_get_integration_status\` reads one provider by id or name, and \`integration.test\` / \`rost_test_integration_connection\` runs the installed provider-specific health check. For Google, the test refreshes the vaulted OAuth credential and reads the Gmail profile, then records only account, scope, and health metadata.
|
|
@@ -55804,7 +55825,8 @@ var RUNNER_ASKPASS_DIR = path4.join(tmpdir(), "rost-runner-askpass");
|
|
|
55804
55825
|
var RUNNER_TEMP_PREFIXES = [
|
|
55805
55826
|
"rost-runner-mcp-",
|
|
55806
55827
|
"rost-runner-sandbox-",
|
|
55807
|
-
"rost-runner-askpass-"
|
|
55828
|
+
"rost-runner-askpass-",
|
|
55829
|
+
"rost-runner-claude-config-"
|
|
55808
55830
|
];
|
|
55809
55831
|
var RUNNER_TEMP_STARTUP_SWEEP_MAX_AGE_MS = 60 * 60 * 1e3;
|
|
55810
55832
|
var RUNNER_TEMP_TURN_SWEEP_MAX_AGE_MS = 45 * 60 * 1e3;
|
|
@@ -56028,6 +56050,20 @@ function forgeBuildUnsupportedReason(platform2) {
|
|
|
56028
56050
|
}
|
|
56029
56051
|
return `Forge build execution is unsupported on this host: no macOS seatbelt (sandbox-exec) confinement is available on '${platform2}', so this runner cannot prove filesystem/egress isolation for an untrusted build turn. The server will not offer Forge-phase work to this runner \u2014 unless its tenant is on the narrow, operator-established trust allowlist (FORGE_SANDBOX_TRUSTED_TENANT_IDS) \u2014 until real Linux confinement (namespaces/Landlock, tracked as a DER-1343 follow-up) ships.`;
|
|
56030
56052
|
}
|
|
56053
|
+
function sandboxRequiredTurnRefusal(input) {
|
|
56054
|
+
if (input.spec.kind === "seatbelt") {
|
|
56055
|
+
return null;
|
|
56056
|
+
}
|
|
56057
|
+
if (input.mode === "off") {
|
|
56058
|
+
return null;
|
|
56059
|
+
}
|
|
56060
|
+
const postureLabel = input.mode === "strict" ? "strict" : "guard";
|
|
56061
|
+
const override = "Set RUNNER_SANDBOX=off only on a trusted, founder-authorized host to override.";
|
|
56062
|
+
if (input.platform !== "darwin") {
|
|
56063
|
+
return `Refusing to run this turn unconfined: sandbox mode is '${postureLabel}', but macOS seatbelt confinement does not exist on this host ('${input.platform}') \u2014 seatbelt is macOS-only, and Linux confinement is a DER-1343 follow-up. The runner refuses rather than running an attacker-influenceable build turn unconfined. ${override}`;
|
|
56064
|
+
}
|
|
56065
|
+
return `Refusing to run this turn unconfined: sandbox mode is '${postureLabel}', but sandbox-exec is unavailable on this macOS host, so filesystem/egress confinement cannot be applied (${input.spec.reason}). The runner refuses rather than silently degrading to no sandbox. ${override}`;
|
|
56066
|
+
}
|
|
56031
56067
|
function wrapCommandWithSandbox(spec, command, args, profilePath) {
|
|
56032
56068
|
if (spec.kind === "none" || profilePath === null) {
|
|
56033
56069
|
return { command, args, profilePath: null };
|
|
@@ -56901,7 +56937,7 @@ function parseSemver2(value) {
|
|
|
56901
56937
|
import { execFile as execFileCallback5, spawn } from "node:child_process";
|
|
56902
56938
|
import { createHash as createHash2 } from "node:crypto";
|
|
56903
56939
|
import { constants as fsConstants2 } from "node:fs";
|
|
56904
|
-
import { access as access3, chmod as chmod2, mkdir as mkdir5, readFile as
|
|
56940
|
+
import { access as access3, chmod as chmod2, mkdir as mkdir5, readFile as readFile6, rename, rm as rm5, statfs, writeFile as writeFile5 } from "node:fs/promises";
|
|
56905
56941
|
import { cpus, homedir as homedir4, hostname as hostname3, platform, totalmem, tmpdir as tmpdir2 } from "node:os";
|
|
56906
56942
|
import path7 from "node:path";
|
|
56907
56943
|
import { setTimeout as sleep3 } from "node:timers/promises";
|
|
@@ -56910,7 +56946,8 @@ import { promisify as promisify5 } from "node:util";
|
|
|
56910
56946
|
// src/forge-workspace.ts
|
|
56911
56947
|
import { execFile as execFileCallback4 } from "node:child_process";
|
|
56912
56948
|
import { createHash } from "node:crypto";
|
|
56913
|
-
import { chmod, lstat, mkdir as mkdir4, mkdtemp, realpath, rm as rm4, stat as stat3, writeFile as writeFile4 } from "node:fs/promises";
|
|
56949
|
+
import { chmod, lstat, mkdir as mkdir4, mkdtemp, readFile as readFile5, realpath, rm as rm4, stat as stat3, writeFile as writeFile4 } from "node:fs/promises";
|
|
56950
|
+
import os2 from "node:os";
|
|
56914
56951
|
import path6 from "node:path";
|
|
56915
56952
|
import { setTimeout as sleep2 } from "node:timers/promises";
|
|
56916
56953
|
import { promisify as promisify4 } from "node:util";
|
|
@@ -56989,7 +57026,7 @@ var MODEL_SPAWN_ENV_ALLOWLIST = [
|
|
|
56989
57026
|
"SHELL",
|
|
56990
57027
|
"NODE_OPTIONS"
|
|
56991
57028
|
];
|
|
56992
|
-
function buildModelSpawnEnv(base) {
|
|
57029
|
+
function buildModelSpawnEnv(base, claudeConfigDir) {
|
|
56993
57030
|
const out = {};
|
|
56994
57031
|
for (const key of MODEL_SPAWN_ENV_ALLOWLIST) {
|
|
56995
57032
|
const value = base[key];
|
|
@@ -56997,8 +57034,36 @@ function buildModelSpawnEnv(base) {
|
|
|
56997
57034
|
out[key] = value;
|
|
56998
57035
|
}
|
|
56999
57036
|
}
|
|
57037
|
+
if (typeof claudeConfigDir === "string" && claudeConfigDir.length > 0) {
|
|
57038
|
+
out.CLAUDE_CONFIG_DIR = claudeConfigDir;
|
|
57039
|
+
}
|
|
57000
57040
|
return out;
|
|
57001
57041
|
}
|
|
57042
|
+
var CLAUDE_CONFIG_AUTH_FILES = [".credentials.json", ".claude.json"];
|
|
57043
|
+
function resolveSourceClaudeConfigDir(base) {
|
|
57044
|
+
const explicit = base.CLAUDE_CONFIG_DIR;
|
|
57045
|
+
if (typeof explicit === "string" && explicit.length > 0) {
|
|
57046
|
+
return explicit;
|
|
57047
|
+
}
|
|
57048
|
+
const home = base.HOME;
|
|
57049
|
+
const homeDir = typeof home === "string" && home.length > 0 ? home : os2.homedir();
|
|
57050
|
+
return path6.join(homeDir, ".claude");
|
|
57051
|
+
}
|
|
57052
|
+
async function provisionIsolatedClaudeConfigDir(base, configDir) {
|
|
57053
|
+
await mkdir4(configDir, { recursive: true, mode: 448 });
|
|
57054
|
+
await chmod(configDir, 448);
|
|
57055
|
+
const source = resolveSourceClaudeConfigDir(base);
|
|
57056
|
+
for (const file2 of CLAUDE_CONFIG_AUTH_FILES) {
|
|
57057
|
+
try {
|
|
57058
|
+
const data = await readFile5(path6.join(source, file2));
|
|
57059
|
+
const dest = path6.join(configDir, file2);
|
|
57060
|
+
await writeFile4(dest, data, { mode: 384 });
|
|
57061
|
+
await chmod(dest, 384);
|
|
57062
|
+
} catch {
|
|
57063
|
+
}
|
|
57064
|
+
}
|
|
57065
|
+
return configDir;
|
|
57066
|
+
}
|
|
57002
57067
|
var GIT_HARDENING_ARGS = [
|
|
57003
57068
|
"-c",
|
|
57004
57069
|
"core.hooksPath=/dev/null",
|
|
@@ -58192,7 +58257,7 @@ async function pairIfNeeded(client, fetchImpl, appUrl2, config2, io) {
|
|
|
58192
58257
|
}
|
|
58193
58258
|
async function loadState(filePath, expectedTenantId) {
|
|
58194
58259
|
try {
|
|
58195
|
-
const parsed = JSON.parse(await
|
|
58260
|
+
const parsed = JSON.parse(await readFile6(filePath, "utf8"));
|
|
58196
58261
|
if (typeof parsed.runner_id === "string" && typeof parsed.runner_secret === "string") {
|
|
58197
58262
|
if (expectedTenantId && parsed.tenant_id !== expectedTenantId) {
|
|
58198
58263
|
return null;
|
|
@@ -58659,6 +58724,9 @@ function resolveTurnPlan(input) {
|
|
|
58659
58724
|
return {
|
|
58660
58725
|
profile: "read_status",
|
|
58661
58726
|
claudeAllowedTools: [...input.kind === "turn_execution" ? RUNNER_AICOS_TURN_TOOLS : RUNNER_WORK_ORDER_TOOLS],
|
|
58727
|
+
// DER-2104: the read stub is MCP-only — no local built-ins. `--tools ""` now enforces
|
|
58728
|
+
// that (previously nothing did, so a stub turn silently inherited full local access).
|
|
58729
|
+
localBuiltinTools: [],
|
|
58662
58730
|
codexSandbox: "read-only",
|
|
58663
58731
|
outputFormat: input.kind === "turn_execution" ? "json" : "text",
|
|
58664
58732
|
repoDir: null,
|
|
@@ -58673,6 +58741,9 @@ function resolveTurnPlan(input) {
|
|
|
58673
58741
|
...execution.allowed_mcp_tools.map(claudeMcpToolName),
|
|
58674
58742
|
...claudeLocalToolsFor(execution.local_tools)
|
|
58675
58743
|
],
|
|
58744
|
+
// DER-2104: the same built-in set, but as the `--tools` HARD whitelist (not the
|
|
58745
|
+
// auto-approve list). `none` → `[]` → `--tools ""` → zero built-ins for a seat_work turn.
|
|
58746
|
+
localBuiltinTools: claudeLocalToolsFor(execution.local_tools),
|
|
58676
58747
|
codexSandbox: execution.local_tools === "workspace_write" ? "workspace-write" : "read-only",
|
|
58677
58748
|
// Forge build/review turns emit a structured JSON envelope (reusing the AICOS
|
|
58678
58749
|
// --output-format json pattern) so their summary is a real contract, not a stdout slice.
|
|
@@ -58722,8 +58793,19 @@ function buildTurnCommand(input) {
|
|
|
58722
58793
|
"--mcp-config",
|
|
58723
58794
|
configPath,
|
|
58724
58795
|
"--strict-mcp-config",
|
|
58796
|
+
// DER-2104: --tools is the HARD built-in whitelist. `--tools ""` (empty join for
|
|
58797
|
+
// local_tools "none") removes Bash/Read/Write/Edit/… entirely, and the operator's
|
|
58798
|
+
// settings.json cannot widen it — the real enforcement of `local_tools`. --allowedTools
|
|
58799
|
+
// stays as the auto-approve list for the governed MCP + any declared local built-ins.
|
|
58800
|
+
"--tools",
|
|
58801
|
+
plan.localBuiltinTools.join(","),
|
|
58725
58802
|
"--allowedTools",
|
|
58726
58803
|
plan.claudeAllowedTools.join(","),
|
|
58804
|
+
// DER-2104: unattended turns must never wait on a prompt — dontAsk auto-denies any tool
|
|
58805
|
+
// call that would otherwise prompt. Governed MCP tools and the declared local built-ins
|
|
58806
|
+
// are in --allowedTools (auto-approved, never prompt), so this only denies the unexpected.
|
|
58807
|
+
"--permission-mode",
|
|
58808
|
+
"dontAsk",
|
|
58727
58809
|
"--output-format",
|
|
58728
58810
|
plan.outputFormat,
|
|
58729
58811
|
// DER-1397: turn off Claude Code's own nested Bash sandbox when the outer seatbelt confines
|
|
@@ -58955,6 +59037,17 @@ async function spawnRunnerTurn(ctx, workOrder, runtime, kind) {
|
|
|
58955
59037
|
if (execution?.profile === "forge_merge") {
|
|
58956
59038
|
return runForgeMergeTurn(ctx, workOrder, execution, runtime);
|
|
58957
59039
|
}
|
|
59040
|
+
const requiresSandbox = Boolean(execution?.needs_repo) || execution?.local_tools === "workspace_write";
|
|
59041
|
+
if (requiresSandbox) {
|
|
59042
|
+
const refusal = sandboxRequiredTurnRefusal({
|
|
59043
|
+
mode: ctx.config.sandboxMode,
|
|
59044
|
+
platform: process.platform,
|
|
59045
|
+
spec: ctx.config.sandbox
|
|
59046
|
+
});
|
|
59047
|
+
if (refusal) {
|
|
59048
|
+
return { ok: false, summary: refusal };
|
|
59049
|
+
}
|
|
59050
|
+
}
|
|
58958
59051
|
let forgePrep = null;
|
|
58959
59052
|
if (execution?.needs_repo) {
|
|
58960
59053
|
const prepared = await prepareForgeTurn(ctx, workOrder, execution);
|
|
@@ -58975,6 +59068,10 @@ async function spawnRunnerTurn(ctx, workOrder, runtime, kind) {
|
|
|
58975
59068
|
await writeFile5(configPath, `${mcpConfig}
|
|
58976
59069
|
`, { mode: 384 });
|
|
58977
59070
|
await chmod2(configPath, 384);
|
|
59071
|
+
const claudeConfigDir = runtime === "claude" ? await provisionIsolatedClaudeConfigDir(
|
|
59072
|
+
process.env,
|
|
59073
|
+
path7.join(tmpdir2(), `rost-runner-claude-config-${createHash2("sha256").update(`${configPath}:cfg`).digest("hex").slice(0, 16)}`)
|
|
59074
|
+
) : null;
|
|
58978
59075
|
const resumeSessionId = runtime === "claude" ? getClaudeResumeSessionId(ctx.state, workOrder) : void 0;
|
|
58979
59076
|
const built = buildTurnCommand({
|
|
58980
59077
|
runtime,
|
|
@@ -59003,7 +59100,7 @@ async function spawnRunnerTurn(ctx, workOrder, runtime, kind) {
|
|
|
59003
59100
|
// A Forge turn's cwd is its worktree; every other turn stays in tmpdir.
|
|
59004
59101
|
cwd: plan.repoDir ?? tmpdir2(),
|
|
59005
59102
|
timeoutMs: plan.timeoutMs,
|
|
59006
|
-
env: buildModelSpawnEnv(process.env),
|
|
59103
|
+
env: buildModelSpawnEnv(process.env, claudeConfigDir ?? void 0),
|
|
59007
59104
|
expectSessionEnvelope,
|
|
59008
59105
|
resumeSessionId,
|
|
59009
59106
|
// T1.6: only Forge turns carry a structured evidence contract worth parsing.
|
|
@@ -59032,6 +59129,9 @@ async function spawnRunnerTurn(ctx, workOrder, runtime, kind) {
|
|
|
59032
59129
|
if (sandboxProfilePath) {
|
|
59033
59130
|
void rm5(sandboxProfilePath, { force: true });
|
|
59034
59131
|
}
|
|
59132
|
+
if (claudeConfigDir) {
|
|
59133
|
+
void rm5(claudeConfigDir, { recursive: true, force: true });
|
|
59134
|
+
}
|
|
59035
59135
|
void sweepOrphanedTurnTempFiles();
|
|
59036
59136
|
if (forgePrep) {
|
|
59037
59137
|
if (plan.profile === "forge_build" && result.ok && forgePrep.credential.canPush) {
|
|
@@ -59149,6 +59249,11 @@ async function resolveMergeConflictTurn(ctx, input) {
|
|
|
59149
59249
|
],
|
|
59150
59250
|
allowNetwork: ctx.config.sandboxAllowNetwork
|
|
59151
59251
|
});
|
|
59252
|
+
const sandboxRefusal = sandboxRequiredTurnRefusal({ mode: ctx.config.sandboxMode, platform: process.platform, spec: sandbox });
|
|
59253
|
+
if (sandboxRefusal) {
|
|
59254
|
+
await removeWorkspace(workspace).catch(() => void 0);
|
|
59255
|
+
return { pushed: false, result: { ok: false, summary: sandboxRefusal.slice(0, MERGE_CONFLICT_SUMMARY_MAX) } };
|
|
59256
|
+
}
|
|
59152
59257
|
const modelResult = await spawnMergeConflictModel(ctx, {
|
|
59153
59258
|
workOrder: input.workOrder,
|
|
59154
59259
|
execution: input.execution,
|
|
@@ -59179,6 +59284,10 @@ async function spawnMergeConflictModel(ctx, input) {
|
|
|
59179
59284
|
await writeFile5(configPath, `${mcpConfig}
|
|
59180
59285
|
`, { mode: 384 });
|
|
59181
59286
|
await chmod2(configPath, 384);
|
|
59287
|
+
const claudeConfigDir = input.runtime === "claude" ? await provisionIsolatedClaudeConfigDir(
|
|
59288
|
+
process.env,
|
|
59289
|
+
path7.join(tmpdir2(), `rost-runner-claude-config-${createHash2("sha256").update(`${configPath}:cfg`).digest("hex").slice(0, 16)}`)
|
|
59290
|
+
) : null;
|
|
59182
59291
|
const plan = resolveTurnPlan({ kind: "work_order", execution: input.execution, repoDir: input.workspace.worktreeDir });
|
|
59183
59292
|
const prompt = [
|
|
59184
59293
|
"You are resolving a merge conflict on a pull request branch so it can merge into the base branch.",
|
|
@@ -59208,7 +59317,7 @@ async function spawnMergeConflictModel(ctx, input) {
|
|
|
59208
59317
|
cwd: input.workspace.worktreeDir,
|
|
59209
59318
|
timeoutMs: plan.timeoutMs,
|
|
59210
59319
|
// The model gets the EXPLICIT allowlist env — never the merge token, GIT_ASKPASS, or the bearer.
|
|
59211
|
-
env: buildModelSpawnEnv(process.env),
|
|
59320
|
+
env: buildModelSpawnEnv(process.env, claudeConfigDir ?? void 0),
|
|
59212
59321
|
expectSessionEnvelope: false
|
|
59213
59322
|
});
|
|
59214
59323
|
const exitClass = classifyModelExit(result);
|
|
@@ -59224,6 +59333,9 @@ async function spawnMergeConflictModel(ctx, input) {
|
|
|
59224
59333
|
if (sandboxProfilePath) {
|
|
59225
59334
|
void rm5(sandboxProfilePath, { force: true });
|
|
59226
59335
|
}
|
|
59336
|
+
if (claudeConfigDir) {
|
|
59337
|
+
void rm5(claudeConfigDir, { recursive: true, force: true });
|
|
59338
|
+
}
|
|
59227
59339
|
void sweepOrphanedTurnTempFiles();
|
|
59228
59340
|
return result;
|
|
59229
59341
|
}
|