@rallycry/conveyor-agent 10.13.65 → 10.13.67
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-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/{chunk-BAQ2OAKB.js → chunk-XPZPR6NS.js} +591 -216
- 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 +2 -2
- 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(),
|
|
@@ -2298,6 +2435,13 @@ var DeleteSubtaskRequestSchema = z4.object({
|
|
|
2298
2435
|
sessionId: z4.string(),
|
|
2299
2436
|
subtaskId: z4.string()
|
|
2300
2437
|
});
|
|
2438
|
+
var SetSubtaskParentRequestSchema = z4.object({
|
|
2439
|
+
sessionId: z4.string(),
|
|
2440
|
+
taskId: z4.string().min(1),
|
|
2441
|
+
detach: z4.boolean().optional(),
|
|
2442
|
+
ordinal: z4.number().int().nonnegative().optional(),
|
|
2443
|
+
followParentStatus: z4.boolean().optional()
|
|
2444
|
+
});
|
|
2301
2445
|
var GetTaskPropertiesRequestSchema = z4.object({
|
|
2302
2446
|
sessionId: z4.string()
|
|
2303
2447
|
});
|
|
@@ -2481,7 +2625,14 @@ var EmitAgentEventRequestSchema = z4.object({
|
|
|
2481
2625
|
events: z4.array(AgentEventSchema).max(500)
|
|
2482
2626
|
});
|
|
2483
2627
|
var RefreshGithubTokenRequestSchema = z4.object({
|
|
2484
|
-
sessionId: z4.string()
|
|
2628
|
+
sessionId: z4.string(),
|
|
2629
|
+
forceFresh: z4.boolean().optional()
|
|
2630
|
+
});
|
|
2631
|
+
var ReportCredentialFailureRequestSchema = z4.object({
|
|
2632
|
+
sessionId: z4.string(),
|
|
2633
|
+
error: z4.string().max(2e3).optional(),
|
|
2634
|
+
tokenShape: z4.string().max(500).optional(),
|
|
2635
|
+
healed: z4.boolean().optional()
|
|
2485
2636
|
});
|
|
2486
2637
|
var ReportReviewSpawnFailureRequestSchema = z4.object({
|
|
2487
2638
|
sessionId: z4.string(),
|
|
@@ -2621,6 +2772,18 @@ var SessionStopResponseSchema = z4.object({
|
|
|
2621
2772
|
var DeleteSubtaskResponseSchema = z4.object({
|
|
2622
2773
|
deleted: z4.boolean()
|
|
2623
2774
|
});
|
|
2775
|
+
var GIT_BRANCH_NAME_MAX = 255;
|
|
2776
|
+
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'";
|
|
2777
|
+
var ALLOWED_REF = /^[A-Za-z0-9][A-Za-z0-9._/-]*$/;
|
|
2778
|
+
function isValidGitBranchName(name) {
|
|
2779
|
+
if (typeof name !== "string") return false;
|
|
2780
|
+
if (name.length === 0 || name.length > GIT_BRANCH_NAME_MAX) return false;
|
|
2781
|
+
if (!ALLOWED_REF.test(name)) return false;
|
|
2782
|
+
if (name.includes("..") || name.includes("@{") || name.includes("//")) return false;
|
|
2783
|
+
if (name.endsWith("/") || name.endsWith("-")) return false;
|
|
2784
|
+
if (name.endsWith(".") || name.endsWith(".lock")) return false;
|
|
2785
|
+
return true;
|
|
2786
|
+
}
|
|
2624
2787
|
var cardDescription2 = z5.string().max(CARD_DESCRIPTION_MAX, CARD_DESCRIPTION_LIMIT_MESSAGE).optional();
|
|
2625
2788
|
var ListAccessibleProjectsRequestSchema = z5.object({
|
|
2626
2789
|
pageSize: z5.number().int().positive().max(100).optional().default(100)
|
|
@@ -2737,6 +2900,16 @@ var CreateProjectTaskRequestSchema = z5.object({
|
|
|
2737
2900
|
subProjectId: z5.string().nullable().optional(),
|
|
2738
2901
|
requestingUserId: z5.string().optional()
|
|
2739
2902
|
});
|
|
2903
|
+
var SetProjectTaskParentRequestSchema = z5.object({
|
|
2904
|
+
projectId: z5.string(),
|
|
2905
|
+
/** Card to move — id or slug. */
|
|
2906
|
+
taskId: z5.string().min(1),
|
|
2907
|
+
/** New parent (id or slug), or null to detach. */
|
|
2908
|
+
parentTaskId: z5.string().min(1).nullable(),
|
|
2909
|
+
ordinal: z5.number().int().nonnegative().optional(),
|
|
2910
|
+
followParentStatus: z5.boolean().optional(),
|
|
2911
|
+
requestingUserId: z5.string().optional()
|
|
2912
|
+
}).strict();
|
|
2740
2913
|
var UpdateProjectTaskRequestSchema = z5.object({
|
|
2741
2914
|
projectId: z5.string(),
|
|
2742
2915
|
taskId: z5.string(),
|
|
@@ -2758,11 +2931,17 @@ var UpdateProjectTaskRequestSchema = z5.object({
|
|
|
2758
2931
|
// Move to a different sub-project board, or null to move to the parent board.
|
|
2759
2932
|
// Validated to belong to `projectId` in the handler.
|
|
2760
2933
|
subProjectId: z5.string().nullable().optional(),
|
|
2934
|
+
// Record the task's ACTUAL working branch (e.g. a locally-driven pack's
|
|
2935
|
+
// branch, so identification never mints a competing name and pack-child
|
|
2936
|
+
// merge handling matches reality), or null to detach. Guarded in the
|
|
2937
|
+
// handler: the ref must exist on origin and no live workspace may be bound
|
|
2938
|
+
// to a different branch.
|
|
2939
|
+
githubBranch: z5.string().min(1).max(GIT_BRANCH_NAME_MAX).refine(isValidGitBranchName, { message: GIT_BRANCH_NAME_MESSAGE }).nullable().optional(),
|
|
2761
2940
|
requestingUserId: z5.string().optional()
|
|
2762
2941
|
}).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,
|
|
2942
|
+
(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
2943
|
{
|
|
2765
|
-
message: "update_task requires at least one field to change (title, description, plan, status, risk, storyPointValue, assignedUserId, or
|
|
2944
|
+
message: "update_task requires at least one field to change (title, description, plan, status, risk, storyPointValue, assignedUserId, subProjectId, or githubBranch)"
|
|
2766
2945
|
}
|
|
2767
2946
|
);
|
|
2768
2947
|
var TransitionProjectTaskStatusRequestSchema = z5.object({
|
|
@@ -3306,7 +3485,7 @@ var AGENT_CHAT_HISTORY_FETCH_LIMIT = Math.max(TASK_CHAT_HISTORY_LIMIT, PM_CHAT_H
|
|
|
3306
3485
|
function formatModelId(provider, model) {
|
|
3307
3486
|
return `${provider}/${model}`;
|
|
3308
3487
|
}
|
|
3309
|
-
function anthropicEntry(model, label, inputPerMillion, outputPerMillion,
|
|
3488
|
+
function anthropicEntry(model, label, inputPerMillion, outputPerMillion, opts = {}) {
|
|
3310
3489
|
return {
|
|
3311
3490
|
provider: "anthropic",
|
|
3312
3491
|
model,
|
|
@@ -3316,7 +3495,8 @@ function anthropicEntry(model, label, inputPerMillion, outputPerMillion, experim
|
|
|
3316
3495
|
inputPrice: inputPerMillion / 1e6,
|
|
3317
3496
|
outputPrice: outputPerMillion / 1e6,
|
|
3318
3497
|
supportsTools: true,
|
|
3319
|
-
|
|
3498
|
+
supportsEffort: opts.supportsEffort ?? true,
|
|
3499
|
+
...opts.experimental ? { experimental: true } : {}
|
|
3320
3500
|
};
|
|
3321
3501
|
}
|
|
3322
3502
|
var ANTHROPIC_CATALOG = [
|
|
@@ -3324,8 +3504,9 @@ var ANTHROPIC_CATALOG = [
|
|
|
3324
3504
|
anthropicEntry(PREVIOUS_OPUS_MODEL, "Opus 4.8", 5, 25),
|
|
3325
3505
|
anthropicEntry(DEFAULT_SONNET_MODEL, "Sonnet 5 Latest", 3, 15),
|
|
3326
3506
|
anthropicEntry(PREVIOUS_SONNET_MODEL, "Sonnet 4.6", 3, 15),
|
|
3327
|
-
|
|
3328
|
-
anthropicEntry(
|
|
3507
|
+
// The Haiku line (4.5 and older) predates the tuning surface and 400s on it.
|
|
3508
|
+
anthropicEntry(DEFAULT_HAIKU_MODEL, "Haiku 4.5", 1, 5, { supportsEffort: false }),
|
|
3509
|
+
anthropicEntry(FABLE_MODEL, "Fable 5 (experimental)", 10, 50, { experimental: true })
|
|
3329
3510
|
];
|
|
3330
3511
|
var HUMAN_PROSE_WRITING_STYLE = `## Writing style for humans
|
|
3331
3512
|
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 +7559,7 @@ function findOnPath(binary, env = process.env) {
|
|
|
7378
7559
|
import { promises as fs } from "fs";
|
|
7379
7560
|
import { dirname as dirname2, join as join9 } from "path";
|
|
7380
7561
|
import { homedir as homedir4 } from "os";
|
|
7381
|
-
var
|
|
7562
|
+
var logger2 = createServiceLogger("opencode-auth");
|
|
7382
7563
|
var OPENCODE_CODEX_PLUGIN = "opencode-openai-codex-auth@4.4.0";
|
|
7383
7564
|
var PLUGIN_PACKAGE = "opencode-openai-codex-auth";
|
|
7384
7565
|
function opencodeAuthPath(env) {
|
|
@@ -7425,7 +7606,7 @@ async function ensureAuthEntry(env, seed) {
|
|
|
7425
7606
|
const path2 = opencodeAuthPath(env);
|
|
7426
7607
|
const store = await readJsonFile(path2);
|
|
7427
7608
|
if (!shouldSeed(store.openai, seed)) {
|
|
7428
|
-
|
|
7609
|
+
logger2.info("opencode oauth store is fresher than the seed; leaving it alone");
|
|
7429
7610
|
return;
|
|
7430
7611
|
}
|
|
7431
7612
|
store.openai = {
|
|
@@ -7435,7 +7616,7 @@ async function ensureAuthEntry(env, seed) {
|
|
|
7435
7616
|
expires: seed.expires
|
|
7436
7617
|
};
|
|
7437
7618
|
await writeJsonFile(path2, store);
|
|
7438
|
-
|
|
7619
|
+
logger2.info("seeded opencode oauth store entry");
|
|
7439
7620
|
}
|
|
7440
7621
|
async function ensurePluginConfig(env) {
|
|
7441
7622
|
const path2 = opencodeConfigPath(env);
|
|
@@ -7448,7 +7629,7 @@ async function ensurePluginConfig(env) {
|
|
|
7448
7629
|
const kept = plugins.filter((p) => !isOurs(p));
|
|
7449
7630
|
config.plugin = [...kept, OPENCODE_CODEX_PLUGIN];
|
|
7450
7631
|
await writeJsonFile(path2, config);
|
|
7451
|
-
|
|
7632
|
+
logger2.info("ensured opencode codex-auth plugin in config");
|
|
7452
7633
|
}
|
|
7453
7634
|
async function seedOpenCodeOauth(env) {
|
|
7454
7635
|
const seed = parseOauthSeed(env.CONVEYOR_OPENCODE_OAUTH);
|
|
@@ -7457,7 +7638,7 @@ async function seedOpenCodeOauth(env) {
|
|
|
7457
7638
|
await ensureAuthEntry(env, seed);
|
|
7458
7639
|
await ensurePluginConfig(env);
|
|
7459
7640
|
} catch (err) {
|
|
7460
|
-
|
|
7641
|
+
logger2.warn(
|
|
7461
7642
|
`failed to seed opencode oauth store: ${err instanceof Error ? err.message : String(err)}`
|
|
7462
7643
|
);
|
|
7463
7644
|
}
|
|
@@ -7826,14 +8007,14 @@ function resolveTuiAdapter(kind = "claude-code") {
|
|
|
7826
8007
|
|
|
7827
8008
|
// src/harness/pty/stream-server.ts
|
|
7828
8009
|
import net from "net";
|
|
7829
|
-
var
|
|
8010
|
+
var logger3 = createServiceLogger("PtyStreamServer");
|
|
7830
8011
|
var RING_MAX_CHARS = 256 * 1024;
|
|
7831
8012
|
var PtyStreamServer = class {
|
|
7832
8013
|
constructor(options) {
|
|
7833
8014
|
this.options = options;
|
|
7834
8015
|
this.server = net.createServer((socket) => this.handleConnection(socket));
|
|
7835
8016
|
this.server.on("error", (err) => {
|
|
7836
|
-
|
|
8017
|
+
logger3.warn(`PTY stream server error: ${err.message}`);
|
|
7837
8018
|
});
|
|
7838
8019
|
}
|
|
7839
8020
|
options;
|
|
@@ -7874,7 +8055,7 @@ var PtyStreamServer = class {
|
|
|
7874
8055
|
return port;
|
|
7875
8056
|
}
|
|
7876
8057
|
}
|
|
7877
|
-
|
|
8058
|
+
logger3.warn(`PTY stream server could not bind any port in ${base}..${base + attempts - 1}`);
|
|
7878
8059
|
return null;
|
|
7879
8060
|
}
|
|
7880
8061
|
tryListen(port) {
|
|
@@ -7995,7 +8176,7 @@ var PtyStreamServer = class {
|
|
|
7995
8176
|
};
|
|
7996
8177
|
|
|
7997
8178
|
// src/harness/pty/direct-stream.ts
|
|
7998
|
-
var
|
|
8179
|
+
var logger4 = createServiceLogger("PtyDirectStream");
|
|
7999
8180
|
var RELAY_COALESCE_MS = 2e3;
|
|
8000
8181
|
var RELAY_MAX_BUFFER_CHARS = 48 * 1024;
|
|
8001
8182
|
var DirectStreamController = class {
|
|
@@ -8044,7 +8225,7 @@ var DirectStreamController = class {
|
|
|
8044
8225
|
});
|
|
8045
8226
|
void created.listen().then((port) => this.onListening(created, port)).catch((err) => {
|
|
8046
8227
|
this.starting = false;
|
|
8047
|
-
|
|
8228
|
+
logger4.warn(
|
|
8048
8229
|
`PTY stream server failed to start: ${err instanceof Error ? err.message : String(err)}`
|
|
8049
8230
|
);
|
|
8050
8231
|
});
|
|
@@ -8057,7 +8238,7 @@ var DirectStreamController = class {
|
|
|
8057
8238
|
}
|
|
8058
8239
|
this.server = created;
|
|
8059
8240
|
this.reporter.reportPtyStream(port);
|
|
8060
|
-
|
|
8241
|
+
logger4.info(`PTY stream server listening on ${port} (session ${this.reporter.sessionId})`);
|
|
8061
8242
|
}
|
|
8062
8243
|
/** Push the min box across both transports to the pty. */
|
|
8063
8244
|
applyDims() {
|
|
@@ -10083,7 +10264,10 @@ var getTaskContract = defineToolContract({
|
|
|
10083
10264
|
fields: {
|
|
10084
10265
|
projectId: mcpProjectId,
|
|
10085
10266
|
taskId: f.string({
|
|
10086
|
-
|
|
10267
|
+
// The path form is MCP-only on purpose: an in-pod agent is bound to one
|
|
10268
|
+
// project, where a bare slug already resolves, so the `agent` half of
|
|
10269
|
+
// this contract deliberately says nothing about it.
|
|
10270
|
+
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
10271
|
})
|
|
10088
10272
|
}
|
|
10089
10273
|
}
|
|
@@ -10625,11 +10809,46 @@ var listSubtasksContract = defineToolContract({
|
|
|
10625
10809
|
}
|
|
10626
10810
|
}
|
|
10627
10811
|
});
|
|
10812
|
+
var SET_PARENT_ORDINAL = "Position among the new parent's children. Defaults to after every existing sibling. A detached card has no siblings to order against, so it only matters when you adopt.";
|
|
10813
|
+
var SET_PARENT_WARNINGS = "The move is reported, never refused: a card that already has a pull request, live compute, a release, children of its own, or dependency edges that now cross packs comes back with a warning in the result. Its branch is NOT re-cut against the new parent's feature branch.";
|
|
10814
|
+
var setTaskParentContract = defineToolContract({
|
|
10815
|
+
name: "set_task_parent",
|
|
10816
|
+
agent: {
|
|
10817
|
+
description: `Adopt an EXISTING card into this card's pack, or eject one of this card's children. The card keeps its own chat, plan, and history \u2014 it moves under (or out from) the current card, inherits the board, and lands after the current children. To make a NEW child use create_subtask instead. ${SET_PARENT_WARNINGS}`,
|
|
10818
|
+
fields: {
|
|
10819
|
+
taskId: f.string({
|
|
10820
|
+
desc: "Id, slug, or branch name of the card to move. Must live in this project."
|
|
10821
|
+
}),
|
|
10822
|
+
detach: f.optional(
|
|
10823
|
+
f.boolean({
|
|
10824
|
+
desc: "Eject the card from this pack instead of adopting it \u2014 it becomes a standalone card and stops following the parent's status. The card must already be a child of the current card."
|
|
10825
|
+
})
|
|
10826
|
+
),
|
|
10827
|
+
ordinal: f.optional(f.number({ desc: SET_PARENT_ORDINAL })),
|
|
10828
|
+
followParentStatus: f.optional(f.boolean({ desc: AGENT_FOLLOW_PARENT_STATUS }))
|
|
10829
|
+
}
|
|
10830
|
+
},
|
|
10831
|
+
mcp: {
|
|
10832
|
+
description: `Re-parent an existing card: pass a parentTaskId to adopt it into that card's pack, or null to detach it into a standalone card. The card keeps its chat, plan, and history; adopting also moves it to the parent's board and orders it after the parent's existing children. Use create_subtask for a NEW child, and move_card to move a card to another PROJECT. Pass projectId to target a specific project; otherwise the configured default project is used. ${SET_PARENT_WARNINGS}`,
|
|
10833
|
+
fields: {
|
|
10834
|
+
projectId: mcpProjectId,
|
|
10835
|
+
taskId: f.string({ desc: "Id or slug of the card to move" }),
|
|
10836
|
+
parentTaskId: f.nullable(
|
|
10837
|
+
f.string({
|
|
10838
|
+
desc: "Id or slug of the new parent, or null to detach the card from its current parent. Must be in the same project, and can be neither the card itself nor one of its own descendants."
|
|
10839
|
+
})
|
|
10840
|
+
),
|
|
10841
|
+
ordinal: f.optional(f.number({ desc: SET_PARENT_ORDINAL })),
|
|
10842
|
+
followParentStatus: f.optional(f.boolean({ desc: MCP_FOLLOW_PARENT_STATUS }))
|
|
10843
|
+
}
|
|
10844
|
+
}
|
|
10845
|
+
});
|
|
10628
10846
|
var subtasksContracts = [
|
|
10629
10847
|
createSubtaskContract,
|
|
10630
10848
|
updateSubtaskContract,
|
|
10631
10849
|
deleteSubtaskContract,
|
|
10632
|
-
listSubtasksContract
|
|
10850
|
+
listSubtasksContract,
|
|
10851
|
+
setTaskParentContract
|
|
10633
10852
|
];
|
|
10634
10853
|
var mcpTaskIdOrSlug = f.string({ desc: "The task ID or slug" });
|
|
10635
10854
|
var listTaskFilesContract = defineToolContract({
|
|
@@ -11064,6 +11283,80 @@ function buildGetSuggestionsTool(connection) {
|
|
|
11064
11283
|
|
|
11065
11284
|
// src/tools/mutation-tools.ts
|
|
11066
11285
|
import { z as z13 } from "zod";
|
|
11286
|
+
|
|
11287
|
+
// src/runner/refresh-verify-heal.ts
|
|
11288
|
+
async function refreshAndVerifyGithubCredential(cwd, mint) {
|
|
11289
|
+
const first = await mintWriteProbe(cwd, mint, false);
|
|
11290
|
+
if (first.probe.ok) return first;
|
|
11291
|
+
if (first.probe.outcome !== "denied") return first;
|
|
11292
|
+
if (!first.written) return first;
|
|
11293
|
+
const healed = await mintWriteProbe(cwd, mint, true);
|
|
11294
|
+
return { ...healed, healAttempted: true };
|
|
11295
|
+
}
|
|
11296
|
+
async function mintWriteProbe(cwd, mint, forceFresh) {
|
|
11297
|
+
let token;
|
|
11298
|
+
try {
|
|
11299
|
+
token = await mint(forceFresh ? { forceFresh: true } : {});
|
|
11300
|
+
} catch (err) {
|
|
11301
|
+
return {
|
|
11302
|
+
ok: false,
|
|
11303
|
+
probe: { ok: false, outcome: "unavailable", error: errorText(err) },
|
|
11304
|
+
healAttempted: false,
|
|
11305
|
+
failures: [`- could not mint a GitHub token: ${errorText(err)}`]
|
|
11306
|
+
};
|
|
11307
|
+
}
|
|
11308
|
+
if (!token) {
|
|
11309
|
+
return {
|
|
11310
|
+
ok: false,
|
|
11311
|
+
probe: { ok: false, outcome: "unavailable", error: "no token returned" },
|
|
11312
|
+
healAttempted: false,
|
|
11313
|
+
failures: ["- the server returned no GitHub token"]
|
|
11314
|
+
};
|
|
11315
|
+
}
|
|
11316
|
+
const written = await updateRemoteToken(cwd, token);
|
|
11317
|
+
process.env.GITHUB_TOKEN = token;
|
|
11318
|
+
process.env.GH_TOKEN = token;
|
|
11319
|
+
const probe = await verifyGitCredential(cwd);
|
|
11320
|
+
const failures = describeFailures(written, probe);
|
|
11321
|
+
return {
|
|
11322
|
+
ok: probe.ok && failures.length === 0,
|
|
11323
|
+
probe,
|
|
11324
|
+
written,
|
|
11325
|
+
healAttempted: false,
|
|
11326
|
+
failures,
|
|
11327
|
+
...probe.ok ? {} : { tokenShape: describeTokenFile() }
|
|
11328
|
+
};
|
|
11329
|
+
}
|
|
11330
|
+
function describeFailures(written, probe) {
|
|
11331
|
+
const failures = [];
|
|
11332
|
+
const credentialError = written.credential.error ? `: ${written.credential.error}` : "";
|
|
11333
|
+
if (!written.credential.storeWritten) {
|
|
11334
|
+
failures.push(`- git credential store NOT written${credentialError}`);
|
|
11335
|
+
} else if (!written.credential.helperConfigured) {
|
|
11336
|
+
failures.push(`- git credential helper NOT configured${credentialError}`);
|
|
11337
|
+
}
|
|
11338
|
+
if (!written.files.tokenFile) {
|
|
11339
|
+
const why = written.files.errors?.tokenFile;
|
|
11340
|
+
failures.push(`- token file NOT written${why ? `: ${why}` : ""}`);
|
|
11341
|
+
}
|
|
11342
|
+
if (!written.files.ghHosts && !ghHostsExternallyOwned()) {
|
|
11343
|
+
const why = written.files.errors?.ghHosts;
|
|
11344
|
+
failures.push(`- gh CLI config NOT written${why ? `: ${why}` : ""}`);
|
|
11345
|
+
}
|
|
11346
|
+
if (probe.outcome === "denied") {
|
|
11347
|
+
failures.push(`- git could not authenticate to origin: ${probe.error ?? "unknown error"}`);
|
|
11348
|
+
} else if (probe.outcome === "timeout") {
|
|
11349
|
+
failures.push(
|
|
11350
|
+
`- 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.`
|
|
11351
|
+
);
|
|
11352
|
+
}
|
|
11353
|
+
return failures;
|
|
11354
|
+
}
|
|
11355
|
+
function errorText(err) {
|
|
11356
|
+
return err instanceof Error ? err.message : String(err);
|
|
11357
|
+
}
|
|
11358
|
+
|
|
11359
|
+
// src/tools/mutation-tools.ts
|
|
11067
11360
|
function buildPostToChatTool(connection) {
|
|
11068
11361
|
return defineContractTool(
|
|
11069
11362
|
postToChatContract,
|
|
@@ -11353,60 +11646,85 @@ function buildVoteSuggestionTool(connection) {
|
|
|
11353
11646
|
}
|
|
11354
11647
|
);
|
|
11355
11648
|
}
|
|
11649
|
+
function describeRefreshOutcome(outcome, tokenFile) {
|
|
11650
|
+
const healedNote = outcome.healAttempted ? " (the first attempt was rejected; a force-fresh retry fixed it)" : "";
|
|
11651
|
+
if (outcome.probe.ok) {
|
|
11652
|
+
if (outcome.failures.length === 0) {
|
|
11653
|
+
return {
|
|
11654
|
+
report: false,
|
|
11655
|
+
text: [
|
|
11656
|
+
`GitHub token refreshed and verified against origin${healedNote}.`,
|
|
11657
|
+
"- git: the credential store and helper are updated, so `git push` works now with no extra step.",
|
|
11658
|
+
"- gh: the CLI config is updated, so `gh` works now with no extra step.",
|
|
11659
|
+
`- shell/scripts: read the current token from ${tokenFile}.`,
|
|
11660
|
+
` For a command that needs it in the environment: GITHUB_TOKEN=$(cat ${tokenFile}) <command>`,
|
|
11661
|
+
"Retry the failed command."
|
|
11662
|
+
].join("\n")
|
|
11663
|
+
};
|
|
11664
|
+
}
|
|
11665
|
+
return {
|
|
11666
|
+
report: false,
|
|
11667
|
+
text: [
|
|
11668
|
+
`GitHub token refreshed, but some credential copies did not update.`,
|
|
11669
|
+
...outcome.failures,
|
|
11670
|
+
`A current token is available at ${tokenFile} when that file was written.`
|
|
11671
|
+
].join("\n")
|
|
11672
|
+
};
|
|
11673
|
+
}
|
|
11674
|
+
if (outcome.probe.outcome === "timeout") {
|
|
11675
|
+
return {
|
|
11676
|
+
report: false,
|
|
11677
|
+
text: [
|
|
11678
|
+
"GitHub token refreshed, but the check against origin timed out \u2014 the credential is UNPROVEN, not known bad.",
|
|
11679
|
+
...outcome.failures,
|
|
11680
|
+
"The pod is probably under load (a heavy gate). Retry your git command directly; if it succeeds, nothing is wrong."
|
|
11681
|
+
].join("\n")
|
|
11682
|
+
};
|
|
11683
|
+
}
|
|
11684
|
+
if (outcome.probe.outcome === "unavailable") {
|
|
11685
|
+
return {
|
|
11686
|
+
report: false,
|
|
11687
|
+
text: [
|
|
11688
|
+
"The server did not hand back a GitHub token, so nothing was refreshed and nothing was tested.",
|
|
11689
|
+
...outcome.failures,
|
|
11690
|
+
"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."
|
|
11691
|
+
].join("\n")
|
|
11692
|
+
};
|
|
11693
|
+
}
|
|
11694
|
+
return {
|
|
11695
|
+
report: true,
|
|
11696
|
+
text: [
|
|
11697
|
+
`GitHub token refresh did NOT produce a working git credential${outcome.healAttempted ? ", including after a force-fresh retry" : ""}.`,
|
|
11698
|
+
...outcome.failures,
|
|
11699
|
+
...outcome.tokenShape ? [`- credential in play: ${outcome.tokenShape}`] : [],
|
|
11700
|
+
"This has been reported to the card, so the team can see it.",
|
|
11701
|
+
"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`."
|
|
11702
|
+
].join("\n")
|
|
11703
|
+
};
|
|
11704
|
+
}
|
|
11356
11705
|
function buildRefreshGithubTokenTool(connection, config) {
|
|
11357
11706
|
return defineTool(
|
|
11358
11707
|
"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.",
|
|
11708
|
+
"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
11709
|
{},
|
|
11361
11710
|
async () => {
|
|
11362
11711
|
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")
|
|
11712
|
+
const outcome = await refreshAndVerifyGithubCredential(
|
|
11713
|
+
config.workspaceDir,
|
|
11714
|
+
(opts) => connection.call("refreshGithubToken", {
|
|
11715
|
+
sessionId: connection.sessionId,
|
|
11716
|
+
...opts?.forceFresh ? { forceFresh: true } : {}
|
|
11717
|
+
}).then((r) => r.token)
|
|
11409
11718
|
);
|
|
11719
|
+
const { text, report } = describeRefreshOutcome(outcome, githubTokenFilePath());
|
|
11720
|
+
if (report) {
|
|
11721
|
+
connection.reportCredentialFailure({
|
|
11722
|
+
error: outcome.failures.join("\n"),
|
|
11723
|
+
...outcome.tokenShape ? { tokenShape: outcome.tokenShape } : {},
|
|
11724
|
+
healed: outcome.healAttempted
|
|
11725
|
+
});
|
|
11726
|
+
}
|
|
11727
|
+
return textResult(text);
|
|
11410
11728
|
} catch (error) {
|
|
11411
11729
|
return textResult(
|
|
11412
11730
|
`Failed to refresh the GitHub token: ${error instanceof Error ? error.message : "Unknown error"}
|
|
@@ -11773,12 +12091,33 @@ function buildCreateSubtaskTool(connection) {
|
|
|
11773
12091
|
});
|
|
11774
12092
|
const unmatched = result.unmatchedTags ?? [];
|
|
11775
12093
|
const tagNote = unmatched.length > 0 ? ` These tag names matched no project tag and were skipped: ${unmatched.join(", ")}. Use list_tags to see the glossary.` : "";
|
|
12094
|
+
return textResult(`Subtask created with ID: ${result.id} (slug: ${result.slug})${tagNote}`);
|
|
12095
|
+
} catch (error) {
|
|
11776
12096
|
return textResult(
|
|
11777
|
-
`
|
|
12097
|
+
`Failed to create subtask: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
11778
12098
|
);
|
|
12099
|
+
}
|
|
12100
|
+
}
|
|
12101
|
+
);
|
|
12102
|
+
}
|
|
12103
|
+
function buildSetTaskParentTool(connection) {
|
|
12104
|
+
return defineContractTool(
|
|
12105
|
+
setTaskParentContract,
|
|
12106
|
+
async ({ taskId, detach, ordinal, followParentStatus }) => {
|
|
12107
|
+
try {
|
|
12108
|
+
const result = await connection.call("setSubtaskParent", {
|
|
12109
|
+
sessionId: connection.sessionId,
|
|
12110
|
+
taskId,
|
|
12111
|
+
...detach !== void 0 && { detach },
|
|
12112
|
+
...ordinal !== void 0 && { ordinal },
|
|
12113
|
+
...followParentStatus !== void 0 && { followParentStatus }
|
|
12114
|
+
});
|
|
12115
|
+
const warnings = result.warnings.length > 0 ? ` Warnings: ${result.warnings.join(", ")}.` : "";
|
|
12116
|
+
const move = detach ? `Card ${result.slug} detached \u2014 it is now a standalone card` : `Card ${result.slug} adopted into this pack`;
|
|
12117
|
+
return textResult(`${move} (status: ${result.status}).${warnings}`);
|
|
11779
12118
|
} catch (error) {
|
|
11780
12119
|
return textResult(
|
|
11781
|
-
`Failed to
|
|
12120
|
+
`Failed to set task parent: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
11782
12121
|
);
|
|
11783
12122
|
}
|
|
11784
12123
|
}
|
|
@@ -11925,6 +12264,7 @@ function buildPmTools(connection, options) {
|
|
|
11925
12264
|
const tools = [
|
|
11926
12265
|
buildUpdateTaskTool(connection),
|
|
11927
12266
|
buildCreateSubtaskTool(connection),
|
|
12267
|
+
buildSetTaskParentTool(connection),
|
|
11928
12268
|
buildUpdateSubtaskTool(connection),
|
|
11929
12269
|
buildDeleteSubtaskTool(connection),
|
|
11930
12270
|
buildListSubtasksTool(connection)
|
|
@@ -12893,7 +13233,7 @@ function resolvePlaywrightMcpServer(env = process.env) {
|
|
|
12893
13233
|
}
|
|
12894
13234
|
|
|
12895
13235
|
// src/execution/event-handlers.ts
|
|
12896
|
-
var
|
|
13236
|
+
var logger5 = createServiceLogger("event-handlers");
|
|
12897
13237
|
function safeVoid(promise, context) {
|
|
12898
13238
|
if (promise && typeof promise.catch === "function") {
|
|
12899
13239
|
promise.catch((err) => {
|
|
@@ -13048,7 +13388,7 @@ async function emitResultEvent(event, host, context, startTime, lastAssistantUsa
|
|
|
13048
13388
|
}
|
|
13049
13389
|
function handleRateLimitEvent(event, host) {
|
|
13050
13390
|
const { rate_limit_info } = event;
|
|
13051
|
-
|
|
13391
|
+
logger5.info("Rate limit event received", { rate_limit_info });
|
|
13052
13392
|
const status = rate_limit_info.status;
|
|
13053
13393
|
const utilization = rate_limit_info.utilization ?? (status === "rejected" ? 1 : void 0);
|
|
13054
13394
|
if (utilization !== void 0 && rate_limit_info.rateLimitType) {
|
|
@@ -13772,7 +14112,7 @@ function buildCanUseTool(host) {
|
|
|
13772
14112
|
}
|
|
13773
14113
|
|
|
13774
14114
|
// src/execution/query-executor.ts
|
|
13775
|
-
var
|
|
14115
|
+
var logger6 = createServiceLogger("QueryExecutor");
|
|
13776
14116
|
var IMAGE_ERROR_PATTERN2 = /Could not process image/i;
|
|
13777
14117
|
var RETRY_DELAYS_MS2 = [6e4, 12e4, 18e4, 3e5];
|
|
13778
14118
|
function buildHooks(host) {
|
|
@@ -13864,7 +14204,7 @@ function repairTornSessionFile(path2) {
|
|
|
13864
14204
|
}
|
|
13865
14205
|
if (keepEnd === content.length) return false;
|
|
13866
14206
|
truncateSync(path2, Buffer.byteLength(content.slice(0, keepEnd), "utf8"));
|
|
13867
|
-
|
|
14207
|
+
logger6.warn("Repaired torn transcript before resume", {
|
|
13868
14208
|
path: path2,
|
|
13869
14209
|
trimmedBytes: content.length - keepEnd
|
|
13870
14210
|
});
|
|
@@ -13945,7 +14285,7 @@ function buildQueryOptions(host, context) {
|
|
|
13945
14285
|
disallowedTools: buildDisallowedTools(settings, mode, host.hasExitedPlanMode),
|
|
13946
14286
|
enableFileCheckpointing: settings.enableFileCheckpointing,
|
|
13947
14287
|
stderr: (data) => {
|
|
13948
|
-
|
|
14288
|
+
logger6.warn("Claude Code stderr", { data: data.trimEnd() });
|
|
13949
14289
|
}
|
|
13950
14290
|
};
|
|
13951
14291
|
}
|
|
@@ -14501,7 +14841,7 @@ async function runWithRetry(initialQuery, context, host, options) {
|
|
|
14501
14841
|
}
|
|
14502
14842
|
|
|
14503
14843
|
// src/runner/query-bridge.ts
|
|
14504
|
-
var
|
|
14844
|
+
var logger7 = createServiceLogger("QueryBridge");
|
|
14505
14845
|
function resolveHarnessKind() {
|
|
14506
14846
|
if (process.env.CONVEYOR_FORCE_SDK_CARDS === "1") return "sdk";
|
|
14507
14847
|
return "pty";
|
|
@@ -14683,9 +15023,9 @@ var QueryBridge = class {
|
|
|
14683
15023
|
const msg = err instanceof Error ? err.message : String(err);
|
|
14684
15024
|
const isAbort = this._stopped || /abort/i.test(msg);
|
|
14685
15025
|
if (isAbort) {
|
|
14686
|
-
|
|
15026
|
+
logger7.info("Query stopped by user", { error: msg });
|
|
14687
15027
|
} else {
|
|
14688
|
-
|
|
15028
|
+
logger7.error("Query execution failed", { error: msg });
|
|
14689
15029
|
this.connection.sendEvent({ type: "error", message: msg });
|
|
14690
15030
|
}
|
|
14691
15031
|
} finally {
|
|
@@ -14711,9 +15051,9 @@ var QueryBridge = class {
|
|
|
14711
15051
|
const msg = err instanceof Error ? err.message : String(err);
|
|
14712
15052
|
const isAbort = this._stopped || /abort/i.test(msg);
|
|
14713
15053
|
if (isAbort) {
|
|
14714
|
-
|
|
15054
|
+
logger7.info("Passive turn stopped", { error: msg });
|
|
14715
15055
|
} else {
|
|
14716
|
-
|
|
15056
|
+
logger7.error("Passive turn failed", { error: msg });
|
|
14717
15057
|
this.connection.sendEvent({ type: "error", message: msg });
|
|
14718
15058
|
}
|
|
14719
15059
|
} finally {
|
|
@@ -15040,7 +15380,8 @@ async function runUsageProbe(deps = {}) {
|
|
|
15040
15380
|
}
|
|
15041
15381
|
|
|
15042
15382
|
// src/execution/usage-sampler.ts
|
|
15043
|
-
var
|
|
15383
|
+
var logger8 = createServiceLogger("usage-sampler");
|
|
15384
|
+
var NO_SAMPLES = { samples: [], unmeasurable: null };
|
|
15044
15385
|
function isAttributable(identity, sessionToken) {
|
|
15045
15386
|
if (!identity) return { ok: true };
|
|
15046
15387
|
if (identity.hasRefreshToken) {
|
|
@@ -15052,14 +15393,14 @@ function isAttributable(identity, sessionToken) {
|
|
|
15052
15393
|
return { ok: true };
|
|
15053
15394
|
}
|
|
15054
15395
|
async function sampleKeyUsage(token, probe = () => runUsageProbe(), hasSubscriptionCredentials = () => existsSync3(claudeCredentialsPath()), readIdentity = readCredentialsIdentity) {
|
|
15055
|
-
if (!token && !hasSubscriptionCredentials()) return
|
|
15396
|
+
if (!token && !hasSubscriptionCredentials()) return NO_SAMPLES;
|
|
15056
15397
|
try {
|
|
15057
15398
|
const attributable = isAttributable(await readIdentity(), token);
|
|
15058
15399
|
if (!attributable.ok) {
|
|
15059
|
-
|
|
15400
|
+
logger8.info("usage sample skipped \u2014 credentials not attributable to this session's key", {
|
|
15060
15401
|
reason: attributable.reason
|
|
15061
15402
|
});
|
|
15062
|
-
return [];
|
|
15403
|
+
return { samples: [], unmeasurable: { reason: attributable.reason ?? "unattributable" } };
|
|
15063
15404
|
}
|
|
15064
15405
|
const stdout = await probe();
|
|
15065
15406
|
const { sessionUsage, weeklyUsage, sessionResetsAt, weeklyResetsAt, gauges } = parseUsageGauges(stdout);
|
|
@@ -15083,18 +15424,37 @@ async function sampleKeyUsage(token, probe = () => runUsageProbe(), hasSubscript
|
|
|
15083
15424
|
});
|
|
15084
15425
|
}
|
|
15085
15426
|
if (samples.length === 0) {
|
|
15086
|
-
|
|
15427
|
+
logger8.info("usage sample produced no gauges", {
|
|
15087
15428
|
stdoutLength: stdout.length,
|
|
15088
15429
|
stdoutHead: stdout.slice(0, 200).replaceAll("\n", " ")
|
|
15089
15430
|
});
|
|
15090
15431
|
}
|
|
15091
|
-
return samples;
|
|
15432
|
+
return { samples, unmeasurable: null };
|
|
15092
15433
|
} catch (error) {
|
|
15093
|
-
|
|
15434
|
+
logger8.info("usage sample failed", {
|
|
15094
15435
|
error: error instanceof Error ? error.message : String(error)
|
|
15095
15436
|
});
|
|
15096
|
-
return
|
|
15097
|
-
}
|
|
15437
|
+
return NO_SAMPLES;
|
|
15438
|
+
}
|
|
15439
|
+
}
|
|
15440
|
+
function buildRateLimitEvents(samples, codingAgentKeyId) {
|
|
15441
|
+
return samples.map((sample) => ({
|
|
15442
|
+
type: "rate_limit_update",
|
|
15443
|
+
rateLimitType: sample.rateLimitType,
|
|
15444
|
+
utilization: sample.utilization,
|
|
15445
|
+
status: sample.status,
|
|
15446
|
+
resetsAt: sample.resetsAt ?? void 0,
|
|
15447
|
+
gauges: sample.gauges,
|
|
15448
|
+
...codingAgentKeyId ? { codingAgentKeyId } : {}
|
|
15449
|
+
}));
|
|
15450
|
+
}
|
|
15451
|
+
function buildUnmeasurableEvent(reason, codingAgentKeyId) {
|
|
15452
|
+
return {
|
|
15453
|
+
type: "rate_limit_update",
|
|
15454
|
+
unmeasurable: true,
|
|
15455
|
+
reason,
|
|
15456
|
+
...codingAgentKeyId ? { codingAgentKeyId } : {}
|
|
15457
|
+
};
|
|
15098
15458
|
}
|
|
15099
15459
|
|
|
15100
15460
|
// src/setup/git-ready.ts
|
|
@@ -16243,12 +16603,9 @@ var SessionRunner = class _SessionRunner {
|
|
|
16243
16603
|
/** Shared token-refresh closure for the git-flush call sites (periodic
|
|
16244
16604
|
* backstop + per-turn flush). Never throws — returns undefined so the
|
|
16245
16605
|
* caller falls back to its existing remote credentials. */
|
|
16246
|
-
async refreshGithubTokenForFlush() {
|
|
16606
|
+
async refreshGithubTokenForFlush(opts) {
|
|
16247
16607
|
try {
|
|
16248
|
-
|
|
16249
|
-
sessionId: this.connection.sessionId
|
|
16250
|
-
});
|
|
16251
|
-
return res.token;
|
|
16608
|
+
return await this.mintGithubToken(opts);
|
|
16252
16609
|
} catch {
|
|
16253
16610
|
return void 0;
|
|
16254
16611
|
}
|
|
@@ -16263,7 +16620,7 @@ var SessionRunner = class _SessionRunner {
|
|
|
16263
16620
|
try {
|
|
16264
16621
|
await flushPendingChanges(this.config.workspaceDir, {
|
|
16265
16622
|
wipMessage,
|
|
16266
|
-
refreshToken: () => this.refreshGithubTokenForFlush()
|
|
16623
|
+
refreshToken: (opts) => this.refreshGithubTokenForFlush(opts)
|
|
16267
16624
|
});
|
|
16268
16625
|
} catch {
|
|
16269
16626
|
} finally {
|
|
@@ -16288,7 +16645,7 @@ var SessionRunner = class _SessionRunner {
|
|
|
16288
16645
|
try {
|
|
16289
16646
|
const result = await flushAllPendingWork(this.config.workspaceDir, {
|
|
16290
16647
|
wipMessage: "WIP: periodic auto-commit",
|
|
16291
|
-
refreshToken: () => this.refreshGithubTokenForFlush()
|
|
16648
|
+
refreshToken: (opts) => this.refreshGithubTokenForFlush(opts)
|
|
16292
16649
|
});
|
|
16293
16650
|
if (result.hadWork) {
|
|
16294
16651
|
process.stderr.write(
|
|
@@ -16303,22 +16660,20 @@ var SessionRunner = class _SessionRunner {
|
|
|
16303
16660
|
}
|
|
16304
16661
|
/** Sample the running Claude subscription key's rate-limit utilization from
|
|
16305
16662
|
* the Claude CLI `/usage` command and report it as `rate_limit_update` events.
|
|
16306
|
-
* The API (`persistRateLimitSnapshot`) attributes them to the key
|
|
16307
|
-
*
|
|
16663
|
+
* The API (`persistRateLimitSnapshot`) attributes them to the key named on the
|
|
16664
|
+
* event, keeping User Settings + the PtY-tab usage widget fresh and
|
|
16308
16665
|
* `selectBestKey` rotation honest. Best-effort — never throws, no-op when the
|
|
16309
16666
|
* pod has no OAuth token (e.g. API-key projects). */
|
|
16310
16667
|
async sampleAndReportKeyUsage() {
|
|
16311
16668
|
if (this.stopped) return;
|
|
16312
|
-
const
|
|
16313
|
-
|
|
16314
|
-
|
|
16315
|
-
|
|
16316
|
-
|
|
16317
|
-
|
|
16318
|
-
|
|
16319
|
-
|
|
16320
|
-
gauges: sample.gauges
|
|
16321
|
-
});
|
|
16669
|
+
const codingAgentKeyId = process.env.CONVEYOR_CODING_AGENT_KEY_ID;
|
|
16670
|
+
const { samples, unmeasurable } = await sampleKeyUsage(process.env.CLAUDE_CODE_OAUTH_TOKEN);
|
|
16671
|
+
if (unmeasurable) {
|
|
16672
|
+
this.connection.sendEvent(buildUnmeasurableEvent(unmeasurable.reason, codingAgentKeyId));
|
|
16673
|
+
return;
|
|
16674
|
+
}
|
|
16675
|
+
for (const event of buildRateLimitEvents(samples, codingAgentKeyId)) {
|
|
16676
|
+
this.connection.sendEvent(event);
|
|
16322
16677
|
}
|
|
16323
16678
|
}
|
|
16324
16679
|
/** Wait (bounded) for any in-flight periodic/turn flush to release the shared
|
|
@@ -16347,16 +16702,7 @@ var SessionRunner = class _SessionRunner {
|
|
|
16347
16702
|
try {
|
|
16348
16703
|
const result = await flushAllPendingWork(this.config.workspaceDir, {
|
|
16349
16704
|
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
|
-
}
|
|
16705
|
+
refreshToken: (opts) => this.refreshGithubTokenForFlush(opts)
|
|
16360
16706
|
});
|
|
16361
16707
|
if (result.hadWork) {
|
|
16362
16708
|
process.stderr.write(
|
|
@@ -16537,22 +16883,42 @@ var SessionRunner = class _SessionRunner {
|
|
|
16537
16883
|
void handlePullBranch(this.config.workspaceDir, branch);
|
|
16538
16884
|
});
|
|
16539
16885
|
}
|
|
16540
|
-
/**
|
|
16886
|
+
/**
|
|
16887
|
+
* Proactively refresh the GitHub token before the 1-hour expiry — and PROVE
|
|
16888
|
+
* the result works.
|
|
16889
|
+
*
|
|
16890
|
+
* The old version logged "Proactively refreshed GitHub token" whenever the
|
|
16891
|
+
* credential store write succeeded. It never inspected the token-file write
|
|
16892
|
+
* and never asked git anything, so a pod whose token file was stale printed
|
|
16893
|
+
* that line every 45 minutes while every push 401'd. Three of those lines,
|
|
16894
|
+
* perfectly spaced, are what made the 2026-08-19 occurrence unreadable. A
|
|
16895
|
+
* success line here now means the credential authenticated against origin.
|
|
16896
|
+
*/
|
|
16541
16897
|
async refreshGithubToken() {
|
|
16542
16898
|
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 {
|
|
16899
|
+
const outcome = await refreshAndVerifyGithubCredential(
|
|
16900
|
+
this.config.workspaceDir,
|
|
16901
|
+
(opts) => this.mintGithubToken(opts)
|
|
16902
|
+
);
|
|
16903
|
+
if (outcome.ok) {
|
|
16552
16904
|
process.stderr.write(
|
|
16553
|
-
`[conveyor-agent]
|
|
16905
|
+
`[conveyor-agent] Proactively refreshed GitHub token \u2014 verified against origin${outcome.healAttempted ? " (after a force-fresh retry)" : ""}
|
|
16554
16906
|
`
|
|
16555
16907
|
);
|
|
16908
|
+
return;
|
|
16909
|
+
}
|
|
16910
|
+
process.stderr.write(
|
|
16911
|
+
`[conveyor-agent] Warning: proactive GitHub token refresh did not produce a working credential:
|
|
16912
|
+
${outcome.failures.join("\n")}
|
|
16913
|
+
` + (outcome.tokenShape ? ` token file: ${outcome.tokenShape}
|
|
16914
|
+
` : "")
|
|
16915
|
+
);
|
|
16916
|
+
if (outcome.probe.outcome === "denied") {
|
|
16917
|
+
this.connection.reportCredentialFailure({
|
|
16918
|
+
error: outcome.failures.join("\n"),
|
|
16919
|
+
...outcome.tokenShape ? { tokenShape: outcome.tokenShape } : {},
|
|
16920
|
+
healed: outcome.healAttempted
|
|
16921
|
+
});
|
|
16556
16922
|
}
|
|
16557
16923
|
} catch (err) {
|
|
16558
16924
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -16560,6 +16926,14 @@ var SessionRunner = class _SessionRunner {
|
|
|
16560
16926
|
`);
|
|
16561
16927
|
}
|
|
16562
16928
|
}
|
|
16929
|
+
/** Mint a GitHub token over the RPC. `forceFresh` bypasses the API's cache. */
|
|
16930
|
+
async mintGithubToken(opts) {
|
|
16931
|
+
const res = await this.connection.call("refreshGithubToken", {
|
|
16932
|
+
sessionId: this.connection.sessionId,
|
|
16933
|
+
...opts?.forceFresh ? { forceFresh: true } : {}
|
|
16934
|
+
});
|
|
16935
|
+
return res.token;
|
|
16936
|
+
}
|
|
16563
16937
|
/** Re-fetch task context to pick up a newly created branch and check it out. */
|
|
16564
16938
|
async refreshBranchForBuilding() {
|
|
16565
16939
|
try {
|
|
@@ -16732,6 +17106,7 @@ function unshallowRepo(workspaceDir) {
|
|
|
16732
17106
|
export {
|
|
16733
17107
|
fetchBootstrap,
|
|
16734
17108
|
applyBootstrapToEnv,
|
|
17109
|
+
createServiceLogger,
|
|
16735
17110
|
AgentConnection,
|
|
16736
17111
|
DEFAULT_SONNET_MODEL,
|
|
16737
17112
|
isPermissionDeniedError,
|
|
@@ -16739,7 +17114,6 @@ export {
|
|
|
16739
17114
|
Lifecycle,
|
|
16740
17115
|
buildSynthesizedCredentials,
|
|
16741
17116
|
claudeJsonPath,
|
|
16742
|
-
createServiceLogger,
|
|
16743
17117
|
PtyHarness,
|
|
16744
17118
|
resolveTuiKindFromEnv,
|
|
16745
17119
|
resolveTuiAdapter,
|
|
@@ -16747,11 +17121,11 @@ export {
|
|
|
16747
17121
|
statWorkspacePath,
|
|
16748
17122
|
workspacePathExists,
|
|
16749
17123
|
GIT_TIMEOUT_MS,
|
|
17124
|
+
updateRemoteToken,
|
|
16750
17125
|
hasUncommittedChanges,
|
|
16751
17126
|
getCurrentBranch,
|
|
16752
17127
|
hasUnpushedCommits,
|
|
16753
17128
|
stageAndCommit,
|
|
16754
|
-
updateRemoteToken,
|
|
16755
17129
|
flushPendingChanges,
|
|
16756
17130
|
pushToOrigin,
|
|
16757
17131
|
buildProjectTools,
|
|
@@ -16760,6 +17134,8 @@ export {
|
|
|
16760
17134
|
parseUsageGauges,
|
|
16761
17135
|
runUsageProbe,
|
|
16762
17136
|
sampleKeyUsage,
|
|
17137
|
+
buildRateLimitEvents,
|
|
17138
|
+
buildUnmeasurableEvent,
|
|
16763
17139
|
awaitGitReady,
|
|
16764
17140
|
PortDiscovery,
|
|
16765
17141
|
CodespacePortVisibility,
|
|
@@ -16769,4 +17145,3 @@ export {
|
|
|
16769
17145
|
loadConveyorConfig,
|
|
16770
17146
|
unshallowRepo
|
|
16771
17147
|
};
|
|
16772
|
-
//# sourceMappingURL=chunk-BAQ2OAKB.js.map
|