@sagentlab/navarch-runtime 0.1.20 → 0.1.22
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/adapters/codex.cjs +7 -4
- package/dist/codex-pricing.cjs +94 -0
- package/dist/git-worktree.cjs +133 -8
- package/dist/session.cjs +6 -3
- package/package.json +1 -1
package/dist/adapters/codex.cjs
CHANGED
|
@@ -6,6 +6,7 @@ exports.hasExplicitPermissionPolicy = hasExplicitPermissionPolicy;
|
|
|
6
6
|
const node_child_process_1 = require("node:child_process");
|
|
7
7
|
const node_fs_1 = require("node:fs");
|
|
8
8
|
const exit_conditions_cjs_1 = require("../exit-conditions.cjs");
|
|
9
|
+
const codex_pricing_cjs_1 = require("../codex-pricing.cjs");
|
|
9
10
|
/**
|
|
10
11
|
* Headless OpenAI Codex CLI adapter — the Codex sibling of claude.cts's
|
|
11
12
|
* `runClaudeCodeAdapter`, implementing the same AgentAdapter interface
|
|
@@ -63,7 +64,7 @@ async function runCodexAdapter(options) {
|
|
|
63
64
|
const raw = runOptions.dockerExec
|
|
64
65
|
? await runViaDocker(runOptions, args)
|
|
65
66
|
: await runOnHost(runOptions, args);
|
|
66
|
-
return attachUsage(raw);
|
|
67
|
+
return attachUsage(raw, options.model);
|
|
67
68
|
}
|
|
68
69
|
/** Operator policy wins over Navarch's generated host-mode profile. */
|
|
69
70
|
function hasExplicitPermissionPolicy(args) {
|
|
@@ -139,13 +140,15 @@ function safeEnvSegment(value) {
|
|
|
139
140
|
/**
|
|
140
141
|
* Parses stdout for `codex exec --json` usage/final-message events and folds
|
|
141
142
|
* them onto the raw result. Codex currently reports token counts but no USD
|
|
142
|
-
* cost, so
|
|
143
|
+
* cost, so known models receive a standard-tier API-equivalent estimate. An
|
|
144
|
+
* explicit cost from a compatible future event always takes precedence.
|
|
143
145
|
*/
|
|
144
|
-
function attachUsage(result) {
|
|
146
|
+
function attachUsage(result, model) {
|
|
145
147
|
const events = (0, exit_conditions_cjs_1.parseCodexJsonEvents)(result.stdout);
|
|
146
148
|
if (events.length === 0)
|
|
147
149
|
return result;
|
|
148
150
|
const usage = (0, exit_conditions_cjs_1.extractUsageFromCodexEvents)(events);
|
|
151
|
+
const costUsd = usage.costUsd ?? (0, codex_pricing_cjs_1.estimateCodexCostUsd)(events, model);
|
|
149
152
|
const reportText = (0, exit_conditions_cjs_1.extractFinalMessageFromCodexEvents)(events) ?? undefined;
|
|
150
153
|
return {
|
|
151
154
|
...result,
|
|
@@ -153,7 +156,7 @@ function attachUsage(result) {
|
|
|
153
156
|
// measured zero — leave the fields unset so aggregation skips them.
|
|
154
157
|
...(usage.tokensIn !== undefined ? { tokensIn: usage.tokensIn } : {}),
|
|
155
158
|
...(usage.tokensOut !== undefined ? { tokensOut: usage.tokensOut } : {}),
|
|
156
|
-
...(
|
|
159
|
+
...(costUsd !== undefined ? { costUsd } : {}),
|
|
157
160
|
...(reportText !== undefined ? { reportText } : {}),
|
|
158
161
|
};
|
|
159
162
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.estimateCodexCostUsd = estimateCodexCostUsd;
|
|
4
|
+
/**
|
|
5
|
+
* Standard-tier USD prices per 1M tokens. Codex's JSONL stream reports usage
|
|
6
|
+
* but not dollars, so the runtime needs a local price snapshot to turn that
|
|
7
|
+
* usage into project spend. Keep this deliberately limited to models Navarch
|
|
8
|
+
* offers rather than silently applying the wrong price to custom/gateway ids.
|
|
9
|
+
*
|
|
10
|
+
* Source (checked 2026-08-05): https://developers.openai.com/api/docs/pricing
|
|
11
|
+
*/
|
|
12
|
+
const CODEX_RATES = {
|
|
13
|
+
"gpt-5.6-sol": {
|
|
14
|
+
input: 5,
|
|
15
|
+
cachedInput: 0.5,
|
|
16
|
+
cacheWrite: 6.25,
|
|
17
|
+
output: 30,
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
function nonnegative(value) {
|
|
21
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
|
|
22
|
+
}
|
|
23
|
+
function priceUsage(usage, rates) {
|
|
24
|
+
const input = nonnegative(usage.input);
|
|
25
|
+
// Cached reads and cache writes are components of input_tokens, not extra
|
|
26
|
+
// tokens. Clamp malformed/provider-newer detail fields to the reported total
|
|
27
|
+
// so an odd event cannot produce negative uncached input or inflated spend.
|
|
28
|
+
const cachedInput = Math.min(input, nonnegative(usage.cachedInput));
|
|
29
|
+
const cacheWrite = Math.min(input - cachedInput, nonnegative(usage.cacheWrite));
|
|
30
|
+
const uncachedInput = input - cachedInput - cacheWrite;
|
|
31
|
+
const output = nonnegative(usage.output);
|
|
32
|
+
return (uncachedInput * rates.input +
|
|
33
|
+
cachedInput * rates.cachedInput +
|
|
34
|
+
cacheWrite * rates.cacheWrite +
|
|
35
|
+
output * rates.output) / 1_000_000;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Estimate standard-tier API-equivalent USD cost for a Codex JSONL run.
|
|
39
|
+
* Verified turn.completed events are per-turn and must be summed; legacy
|
|
40
|
+
* token_count envelopes are cumulative, so only their final total is priced.
|
|
41
|
+
* Returns undefined for unknown models or when no usage event was observed.
|
|
42
|
+
*/
|
|
43
|
+
function estimateCodexCostUsd(events, model) {
|
|
44
|
+
if (!model)
|
|
45
|
+
return undefined;
|
|
46
|
+
const rates = CODEX_RATES[model];
|
|
47
|
+
if (!rates)
|
|
48
|
+
return undefined;
|
|
49
|
+
let sawTurnUsage = false;
|
|
50
|
+
let turnCost = 0;
|
|
51
|
+
let legacyUsage;
|
|
52
|
+
for (const event of events) {
|
|
53
|
+
if (event.type === "turn.completed" && event.usage) {
|
|
54
|
+
sawTurnUsage = true;
|
|
55
|
+
turnCost += priceUsage({
|
|
56
|
+
input: event.usage.input_tokens,
|
|
57
|
+
cachedInput: event.usage.cached_input_tokens,
|
|
58
|
+
cacheWrite: event.usage.cache_write_input_tokens,
|
|
59
|
+
output: event.usage.output_tokens,
|
|
60
|
+
}, rates);
|
|
61
|
+
}
|
|
62
|
+
if (event.msg?.type === "token_count") {
|
|
63
|
+
const usage = event.msg.info?.total_token_usage;
|
|
64
|
+
legacyUsage = usage
|
|
65
|
+
? {
|
|
66
|
+
input: usage.input_tokens,
|
|
67
|
+
cachedInput: usage.cached_input_tokens,
|
|
68
|
+
cacheWrite: usage.cache_write_input_tokens,
|
|
69
|
+
output: usage.output_tokens,
|
|
70
|
+
}
|
|
71
|
+
: {
|
|
72
|
+
// In the oldest flat envelope cached_input_tokens was additional
|
|
73
|
+
// to input_tokens; normalize it to the newer inclusive total.
|
|
74
|
+
input: nonnegative(event.msg.input_tokens) + nonnegative(event.msg.cached_input_tokens),
|
|
75
|
+
cachedInput: event.msg.cached_input_tokens,
|
|
76
|
+
output: event.msg.output_tokens,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (event.type === "event_msg" && event.payload?.type === "token_count") {
|
|
80
|
+
const usage = event.payload.info?.total_token_usage;
|
|
81
|
+
if (usage) {
|
|
82
|
+
legacyUsage = {
|
|
83
|
+
input: usage.input_tokens,
|
|
84
|
+
cachedInput: usage.cached_input_tokens,
|
|
85
|
+
cacheWrite: usage.cache_write_input_tokens,
|
|
86
|
+
output: usage.output_tokens,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (sawTurnUsage)
|
|
92
|
+
return turnCost;
|
|
93
|
+
return legacyUsage ? priceUsage(legacyUsage, rates) : undefined;
|
|
94
|
+
}
|
package/dist/git-worktree.cjs
CHANGED
|
@@ -11,9 +11,9 @@ const sandbox_cjs_1 = require("./sandbox.cjs");
|
|
|
11
11
|
const repositoryLocks = new Map();
|
|
12
12
|
/**
|
|
13
13
|
* Maintains one bare repository cache per project and checks out each session
|
|
14
|
-
* into its own uniquely named worktree.
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* into its own uniquely named worktree. Sessions that need a repo-local secret
|
|
15
|
+
* use an isolated bare repository under their session root because linked
|
|
16
|
+
* worktrees share their repository-local config.
|
|
17
17
|
*/
|
|
18
18
|
class GitWorktree {
|
|
19
19
|
sessionRoot;
|
|
@@ -23,22 +23,33 @@ class GitWorktree {
|
|
|
23
23
|
runner;
|
|
24
24
|
cloneUrl;
|
|
25
25
|
githubToken;
|
|
26
|
+
repoLocalGithubToken;
|
|
26
27
|
taskBranchSuffix;
|
|
28
|
+
legacyTaskBranchSuffix;
|
|
29
|
+
taskOwnershipTrailer;
|
|
30
|
+
sessionTokenMarker;
|
|
27
31
|
constructor(options) {
|
|
28
32
|
const projectKey = safePathSegment(options.projectId);
|
|
29
33
|
const sessionKey = safePathSegment(options.sessionId);
|
|
30
34
|
const taskKey = safePathSegment(options.taskId);
|
|
31
35
|
this.sessionRoot = node_path_1.default.join(options.workspaceRoot, "sessions", sessionKey);
|
|
32
36
|
this.worktreePath = node_path_1.default.join(this.sessionRoot, "repo");
|
|
33
|
-
this.repositoryPath =
|
|
37
|
+
this.repositoryPath = options.repoLocalGithubToken
|
|
38
|
+
? node_path_1.default.join(this.sessionRoot, "repository.git")
|
|
39
|
+
: node_path_1.default.join(options.workspaceRoot, "repositories", `${projectKey}.git`);
|
|
34
40
|
// One delivery task owns one remote branch across every retry. A
|
|
35
41
|
// session-scoped branch lets a failed attempt push useful work, then makes
|
|
36
42
|
// the retry start from main and open a second PR for the same task.
|
|
37
|
-
|
|
43
|
+
const normalizedTaskId = taskKey.toLowerCase();
|
|
44
|
+
this.taskBranchSuffix = `-${normalizedTaskId.slice(0, 8)}`;
|
|
45
|
+
this.legacyTaskBranchSuffix = `-${normalizedTaskId}`;
|
|
46
|
+
this.taskOwnershipTrailer = `Navarch-Task-ID: ${normalizedTaskId}`;
|
|
47
|
+
this.sessionTokenMarker = `navarch-session-github-token:${sessionKey}`;
|
|
38
48
|
this.branch = `navarch/${branchSlug(options)}${this.taskBranchSuffix}`;
|
|
39
49
|
this.runner = options.runner ?? sandbox_cjs_1.nodeCommandRunner;
|
|
40
50
|
this.cloneUrl = options.cloneUrl;
|
|
41
51
|
this.githubToken = options.githubToken;
|
|
52
|
+
this.repoLocalGithubToken = options.repoLocalGithubToken;
|
|
42
53
|
}
|
|
43
54
|
async prepare() {
|
|
44
55
|
await node_fs_1.promises.mkdir(node_path_1.default.dirname(this.repositoryPath), { recursive: true });
|
|
@@ -58,8 +69,31 @@ class GitWorktree {
|
|
|
58
69
|
`could not resolve the fetched start ref. The shared cache was preserved to avoid invalidating ` +
|
|
59
70
|
`active worktrees; retry after active sessions finish or repair the cache in place: ${errorMessage(error)}`);
|
|
60
71
|
}
|
|
72
|
+
await this.installRepoLocalGithubToken();
|
|
61
73
|
});
|
|
62
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Makes a broker-issued GitHub credential visible to workflows that require
|
|
77
|
+
* `git config --local --get codex.githubToken`.
|
|
78
|
+
*
|
|
79
|
+
* Git ignores command-scope, worktree-scope, and included values when
|
|
80
|
+
* `--local` is requested, so the compatibility key has to live in the bare
|
|
81
|
+
* repository config. Token-bearing sessions use an isolated repository to
|
|
82
|
+
* keep concurrent worktrees from observing the value. The marked block lets
|
|
83
|
+
* cleanup remove exactly this session's value. The secret is written via fs
|
|
84
|
+
* and never passed in argv or emitted by the command runner.
|
|
85
|
+
*/
|
|
86
|
+
async installRepoLocalGithubToken() {
|
|
87
|
+
if (!this.repoLocalGithubToken)
|
|
88
|
+
return;
|
|
89
|
+
if (/[\0\r]/.test(this.repoLocalGithubToken)) {
|
|
90
|
+
throw new Error("Broker-issued GitHub credential contains unsupported control characters.");
|
|
91
|
+
}
|
|
92
|
+
const configPath = node_path_1.default.join(this.repositoryPath, "config");
|
|
93
|
+
const block = repoLocalGithubTokenBlock(this.sessionTokenMarker, this.repoLocalGithubToken);
|
|
94
|
+
await node_fs_1.promises.chmod(configPath, 0o600);
|
|
95
|
+
await node_fs_1.promises.appendFile(configPath, block, { encoding: "utf8", mode: 0o600 });
|
|
96
|
+
}
|
|
63
97
|
/** Clones the bare cache if missing, repoints origin if needed, and fetches all branches. */
|
|
64
98
|
async ensureRepositoryCache() {
|
|
65
99
|
if (!(await pathExists(node_path_1.default.join(this.repositoryPath, "HEAD")))) {
|
|
@@ -84,11 +118,13 @@ class GitWorktree {
|
|
|
84
118
|
// A failed session may have pushed before it could report completion. The
|
|
85
119
|
// next session must resume that task-owned branch, not fork again from the
|
|
86
120
|
// default branch and create a competing delivery PR.
|
|
87
|
-
const
|
|
121
|
+
const taskRef = await this.resolveTaskBranchRef();
|
|
122
|
+
const startRef = taskRef ?? (await this.resolveStartRef());
|
|
88
123
|
if (!startRef) {
|
|
89
124
|
// Empty remote (no commits yet): there is nothing to base the session on,
|
|
90
125
|
// so bootstrap an orphan branch the session can push as the first commit.
|
|
91
126
|
await this.addOrphanWorktree();
|
|
127
|
+
await this.createTaskOwnershipCommit();
|
|
92
128
|
return;
|
|
93
129
|
}
|
|
94
130
|
await this.runGit([
|
|
@@ -101,16 +137,32 @@ class GitWorktree {
|
|
|
101
137
|
this.worktreePath,
|
|
102
138
|
startRef,
|
|
103
139
|
], false);
|
|
140
|
+
if (!taskRef)
|
|
141
|
+
await this.createTaskOwnershipCommit();
|
|
104
142
|
}
|
|
105
143
|
/** Returns the fetched task-owned remote branch when an earlier attempt pushed it. */
|
|
106
144
|
async resolveTaskBranchRef() {
|
|
107
145
|
const refs = await this.runGit(["--git-dir", this.repositoryPath, "for-each-ref", "--format=%(refname)", "refs/remotes/origin/navarch"], false);
|
|
108
|
-
const
|
|
146
|
+
const suffixMatches = refs.stdout
|
|
109
147
|
.split("\n")
|
|
110
148
|
.map((line) => line.trim())
|
|
111
149
|
.filter((ref) => ref.endsWith(this.taskBranchSuffix));
|
|
112
|
-
|
|
150
|
+
const legacyMatches = refs.stdout
|
|
151
|
+
.split("\n")
|
|
152
|
+
.map((line) => line.trim())
|
|
153
|
+
.filter((ref) => ref.endsWith(this.legacyTaskBranchSuffix));
|
|
154
|
+
const shortMatches = suffixMatches.filter((ref) => !legacyMatches.includes(ref));
|
|
155
|
+
const ownedShortMatches = (await Promise.all(shortMatches.map(async (ref) => ({ ref, owned: await this.hasTaskOwnershipMarker(ref) }))))
|
|
156
|
+
.filter(({ owned }) => owned)
|
|
157
|
+
.map(({ ref }) => ref);
|
|
158
|
+
const matches = [...ownedShortMatches, ...legacyMatches];
|
|
159
|
+
if (matches.length === 0) {
|
|
160
|
+
if (shortMatches.length > 0) {
|
|
161
|
+
throw new Error(`Remote branch ${shortMatches[0]} shares task suffix ${this.taskBranchSuffix} but does not ` +
|
|
162
|
+
`contain ownership marker ${this.taskOwnershipTrailer}; refusing to resume or fork.`);
|
|
163
|
+
}
|
|
113
164
|
return null;
|
|
165
|
+
}
|
|
114
166
|
if (matches.length > 1) {
|
|
115
167
|
throw new Error(`Multiple remote branches claim task suffix ${this.taskBranchSuffix}; refusing to fork another delivery branch.`);
|
|
116
168
|
}
|
|
@@ -118,6 +170,38 @@ class GitWorktree {
|
|
|
118
170
|
this.branch = remoteRef.replace(/^refs\/remotes\/origin\//, "");
|
|
119
171
|
return remoteRef;
|
|
120
172
|
}
|
|
173
|
+
/** Short branch names are ambiguous without a full task-ID marker in their history. */
|
|
174
|
+
async hasTaskOwnershipMarker(ref) {
|
|
175
|
+
const log = await this.runGit([
|
|
176
|
+
"--git-dir",
|
|
177
|
+
this.repositoryPath,
|
|
178
|
+
"log",
|
|
179
|
+
"--format=%B",
|
|
180
|
+
"--fixed-strings",
|
|
181
|
+
`--grep=${this.taskOwnershipTrailer}`,
|
|
182
|
+
ref,
|
|
183
|
+
], false);
|
|
184
|
+
return log.stdout
|
|
185
|
+
.split("\n")
|
|
186
|
+
.some((line) => line.trim() === this.taskOwnershipTrailer);
|
|
187
|
+
}
|
|
188
|
+
/** Records full task ownership without lengthening the human-facing branch name. */
|
|
189
|
+
async createTaskOwnershipCommit() {
|
|
190
|
+
await this.runGit([
|
|
191
|
+
"-C",
|
|
192
|
+
this.worktreePath,
|
|
193
|
+
"-c",
|
|
194
|
+
"user.name=Navarch",
|
|
195
|
+
"-c",
|
|
196
|
+
"user.email=runtime@navarch.local",
|
|
197
|
+
"commit",
|
|
198
|
+
"--allow-empty",
|
|
199
|
+
"-m",
|
|
200
|
+
"chore(navarch): claim task branch",
|
|
201
|
+
"-m",
|
|
202
|
+
this.taskOwnershipTrailer,
|
|
203
|
+
], false);
|
|
204
|
+
}
|
|
121
205
|
/**
|
|
122
206
|
* Resolves the remote-tracking ref new session branches start from, or null
|
|
123
207
|
* when the remote has no branches at all (a freshly provisioned empty repo).
|
|
@@ -176,6 +260,7 @@ class GitWorktree {
|
|
|
176
260
|
}
|
|
177
261
|
async cleanup() {
|
|
178
262
|
await withRepositoryLock(this.repositoryPath, async () => {
|
|
263
|
+
await this.removeRepoLocalGithubToken();
|
|
179
264
|
await this.runner
|
|
180
265
|
.run("git", ["--git-dir", this.repositoryPath, "worktree", "remove", "--force", this.worktreePath])
|
|
181
266
|
.catch(() => undefined);
|
|
@@ -187,6 +272,21 @@ class GitWorktree {
|
|
|
187
272
|
.catch(() => undefined);
|
|
188
273
|
});
|
|
189
274
|
}
|
|
275
|
+
async removeRepoLocalGithubToken() {
|
|
276
|
+
if (!this.repoLocalGithubToken)
|
|
277
|
+
return;
|
|
278
|
+
const configPath = node_path_1.default.join(this.repositoryPath, "config");
|
|
279
|
+
try {
|
|
280
|
+
const config = await node_fs_1.promises.readFile(configPath, "utf8");
|
|
281
|
+
const cleaned = removeMarkedConfigBlock(config, this.sessionTokenMarker);
|
|
282
|
+
if (cleaned !== config)
|
|
283
|
+
await node_fs_1.promises.writeFile(configPath, cleaned, { mode: 0o600 });
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
if (error.code !== "ENOENT")
|
|
287
|
+
throw error;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
190
290
|
async runGit(args, authenticated) {
|
|
191
291
|
const credentialArgs = authenticated && this.githubToken
|
|
192
292
|
? [
|
|
@@ -208,6 +308,31 @@ class GitWorktree {
|
|
|
208
308
|
}
|
|
209
309
|
}
|
|
210
310
|
exports.GitWorktree = GitWorktree;
|
|
311
|
+
function repoLocalGithubTokenBlock(marker, token) {
|
|
312
|
+
const escaped = token
|
|
313
|
+
.replace(/\\/g, "\\\\")
|
|
314
|
+
.replace(/"/g, '\\"')
|
|
315
|
+
.replace(/\n/g, "\\n")
|
|
316
|
+
.replace(/\t/g, "\\t")
|
|
317
|
+
.replace(/\u0008/g, "\\b");
|
|
318
|
+
return (`\n# ${marker}:begin\n` +
|
|
319
|
+
`[codex]\n` +
|
|
320
|
+
`\tgithubToken = "${escaped}"\n` +
|
|
321
|
+
`# ${marker}:end\n`);
|
|
322
|
+
}
|
|
323
|
+
function removeMarkedConfigBlock(config, marker) {
|
|
324
|
+
const begin = `# ${marker}:begin`;
|
|
325
|
+
const end = `# ${marker}:end`;
|
|
326
|
+
const start = config.indexOf(begin);
|
|
327
|
+
if (start < 0)
|
|
328
|
+
return config;
|
|
329
|
+
const blockStart = start > 0 && config[start - 1] === "\n" ? start - 1 : start;
|
|
330
|
+
const endStart = config.indexOf(end, start + begin.length);
|
|
331
|
+
if (endStart < 0)
|
|
332
|
+
return config;
|
|
333
|
+
const endNewline = config.indexOf("\n", endStart + end.length);
|
|
334
|
+
return config.slice(0, blockStart) + config.slice(endNewline < 0 ? config.length : endNewline + 1);
|
|
335
|
+
}
|
|
211
336
|
async function withRepositoryLock(key, work) {
|
|
212
337
|
const previous = repositoryLocks.get(key) ?? Promise.resolve();
|
|
213
338
|
let release;
|
package/dist/session.cjs
CHANGED
|
@@ -71,9 +71,11 @@ async function runSession(deps, claimed, sessionId) {
|
|
|
71
71
|
await (0, worktree_janitor_cjs_1.markSessionWorkspaceActive)(workDir);
|
|
72
72
|
const promptText = (0, prompt_cjs_1.renderPrompt)(task, bundle);
|
|
73
73
|
await node_fs_1.promises.writeFile(node_path_1.default.join(workDir, "prompt.md"), promptText, "utf8");
|
|
74
|
-
// Secrets: initially fetched once
|
|
75
|
-
//
|
|
76
|
-
//
|
|
74
|
+
// Secrets: initially fetched once and held in the registry + child env map.
|
|
75
|
+
// The broker-issued github-pat is additionally installed in the project-local
|
|
76
|
+
// git config after worktree creation for the mandated Codex gh bootstrap;
|
|
77
|
+
// GitWorktree.cleanup removes that session-marked entry before teardown.
|
|
78
|
+
// Docker env injection remains tmpfs-backed (sandbox.cts injectEnv).
|
|
77
79
|
const registry = new redact_cjs_1.SecretRegistry();
|
|
78
80
|
let secrets = {};
|
|
79
81
|
let managedGithubCredential = false;
|
|
@@ -127,6 +129,7 @@ async function runSession(deps, claimed, sessionId) {
|
|
|
127
129
|
sessionId,
|
|
128
130
|
cloneUrl,
|
|
129
131
|
githubToken,
|
|
132
|
+
repoLocalGithubToken: secrets["github-pat"],
|
|
130
133
|
});
|
|
131
134
|
const knownGuidanceIds = new Set((bundle.guidance ?? []).map((entry) => entry.id));
|
|
132
135
|
const deliveredGuidance = [...(bundle.guidance ?? [])];
|
package/package.json
CHANGED