@rallycry/conveyor-agent 10.13.65 → 10.13.66
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/{boot-7OPX55AO.js → boot-UUPTBQ6R.js} +7 -8
- package/dist/{chunk-JIGG755T.js → chunk-6Q6LQBWO.js} +0 -1
- package/dist/{chunk-ULS4QPRE.js → chunk-6W6UZ4SJ.js} +0 -1
- package/dist/{chunk-BAQ2OAKB.js → chunk-75FUOUYX.js} +514 -213
- package/dist/{chunk-AXA55U4Z.js → chunk-E2SHIH6Y.js} +3 -4
- package/dist/{chunk-QOJTJCYZ.js → chunk-EXQ6AHOY.js} +2 -3
- package/dist/{chunk-5OQQSDVT.js → chunk-IA45XHOA.js} +0 -1
- package/dist/{chunk-QZN6HVUY.js → chunk-KG4ORL3Y.js} +1 -2
- package/dist/{chunk-4VUQ2NPF.js → chunk-KMB3BU4S.js} +0 -1
- package/dist/{chunk-NAK6FY5U.js → chunk-LE6ZUDZT.js} +37 -6
- package/dist/{chunk-6GS5ADIY.js → chunk-R3FDJQL6.js} +0 -1
- package/dist/cli.js +35 -43
- package/dist/{client-LRVVHTNG.js → client-IG6C5F2G.js} +3 -4
- package/dist/heartbeat-worker.js +1 -2
- package/dist/index.d.ts +95 -13
- package/dist/index.js +7 -8
- package/dist/{mode-6RL3SVMV.js → mode-ZJSOSLGU.js} +1 -2
- package/dist/{oom-watchdog-U7JERHA2.js → oom-watchdog-PAC5OJJG.js} +1 -2
- package/dist/{protocol-QLVS5W6O.js → protocol-QBCYO4GI.js} +1 -2
- package/dist/server-US2DDQSW.js +9 -0
- package/package.json +1 -1
- package/dist/boot-7OPX55AO.js.map +0 -1
- package/dist/chunk-4VUQ2NPF.js.map +0 -1
- package/dist/chunk-5OQQSDVT.js.map +0 -1
- package/dist/chunk-6GS5ADIY.js.map +0 -1
- package/dist/chunk-AXA55U4Z.js.map +0 -1
- package/dist/chunk-BAQ2OAKB.js.map +0 -1
- package/dist/chunk-JIGG755T.js.map +0 -1
- package/dist/chunk-NAK6FY5U.js.map +0 -1
- package/dist/chunk-QOJTJCYZ.js.map +0 -1
- package/dist/chunk-QZN6HVUY.js.map +0 -1
- package/dist/chunk-ULS4QPRE.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/client-LRVVHTNG.js.map +0 -1
- package/dist/heartbeat-worker.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/mode-6RL3SVMV.js.map +0 -1
- package/dist/oom-watchdog-U7JERHA2.js.map +0 -1
- package/dist/protocol-QLVS5W6O.js.map +0 -1
- package/dist/server-ZACB5T5S.js +0 -10
- package/dist/server-ZACB5T5S.js.map +0 -1
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
readAgentVersion,
|
|
8
8
|
registerBootMilestoneSocketFallback,
|
|
9
9
|
reportBootMilestone
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-KG4ORL3Y.js";
|
|
11
11
|
import {
|
|
12
12
|
LoopLagMonitor,
|
|
13
13
|
buildConveyorSocketOptions,
|
|
@@ -15,14 +15,14 @@ import {
|
|
|
15
15
|
heartbeatStatusFor,
|
|
16
16
|
loopStatusForRunnerStatus,
|
|
17
17
|
waitForConnected
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-IA45XHOA.js";
|
|
19
19
|
import {
|
|
20
20
|
WorkbenchError,
|
|
21
21
|
getWorkbenchClient
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-EXQ6AHOY.js";
|
|
23
23
|
import {
|
|
24
24
|
workbenchEnabled
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-KMB3BU4S.js";
|
|
26
26
|
import {
|
|
27
27
|
MAX_BETWEEN_TURN_BUFFER,
|
|
28
28
|
MAX_DIAGNOSTIC_OUTPUT,
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
buildPromptBytes,
|
|
31
31
|
buildSpawnArgs,
|
|
32
32
|
cleanTerminalOutput,
|
|
33
|
+
describeTokenFile,
|
|
33
34
|
ghHostsExternallyOwned,
|
|
34
35
|
gitCredentialHelper,
|
|
35
36
|
githubTokenFilePath,
|
|
@@ -54,7 +55,7 @@ import {
|
|
|
54
55
|
transcriptSize,
|
|
55
56
|
turnOptionsFrom,
|
|
56
57
|
writeGitCredential
|
|
57
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-LE6ZUDZT.js";
|
|
58
59
|
|
|
59
60
|
// src/setup/bootstrap.ts
|
|
60
61
|
var BOOTSTRAP_TIMEOUT_MS = 3e4;
|
|
@@ -73,11 +74,11 @@ async function singleBootstrapAttempt(apiUrl, instanceName, bootstrapToken, time
|
|
|
73
74
|
signal: controller.signal
|
|
74
75
|
});
|
|
75
76
|
if (!response.ok) {
|
|
76
|
-
const
|
|
77
|
+
const errorText2 = await response.text().catch(() => "");
|
|
77
78
|
return {
|
|
78
79
|
ok: false,
|
|
79
80
|
status: response.status,
|
|
80
|
-
errorText:
|
|
81
|
+
errorText: errorText2.slice(0, 500),
|
|
81
82
|
reason: response.status === 401 || response.status === 403 ? "auth_rejected" : "http_error"
|
|
82
83
|
};
|
|
83
84
|
}
|
|
@@ -167,6 +168,28 @@ function applyBootstrapToEnv(config) {
|
|
|
167
168
|
if (config.taskBranch) process.env.CONVEYOR_TASK_BRANCH = config.taskBranch;
|
|
168
169
|
}
|
|
169
170
|
|
|
171
|
+
// src/utils/logger.ts
|
|
172
|
+
function createServiceLogger(service) {
|
|
173
|
+
const prefix = `[conveyor-agent:${service}]`;
|
|
174
|
+
return {
|
|
175
|
+
info(message, data) {
|
|
176
|
+
const extra = data ? ` ${JSON.stringify(data)}` : "";
|
|
177
|
+
process.stderr.write(`${prefix} ${message}${extra}
|
|
178
|
+
`);
|
|
179
|
+
},
|
|
180
|
+
warn(message, data) {
|
|
181
|
+
const extra = data ? ` ${JSON.stringify(data)}` : "";
|
|
182
|
+
process.stderr.write(`${prefix} WARN ${message}${extra}
|
|
183
|
+
`);
|
|
184
|
+
},
|
|
185
|
+
error(message, data) {
|
|
186
|
+
const extra = data ? ` ${JSON.stringify(data)}` : "";
|
|
187
|
+
process.stderr.write(`${prefix} ERROR ${message}${extra}
|
|
188
|
+
`);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
170
193
|
// src/connection/agent-connection.ts
|
|
171
194
|
import { existsSync } from "fs";
|
|
172
195
|
import { fileURLToPath } from "url";
|
|
@@ -204,7 +227,53 @@ async function pollUntilBound(opts) {
|
|
|
204
227
|
}
|
|
205
228
|
}
|
|
206
229
|
|
|
230
|
+
// src/connection/bundle-credentials.ts
|
|
231
|
+
function readBundleIdentity(sessionJwt) {
|
|
232
|
+
if (!sessionJwt) return {};
|
|
233
|
+
const segments = sessionJwt.split(".");
|
|
234
|
+
if (segments.length !== 3) return {};
|
|
235
|
+
try {
|
|
236
|
+
const json = Buffer.from(segments[1], "base64url").toString("utf8");
|
|
237
|
+
const claims = JSON.parse(json);
|
|
238
|
+
return {
|
|
239
|
+
...typeof claims.sessionId === "string" ? { sessionId: claims.sessionId } : {},
|
|
240
|
+
...typeof claims.role === "string" ? { role: claims.role } : {}
|
|
241
|
+
};
|
|
242
|
+
} catch {
|
|
243
|
+
return {};
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function bundleMayWriteGithubFiles(bundle, self) {
|
|
247
|
+
const identity = readBundleIdentity(bundle.sessionJwt);
|
|
248
|
+
if (identity.sessionId && self.sessionId && identity.sessionId !== self.sessionId) {
|
|
249
|
+
return {
|
|
250
|
+
allowed: false,
|
|
251
|
+
reason: `bundle resolved to session ${identity.sessionId}, not ours (${self.sessionId})`
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
if (identity.role === "reader" && self.role && self.role !== "reader") {
|
|
255
|
+
return {
|
|
256
|
+
allowed: false,
|
|
257
|
+
reason: `bundle carries a reader-scoped token but this session is a ${self.role}`
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
return { allowed: true };
|
|
261
|
+
}
|
|
262
|
+
function applyBundleGithubToken(bundle, self) {
|
|
263
|
+
if (!bundle.githubToken) return { written: false, reason: "bundle carried no GitHub token" };
|
|
264
|
+
const permitted = bundleMayWriteGithubFiles(bundle, self);
|
|
265
|
+
if (!permitted.allowed) {
|
|
266
|
+
return { written: false, ...permitted.reason ? { reason: permitted.reason } : {} };
|
|
267
|
+
}
|
|
268
|
+
syncGithubTokenFiles(bundle.githubToken);
|
|
269
|
+
return { written: true };
|
|
270
|
+
}
|
|
271
|
+
function syncBundleGithubToken(token) {
|
|
272
|
+
syncGithubTokenFiles(token);
|
|
273
|
+
}
|
|
274
|
+
|
|
207
275
|
// src/connection/agent-connection.ts
|
|
276
|
+
var logger = createServiceLogger("agent-connection");
|
|
208
277
|
var EVENT_BATCH_MS = 500;
|
|
209
278
|
var MAX_EVENT_BUFFER = 5e3;
|
|
210
279
|
var TOKEN_REFRESH_INTERVAL_MS = 45 * 60 * 1e3;
|
|
@@ -237,6 +306,7 @@ var AgentConnection = class _AgentConnection {
|
|
|
237
306
|
apiKeyUpdateCallback = null;
|
|
238
307
|
pullBranchCallback = null;
|
|
239
308
|
runStartCommandCallback = null;
|
|
309
|
+
earlyRunStartCommand = false;
|
|
240
310
|
earlyPullBranches = [];
|
|
241
311
|
spawnReviewCallback = null;
|
|
242
312
|
earlySpawnReviews = [];
|
|
@@ -426,7 +496,8 @@ var AgentConnection = class _AgentConnection {
|
|
|
426
496
|
else this.earlyProbeUsage = true;
|
|
427
497
|
});
|
|
428
498
|
this.socket.on("session:runStartCommand", () => {
|
|
429
|
-
this.runStartCommandCallback
|
|
499
|
+
if (this.runStartCommandCallback) this.runStartCommandCallback();
|
|
500
|
+
else this.earlyRunStartCommand = true;
|
|
430
501
|
});
|
|
431
502
|
this.socket.on("pty:input", (data) => {
|
|
432
503
|
if (data.sessionId && data.sessionId !== this.config.sessionId) return;
|
|
@@ -728,6 +799,26 @@ var AgentConnection = class _AgentConnection {
|
|
|
728
799
|
}).catch(() => {
|
|
729
800
|
});
|
|
730
801
|
}
|
|
802
|
+
/**
|
|
803
|
+
* Report that this pod's git credential is dead and refreshing did not fix
|
|
804
|
+
* it (fire-and-forget).
|
|
805
|
+
*
|
|
806
|
+
* Purely diagnostic. Until this existed a pod could lose git entirely and
|
|
807
|
+
* leave no server-side trace at all — the refresh RPC succeeded every time,
|
|
808
|
+
* so the failure was visible only in pod stderr, which is why it took two
|
|
809
|
+
* investigations to attribute. `tokenShape` describes the served credential
|
|
810
|
+
* (length, prefix class, mtime) and NEVER carries its value.
|
|
811
|
+
*/
|
|
812
|
+
reportCredentialFailure(details) {
|
|
813
|
+
if (!this.socket) return;
|
|
814
|
+
void this.call("reportCredentialFailure", {
|
|
815
|
+
sessionId: this.config.sessionId,
|
|
816
|
+
...details.error ? { error: details.error.slice(0, 2e3) } : {},
|
|
817
|
+
...details.tokenShape ? { tokenShape: details.tokenShape.slice(0, 500) } : {},
|
|
818
|
+
...details.healed === void 0 ? {} : { healed: details.healed }
|
|
819
|
+
}).catch(() => {
|
|
820
|
+
});
|
|
821
|
+
}
|
|
731
822
|
/**
|
|
732
823
|
* Ask the server to destroy and recreate this pod (fire-and-forget). The
|
|
733
824
|
* agent calls this only when it has proven it cannot recover in place — the
|
|
@@ -772,8 +863,15 @@ var AgentConnection = class _AgentConnection {
|
|
|
772
863
|
}).catch(() => {
|
|
773
864
|
});
|
|
774
865
|
}
|
|
866
|
+
/** Register the restart handler, draining a `session:runStartCommand` that
|
|
867
|
+
* arrived before the supervisor was ready. Collapsed to one drain: two
|
|
868
|
+
* clicks during boot should produce one restart, not two competing ones. */
|
|
775
869
|
onRunStartCommand(callback) {
|
|
776
870
|
this.runStartCommandCallback = callback;
|
|
871
|
+
if (this.earlyRunStartCommand) {
|
|
872
|
+
this.earlyRunStartCommand = false;
|
|
873
|
+
callback();
|
|
874
|
+
}
|
|
777
875
|
}
|
|
778
876
|
// ── PTY relay (S5 Connected-TUI terminal) ──────────────────────────
|
|
779
877
|
/**
|
|
@@ -1159,11 +1257,20 @@ ${q.question}${q.options.length ? "\n" + q.options.map((o) => `- ${o.label}: ${o
|
|
|
1159
1257
|
// means the token is consumed / session terminal and retrying won't
|
|
1160
1258
|
// help. Network/timeout still retry inside fetchBootstrap.
|
|
1161
1259
|
});
|
|
1162
|
-
if (!result.ok)
|
|
1260
|
+
if (!result.ok) {
|
|
1261
|
+
logger.warn("bootstrap refresh abandoned \u2014 pod credentials will go stale", {
|
|
1262
|
+
path: "codespace",
|
|
1263
|
+
reason: result.reason,
|
|
1264
|
+
status: result.status,
|
|
1265
|
+
attempts: result.attempts,
|
|
1266
|
+
detail: result.detail
|
|
1267
|
+
});
|
|
1268
|
+
return { refreshedClaude: false, refreshedTaskToken: false };
|
|
1269
|
+
}
|
|
1163
1270
|
const previousTaskToken = process.env.CONVEYOR_TASK_TOKEN;
|
|
1164
1271
|
applyBootstrapToEnv(result.config);
|
|
1165
1272
|
const env = result.config.envVars ?? {};
|
|
1166
|
-
|
|
1273
|
+
syncBundleGithubToken(env.CONVEYOR_GITHUB_TOKEN ?? env.GH_TOKEN ?? env.GITHUB_TOKEN);
|
|
1167
1274
|
const refreshedTaskToken = result.config.mode !== "project" && Boolean(result.config.taskToken) && result.config.taskToken !== previousTaskToken;
|
|
1168
1275
|
if (refreshedTaskToken && result.config.taskToken) {
|
|
1169
1276
|
this.config.taskToken = result.config.taskToken;
|
|
@@ -1195,7 +1302,7 @@ ${q.question}${q.options.length ? "\n" + q.options.map((o) => `- ${o.label}: ${o
|
|
|
1195
1302
|
}
|
|
1196
1303
|
if (bundle.githubToken) {
|
|
1197
1304
|
process.env.CONVEYOR_GITHUB_TOKEN = bundle.githubToken;
|
|
1198
|
-
|
|
1305
|
+
this.applyBundleCredentialFiles(bundle, previousTaskToken);
|
|
1199
1306
|
}
|
|
1200
1307
|
if (bundle.anthropicKey) process.env.ANTHROPIC_API_KEY = bundle.anthropicKey;
|
|
1201
1308
|
if (bundle.gcpToken) process.env.CLOUDSDK_AUTH_ACCESS_TOKEN = bundle.gcpToken;
|
|
@@ -1214,6 +1321,32 @@ ${q.question}${q.options.length ? "\n" + q.options.map((o) => `- ${o.label}: ${o
|
|
|
1214
1321
|
const refreshedClaude = Boolean(bundle.envVars?.CLAUDE_CODE_OAUTH_TOKEN);
|
|
1215
1322
|
return { refreshedClaude, refreshedTaskToken };
|
|
1216
1323
|
}
|
|
1324
|
+
/**
|
|
1325
|
+
* Write the bundle's GitHub token to the shared credential files — unless the
|
|
1326
|
+
* bundle belongs to another session.
|
|
1327
|
+
*
|
|
1328
|
+
* The bootstrap GET is keyed by the POD, so on a pod also hosting a same-pod
|
|
1329
|
+
* review it can resolve to the reader session, whose token is read-only.
|
|
1330
|
+
* Writing that over the shared files silently downgrades the builder's push
|
|
1331
|
+
* credential. Our own taskToken carries the same claims, so it is what we
|
|
1332
|
+
* compare against.
|
|
1333
|
+
*
|
|
1334
|
+
* The legitimate case this path exists for — our own session's bundle
|
|
1335
|
+
* refreshing the token when the RPC is failing — is unaffected.
|
|
1336
|
+
*/
|
|
1337
|
+
applyBundleCredentialFiles(bundle, previousTaskToken) {
|
|
1338
|
+
const self = readBundleIdentity(previousTaskToken);
|
|
1339
|
+
const result = applyBundleGithubToken(bundle, {
|
|
1340
|
+
sessionId: this.config.sessionId || self.sessionId,
|
|
1341
|
+
...self.role ? { role: self.role } : {}
|
|
1342
|
+
});
|
|
1343
|
+
if (!result.written && result.reason) {
|
|
1344
|
+
process.stderr.write(
|
|
1345
|
+
`[conveyor-agent] Skipped writing GitHub credential files from the bootstrap bundle: ${result.reason}
|
|
1346
|
+
`
|
|
1347
|
+
);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1217
1350
|
/**
|
|
1218
1351
|
* Poll the bootstrap bundle once (maxWaitMs 0), retrying only on a transient
|
|
1219
1352
|
* 429 (standby-pool pods share one Cloud NAT IP against podBootstrapLimiter)
|
|
@@ -1229,6 +1362,12 @@ ${q.question}${q.options.length ? "\n" + q.options.map((o) => `- ${o.label}: ${o
|
|
|
1229
1362
|
} catch (err) {
|
|
1230
1363
|
const isRateLimited = err instanceof PollUntilBoundHttpError && err.status === 429;
|
|
1231
1364
|
if (!isRateLimited || attempt >= retryDelaysMs.length) {
|
|
1365
|
+
logger.warn("bootstrap refresh abandoned \u2014 pod credentials will go stale", {
|
|
1366
|
+
path: "v3",
|
|
1367
|
+
attempt,
|
|
1368
|
+
rateLimited: isRateLimited,
|
|
1369
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1370
|
+
});
|
|
1232
1371
|
return null;
|
|
1233
1372
|
}
|
|
1234
1373
|
await new Promise((resolve) => {
|
|
@@ -1290,31 +1429,8 @@ function isPermissionDeniedError(err) {
|
|
|
1290
1429
|
return /insufficient permissions|authentication required/i.test(message);
|
|
1291
1430
|
}
|
|
1292
1431
|
|
|
1293
|
-
// src/
|
|
1294
|
-
function createServiceLogger(service) {
|
|
1295
|
-
const prefix = `[conveyor-agent:${service}]`;
|
|
1296
|
-
return {
|
|
1297
|
-
info(message, data) {
|
|
1298
|
-
const extra = data ? ` ${JSON.stringify(data)}` : "";
|
|
1299
|
-
process.stderr.write(`${prefix} ${message}${extra}
|
|
1300
|
-
`);
|
|
1301
|
-
},
|
|
1302
|
-
warn(message, data) {
|
|
1303
|
-
const extra = data ? ` ${JSON.stringify(data)}` : "";
|
|
1304
|
-
process.stderr.write(`${prefix} WARN ${message}${extra}
|
|
1305
|
-
`);
|
|
1306
|
-
},
|
|
1307
|
-
error(message, data) {
|
|
1308
|
-
const extra = data ? ` ${JSON.stringify(data)}` : "";
|
|
1309
|
-
process.stderr.write(`${prefix} ERROR ${message}${extra}
|
|
1310
|
-
`);
|
|
1311
|
-
}
|
|
1312
|
-
};
|
|
1313
|
-
}
|
|
1314
|
-
|
|
1315
|
-
// src/runner/git-utils.ts
|
|
1432
|
+
// src/runner/git-run.ts
|
|
1316
1433
|
import { execFile } from "child_process";
|
|
1317
|
-
import { realpathSync } from "fs";
|
|
1318
1434
|
import { promisify } from "util";
|
|
1319
1435
|
var execFileAsync = promisify(execFile);
|
|
1320
1436
|
var GIT_TIMEOUT_MS = 6e4;
|
|
@@ -1322,12 +1438,19 @@ var GIT_SLOW_TIMEOUT_MS = 12e4;
|
|
|
1322
1438
|
var GIT_MAX_BUFFER = 16 * 1024 * 1024;
|
|
1323
1439
|
async function git(cwd, args, timeoutMs = GIT_TIMEOUT_MS) {
|
|
1324
1440
|
if (workbenchEnabled()) {
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1441
|
+
try {
|
|
1442
|
+
const { stdout: stdout2 } = await getWorkbenchClient().execFile("git", args, {
|
|
1443
|
+
cwd,
|
|
1444
|
+
timeout: timeoutMs,
|
|
1445
|
+
maxBuffer: GIT_MAX_BUFFER
|
|
1446
|
+
});
|
|
1447
|
+
return stdout2.trim();
|
|
1448
|
+
} catch (err) {
|
|
1449
|
+
if (err instanceof Error && err.message.startsWith("Command timed out:")) {
|
|
1450
|
+
err.killed = true;
|
|
1451
|
+
}
|
|
1452
|
+
throw err;
|
|
1453
|
+
}
|
|
1331
1454
|
}
|
|
1332
1455
|
const { stdout } = await execFileAsync("git", args, {
|
|
1333
1456
|
cwd,
|
|
@@ -1336,6 +1459,54 @@ async function git(cwd, args, timeoutMs = GIT_TIMEOUT_MS) {
|
|
|
1336
1459
|
});
|
|
1337
1460
|
return stdout.toString().trim();
|
|
1338
1461
|
}
|
|
1462
|
+
|
|
1463
|
+
// src/runner/git-credential-ops.ts
|
|
1464
|
+
function credentialErrorText(err) {
|
|
1465
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
1466
|
+
return raw.replace(/\/\/[^@\s]+@/g, "//***@");
|
|
1467
|
+
}
|
|
1468
|
+
async function updateRemoteCredential(cwd, credential) {
|
|
1469
|
+
const result = { ok: false, storeWritten: false, helperConfigured: false };
|
|
1470
|
+
try {
|
|
1471
|
+
const currentUrl = await git(cwd, ["remote", "get-url", "origin"]);
|
|
1472
|
+
const cloneUrl = credential.cloneUrl ?? currentUrl;
|
|
1473
|
+
const normalizedUrl = writeGitCredential(cwd, cloneUrl, credential);
|
|
1474
|
+
result.storeWritten = true;
|
|
1475
|
+
if (currentUrl !== normalizedUrl) {
|
|
1476
|
+
await git(cwd, ["remote", "set-url", "origin", normalizedUrl]);
|
|
1477
|
+
}
|
|
1478
|
+
await git(cwd, ["config", "--local", "credential.helper", gitCredentialHelper(cwd)]);
|
|
1479
|
+
result.helperConfigured = true;
|
|
1480
|
+
result.ok = true;
|
|
1481
|
+
} catch (err) {
|
|
1482
|
+
result.error = credentialErrorText(err);
|
|
1483
|
+
}
|
|
1484
|
+
return result;
|
|
1485
|
+
}
|
|
1486
|
+
async function updateRemoteToken(cwd, token) {
|
|
1487
|
+
const username = process.env.CONVEYOR_GIT_USERNAME || "x-access-token";
|
|
1488
|
+
const cloneUrl = process.env.CONVEYOR_GIT_CLONE_URL || void 0;
|
|
1489
|
+
const credential = await updateRemoteCredential(cwd, { username, secret: token, cloneUrl });
|
|
1490
|
+
process.env.CONVEYOR_GIT_SECRET = token;
|
|
1491
|
+
const files = syncGithubTokenFiles(token);
|
|
1492
|
+
return { credential, files };
|
|
1493
|
+
}
|
|
1494
|
+
async function verifyGitCredential(cwd) {
|
|
1495
|
+
try {
|
|
1496
|
+
await git(cwd, ["ls-remote", "--heads", "origin"], 3e4);
|
|
1497
|
+
return { ok: true, outcome: "ok" };
|
|
1498
|
+
} catch (err) {
|
|
1499
|
+
const killed = err.killed === true;
|
|
1500
|
+
return {
|
|
1501
|
+
ok: false,
|
|
1502
|
+
outcome: killed ? "timeout" : "denied",
|
|
1503
|
+
error: credentialErrorText(err)
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
// src/runner/git-utils.ts
|
|
1509
|
+
import { realpathSync } from "fs";
|
|
1339
1510
|
async function ensureOnTaskBranch(cwd, taskBranch, baseBranch) {
|
|
1340
1511
|
if (!taskBranch) return true;
|
|
1341
1512
|
try {
|
|
@@ -1464,44 +1635,6 @@ async function isAuthError(cwd) {
|
|
|
1464
1635
|
return errLooksLikeAuth(err);
|
|
1465
1636
|
}
|
|
1466
1637
|
}
|
|
1467
|
-
function credentialErrorText(err) {
|
|
1468
|
-
const raw = err instanceof Error ? err.message : String(err);
|
|
1469
|
-
return raw.replace(/\/\/[^@\s]+@/g, "//***@");
|
|
1470
|
-
}
|
|
1471
|
-
async function updateRemoteCredential(cwd, credential) {
|
|
1472
|
-
const result = { ok: false, storeWritten: false, helperConfigured: false };
|
|
1473
|
-
try {
|
|
1474
|
-
const currentUrl = await git(cwd, ["remote", "get-url", "origin"]);
|
|
1475
|
-
const cloneUrl = credential.cloneUrl ?? currentUrl;
|
|
1476
|
-
const normalizedUrl = writeGitCredential(cwd, cloneUrl, credential);
|
|
1477
|
-
result.storeWritten = true;
|
|
1478
|
-
if (currentUrl !== normalizedUrl) {
|
|
1479
|
-
await git(cwd, ["remote", "set-url", "origin", normalizedUrl]);
|
|
1480
|
-
}
|
|
1481
|
-
await git(cwd, ["config", "--local", "credential.helper", gitCredentialHelper(cwd)]);
|
|
1482
|
-
result.helperConfigured = true;
|
|
1483
|
-
result.ok = true;
|
|
1484
|
-
} catch (err) {
|
|
1485
|
-
result.error = credentialErrorText(err);
|
|
1486
|
-
}
|
|
1487
|
-
return result;
|
|
1488
|
-
}
|
|
1489
|
-
async function updateRemoteToken(cwd, token) {
|
|
1490
|
-
const username = process.env.CONVEYOR_GIT_USERNAME || "x-access-token";
|
|
1491
|
-
const cloneUrl = process.env.CONVEYOR_GIT_CLONE_URL || void 0;
|
|
1492
|
-
const credential = await updateRemoteCredential(cwd, { username, secret: token, cloneUrl });
|
|
1493
|
-
process.env.CONVEYOR_GIT_SECRET = token;
|
|
1494
|
-
const files = syncGithubTokenFiles(token);
|
|
1495
|
-
return { credential, files };
|
|
1496
|
-
}
|
|
1497
|
-
async function verifyGitCredential(cwd) {
|
|
1498
|
-
try {
|
|
1499
|
-
await git(cwd, ["ls-remote", "--heads", "origin"], 3e4);
|
|
1500
|
-
return { ok: true };
|
|
1501
|
-
} catch (err) {
|
|
1502
|
-
return { ok: false, error: credentialErrorText(err) };
|
|
1503
|
-
}
|
|
1504
|
-
}
|
|
1505
1638
|
function wipRefForBranch(branch) {
|
|
1506
1639
|
return `conveyor-wip/${branch}`;
|
|
1507
1640
|
}
|
|
@@ -1645,7 +1778,7 @@ async function pushToOrigin(cwd, refreshToken, skipVerify = false) {
|
|
|
1645
1778
|
}
|
|
1646
1779
|
if (await tryPush(cwd, currentBranch, skipVerify)) return true;
|
|
1647
1780
|
if (refreshToken && await isAuthError(cwd)) {
|
|
1648
|
-
const token = await refreshToken();
|
|
1781
|
+
const token = await refreshToken({ forceFresh: true });
|
|
1649
1782
|
if (token) {
|
|
1650
1783
|
await updateRemoteToken(cwd, token);
|
|
1651
1784
|
process.env.GITHUB_TOKEN = token;
|
|
@@ -2021,16 +2154,19 @@ var KnownAgentEventSchema = z3.discriminatedUnion("type", [
|
|
|
2021
2154
|
cacheCreationInputTokens: z3.number().optional(),
|
|
2022
2155
|
totalTokensUsed: z3.number().optional()
|
|
2023
2156
|
}).passthrough(),
|
|
2024
|
-
//
|
|
2025
|
-
// (SDK rate_limit_event), {resetsAt} (agent-connection resume notice),
|
|
2026
|
-
//
|
|
2027
|
-
// — resetsAt matches rateLimitType; gauges survives via .passthrough())
|
|
2157
|
+
// Four producer shapes share this type: {rateLimitType, utilization, status}
|
|
2158
|
+
// (SDK rate_limit_event), {resetsAt} (agent-connection resume notice), the
|
|
2159
|
+
// usage-sampler ({rateLimitType, utilization, status, resetsAt, gauges}
|
|
2160
|
+
// — resetsAt matches rateLimitType; gauges survives via .passthrough()), and
|
|
2161
|
+
// {unmeasurable, reason} (the sampler reporting it cannot read this key).
|
|
2028
2162
|
z3.object({
|
|
2029
2163
|
type: z3.literal("rate_limit_update"),
|
|
2030
2164
|
rateLimitType: z3.string().optional(),
|
|
2031
2165
|
utilization: z3.number().optional(),
|
|
2032
2166
|
status: z3.string().optional(),
|
|
2033
|
-
resetsAt: z3.string().optional()
|
|
2167
|
+
resetsAt: z3.string().optional(),
|
|
2168
|
+
unmeasurable: z3.boolean().optional(),
|
|
2169
|
+
reason: z3.string().optional()
|
|
2034
2170
|
}).passthrough(),
|
|
2035
2171
|
z3.object({
|
|
2036
2172
|
type: z3.literal("context_compacted"),
|
|
@@ -2074,6 +2210,7 @@ var KnownAgentEventSchema = z3.discriminatedUnion("type", [
|
|
|
2074
2210
|
z3.object({
|
|
2075
2211
|
type: z3.literal("setup_complete"),
|
|
2076
2212
|
startCommandRunning: z3.boolean().optional(),
|
|
2213
|
+
startCommandConfigured: z3.boolean().optional(),
|
|
2077
2214
|
// Sanitized server-side by sanitizeSessionPreviewPorts — stays unknown.
|
|
2078
2215
|
previewPorts: z3.unknown().optional()
|
|
2079
2216
|
}).passthrough(),
|
|
@@ -2481,7 +2618,14 @@ var EmitAgentEventRequestSchema = z4.object({
|
|
|
2481
2618
|
events: z4.array(AgentEventSchema).max(500)
|
|
2482
2619
|
});
|
|
2483
2620
|
var RefreshGithubTokenRequestSchema = z4.object({
|
|
2484
|
-
sessionId: z4.string()
|
|
2621
|
+
sessionId: z4.string(),
|
|
2622
|
+
forceFresh: z4.boolean().optional()
|
|
2623
|
+
});
|
|
2624
|
+
var ReportCredentialFailureRequestSchema = z4.object({
|
|
2625
|
+
sessionId: z4.string(),
|
|
2626
|
+
error: z4.string().max(2e3).optional(),
|
|
2627
|
+
tokenShape: z4.string().max(500).optional(),
|
|
2628
|
+
healed: z4.boolean().optional()
|
|
2485
2629
|
});
|
|
2486
2630
|
var ReportReviewSpawnFailureRequestSchema = z4.object({
|
|
2487
2631
|
sessionId: z4.string(),
|
|
@@ -2621,6 +2765,18 @@ var SessionStopResponseSchema = z4.object({
|
|
|
2621
2765
|
var DeleteSubtaskResponseSchema = z4.object({
|
|
2622
2766
|
deleted: z4.boolean()
|
|
2623
2767
|
});
|
|
2768
|
+
var GIT_BRANCH_NAME_MAX = 255;
|
|
2769
|
+
var GIT_BRANCH_NAME_MESSAGE = "Invalid git branch name \u2014 use only letters, numbers, '.', '_', '/' and '-', starting with a letter or number, with no '..', '@{' or '//', and no trailing '/', '-', '.' or '.lock'";
|
|
2770
|
+
var ALLOWED_REF = /^[A-Za-z0-9][A-Za-z0-9._/-]*$/;
|
|
2771
|
+
function isValidGitBranchName(name) {
|
|
2772
|
+
if (typeof name !== "string") return false;
|
|
2773
|
+
if (name.length === 0 || name.length > GIT_BRANCH_NAME_MAX) return false;
|
|
2774
|
+
if (!ALLOWED_REF.test(name)) return false;
|
|
2775
|
+
if (name.includes("..") || name.includes("@{") || name.includes("//")) return false;
|
|
2776
|
+
if (name.endsWith("/") || name.endsWith("-")) return false;
|
|
2777
|
+
if (name.endsWith(".") || name.endsWith(".lock")) return false;
|
|
2778
|
+
return true;
|
|
2779
|
+
}
|
|
2624
2780
|
var cardDescription2 = z5.string().max(CARD_DESCRIPTION_MAX, CARD_DESCRIPTION_LIMIT_MESSAGE).optional();
|
|
2625
2781
|
var ListAccessibleProjectsRequestSchema = z5.object({
|
|
2626
2782
|
pageSize: z5.number().int().positive().max(100).optional().default(100)
|
|
@@ -2758,11 +2914,17 @@ var UpdateProjectTaskRequestSchema = z5.object({
|
|
|
2758
2914
|
// Move to a different sub-project board, or null to move to the parent board.
|
|
2759
2915
|
// Validated to belong to `projectId` in the handler.
|
|
2760
2916
|
subProjectId: z5.string().nullable().optional(),
|
|
2917
|
+
// Record the task's ACTUAL working branch (e.g. a locally-driven pack's
|
|
2918
|
+
// branch, so identification never mints a competing name and pack-child
|
|
2919
|
+
// merge handling matches reality), or null to detach. Guarded in the
|
|
2920
|
+
// handler: the ref must exist on origin and no live workspace may be bound
|
|
2921
|
+
// to a different branch.
|
|
2922
|
+
githubBranch: z5.string().min(1).max(GIT_BRANCH_NAME_MAX).refine(isValidGitBranchName, { message: GIT_BRANCH_NAME_MESSAGE }).nullable().optional(),
|
|
2761
2923
|
requestingUserId: z5.string().optional()
|
|
2762
2924
|
}).strict().refine(
|
|
2763
|
-
(v) => v.title !== void 0 || v.description !== void 0 || v.plan !== void 0 || v.status !== void 0 || v.risk !== void 0 || v.storyPointValue !== void 0 || v.assignedUserId !== void 0 || v.subProjectId !== void 0,
|
|
2925
|
+
(v) => v.title !== void 0 || v.description !== void 0 || v.plan !== void 0 || v.status !== void 0 || v.risk !== void 0 || v.storyPointValue !== void 0 || v.assignedUserId !== void 0 || v.subProjectId !== void 0 || v.githubBranch !== void 0,
|
|
2764
2926
|
{
|
|
2765
|
-
message: "update_task requires at least one field to change (title, description, plan, status, risk, storyPointValue, assignedUserId, or
|
|
2927
|
+
message: "update_task requires at least one field to change (title, description, plan, status, risk, storyPointValue, assignedUserId, subProjectId, or githubBranch)"
|
|
2766
2928
|
}
|
|
2767
2929
|
);
|
|
2768
2930
|
var TransitionProjectTaskStatusRequestSchema = z5.object({
|
|
@@ -3306,7 +3468,7 @@ var AGENT_CHAT_HISTORY_FETCH_LIMIT = Math.max(TASK_CHAT_HISTORY_LIMIT, PM_CHAT_H
|
|
|
3306
3468
|
function formatModelId(provider, model) {
|
|
3307
3469
|
return `${provider}/${model}`;
|
|
3308
3470
|
}
|
|
3309
|
-
function anthropicEntry(model, label, inputPerMillion, outputPerMillion,
|
|
3471
|
+
function anthropicEntry(model, label, inputPerMillion, outputPerMillion, opts = {}) {
|
|
3310
3472
|
return {
|
|
3311
3473
|
provider: "anthropic",
|
|
3312
3474
|
model,
|
|
@@ -3316,7 +3478,8 @@ function anthropicEntry(model, label, inputPerMillion, outputPerMillion, experim
|
|
|
3316
3478
|
inputPrice: inputPerMillion / 1e6,
|
|
3317
3479
|
outputPrice: outputPerMillion / 1e6,
|
|
3318
3480
|
supportsTools: true,
|
|
3319
|
-
|
|
3481
|
+
supportsEffort: opts.supportsEffort ?? true,
|
|
3482
|
+
...opts.experimental ? { experimental: true } : {}
|
|
3320
3483
|
};
|
|
3321
3484
|
}
|
|
3322
3485
|
var ANTHROPIC_CATALOG = [
|
|
@@ -3324,8 +3487,9 @@ var ANTHROPIC_CATALOG = [
|
|
|
3324
3487
|
anthropicEntry(PREVIOUS_OPUS_MODEL, "Opus 4.8", 5, 25),
|
|
3325
3488
|
anthropicEntry(DEFAULT_SONNET_MODEL, "Sonnet 5 Latest", 3, 15),
|
|
3326
3489
|
anthropicEntry(PREVIOUS_SONNET_MODEL, "Sonnet 4.6", 3, 15),
|
|
3327
|
-
|
|
3328
|
-
anthropicEntry(
|
|
3490
|
+
// The Haiku line (4.5 and older) predates the tuning surface and 400s on it.
|
|
3491
|
+
anthropicEntry(DEFAULT_HAIKU_MODEL, "Haiku 4.5", 1, 5, { supportsEffort: false }),
|
|
3492
|
+
anthropicEntry(FABLE_MODEL, "Fable 5 (experimental)", 10, 50, { experimental: true })
|
|
3329
3493
|
];
|
|
3330
3494
|
var HUMAN_PROSE_WRITING_STYLE = `## Writing style for humans
|
|
3331
3495
|
When you write prose a person will read \u2014 chat messages, plan updates, PR titles and bodies, PR review guides (the \`publish_review_guide\` overview and section explanations), review comments \u2014 follow these rules (based on ASD-STE100 Simplified Technical English):
|
|
@@ -7378,7 +7542,7 @@ function findOnPath(binary, env = process.env) {
|
|
|
7378
7542
|
import { promises as fs } from "fs";
|
|
7379
7543
|
import { dirname as dirname2, join as join9 } from "path";
|
|
7380
7544
|
import { homedir as homedir4 } from "os";
|
|
7381
|
-
var
|
|
7545
|
+
var logger2 = createServiceLogger("opencode-auth");
|
|
7382
7546
|
var OPENCODE_CODEX_PLUGIN = "opencode-openai-codex-auth@4.4.0";
|
|
7383
7547
|
var PLUGIN_PACKAGE = "opencode-openai-codex-auth";
|
|
7384
7548
|
function opencodeAuthPath(env) {
|
|
@@ -7425,7 +7589,7 @@ async function ensureAuthEntry(env, seed) {
|
|
|
7425
7589
|
const path2 = opencodeAuthPath(env);
|
|
7426
7590
|
const store = await readJsonFile(path2);
|
|
7427
7591
|
if (!shouldSeed(store.openai, seed)) {
|
|
7428
|
-
|
|
7592
|
+
logger2.info("opencode oauth store is fresher than the seed; leaving it alone");
|
|
7429
7593
|
return;
|
|
7430
7594
|
}
|
|
7431
7595
|
store.openai = {
|
|
@@ -7435,7 +7599,7 @@ async function ensureAuthEntry(env, seed) {
|
|
|
7435
7599
|
expires: seed.expires
|
|
7436
7600
|
};
|
|
7437
7601
|
await writeJsonFile(path2, store);
|
|
7438
|
-
|
|
7602
|
+
logger2.info("seeded opencode oauth store entry");
|
|
7439
7603
|
}
|
|
7440
7604
|
async function ensurePluginConfig(env) {
|
|
7441
7605
|
const path2 = opencodeConfigPath(env);
|
|
@@ -7448,7 +7612,7 @@ async function ensurePluginConfig(env) {
|
|
|
7448
7612
|
const kept = plugins.filter((p) => !isOurs(p));
|
|
7449
7613
|
config.plugin = [...kept, OPENCODE_CODEX_PLUGIN];
|
|
7450
7614
|
await writeJsonFile(path2, config);
|
|
7451
|
-
|
|
7615
|
+
logger2.info("ensured opencode codex-auth plugin in config");
|
|
7452
7616
|
}
|
|
7453
7617
|
async function seedOpenCodeOauth(env) {
|
|
7454
7618
|
const seed = parseOauthSeed(env.CONVEYOR_OPENCODE_OAUTH);
|
|
@@ -7457,7 +7621,7 @@ async function seedOpenCodeOauth(env) {
|
|
|
7457
7621
|
await ensureAuthEntry(env, seed);
|
|
7458
7622
|
await ensurePluginConfig(env);
|
|
7459
7623
|
} catch (err) {
|
|
7460
|
-
|
|
7624
|
+
logger2.warn(
|
|
7461
7625
|
`failed to seed opencode oauth store: ${err instanceof Error ? err.message : String(err)}`
|
|
7462
7626
|
);
|
|
7463
7627
|
}
|
|
@@ -7826,14 +7990,14 @@ function resolveTuiAdapter(kind = "claude-code") {
|
|
|
7826
7990
|
|
|
7827
7991
|
// src/harness/pty/stream-server.ts
|
|
7828
7992
|
import net from "net";
|
|
7829
|
-
var
|
|
7993
|
+
var logger3 = createServiceLogger("PtyStreamServer");
|
|
7830
7994
|
var RING_MAX_CHARS = 256 * 1024;
|
|
7831
7995
|
var PtyStreamServer = class {
|
|
7832
7996
|
constructor(options) {
|
|
7833
7997
|
this.options = options;
|
|
7834
7998
|
this.server = net.createServer((socket) => this.handleConnection(socket));
|
|
7835
7999
|
this.server.on("error", (err) => {
|
|
7836
|
-
|
|
8000
|
+
logger3.warn(`PTY stream server error: ${err.message}`);
|
|
7837
8001
|
});
|
|
7838
8002
|
}
|
|
7839
8003
|
options;
|
|
@@ -7874,7 +8038,7 @@ var PtyStreamServer = class {
|
|
|
7874
8038
|
return port;
|
|
7875
8039
|
}
|
|
7876
8040
|
}
|
|
7877
|
-
|
|
8041
|
+
logger3.warn(`PTY stream server could not bind any port in ${base}..${base + attempts - 1}`);
|
|
7878
8042
|
return null;
|
|
7879
8043
|
}
|
|
7880
8044
|
tryListen(port) {
|
|
@@ -7995,7 +8159,7 @@ var PtyStreamServer = class {
|
|
|
7995
8159
|
};
|
|
7996
8160
|
|
|
7997
8161
|
// src/harness/pty/direct-stream.ts
|
|
7998
|
-
var
|
|
8162
|
+
var logger4 = createServiceLogger("PtyDirectStream");
|
|
7999
8163
|
var RELAY_COALESCE_MS = 2e3;
|
|
8000
8164
|
var RELAY_MAX_BUFFER_CHARS = 48 * 1024;
|
|
8001
8165
|
var DirectStreamController = class {
|
|
@@ -8044,7 +8208,7 @@ var DirectStreamController = class {
|
|
|
8044
8208
|
});
|
|
8045
8209
|
void created.listen().then((port) => this.onListening(created, port)).catch((err) => {
|
|
8046
8210
|
this.starting = false;
|
|
8047
|
-
|
|
8211
|
+
logger4.warn(
|
|
8048
8212
|
`PTY stream server failed to start: ${err instanceof Error ? err.message : String(err)}`
|
|
8049
8213
|
);
|
|
8050
8214
|
});
|
|
@@ -8057,7 +8221,7 @@ var DirectStreamController = class {
|
|
|
8057
8221
|
}
|
|
8058
8222
|
this.server = created;
|
|
8059
8223
|
this.reporter.reportPtyStream(port);
|
|
8060
|
-
|
|
8224
|
+
logger4.info(`PTY stream server listening on ${port} (session ${this.reporter.sessionId})`);
|
|
8061
8225
|
}
|
|
8062
8226
|
/** Push the min box across both transports to the pty. */
|
|
8063
8227
|
applyDims() {
|
|
@@ -10083,7 +10247,10 @@ var getTaskContract = defineToolContract({
|
|
|
10083
10247
|
fields: {
|
|
10084
10248
|
projectId: mcpProjectId,
|
|
10085
10249
|
taskId: f.string({
|
|
10086
|
-
|
|
10250
|
+
// The path form is MCP-only on purpose: an in-pod agent is bound to one
|
|
10251
|
+
// project, where a bare slug already resolves, so the `agent` half of
|
|
10252
|
+
// this contract deliberately says nothing about it.
|
|
10253
|
+
desc: "The task ID or slug (the value in a card URL, /cards/<slug>), or a path-form reference '<project-slug>/<card-slug>' from the board's Copy path action"
|
|
10087
10254
|
})
|
|
10088
10255
|
}
|
|
10089
10256
|
}
|
|
@@ -11064,6 +11231,80 @@ function buildGetSuggestionsTool(connection) {
|
|
|
11064
11231
|
|
|
11065
11232
|
// src/tools/mutation-tools.ts
|
|
11066
11233
|
import { z as z13 } from "zod";
|
|
11234
|
+
|
|
11235
|
+
// src/runner/refresh-verify-heal.ts
|
|
11236
|
+
async function refreshAndVerifyGithubCredential(cwd, mint) {
|
|
11237
|
+
const first = await mintWriteProbe(cwd, mint, false);
|
|
11238
|
+
if (first.probe.ok) return first;
|
|
11239
|
+
if (first.probe.outcome !== "denied") return first;
|
|
11240
|
+
if (!first.written) return first;
|
|
11241
|
+
const healed = await mintWriteProbe(cwd, mint, true);
|
|
11242
|
+
return { ...healed, healAttempted: true };
|
|
11243
|
+
}
|
|
11244
|
+
async function mintWriteProbe(cwd, mint, forceFresh) {
|
|
11245
|
+
let token;
|
|
11246
|
+
try {
|
|
11247
|
+
token = await mint(forceFresh ? { forceFresh: true } : {});
|
|
11248
|
+
} catch (err) {
|
|
11249
|
+
return {
|
|
11250
|
+
ok: false,
|
|
11251
|
+
probe: { ok: false, outcome: "unavailable", error: errorText(err) },
|
|
11252
|
+
healAttempted: false,
|
|
11253
|
+
failures: [`- could not mint a GitHub token: ${errorText(err)}`]
|
|
11254
|
+
};
|
|
11255
|
+
}
|
|
11256
|
+
if (!token) {
|
|
11257
|
+
return {
|
|
11258
|
+
ok: false,
|
|
11259
|
+
probe: { ok: false, outcome: "unavailable", error: "no token returned" },
|
|
11260
|
+
healAttempted: false,
|
|
11261
|
+
failures: ["- the server returned no GitHub token"]
|
|
11262
|
+
};
|
|
11263
|
+
}
|
|
11264
|
+
const written = await updateRemoteToken(cwd, token);
|
|
11265
|
+
process.env.GITHUB_TOKEN = token;
|
|
11266
|
+
process.env.GH_TOKEN = token;
|
|
11267
|
+
const probe = await verifyGitCredential(cwd);
|
|
11268
|
+
const failures = describeFailures(written, probe);
|
|
11269
|
+
return {
|
|
11270
|
+
ok: probe.ok && failures.length === 0,
|
|
11271
|
+
probe,
|
|
11272
|
+
written,
|
|
11273
|
+
healAttempted: false,
|
|
11274
|
+
failures,
|
|
11275
|
+
...probe.ok ? {} : { tokenShape: describeTokenFile() }
|
|
11276
|
+
};
|
|
11277
|
+
}
|
|
11278
|
+
function describeFailures(written, probe) {
|
|
11279
|
+
const failures = [];
|
|
11280
|
+
const credentialError = written.credential.error ? `: ${written.credential.error}` : "";
|
|
11281
|
+
if (!written.credential.storeWritten) {
|
|
11282
|
+
failures.push(`- git credential store NOT written${credentialError}`);
|
|
11283
|
+
} else if (!written.credential.helperConfigured) {
|
|
11284
|
+
failures.push(`- git credential helper NOT configured${credentialError}`);
|
|
11285
|
+
}
|
|
11286
|
+
if (!written.files.tokenFile) {
|
|
11287
|
+
const why = written.files.errors?.tokenFile;
|
|
11288
|
+
failures.push(`- token file NOT written${why ? `: ${why}` : ""}`);
|
|
11289
|
+
}
|
|
11290
|
+
if (!written.files.ghHosts && !ghHostsExternallyOwned()) {
|
|
11291
|
+
const why = written.files.errors?.ghHosts;
|
|
11292
|
+
failures.push(`- gh CLI config NOT written${why ? `: ${why}` : ""}`);
|
|
11293
|
+
}
|
|
11294
|
+
if (probe.outcome === "denied") {
|
|
11295
|
+
failures.push(`- git could not authenticate to origin: ${probe.error ?? "unknown error"}`);
|
|
11296
|
+
} else if (probe.outcome === "timeout") {
|
|
11297
|
+
failures.push(
|
|
11298
|
+
`- the credential is UNPROVEN: the check against origin timed out (${probe.error ?? "killed"}). The pod is likely under load \u2014 this is not evidence the token is bad.`
|
|
11299
|
+
);
|
|
11300
|
+
}
|
|
11301
|
+
return failures;
|
|
11302
|
+
}
|
|
11303
|
+
function errorText(err) {
|
|
11304
|
+
return err instanceof Error ? err.message : String(err);
|
|
11305
|
+
}
|
|
11306
|
+
|
|
11307
|
+
// src/tools/mutation-tools.ts
|
|
11067
11308
|
function buildPostToChatTool(connection) {
|
|
11068
11309
|
return defineContractTool(
|
|
11069
11310
|
postToChatContract,
|
|
@@ -11353,60 +11594,85 @@ function buildVoteSuggestionTool(connection) {
|
|
|
11353
11594
|
}
|
|
11354
11595
|
);
|
|
11355
11596
|
}
|
|
11597
|
+
function describeRefreshOutcome(outcome, tokenFile) {
|
|
11598
|
+
const healedNote = outcome.healAttempted ? " (the first attempt was rejected; a force-fresh retry fixed it)" : "";
|
|
11599
|
+
if (outcome.probe.ok) {
|
|
11600
|
+
if (outcome.failures.length === 0) {
|
|
11601
|
+
return {
|
|
11602
|
+
report: false,
|
|
11603
|
+
text: [
|
|
11604
|
+
`GitHub token refreshed and verified against origin${healedNote}.`,
|
|
11605
|
+
"- git: the credential store and helper are updated, so `git push` works now with no extra step.",
|
|
11606
|
+
"- gh: the CLI config is updated, so `gh` works now with no extra step.",
|
|
11607
|
+
`- shell/scripts: read the current token from ${tokenFile}.`,
|
|
11608
|
+
` For a command that needs it in the environment: GITHUB_TOKEN=$(cat ${tokenFile}) <command>`,
|
|
11609
|
+
"Retry the failed command."
|
|
11610
|
+
].join("\n")
|
|
11611
|
+
};
|
|
11612
|
+
}
|
|
11613
|
+
return {
|
|
11614
|
+
report: false,
|
|
11615
|
+
text: [
|
|
11616
|
+
`GitHub token refreshed, but some credential copies did not update.`,
|
|
11617
|
+
...outcome.failures,
|
|
11618
|
+
`A current token is available at ${tokenFile} when that file was written.`
|
|
11619
|
+
].join("\n")
|
|
11620
|
+
};
|
|
11621
|
+
}
|
|
11622
|
+
if (outcome.probe.outcome === "timeout") {
|
|
11623
|
+
return {
|
|
11624
|
+
report: false,
|
|
11625
|
+
text: [
|
|
11626
|
+
"GitHub token refreshed, but the check against origin timed out \u2014 the credential is UNPROVEN, not known bad.",
|
|
11627
|
+
...outcome.failures,
|
|
11628
|
+
"The pod is probably under load (a heavy gate). Retry your git command directly; if it succeeds, nothing is wrong."
|
|
11629
|
+
].join("\n")
|
|
11630
|
+
};
|
|
11631
|
+
}
|
|
11632
|
+
if (outcome.probe.outcome === "unavailable") {
|
|
11633
|
+
return {
|
|
11634
|
+
report: false,
|
|
11635
|
+
text: [
|
|
11636
|
+
"The server did not hand back a GitHub token, so nothing was refreshed and nothing was tested.",
|
|
11637
|
+
...outcome.failures,
|
|
11638
|
+
"The agent's connection to the API may be down, or this project may have no GitHub App installed. Wait for the connection to recover and try again."
|
|
11639
|
+
].join("\n")
|
|
11640
|
+
};
|
|
11641
|
+
}
|
|
11642
|
+
return {
|
|
11643
|
+
report: true,
|
|
11644
|
+
text: [
|
|
11645
|
+
`GitHub token refresh did NOT produce a working git credential${outcome.healAttempted ? ", including after a force-fresh retry" : ""}.`,
|
|
11646
|
+
...outcome.failures,
|
|
11647
|
+
...outcome.tokenShape ? [`- credential in play: ${outcome.tokenShape}`] : [],
|
|
11648
|
+
"This has been reported to the card, so the team can see it.",
|
|
11649
|
+
"Do not retry this tool. **Your unpushed work is only on this pod** \u2014 the WIP snapshot needs the same credential, so it is not being saved either. Preserve it before anything else: `git bundle create /tmp/work.bundle --all` (or `git format-patch`), then attach that file to the card with `upload_attachment`."
|
|
11650
|
+
].join("\n")
|
|
11651
|
+
};
|
|
11652
|
+
}
|
|
11356
11653
|
function buildRefreshGithubTokenTool(connection, config) {
|
|
11357
11654
|
return defineTool(
|
|
11358
11655
|
"refresh_github_token",
|
|
11359
|
-
"Mint a fresh GitHub token for this pod when git or gh fails with a 401 / 'Bad credentials'. The pod's token expires about every hour. This updates the git credential store and the gh CLI config, and returns the shell command that puts a current token in your environment.",
|
|
11656
|
+
"Mint a fresh GitHub token for this pod when git or gh fails with a 401 / 'Bad credentials'. The pod's token expires about every hour. This updates the git credential store and the gh CLI config, verifies the result against origin, and returns the shell command that puts a current token in your environment.",
|
|
11360
11657
|
{},
|
|
11361
11658
|
async () => {
|
|
11362
11659
|
try {
|
|
11363
|
-
const
|
|
11364
|
-
|
|
11365
|
-
|
|
11366
|
-
|
|
11367
|
-
|
|
11368
|
-
|
|
11369
|
-
const tokenFile = githubTokenFilePath();
|
|
11370
|
-
const failures = [];
|
|
11371
|
-
if (!written.credential.storeWritten) {
|
|
11372
|
-
failures.push(
|
|
11373
|
-
`- git credential store NOT written${written.credential.error ? `: ${written.credential.error}` : ""}`
|
|
11374
|
-
);
|
|
11375
|
-
} else if (!written.credential.helperConfigured) {
|
|
11376
|
-
failures.push(
|
|
11377
|
-
`- git credential helper NOT configured${written.credential.error ? `: ${written.credential.error}` : ""}`
|
|
11378
|
-
);
|
|
11379
|
-
}
|
|
11380
|
-
if (!written.files.tokenFile) failures.push(`- token file ${tokenFile} NOT written`);
|
|
11381
|
-
if (!written.files.ghHosts && !ghHostsExternallyOwned()) {
|
|
11382
|
-
failures.push("- gh CLI config NOT written");
|
|
11383
|
-
}
|
|
11384
|
-
const probe = await verifyGitCredential(config.workspaceDir);
|
|
11385
|
-
if (!probe.ok) {
|
|
11386
|
-
failures.push(
|
|
11387
|
-
`- git could not authenticate to origin: ${probe.error ?? "unknown error"}`
|
|
11388
|
-
);
|
|
11389
|
-
}
|
|
11390
|
-
if (failures.length > 0) {
|
|
11391
|
-
return textResult(
|
|
11392
|
-
[
|
|
11393
|
-
probe.ok ? "GitHub token refreshed, but some credential copies did not update." : "GitHub token refresh did NOT produce a working git credential.",
|
|
11394
|
-
...failures,
|
|
11395
|
-
`A current token is available at ${tokenFile} when that file was written.`,
|
|
11396
|
-
"Do not retry this tool more than once. Report the failing lines above to the team \u2014 the GitHub App install may be missing for this project, or the pod's credential directory may not be writable."
|
|
11397
|
-
].join("\n")
|
|
11398
|
-
);
|
|
11399
|
-
}
|
|
11400
|
-
return textResult(
|
|
11401
|
-
[
|
|
11402
|
-
"GitHub token refreshed and verified against origin.",
|
|
11403
|
-
"- git: the credential store and helper are updated, so `git push` works now with no extra step.",
|
|
11404
|
-
"- gh: the CLI config is updated, so `gh` works now with no extra step.",
|
|
11405
|
-
`- shell/scripts: read the current token from ${tokenFile}.`,
|
|
11406
|
-
` For a command that needs it in the environment: GITHUB_TOKEN=$(cat ${tokenFile}) <command>`,
|
|
11407
|
-
"Retry the failed command."
|
|
11408
|
-
].join("\n")
|
|
11660
|
+
const outcome = await refreshAndVerifyGithubCredential(
|
|
11661
|
+
config.workspaceDir,
|
|
11662
|
+
(opts) => connection.call("refreshGithubToken", {
|
|
11663
|
+
sessionId: connection.sessionId,
|
|
11664
|
+
...opts?.forceFresh ? { forceFresh: true } : {}
|
|
11665
|
+
}).then((r) => r.token)
|
|
11409
11666
|
);
|
|
11667
|
+
const { text, report } = describeRefreshOutcome(outcome, githubTokenFilePath());
|
|
11668
|
+
if (report) {
|
|
11669
|
+
connection.reportCredentialFailure({
|
|
11670
|
+
error: outcome.failures.join("\n"),
|
|
11671
|
+
...outcome.tokenShape ? { tokenShape: outcome.tokenShape } : {},
|
|
11672
|
+
healed: outcome.healAttempted
|
|
11673
|
+
});
|
|
11674
|
+
}
|
|
11675
|
+
return textResult(text);
|
|
11410
11676
|
} catch (error) {
|
|
11411
11677
|
return textResult(
|
|
11412
11678
|
`Failed to refresh the GitHub token: ${error instanceof Error ? error.message : "Unknown error"}
|
|
@@ -12893,7 +13159,7 @@ function resolvePlaywrightMcpServer(env = process.env) {
|
|
|
12893
13159
|
}
|
|
12894
13160
|
|
|
12895
13161
|
// src/execution/event-handlers.ts
|
|
12896
|
-
var
|
|
13162
|
+
var logger5 = createServiceLogger("event-handlers");
|
|
12897
13163
|
function safeVoid(promise, context) {
|
|
12898
13164
|
if (promise && typeof promise.catch === "function") {
|
|
12899
13165
|
promise.catch((err) => {
|
|
@@ -13048,7 +13314,7 @@ async function emitResultEvent(event, host, context, startTime, lastAssistantUsa
|
|
|
13048
13314
|
}
|
|
13049
13315
|
function handleRateLimitEvent(event, host) {
|
|
13050
13316
|
const { rate_limit_info } = event;
|
|
13051
|
-
|
|
13317
|
+
logger5.info("Rate limit event received", { rate_limit_info });
|
|
13052
13318
|
const status = rate_limit_info.status;
|
|
13053
13319
|
const utilization = rate_limit_info.utilization ?? (status === "rejected" ? 1 : void 0);
|
|
13054
13320
|
if (utilization !== void 0 && rate_limit_info.rateLimitType) {
|
|
@@ -13772,7 +14038,7 @@ function buildCanUseTool(host) {
|
|
|
13772
14038
|
}
|
|
13773
14039
|
|
|
13774
14040
|
// src/execution/query-executor.ts
|
|
13775
|
-
var
|
|
14041
|
+
var logger6 = createServiceLogger("QueryExecutor");
|
|
13776
14042
|
var IMAGE_ERROR_PATTERN2 = /Could not process image/i;
|
|
13777
14043
|
var RETRY_DELAYS_MS2 = [6e4, 12e4, 18e4, 3e5];
|
|
13778
14044
|
function buildHooks(host) {
|
|
@@ -13864,7 +14130,7 @@ function repairTornSessionFile(path2) {
|
|
|
13864
14130
|
}
|
|
13865
14131
|
if (keepEnd === content.length) return false;
|
|
13866
14132
|
truncateSync(path2, Buffer.byteLength(content.slice(0, keepEnd), "utf8"));
|
|
13867
|
-
|
|
14133
|
+
logger6.warn("Repaired torn transcript before resume", {
|
|
13868
14134
|
path: path2,
|
|
13869
14135
|
trimmedBytes: content.length - keepEnd
|
|
13870
14136
|
});
|
|
@@ -13945,7 +14211,7 @@ function buildQueryOptions(host, context) {
|
|
|
13945
14211
|
disallowedTools: buildDisallowedTools(settings, mode, host.hasExitedPlanMode),
|
|
13946
14212
|
enableFileCheckpointing: settings.enableFileCheckpointing,
|
|
13947
14213
|
stderr: (data) => {
|
|
13948
|
-
|
|
14214
|
+
logger6.warn("Claude Code stderr", { data: data.trimEnd() });
|
|
13949
14215
|
}
|
|
13950
14216
|
};
|
|
13951
14217
|
}
|
|
@@ -14501,7 +14767,7 @@ async function runWithRetry(initialQuery, context, host, options) {
|
|
|
14501
14767
|
}
|
|
14502
14768
|
|
|
14503
14769
|
// src/runner/query-bridge.ts
|
|
14504
|
-
var
|
|
14770
|
+
var logger7 = createServiceLogger("QueryBridge");
|
|
14505
14771
|
function resolveHarnessKind() {
|
|
14506
14772
|
if (process.env.CONVEYOR_FORCE_SDK_CARDS === "1") return "sdk";
|
|
14507
14773
|
return "pty";
|
|
@@ -14683,9 +14949,9 @@ var QueryBridge = class {
|
|
|
14683
14949
|
const msg = err instanceof Error ? err.message : String(err);
|
|
14684
14950
|
const isAbort = this._stopped || /abort/i.test(msg);
|
|
14685
14951
|
if (isAbort) {
|
|
14686
|
-
|
|
14952
|
+
logger7.info("Query stopped by user", { error: msg });
|
|
14687
14953
|
} else {
|
|
14688
|
-
|
|
14954
|
+
logger7.error("Query execution failed", { error: msg });
|
|
14689
14955
|
this.connection.sendEvent({ type: "error", message: msg });
|
|
14690
14956
|
}
|
|
14691
14957
|
} finally {
|
|
@@ -14711,9 +14977,9 @@ var QueryBridge = class {
|
|
|
14711
14977
|
const msg = err instanceof Error ? err.message : String(err);
|
|
14712
14978
|
const isAbort = this._stopped || /abort/i.test(msg);
|
|
14713
14979
|
if (isAbort) {
|
|
14714
|
-
|
|
14980
|
+
logger7.info("Passive turn stopped", { error: msg });
|
|
14715
14981
|
} else {
|
|
14716
|
-
|
|
14982
|
+
logger7.error("Passive turn failed", { error: msg });
|
|
14717
14983
|
this.connection.sendEvent({ type: "error", message: msg });
|
|
14718
14984
|
}
|
|
14719
14985
|
} finally {
|
|
@@ -15040,7 +15306,8 @@ async function runUsageProbe(deps = {}) {
|
|
|
15040
15306
|
}
|
|
15041
15307
|
|
|
15042
15308
|
// src/execution/usage-sampler.ts
|
|
15043
|
-
var
|
|
15309
|
+
var logger8 = createServiceLogger("usage-sampler");
|
|
15310
|
+
var NO_SAMPLES = { samples: [], unmeasurable: null };
|
|
15044
15311
|
function isAttributable(identity, sessionToken) {
|
|
15045
15312
|
if (!identity) return { ok: true };
|
|
15046
15313
|
if (identity.hasRefreshToken) {
|
|
@@ -15052,14 +15319,14 @@ function isAttributable(identity, sessionToken) {
|
|
|
15052
15319
|
return { ok: true };
|
|
15053
15320
|
}
|
|
15054
15321
|
async function sampleKeyUsage(token, probe = () => runUsageProbe(), hasSubscriptionCredentials = () => existsSync3(claudeCredentialsPath()), readIdentity = readCredentialsIdentity) {
|
|
15055
|
-
if (!token && !hasSubscriptionCredentials()) return
|
|
15322
|
+
if (!token && !hasSubscriptionCredentials()) return NO_SAMPLES;
|
|
15056
15323
|
try {
|
|
15057
15324
|
const attributable = isAttributable(await readIdentity(), token);
|
|
15058
15325
|
if (!attributable.ok) {
|
|
15059
|
-
|
|
15326
|
+
logger8.info("usage sample skipped \u2014 credentials not attributable to this session's key", {
|
|
15060
15327
|
reason: attributable.reason
|
|
15061
15328
|
});
|
|
15062
|
-
return [];
|
|
15329
|
+
return { samples: [], unmeasurable: { reason: attributable.reason ?? "unattributable" } };
|
|
15063
15330
|
}
|
|
15064
15331
|
const stdout = await probe();
|
|
15065
15332
|
const { sessionUsage, weeklyUsage, sessionResetsAt, weeklyResetsAt, gauges } = parseUsageGauges(stdout);
|
|
@@ -15083,18 +15350,37 @@ async function sampleKeyUsage(token, probe = () => runUsageProbe(), hasSubscript
|
|
|
15083
15350
|
});
|
|
15084
15351
|
}
|
|
15085
15352
|
if (samples.length === 0) {
|
|
15086
|
-
|
|
15353
|
+
logger8.info("usage sample produced no gauges", {
|
|
15087
15354
|
stdoutLength: stdout.length,
|
|
15088
15355
|
stdoutHead: stdout.slice(0, 200).replaceAll("\n", " ")
|
|
15089
15356
|
});
|
|
15090
15357
|
}
|
|
15091
|
-
return samples;
|
|
15358
|
+
return { samples, unmeasurable: null };
|
|
15092
15359
|
} catch (error) {
|
|
15093
|
-
|
|
15360
|
+
logger8.info("usage sample failed", {
|
|
15094
15361
|
error: error instanceof Error ? error.message : String(error)
|
|
15095
15362
|
});
|
|
15096
|
-
return
|
|
15097
|
-
}
|
|
15363
|
+
return NO_SAMPLES;
|
|
15364
|
+
}
|
|
15365
|
+
}
|
|
15366
|
+
function buildRateLimitEvents(samples, codingAgentKeyId) {
|
|
15367
|
+
return samples.map((sample) => ({
|
|
15368
|
+
type: "rate_limit_update",
|
|
15369
|
+
rateLimitType: sample.rateLimitType,
|
|
15370
|
+
utilization: sample.utilization,
|
|
15371
|
+
status: sample.status,
|
|
15372
|
+
resetsAt: sample.resetsAt ?? void 0,
|
|
15373
|
+
gauges: sample.gauges,
|
|
15374
|
+
...codingAgentKeyId ? { codingAgentKeyId } : {}
|
|
15375
|
+
}));
|
|
15376
|
+
}
|
|
15377
|
+
function buildUnmeasurableEvent(reason, codingAgentKeyId) {
|
|
15378
|
+
return {
|
|
15379
|
+
type: "rate_limit_update",
|
|
15380
|
+
unmeasurable: true,
|
|
15381
|
+
reason,
|
|
15382
|
+
...codingAgentKeyId ? { codingAgentKeyId } : {}
|
|
15383
|
+
};
|
|
15098
15384
|
}
|
|
15099
15385
|
|
|
15100
15386
|
// src/setup/git-ready.ts
|
|
@@ -16243,12 +16529,9 @@ var SessionRunner = class _SessionRunner {
|
|
|
16243
16529
|
/** Shared token-refresh closure for the git-flush call sites (periodic
|
|
16244
16530
|
* backstop + per-turn flush). Never throws — returns undefined so the
|
|
16245
16531
|
* caller falls back to its existing remote credentials. */
|
|
16246
|
-
async refreshGithubTokenForFlush() {
|
|
16532
|
+
async refreshGithubTokenForFlush(opts) {
|
|
16247
16533
|
try {
|
|
16248
|
-
|
|
16249
|
-
sessionId: this.connection.sessionId
|
|
16250
|
-
});
|
|
16251
|
-
return res.token;
|
|
16534
|
+
return await this.mintGithubToken(opts);
|
|
16252
16535
|
} catch {
|
|
16253
16536
|
return void 0;
|
|
16254
16537
|
}
|
|
@@ -16263,7 +16546,7 @@ var SessionRunner = class _SessionRunner {
|
|
|
16263
16546
|
try {
|
|
16264
16547
|
await flushPendingChanges(this.config.workspaceDir, {
|
|
16265
16548
|
wipMessage,
|
|
16266
|
-
refreshToken: () => this.refreshGithubTokenForFlush()
|
|
16549
|
+
refreshToken: (opts) => this.refreshGithubTokenForFlush(opts)
|
|
16267
16550
|
});
|
|
16268
16551
|
} catch {
|
|
16269
16552
|
} finally {
|
|
@@ -16288,7 +16571,7 @@ var SessionRunner = class _SessionRunner {
|
|
|
16288
16571
|
try {
|
|
16289
16572
|
const result = await flushAllPendingWork(this.config.workspaceDir, {
|
|
16290
16573
|
wipMessage: "WIP: periodic auto-commit",
|
|
16291
|
-
refreshToken: () => this.refreshGithubTokenForFlush()
|
|
16574
|
+
refreshToken: (opts) => this.refreshGithubTokenForFlush(opts)
|
|
16292
16575
|
});
|
|
16293
16576
|
if (result.hadWork) {
|
|
16294
16577
|
process.stderr.write(
|
|
@@ -16303,22 +16586,20 @@ var SessionRunner = class _SessionRunner {
|
|
|
16303
16586
|
}
|
|
16304
16587
|
/** Sample the running Claude subscription key's rate-limit utilization from
|
|
16305
16588
|
* the Claude CLI `/usage` command and report it as `rate_limit_update` events.
|
|
16306
|
-
* The API (`persistRateLimitSnapshot`) attributes them to the key
|
|
16307
|
-
*
|
|
16589
|
+
* The API (`persistRateLimitSnapshot`) attributes them to the key named on the
|
|
16590
|
+
* event, keeping User Settings + the PtY-tab usage widget fresh and
|
|
16308
16591
|
* `selectBestKey` rotation honest. Best-effort — never throws, no-op when the
|
|
16309
16592
|
* pod has no OAuth token (e.g. API-key projects). */
|
|
16310
16593
|
async sampleAndReportKeyUsage() {
|
|
16311
16594
|
if (this.stopped) return;
|
|
16312
|
-
const
|
|
16313
|
-
|
|
16314
|
-
|
|
16315
|
-
|
|
16316
|
-
|
|
16317
|
-
|
|
16318
|
-
|
|
16319
|
-
|
|
16320
|
-
gauges: sample.gauges
|
|
16321
|
-
});
|
|
16595
|
+
const codingAgentKeyId = process.env.CONVEYOR_CODING_AGENT_KEY_ID;
|
|
16596
|
+
const { samples, unmeasurable } = await sampleKeyUsage(process.env.CLAUDE_CODE_OAUTH_TOKEN);
|
|
16597
|
+
if (unmeasurable) {
|
|
16598
|
+
this.connection.sendEvent(buildUnmeasurableEvent(unmeasurable.reason, codingAgentKeyId));
|
|
16599
|
+
return;
|
|
16600
|
+
}
|
|
16601
|
+
for (const event of buildRateLimitEvents(samples, codingAgentKeyId)) {
|
|
16602
|
+
this.connection.sendEvent(event);
|
|
16322
16603
|
}
|
|
16323
16604
|
}
|
|
16324
16605
|
/** Wait (bounded) for any in-flight periodic/turn flush to release the shared
|
|
@@ -16347,16 +16628,7 @@ var SessionRunner = class _SessionRunner {
|
|
|
16347
16628
|
try {
|
|
16348
16629
|
const result = await flushAllPendingWork(this.config.workspaceDir, {
|
|
16349
16630
|
wipMessage: "WIP: auto-commit on conveyor-agent shutdown",
|
|
16350
|
-
refreshToken:
|
|
16351
|
-
try {
|
|
16352
|
-
const res = await this.connection.call("refreshGithubToken", {
|
|
16353
|
-
sessionId: this.connection.sessionId
|
|
16354
|
-
});
|
|
16355
|
-
return res.token;
|
|
16356
|
-
} catch {
|
|
16357
|
-
return void 0;
|
|
16358
|
-
}
|
|
16359
|
-
}
|
|
16631
|
+
refreshToken: (opts) => this.refreshGithubTokenForFlush(opts)
|
|
16360
16632
|
});
|
|
16361
16633
|
if (result.hadWork) {
|
|
16362
16634
|
process.stderr.write(
|
|
@@ -16537,22 +16809,42 @@ var SessionRunner = class _SessionRunner {
|
|
|
16537
16809
|
void handlePullBranch(this.config.workspaceDir, branch);
|
|
16538
16810
|
});
|
|
16539
16811
|
}
|
|
16540
|
-
/**
|
|
16812
|
+
/**
|
|
16813
|
+
* Proactively refresh the GitHub token before the 1-hour expiry — and PROVE
|
|
16814
|
+
* the result works.
|
|
16815
|
+
*
|
|
16816
|
+
* The old version logged "Proactively refreshed GitHub token" whenever the
|
|
16817
|
+
* credential store write succeeded. It never inspected the token-file write
|
|
16818
|
+
* and never asked git anything, so a pod whose token file was stale printed
|
|
16819
|
+
* that line every 45 minutes while every push 401'd. Three of those lines,
|
|
16820
|
+
* perfectly spaced, are what made the 2026-08-19 occurrence unreadable. A
|
|
16821
|
+
* success line here now means the credential authenticated against origin.
|
|
16822
|
+
*/
|
|
16541
16823
|
async refreshGithubToken() {
|
|
16542
16824
|
try {
|
|
16543
|
-
const
|
|
16544
|
-
|
|
16545
|
-
|
|
16546
|
-
|
|
16547
|
-
|
|
16548
|
-
process.env.GH_TOKEN = res.token;
|
|
16549
|
-
if (written.credential.ok) {
|
|
16550
|
-
process.stderr.write("[conveyor-agent] Proactively refreshed GitHub token\n");
|
|
16551
|
-
} else {
|
|
16825
|
+
const outcome = await refreshAndVerifyGithubCredential(
|
|
16826
|
+
this.config.workspaceDir,
|
|
16827
|
+
(opts) => this.mintGithubToken(opts)
|
|
16828
|
+
);
|
|
16829
|
+
if (outcome.ok) {
|
|
16552
16830
|
process.stderr.write(
|
|
16553
|
-
`[conveyor-agent]
|
|
16831
|
+
`[conveyor-agent] Proactively refreshed GitHub token \u2014 verified against origin${outcome.healAttempted ? " (after a force-fresh retry)" : ""}
|
|
16554
16832
|
`
|
|
16555
16833
|
);
|
|
16834
|
+
return;
|
|
16835
|
+
}
|
|
16836
|
+
process.stderr.write(
|
|
16837
|
+
`[conveyor-agent] Warning: proactive GitHub token refresh did not produce a working credential:
|
|
16838
|
+
${outcome.failures.join("\n")}
|
|
16839
|
+
` + (outcome.tokenShape ? ` token file: ${outcome.tokenShape}
|
|
16840
|
+
` : "")
|
|
16841
|
+
);
|
|
16842
|
+
if (outcome.probe.outcome === "denied") {
|
|
16843
|
+
this.connection.reportCredentialFailure({
|
|
16844
|
+
error: outcome.failures.join("\n"),
|
|
16845
|
+
...outcome.tokenShape ? { tokenShape: outcome.tokenShape } : {},
|
|
16846
|
+
healed: outcome.healAttempted
|
|
16847
|
+
});
|
|
16556
16848
|
}
|
|
16557
16849
|
} catch (err) {
|
|
16558
16850
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -16560,6 +16852,14 @@ var SessionRunner = class _SessionRunner {
|
|
|
16560
16852
|
`);
|
|
16561
16853
|
}
|
|
16562
16854
|
}
|
|
16855
|
+
/** Mint a GitHub token over the RPC. `forceFresh` bypasses the API's cache. */
|
|
16856
|
+
async mintGithubToken(opts) {
|
|
16857
|
+
const res = await this.connection.call("refreshGithubToken", {
|
|
16858
|
+
sessionId: this.connection.sessionId,
|
|
16859
|
+
...opts?.forceFresh ? { forceFresh: true } : {}
|
|
16860
|
+
});
|
|
16861
|
+
return res.token;
|
|
16862
|
+
}
|
|
16563
16863
|
/** Re-fetch task context to pick up a newly created branch and check it out. */
|
|
16564
16864
|
async refreshBranchForBuilding() {
|
|
16565
16865
|
try {
|
|
@@ -16732,6 +17032,7 @@ function unshallowRepo(workspaceDir) {
|
|
|
16732
17032
|
export {
|
|
16733
17033
|
fetchBootstrap,
|
|
16734
17034
|
applyBootstrapToEnv,
|
|
17035
|
+
createServiceLogger,
|
|
16735
17036
|
AgentConnection,
|
|
16736
17037
|
DEFAULT_SONNET_MODEL,
|
|
16737
17038
|
isPermissionDeniedError,
|
|
@@ -16739,7 +17040,6 @@ export {
|
|
|
16739
17040
|
Lifecycle,
|
|
16740
17041
|
buildSynthesizedCredentials,
|
|
16741
17042
|
claudeJsonPath,
|
|
16742
|
-
createServiceLogger,
|
|
16743
17043
|
PtyHarness,
|
|
16744
17044
|
resolveTuiKindFromEnv,
|
|
16745
17045
|
resolveTuiAdapter,
|
|
@@ -16747,11 +17047,11 @@ export {
|
|
|
16747
17047
|
statWorkspacePath,
|
|
16748
17048
|
workspacePathExists,
|
|
16749
17049
|
GIT_TIMEOUT_MS,
|
|
17050
|
+
updateRemoteToken,
|
|
16750
17051
|
hasUncommittedChanges,
|
|
16751
17052
|
getCurrentBranch,
|
|
16752
17053
|
hasUnpushedCommits,
|
|
16753
17054
|
stageAndCommit,
|
|
16754
|
-
updateRemoteToken,
|
|
16755
17055
|
flushPendingChanges,
|
|
16756
17056
|
pushToOrigin,
|
|
16757
17057
|
buildProjectTools,
|
|
@@ -16760,6 +17060,8 @@ export {
|
|
|
16760
17060
|
parseUsageGauges,
|
|
16761
17061
|
runUsageProbe,
|
|
16762
17062
|
sampleKeyUsage,
|
|
17063
|
+
buildRateLimitEvents,
|
|
17064
|
+
buildUnmeasurableEvent,
|
|
16763
17065
|
awaitGitReady,
|
|
16764
17066
|
PortDiscovery,
|
|
16765
17067
|
CodespacePortVisibility,
|
|
@@ -16769,4 +17071,3 @@ export {
|
|
|
16769
17071
|
loadConveyorConfig,
|
|
16770
17072
|
unshallowRepo
|
|
16771
17073
|
};
|
|
16772
|
-
//# sourceMappingURL=chunk-BAQ2OAKB.js.map
|