@sagentlab/navarch-runtime 0.1.136 → 0.1.137
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/bin/git-credential-navarch.cjs +31 -9
- package/dist/github-credential.cjs +84 -1
- package/dist/session.cjs +51 -13
- package/dist/worktree-guard.cjs +24 -1
- package/package.json +1 -1
|
@@ -4,19 +4,43 @@
|
|
|
4
4
|
// for every remote operation, so each lookup gets a fresh installation token
|
|
5
5
|
// from the lease-scoped broker instead of relying on the session-start token.
|
|
6
6
|
|
|
7
|
+
const fs = require("node:fs");
|
|
8
|
+
|
|
7
9
|
const operation = process.argv[2];
|
|
8
10
|
if (operation !== "get") process.exit(0);
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
// Sandboxed sessions (Codex guard) cannot reach the Navarch API, so the
|
|
13
|
+
// runtime parent refreshes the token into this file and the helper only
|
|
14
|
+
// reads it.
|
|
15
|
+
const tokenFile = process.env.NAVARCH_GIT_CREDENTIAL_TOKEN_FILE;
|
|
16
|
+
if (tokenFile) {
|
|
17
|
+
let token;
|
|
18
|
+
try {
|
|
19
|
+
token = fs.readFileSync(tokenFile, "utf8").trim();
|
|
20
|
+
} catch (error) {
|
|
21
|
+
console.error(`navarch git credential is unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
if (!token || /[\r\n\0]/.test(token)) {
|
|
25
|
+
console.error("navarch git credential file is empty or malformed");
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
// No process.exit here: on macOS a piped stdout write is asynchronous.
|
|
29
|
+
process.stdout.write(`username=x-access-token\npassword=${token}\n`);
|
|
30
|
+
} else {
|
|
31
|
+
void main();
|
|
17
32
|
}
|
|
18
33
|
|
|
19
34
|
async function main() {
|
|
35
|
+
const apiBase = process.env.NAVARCH_GIT_CREDENTIAL_API_BASE?.replace(/\/+$/, "");
|
|
36
|
+
const machineToken = process.env.NAVARCH_GIT_CREDENTIAL_MACHINE_TOKEN;
|
|
37
|
+
const leaseId = process.env.NAVARCH_GIT_CREDENTIAL_LEASE_ID;
|
|
38
|
+
|
|
39
|
+
if (!apiBase || !machineToken || !leaseId) {
|
|
40
|
+
console.error("navarch git credential helper is missing broker configuration");
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
20
44
|
try {
|
|
21
45
|
const response = await fetch(`${apiBase}/api/broker/issue`, {
|
|
22
46
|
method: "POST",
|
|
@@ -42,5 +66,3 @@ async function main() {
|
|
|
42
66
|
process.exitCode = 1;
|
|
43
67
|
}
|
|
44
68
|
}
|
|
45
|
-
|
|
46
|
-
void main();
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.GITHUB_CREDENTIAL_SECRET_NAME = void 0;
|
|
6
|
+
exports.GithubTokenFileRefresher = exports.GITHUB_TOKEN_FILENAME = exports.GITHUB_TOKEN_FILE_RETRY_MS = exports.GITHUB_TOKEN_FILE_REFRESH_MS = exports.GITHUB_CREDENTIAL_SECRET_NAME = void 0;
|
|
4
7
|
exports.githubCredentialDenialSummary = githubCredentialDenialSummary;
|
|
8
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const git_grant_cjs_1 = require("./git-grant.cjs");
|
|
5
11
|
/** Wire name the session requests its repository credential under. */
|
|
6
12
|
exports.GITHUB_CREDENTIAL_SECRET_NAME = "github-pat";
|
|
7
13
|
/**
|
|
@@ -41,3 +47,80 @@ function githubCredentialDenialSummary(denied) {
|
|
|
41
47
|
return (`GitHub credential denied (${denial.reason}): ${explanation} ` +
|
|
42
48
|
"The session stopped before cloning instead of falling back to this machine's own git login.");
|
|
43
49
|
}
|
|
50
|
+
/** GitHub App installation tokens live an hour; refresh well inside that. */
|
|
51
|
+
exports.GITHUB_TOKEN_FILE_REFRESH_MS = 10 * 60 * 1000;
|
|
52
|
+
exports.GITHUB_TOKEN_FILE_RETRY_MS = 60 * 1000;
|
|
53
|
+
exports.GITHUB_TOKEN_FILENAME = "github-token";
|
|
54
|
+
/**
|
|
55
|
+
* Keeps a managed repository's installation token in a session file for
|
|
56
|
+
* agents whose OS sandbox cannot reach the Navarch API (the Codex worktree
|
|
57
|
+
* guard allows only GitHub hosts). The runtime parent fetches from the broker,
|
|
58
|
+
* so the machine token never enters the agent environment, and the credential
|
|
59
|
+
* helper only reads the file. Mirrors GitGrantRefresher.
|
|
60
|
+
*/
|
|
61
|
+
class GithubTokenFileRefresher {
|
|
62
|
+
options;
|
|
63
|
+
timer = null;
|
|
64
|
+
stopped = false;
|
|
65
|
+
inflight = null;
|
|
66
|
+
constructor(options) {
|
|
67
|
+
this.options = options;
|
|
68
|
+
}
|
|
69
|
+
/** Writes the session-start token and schedules broker refreshes. */
|
|
70
|
+
async start(initialToken) {
|
|
71
|
+
await this.write(initialToken);
|
|
72
|
+
this.schedule(this.options.refreshMs ?? exports.GITHUB_TOKEN_FILE_REFRESH_MS);
|
|
73
|
+
}
|
|
74
|
+
async refresh() {
|
|
75
|
+
const issued = await this.options.api.issueSecrets({
|
|
76
|
+
lease_id: this.options.leaseId,
|
|
77
|
+
secret_names: [exports.GITHUB_CREDENTIAL_SECRET_NAME],
|
|
78
|
+
});
|
|
79
|
+
const token = issued.secrets[exports.GITHUB_CREDENTIAL_SECRET_NAME];
|
|
80
|
+
if (typeof token !== "string" || token.length === 0)
|
|
81
|
+
throw new Error("broker did not issue github-pat");
|
|
82
|
+
if (this.stopped)
|
|
83
|
+
throw new Error("github token refresher stopped");
|
|
84
|
+
await this.write(token);
|
|
85
|
+
}
|
|
86
|
+
/** Stops, waits for an in-flight refresh, then removes the token file. */
|
|
87
|
+
async stopAndClean() {
|
|
88
|
+
this.stopped = true;
|
|
89
|
+
if (this.timer)
|
|
90
|
+
clearTimeout(this.timer);
|
|
91
|
+
this.timer = null;
|
|
92
|
+
await this.inflight?.catch(() => undefined);
|
|
93
|
+
await promises_1.default.rm(this.options.tokenFile, { force: true }).catch(() => undefined);
|
|
94
|
+
}
|
|
95
|
+
async write(token) {
|
|
96
|
+
// Register before writing so a redaction pass never misses a live value.
|
|
97
|
+
this.options.registry.register(token);
|
|
98
|
+
if (/[\r\n\0]/.test(token))
|
|
99
|
+
throw new Error("github token contains unsupported control characters");
|
|
100
|
+
await promises_1.default.mkdir(node_path_1.default.dirname(this.options.tokenFile), { recursive: true, mode: 0o700 });
|
|
101
|
+
await (0, git_grant_cjs_1.writePrivateFile)(this.options.tokenFile, `${token}\n`);
|
|
102
|
+
}
|
|
103
|
+
schedule(delayMs) {
|
|
104
|
+
if (this.stopped)
|
|
105
|
+
return;
|
|
106
|
+
this.timer = setTimeout(() => {
|
|
107
|
+
this.inflight = this.tick();
|
|
108
|
+
}, delayMs);
|
|
109
|
+
this.timer.unref?.();
|
|
110
|
+
}
|
|
111
|
+
async tick() {
|
|
112
|
+
if (this.stopped)
|
|
113
|
+
return;
|
|
114
|
+
try {
|
|
115
|
+
await this.refresh();
|
|
116
|
+
this.schedule(this.options.refreshMs ?? exports.GITHUB_TOKEN_FILE_REFRESH_MS);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
// The previous token stays until its own expiry; GitHub rejects it after.
|
|
120
|
+
const retryMs = this.options.retryMs ?? exports.GITHUB_TOKEN_FILE_RETRY_MS;
|
|
121
|
+
this.options.log.warn(`github token refresh failed for ${this.options.leaseId}; retrying in ${retryMs / 1000}s: ${String(error)}`);
|
|
122
|
+
this.schedule(retryMs);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
exports.GithubTokenFileRefresher = GithubTokenFileRefresher;
|
package/dist/session.cjs
CHANGED
|
@@ -45,6 +45,7 @@ const commit_attribution_cjs_1 = require("./commit-attribution.cjs");
|
|
|
45
45
|
/** Filename the generated platform MCP config is written under inside the session metadata directory. */
|
|
46
46
|
const MCP_CONFIG_FILENAME = "mcp-config.json";
|
|
47
47
|
const GIT_CREDENTIAL_HELPER_FILENAME = "git-credential-navarch.cjs";
|
|
48
|
+
const SESSION_GIT_GLOBAL_CONFIG_FILENAME = "gitconfig";
|
|
48
49
|
const GIT_GRANT_HELPER_FILENAME = "git-credential-navarch-grant.cjs";
|
|
49
50
|
const COMPLETION_REMEDIATION_RETRIES = 2;
|
|
50
51
|
/**
|
|
@@ -185,6 +186,12 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
185
186
|
// The runtime parent fetches and refreshes the grant; the agent never sees
|
|
186
187
|
// the machine token or a GitHub token in these modes.
|
|
187
188
|
const bundleGitMode = bundle.repository?.git_proxy_mode;
|
|
189
|
+
// Codex's worktree guard is an OS sandbox around the whole agent, git's
|
|
190
|
+
// credential helper included: it denies the session dir and allows only
|
|
191
|
+
// GitHub hosts. Helpers and their token files go in a dir it may read.
|
|
192
|
+
const codexGuarded = config.sandboxMode === "host" && config.worktreeGuard && runtime === "codex";
|
|
193
|
+
const gitCredentialDir = (0, worktree_guard_cjs_1.sessionGitCredentialDir)(workDir);
|
|
194
|
+
let githubTokenRefresher;
|
|
188
195
|
let gitGrantRefresher;
|
|
189
196
|
let gitGrantEnv;
|
|
190
197
|
if (bundleGitMode !== "off") {
|
|
@@ -200,7 +207,8 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
200
207
|
});
|
|
201
208
|
try {
|
|
202
209
|
const issue = await refresher.start();
|
|
203
|
-
|
|
210
|
+
await node_fs_1.promises.mkdir(gitCredentialDir, { recursive: true, mode: 0o700 });
|
|
211
|
+
const helperPath = node_path_1.default.join(gitCredentialDir, GIT_GRANT_HELPER_FILENAME);
|
|
204
212
|
await node_fs_1.promises.copyFile(node_path_1.default.resolve(__dirname, "../bin/git-credential-navarch-grant.cjs"), helperPath);
|
|
205
213
|
gitGrantRefresher = refresher;
|
|
206
214
|
gitGrantEnv = {
|
|
@@ -256,14 +264,23 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
256
264
|
}
|
|
257
265
|
let gitCredentialRefresh;
|
|
258
266
|
if (!gitGrantMode && managedGithubCredential && secrets["github-pat"]) {
|
|
259
|
-
|
|
267
|
+
await node_fs_1.promises.mkdir(gitCredentialDir, { recursive: true, mode: 0o700 });
|
|
268
|
+
const helperPath = node_path_1.default.join(gitCredentialDir, GIT_CREDENTIAL_HELPER_FILENAME);
|
|
260
269
|
await node_fs_1.promises.copyFile(node_path_1.default.resolve(__dirname, "../bin/git-credential-navarch.cjs"), helperPath);
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
}
|
|
270
|
+
if (codexGuarded) {
|
|
271
|
+
const tokenFile = node_path_1.default.join(gitCredentialDir, github_credential_cjs_1.GITHUB_TOKEN_FILENAME);
|
|
272
|
+
githubTokenRefresher = new github_credential_cjs_1.GithubTokenFileRefresher({ api, leaseId, tokenFile, registry, log });
|
|
273
|
+
await githubTokenRefresher.start(secrets["github-pat"]);
|
|
274
|
+
gitCredentialRefresh = { helperPath, tokenFile };
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
gitCredentialRefresh = {
|
|
278
|
+
helperPath,
|
|
279
|
+
apiBase: api.getBaseUrl(),
|
|
280
|
+
machineToken: api.getToken() ?? "",
|
|
281
|
+
leaseId,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
267
284
|
}
|
|
268
285
|
const sessionEnv = toEnvMap(secrets, gitCredentialRefresh, config.gitAuthorName, config.gitAuthorEmail, gitGrantEnv);
|
|
269
286
|
// Proxied projects (CX-3.7): the control-plane identity replaces the
|
|
@@ -274,6 +291,15 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
274
291
|
const ghConfigDir = (0, worktree_guard_cjs_1.sessionGhConfigDir)(workDir);
|
|
275
292
|
await node_fs_1.promises.mkdir(ghConfigDir, { recursive: true, mode: 0o700 });
|
|
276
293
|
sessionEnv.GH_CONFIG_DIR = ghConfigDir;
|
|
294
|
+
if (codexGuarded) {
|
|
295
|
+
// The sandbox cannot read ~/.gitconfig, and git exits on an unreadable
|
|
296
|
+
// global config, so every agent git command failed until the agent found
|
|
297
|
+
// GIT_CONFIG_GLOBAL=/dev/null itself. The session file also replaces the
|
|
298
|
+
// default ~/.config/git/ignore, whose denial warns on every status.
|
|
299
|
+
await node_fs_1.promises.mkdir(gitCredentialDir, { recursive: true, mode: 0o700 });
|
|
300
|
+
sessionEnv.GIT_CONFIG_GLOBAL = node_path_1.default.join(gitCredentialDir, SESSION_GIT_GLOBAL_CONFIG_FILENAME);
|
|
301
|
+
await node_fs_1.promises.writeFile(sessionEnv.GIT_CONFIG_GLOBAL, "[core]\n\texcludesFile = /dev/null\n", { mode: 0o600 });
|
|
302
|
+
}
|
|
277
303
|
if (gitGrantMode) {
|
|
278
304
|
// Adapters spread process.env under this map. Blank any GitHub token the
|
|
279
305
|
// runtime process itself holds so the agent cannot inherit it.
|
|
@@ -297,6 +323,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
297
323
|
});
|
|
298
324
|
secrets = {};
|
|
299
325
|
await gitGrantRefresher?.stopAndClean();
|
|
326
|
+
await githubTokenRefresher?.stopAndClean();
|
|
300
327
|
await node_fs_1.promises.rm(workDir, { recursive: true, force: true }).catch(() => undefined);
|
|
301
328
|
return;
|
|
302
329
|
}
|
|
@@ -730,6 +757,11 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
730
757
|
workspaceRoot: config.workspaceRoot,
|
|
731
758
|
extraRoots: config.guardExtraRoots,
|
|
732
759
|
codexBin: config.codexBin,
|
|
760
|
+
credentialReadRoots: [
|
|
761
|
+
gitCredentialDir,
|
|
762
|
+
...(gitGrantEnv ? [(0, git_grant_cjs_1.gitGrantPaths)(workDir).dir] : []),
|
|
763
|
+
],
|
|
764
|
+
networkHosts: gitGrantEnv ? [gitGrantEnv.proxyHost] : [],
|
|
733
765
|
});
|
|
734
766
|
}
|
|
735
767
|
const restoredCheckpoint = gitWorktree.restoredHostedCheckpoint;
|
|
@@ -1424,9 +1456,10 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
1424
1456
|
// request. A later heartbeat response sees sessionClosing and does nothing.
|
|
1425
1457
|
await Promise.resolve(heartbeatWorkspaceUpdate).catch(() => undefined);
|
|
1426
1458
|
secrets = {};
|
|
1427
|
-
// Waits for an in-flight refresh, then removes the grant
|
|
1428
|
-
// survives in a retained workspace.
|
|
1459
|
+
// Waits for an in-flight refresh, then removes the grant and token files
|
|
1460
|
+
// so none survives in a retained workspace.
|
|
1429
1461
|
await gitGrantRefresher?.stopAndClean();
|
|
1462
|
+
await githubTokenRefresher?.stopAndClean();
|
|
1430
1463
|
// A sandbox teardown failure must not skip worktree cleanup: a leftover
|
|
1431
1464
|
// task branch in the shared cache blocks the task's next retry.
|
|
1432
1465
|
try {
|
|
@@ -1668,9 +1701,14 @@ function toEnvMap(secrets, credentialRefreshOrAuthorName, gitAuthorNameOrEmail =
|
|
|
1668
1701
|
if (secrets["github-pat"] && !out.GITHUB_TOKEN) {
|
|
1669
1702
|
out.GITHUB_TOKEN = secrets["github-pat"];
|
|
1670
1703
|
if (gitCredentialRefresh) {
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1704
|
+
if ("tokenFile" in gitCredentialRefresh) {
|
|
1705
|
+
out.NAVARCH_GIT_CREDENTIAL_TOKEN_FILE = gitCredentialRefresh.tokenFile;
|
|
1706
|
+
}
|
|
1707
|
+
else {
|
|
1708
|
+
out.NAVARCH_GIT_CREDENTIAL_API_BASE = gitCredentialRefresh.apiBase;
|
|
1709
|
+
out.NAVARCH_GIT_CREDENTIAL_MACHINE_TOKEN = gitCredentialRefresh.machineToken;
|
|
1710
|
+
out.NAVARCH_GIT_CREDENTIAL_LEASE_ID = gitCredentialRefresh.leaseId;
|
|
1711
|
+
}
|
|
1674
1712
|
gitConfig.unshift(["credential.helper", ""], ["credential.helper", `!node ${shellQuote(gitCredentialRefresh.helperPath)}`]);
|
|
1675
1713
|
out.GIT_TERMINAL_PROMPT = "0";
|
|
1676
1714
|
}
|
package/dist/worktree-guard.cjs
CHANGED
|
@@ -11,6 +11,7 @@ exports.codexExecutableReadRoots = codexExecutableReadRoots;
|
|
|
11
11
|
exports.codexHelperReadRoots = codexHelperReadRoots;
|
|
12
12
|
exports.macosDeveloperWriteRoots = macosDeveloperWriteRoots;
|
|
13
13
|
exports.sessionGhConfigDir = sessionGhConfigDir;
|
|
14
|
+
exports.sessionGitCredentialDir = sessionGitCredentialDir;
|
|
14
15
|
exports.sessionCodexTmpDir = sessionCodexTmpDir;
|
|
15
16
|
exports.codexWorktreeGuardArgs = codexWorktreeGuardArgs;
|
|
16
17
|
exports.geminiSandboxMounts = geminiSandboxMounts;
|
|
@@ -19,6 +20,7 @@ const node_fs_1 = require("node:fs");
|
|
|
19
20
|
const node_os_1 = __importDefault(require("node:os"));
|
|
20
21
|
const CODEX_GUARD_PROFILE = "navarch-worktree";
|
|
21
22
|
const SESSION_GH_CONFIG_DIRNAME = "gh-config";
|
|
23
|
+
const SESSION_GIT_CREDENTIAL_DIRNAME = "git-credential";
|
|
22
24
|
const CODEX_GITHUB_NETWORK_DOMAINS = {
|
|
23
25
|
"**.github.com": "allow",
|
|
24
26
|
"**.githubusercontent.com": "allow",
|
|
@@ -378,6 +380,14 @@ function macosDeveloperWriteRoots(env = process.env, platform = process.platform
|
|
|
378
380
|
function sessionGhConfigDir(workDir) {
|
|
379
381
|
return node_path_1.default.join(workDir, SESSION_GH_CONFIG_DIRNAME);
|
|
380
382
|
}
|
|
383
|
+
/**
|
|
384
|
+
* Session-owned directory for git's credential helpers and the token file
|
|
385
|
+
* they read. It sits beside the session metadata, which the Codex profile
|
|
386
|
+
* denies, so it is granted read-only by name (credentialReadRoots).
|
|
387
|
+
*/
|
|
388
|
+
function sessionGitCredentialDir(workDir) {
|
|
389
|
+
return node_path_1.default.join(workDir, SESSION_GIT_CREDENTIAL_DIRNAME);
|
|
390
|
+
}
|
|
381
391
|
function sessionCodexTmpDir(workDir) {
|
|
382
392
|
return node_path_1.default.resolve(workDir, "tmp");
|
|
383
393
|
}
|
|
@@ -417,6 +427,16 @@ function codexWorktreeGuardArgs(options, platform = process.platform) {
|
|
|
417
427
|
[node_path_1.default.resolve(options.repositoryPath)]: "write",
|
|
418
428
|
[sessionGhConfigDir(options.workDir)]: "read",
|
|
419
429
|
};
|
|
430
|
+
// git runs the credential helper inside the sandbox. Without these grants
|
|
431
|
+
// the helper copied into the denied session dir looks missing, and every
|
|
432
|
+
// authenticated fetch or push fails (ShiftSwap, 2026-10-27).
|
|
433
|
+
// Only strict subdirectories of this session: never the metadata dir itself.
|
|
434
|
+
const sessionDir = node_path_1.default.resolve(options.workDir);
|
|
435
|
+
for (const root of options.credentialReadRoots ?? []) {
|
|
436
|
+
const resolved = node_path_1.default.resolve(root);
|
|
437
|
+
if (resolved !== sessionDir && isPathInside(resolved, sessionDir))
|
|
438
|
+
filesystem[resolved] = "read";
|
|
439
|
+
}
|
|
420
440
|
// Codex protects a linked worktree's gitdir with a more specific read-only
|
|
421
441
|
// rule. A write grant on the bare repository alone does not override it:
|
|
422
442
|
// fetch/checkout/commit fail on FETCH_HEAD, HEAD.lock, or index.lock. Grant
|
|
@@ -471,7 +491,10 @@ function codexWorktreeGuardArgs(options, platform = process.platform) {
|
|
|
471
491
|
"-c",
|
|
472
492
|
`permissions.${CODEX_GUARD_PROFILE}.network.enabled=true`,
|
|
473
493
|
"-c",
|
|
474
|
-
`permissions.${CODEX_GUARD_PROFILE}.network.domains=${tomlInlineTable(
|
|
494
|
+
`permissions.${CODEX_GUARD_PROFILE}.network.domains=${tomlInlineTable({
|
|
495
|
+
...CODEX_GITHUB_NETWORK_DOMAINS,
|
|
496
|
+
...Object.fromEntries((options.networkHosts ?? []).map((host) => [host, "allow"])),
|
|
497
|
+
})}`,
|
|
475
498
|
];
|
|
476
499
|
}
|
|
477
500
|
function linkedWorktreeGitDir(worktreePath, repositoryPath) {
|
package/package.json
CHANGED