@rallycry/conveyor-agent 11.0.47 → 11.0.49
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-ZPXDDZDF.js → boot-GD5XMLPS.js} +75 -11
- package/dist/{chunk-F7XHSNU7.js → chunk-ATYCG6MT.js} +1 -1
- package/dist/{chunk-4A6GCJGG.js → chunk-K35YJK2U.js} +1 -1
- package/dist/{chunk-KXB55NJP.js → chunk-O3MXZHIS.js} +22 -2
- package/dist/{chunk-7Y3IY5QQ.js → chunk-RNE3JAF5.js} +337 -254
- package/dist/{chunk-EKTPDG3G.js → chunk-SXZS2R52.js} +27 -2
- package/dist/cli.js +21 -47
- package/dist/index.js +3 -3
- package/dist/{serve-boot-TPZ6CPCQ.js → serve-boot-4QBD4LW4.js} +2 -2
- package/dist/{server-GJZKUVBE.js → server-FPDS4CT5.js} +2 -2
- package/package.json +2 -2
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
buildSpawnArgs,
|
|
13
13
|
cleanTerminalOutput,
|
|
14
14
|
ensureSessionTarget,
|
|
15
|
+
flattenTerminalText,
|
|
15
16
|
inheritedEnv,
|
|
16
17
|
killPtyWithEscalation,
|
|
17
18
|
needsRawReadyGate,
|
|
@@ -30,7 +31,7 @@ import {
|
|
|
30
31
|
spawnOptionsFingerprint,
|
|
31
32
|
transcriptSize,
|
|
32
33
|
turnOptionsFrom
|
|
33
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-SXZS2R52.js";
|
|
34
35
|
import {
|
|
35
36
|
AGENT_STATUS_REASON_USER_QUESTION,
|
|
36
37
|
AgentConnection,
|
|
@@ -95,9 +96,10 @@ import {
|
|
|
95
96
|
runQueryGrafanaLogs,
|
|
96
97
|
stageAndCommit,
|
|
97
98
|
statWorkspacePath,
|
|
99
|
+
subProjectScopePaths,
|
|
98
100
|
updateRemoteToken,
|
|
99
101
|
verifyGitCredential
|
|
100
|
-
} from "./chunk-
|
|
102
|
+
} from "./chunk-O3MXZHIS.js";
|
|
101
103
|
import {
|
|
102
104
|
reportBootMilestone
|
|
103
105
|
} from "./chunk-VIUZS7V4.js";
|
|
@@ -859,6 +861,7 @@ function redact(input) {
|
|
|
859
861
|
|
|
860
862
|
// src/harness/pty/jsonl-tailer.ts
|
|
861
863
|
import { open } from "fs/promises";
|
|
864
|
+
import { StringDecoder } from "string_decoder";
|
|
862
865
|
|
|
863
866
|
// src/harness/pty/record-mapper.ts
|
|
864
867
|
function isRecord(value) {
|
|
@@ -994,7 +997,9 @@ function mapTranscriptRecord(raw) {
|
|
|
994
997
|
}
|
|
995
998
|
|
|
996
999
|
// src/harness/pty/jsonl-tailer.ts
|
|
997
|
-
var
|
|
1000
|
+
var ACTIVE_POLL_INTERVAL_MS = 100;
|
|
1001
|
+
var IDLE_POLL_INTERVAL_MS = 1e3;
|
|
1002
|
+
var logger = createServiceLogger("JsonlTailer");
|
|
998
1003
|
var JsonlTailer = class {
|
|
999
1004
|
constructor(path, onEvent, onRawRecord, mapRecord2 = mapTranscriptRecord) {
|
|
1000
1005
|
this.path = path;
|
|
@@ -1008,35 +1013,59 @@ var JsonlTailer = class {
|
|
|
1008
1013
|
mapRecord;
|
|
1009
1014
|
offset = 0;
|
|
1010
1015
|
buffer = "";
|
|
1016
|
+
decoder = new StringDecoder("utf8");
|
|
1011
1017
|
timer = null;
|
|
1012
|
-
chain = Promise.resolve();
|
|
1018
|
+
chain = Promise.resolve(false);
|
|
1019
|
+
started = false;
|
|
1020
|
+
pollIntervalMs = ACTIVE_POLL_INTERVAL_MS;
|
|
1021
|
+
readErrorLogged = false;
|
|
1013
1022
|
_closed = false;
|
|
1014
1023
|
/**
|
|
1015
1024
|
* Begin tailing. Pass `fromOffset` to skip bytes already present when the
|
|
1016
1025
|
* tail starts (used on resume so the prior session's records aren't replayed).
|
|
1017
1026
|
*/
|
|
1018
1027
|
start(fromOffset = 0) {
|
|
1019
|
-
if (this.
|
|
1028
|
+
if (this.started || this._closed) return;
|
|
1029
|
+
this.started = true;
|
|
1020
1030
|
this.offset = fromOffset;
|
|
1021
|
-
this.
|
|
1022
|
-
if (this._closed) return;
|
|
1023
|
-
void this.enqueueRead();
|
|
1024
|
-
}, POLL_INTERVAL_MS);
|
|
1031
|
+
this.scheduleRead();
|
|
1025
1032
|
}
|
|
1026
1033
|
close() {
|
|
1027
1034
|
this._closed = true;
|
|
1028
1035
|
if (this.timer) {
|
|
1029
|
-
|
|
1036
|
+
clearTimeout(this.timer);
|
|
1030
1037
|
this.timer = null;
|
|
1031
1038
|
}
|
|
1032
1039
|
}
|
|
1033
1040
|
/** Read any remaining bytes and flush a trailing line without a newline. */
|
|
1034
1041
|
async flush() {
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
this.
|
|
1038
|
-
this.buffer
|
|
1039
|
-
|
|
1042
|
+
const flushed = this.chain.then(async () => {
|
|
1043
|
+
await this.readOnce();
|
|
1044
|
+
this.buffer += this.decoder.end();
|
|
1045
|
+
if (this.buffer.length > 0) {
|
|
1046
|
+
this.emitLine(this.buffer);
|
|
1047
|
+
this.buffer = "";
|
|
1048
|
+
}
|
|
1049
|
+
return false;
|
|
1050
|
+
});
|
|
1051
|
+
this.chain = flushed;
|
|
1052
|
+
await flushed;
|
|
1053
|
+
}
|
|
1054
|
+
scheduleRead() {
|
|
1055
|
+
if (this._closed) return;
|
|
1056
|
+
this.timer = setTimeout(() => {
|
|
1057
|
+
this.timer = null;
|
|
1058
|
+
void this.enqueueRead().then(
|
|
1059
|
+
(changed) => {
|
|
1060
|
+
this.pollIntervalMs = changed ? ACTIVE_POLL_INTERVAL_MS : Math.min(this.pollIntervalMs * 2, IDLE_POLL_INTERVAL_MS);
|
|
1061
|
+
this.scheduleRead();
|
|
1062
|
+
},
|
|
1063
|
+
(error) => {
|
|
1064
|
+
logger.error("Transcript polling failed", { error: String(error) });
|
|
1065
|
+
this.close();
|
|
1066
|
+
}
|
|
1067
|
+
);
|
|
1068
|
+
}, this.pollIntervalMs);
|
|
1040
1069
|
}
|
|
1041
1070
|
enqueueRead() {
|
|
1042
1071
|
this.chain = this.chain.then(() => this.readOnce());
|
|
@@ -1044,19 +1073,42 @@ var JsonlTailer = class {
|
|
|
1044
1073
|
}
|
|
1045
1074
|
async readOnce() {
|
|
1046
1075
|
let handle = null;
|
|
1076
|
+
let chunk = null;
|
|
1047
1077
|
try {
|
|
1048
1078
|
handle = await open(this.path, "r");
|
|
1049
1079
|
const stats = await handle.stat();
|
|
1050
|
-
if (stats.size <= this.offset)
|
|
1080
|
+
if (stats.size <= this.offset) {
|
|
1081
|
+
this.readErrorLogged = false;
|
|
1082
|
+
return false;
|
|
1083
|
+
}
|
|
1051
1084
|
const length = stats.size - this.offset;
|
|
1052
1085
|
const buf = Buffer.alloc(length);
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1086
|
+
let readBytes = 0;
|
|
1087
|
+
while (readBytes < length) {
|
|
1088
|
+
const { bytesRead } = await handle.read(
|
|
1089
|
+
buf,
|
|
1090
|
+
readBytes,
|
|
1091
|
+
length - readBytes,
|
|
1092
|
+
this.offset + readBytes
|
|
1093
|
+
);
|
|
1094
|
+
if (bytesRead === 0) break;
|
|
1095
|
+
readBytes += bytesRead;
|
|
1096
|
+
}
|
|
1097
|
+
chunk = buf.subarray(0, readBytes);
|
|
1098
|
+
this.readErrorLogged = false;
|
|
1099
|
+
} catch (error) {
|
|
1100
|
+
if (!this.readErrorLogged) {
|
|
1101
|
+
logger.warn("Transcript read failed; retrying", { error: String(error) });
|
|
1102
|
+
this.readErrorLogged = true;
|
|
1103
|
+
}
|
|
1104
|
+
return false;
|
|
1057
1105
|
} finally {
|
|
1058
1106
|
if (handle) await handle.close();
|
|
1059
1107
|
}
|
|
1108
|
+
if (!chunk || chunk.length === 0) return false;
|
|
1109
|
+
this.offset += chunk.length;
|
|
1110
|
+
this.consume(this.decoder.write(chunk));
|
|
1111
|
+
return true;
|
|
1060
1112
|
}
|
|
1061
1113
|
consume(chunk) {
|
|
1062
1114
|
this.buffer += chunk;
|
|
@@ -2550,12 +2602,11 @@ function conveyorCredentialsMarkerPath() {
|
|
|
2550
2602
|
function tokenFingerprint(accessToken) {
|
|
2551
2603
|
return createHash("sha256").update(accessToken).digest("hex");
|
|
2552
2604
|
}
|
|
2553
|
-
function buildCredentialsMarker(accessToken, now, previousHash,
|
|
2605
|
+
function buildCredentialsMarker(accessToken, now, previousHash, codingAgentKeyId) {
|
|
2554
2606
|
const accessTokenSha256 = tokenFingerprint(accessToken);
|
|
2555
2607
|
return JSON.stringify({
|
|
2556
2608
|
accessTokenSha256,
|
|
2557
2609
|
...previousHash && previousHash !== accessTokenSha256 ? { previousAccessTokenSha256: previousHash } : {},
|
|
2558
|
-
...refreshToken ? { refreshTokenSha256: tokenFingerprint(refreshToken) } : {},
|
|
2559
2610
|
...codingAgentKeyId ? { codingAgentKeyId } : {},
|
|
2560
2611
|
writtenAt: now
|
|
2561
2612
|
});
|
|
@@ -2636,9 +2687,6 @@ function parseClaudeOauthEnv(blob) {
|
|
|
2636
2687
|
const scopes = Array.isArray(record.scopes) ? record.scopes.filter((scope) => typeof scope === "string" && scope !== "") : [];
|
|
2637
2688
|
return {
|
|
2638
2689
|
access: record.access,
|
|
2639
|
-
refresh: typeof record.refresh === "string" && record.refresh ? record.refresh : void 0,
|
|
2640
|
-
expires: typeof record.expires === "number" ? record.expires : void 0,
|
|
2641
|
-
refreshExpires: typeof record.refreshExpires === "number" ? record.refreshExpires : void 0,
|
|
2642
2690
|
scopes: scopes.length > 0 ? scopes : void 0,
|
|
2643
2691
|
rateLimitTier: typeof record.rateLimitTier === "string" ? record.rateLimitTier : void 0
|
|
2644
2692
|
};
|
|
@@ -2664,13 +2712,10 @@ function parseClaudeAiOauth(raw) {
|
|
|
2664
2712
|
}
|
|
2665
2713
|
}
|
|
2666
2714
|
function buildCredentialsFile(material, now) {
|
|
2667
|
-
const refresh = material.refresh;
|
|
2668
2715
|
return JSON.stringify({
|
|
2669
2716
|
claudeAiOauth: {
|
|
2670
2717
|
accessToken: material.access,
|
|
2671
|
-
|
|
2672
|
-
expiresAt: refresh && material.expires ? material.expires : now + SYNTH_TOKEN_TTL_MS,
|
|
2673
|
-
...material.refreshExpires ? { refreshTokenExpiresAt: material.refreshExpires } : {},
|
|
2718
|
+
expiresAt: now + SYNTH_TOKEN_TTL_MS,
|
|
2674
2719
|
scopes: material.scopes?.length ? material.scopes : LEGACY_SCOPES,
|
|
2675
2720
|
...material.rateLimitTier ? { rateLimitTier: material.rateLimitTier } : {},
|
|
2676
2721
|
subscriptionType: "max"
|
|
@@ -2686,19 +2731,13 @@ function isConveyorOwnedCredentials(existing, markerHashes) {
|
|
|
2686
2731
|
if (typeof existing.accessToken !== "string") return false;
|
|
2687
2732
|
return markerHashes.includes(tokenFingerprint(existing.accessToken));
|
|
2688
2733
|
}
|
|
2689
|
-
function hasStableRefreshOwnership(existing, markerRaw) {
|
|
2690
|
-
return typeof existing.refreshToken === "string" && parseCredentialsRefreshMarker(markerRaw) === tokenFingerprint(existing.refreshToken);
|
|
2691
|
-
}
|
|
2692
2734
|
function isCurrentCredential(existing, material, now) {
|
|
2693
|
-
if (
|
|
2735
|
+
if (typeof existing.refreshToken === "string" && existing.refreshToken !== "") return false;
|
|
2694
2736
|
return existing.accessToken === material.access && typeof existing.expiresAt === "number" && existing.expiresAt > now + REFRESH_SKEW_MS;
|
|
2695
2737
|
}
|
|
2696
|
-
function needsMarkerUpgrade(input
|
|
2738
|
+
function needsMarkerUpgrade(input) {
|
|
2697
2739
|
if (!input.codingAgentKeyId) return false;
|
|
2698
|
-
|
|
2699
|
-
return Boolean(
|
|
2700
|
-
material.refresh && parseCredentialsRefreshMarker(input.markerRaw ?? null) !== tokenFingerprint(material.refresh)
|
|
2701
|
-
);
|
|
2740
|
+
return parseCredentialsMarkerKeyId(input.markerRaw ?? null) !== input.codingAgentKeyId;
|
|
2702
2741
|
}
|
|
2703
2742
|
function ownsExistingCredential(existing, markerRaw, markerHashes) {
|
|
2704
2743
|
if (isConveyorOwnedCredentials(existing, markerHashes)) return true;
|
|
@@ -2706,11 +2745,6 @@ function ownsExistingCredential(existing, markerRaw, markerHashes) {
|
|
|
2706
2745
|
const refreshToken = typeof existing.refreshToken === "string" ? existing.refreshToken : null;
|
|
2707
2746
|
return credentialMarkerMatches(markerRaw, accessToken, refreshToken);
|
|
2708
2747
|
}
|
|
2709
|
-
function cliRefreshedSameKey(input, material, existing, markerRaw) {
|
|
2710
|
-
if (!material.refresh || !input.codingAgentKeyId) return false;
|
|
2711
|
-
if (parseCredentialsMarkerKeyId(markerRaw) !== input.codingAgentKeyId) return false;
|
|
2712
|
-
return hasStableRefreshOwnership(existing, markerRaw) && existing.accessToken !== material.access;
|
|
2713
|
-
}
|
|
2714
2748
|
function planCredentialsWrite(input) {
|
|
2715
2749
|
if (!input.isCloud) return { action: "skip", reason: "not-cloud" };
|
|
2716
2750
|
const material = parseClaudeOauthEnv(input.oauthBlob) ?? (input.token ? { access: input.token } : null);
|
|
@@ -2722,7 +2756,6 @@ function planCredentialsWrite(input) {
|
|
|
2722
2756
|
material.access,
|
|
2723
2757
|
input.now,
|
|
2724
2758
|
markerHashes[0] ?? null,
|
|
2725
|
-
material.refresh,
|
|
2726
2759
|
input.codingAgentKeyId
|
|
2727
2760
|
);
|
|
2728
2761
|
const existing = parseClaudeAiOauth(input.existingRaw);
|
|
@@ -2730,13 +2763,10 @@ function planCredentialsWrite(input) {
|
|
|
2730
2763
|
if (!ownsExistingCredential(existing, markerRaw, markerHashes)) {
|
|
2731
2764
|
return { action: "skip", reason: "foreign-credentials" };
|
|
2732
2765
|
}
|
|
2733
|
-
if (cliRefreshedSameKey(input, material, existing, markerRaw)) {
|
|
2734
|
-
return { action: "skip", reason: "current" };
|
|
2735
|
-
}
|
|
2736
2766
|
if (!isCurrentCredential(existing, material, input.now)) {
|
|
2737
2767
|
return { action: "write", contents, marker };
|
|
2738
2768
|
}
|
|
2739
|
-
return needsMarkerUpgrade(input
|
|
2769
|
+
return needsMarkerUpgrade(input) ? { action: "mark", marker } : { action: "skip", reason: "current" };
|
|
2740
2770
|
}
|
|
2741
2771
|
|
|
2742
2772
|
// src/harness/pty/credentials.ts
|
|
@@ -4319,6 +4349,9 @@ function isMountDisconnectError(err) {
|
|
|
4319
4349
|
const lower = message.toLowerCase();
|
|
4320
4350
|
return MOUNT_DISCONNECT_MESSAGES.some((needle) => lower.includes(needle));
|
|
4321
4351
|
}
|
|
4352
|
+
function isRequiredHomeDisconnect(err) {
|
|
4353
|
+
return process.env.CONVEYOR_USER_HOME_REQUIRED === "1" && isMountDisconnectError(err);
|
|
4354
|
+
}
|
|
4322
4355
|
function podLocalConfigHome() {
|
|
4323
4356
|
return join9(homedir3(), ".claude-local");
|
|
4324
4357
|
}
|
|
@@ -4367,6 +4400,7 @@ async function ensureUsableClaudeConfigHome(cwd, log) {
|
|
|
4367
4400
|
return { configHome, fellBack: false };
|
|
4368
4401
|
} catch (err) {
|
|
4369
4402
|
if (!isMountDisconnectError(err)) throw err;
|
|
4403
|
+
if (isRequiredHomeDisconnect(err)) throw err;
|
|
4370
4404
|
const fallback = podLocalConfigHome();
|
|
4371
4405
|
log?.warn(
|
|
4372
4406
|
"shared ~/.claude mount is unreachable; falling back to a pod-local config home. Session history will not persist across pods until the mount recovers.",
|
|
@@ -4577,7 +4611,7 @@ var PtyHarness = class _PtyHarness {
|
|
|
4577
4611
|
async parkedHomeDied(options) {
|
|
4578
4612
|
if (!this.ownsClaudeConfigHome) return false;
|
|
4579
4613
|
try {
|
|
4580
|
-
const { fellBack } = await
|
|
4614
|
+
const { fellBack } = await this.ensureConfigHome(options);
|
|
4581
4615
|
if (!fellBack) return false;
|
|
4582
4616
|
_PtyHarness.log.warn(
|
|
4583
4617
|
"shared ~/.claude mount died while the CLI was parked \u2014 respawning on the pod-local config home instead of reusing a process bound to the dead mount"
|
|
@@ -4587,7 +4621,15 @@ var PtyHarness = class _PtyHarness {
|
|
|
4587
4621
|
_PtyHarness.log.warn("parked-session config-home probe failed", {
|
|
4588
4622
|
error: err instanceof Error ? err.message : String(err)
|
|
4589
4623
|
});
|
|
4590
|
-
return
|
|
4624
|
+
return isRequiredHomeDisconnect(err);
|
|
4625
|
+
}
|
|
4626
|
+
}
|
|
4627
|
+
async ensureConfigHome(options) {
|
|
4628
|
+
try {
|
|
4629
|
+
return await ensureUsableClaudeConfigHome(options.cwd, _PtyHarness.log);
|
|
4630
|
+
} catch (err) {
|
|
4631
|
+
if (isRequiredHomeDisconnect(err)) this.escalateToPodRecycle(true);
|
|
4632
|
+
throw err;
|
|
4591
4633
|
}
|
|
4592
4634
|
}
|
|
4593
4635
|
/**
|
|
@@ -4599,7 +4641,7 @@ var PtyHarness = class _PtyHarness {
|
|
|
4599
4641
|
const session = new PtySession(prompt, options, want, this.bridge, this.adapter);
|
|
4600
4642
|
onAllocated?.(session);
|
|
4601
4643
|
if (this.ownsClaudeConfigHome) {
|
|
4602
|
-
await
|
|
4644
|
+
await this.ensureConfigHome(options);
|
|
4603
4645
|
}
|
|
4604
4646
|
await this.adapter.prepareEnvironment({ cwd: options.cwd });
|
|
4605
4647
|
if (this.ownsClaudeConfigHome) {
|
|
@@ -4671,15 +4713,13 @@ var PtyHarness = class _PtyHarness {
|
|
|
4671
4713
|
*
|
|
4672
4714
|
* Fire-and-forget: the escalation must not gate ending the failed turn.
|
|
4673
4715
|
*/
|
|
4674
|
-
escalateToPodRecycle() {
|
|
4716
|
+
escalateToPodRecycle(confirmedRequiredMountFailure = false) {
|
|
4675
4717
|
if (this.recycleRequested || !this.bridge?.requestPodRecycle) return;
|
|
4676
|
-
if (!isConfigHomeFallbackActive()) return;
|
|
4718
|
+
if (!confirmedRequiredMountFailure && !isConfigHomeFallbackActive()) return;
|
|
4677
4719
|
this.recycleRequested = true;
|
|
4678
|
-
_PtyHarness.log.error(
|
|
4679
|
-
"Prompt delivery failed with the shared ~/.claude mount dead \u2014 requesting a pod recycle"
|
|
4680
|
-
);
|
|
4720
|
+
_PtyHarness.log.error("Shared ~/.claude mount is unavailable \u2014 requesting a pod recycle");
|
|
4681
4721
|
this.bridge.requestPodRecycle(
|
|
4682
|
-
"The shared ~/.claude mount died and the Claude CLI
|
|
4722
|
+
"The shared ~/.claude mount died and the Claude CLI cannot safely continue on this pod. Only a new pod remounts it."
|
|
4683
4723
|
);
|
|
4684
4724
|
}
|
|
4685
4725
|
/**
|
|
@@ -4846,7 +4886,7 @@ function findOnPath(binary, env = process.env) {
|
|
|
4846
4886
|
import { promises as fs } from "fs";
|
|
4847
4887
|
import { dirname as dirname2, join as join11 } from "path";
|
|
4848
4888
|
import { homedir as homedir4 } from "os";
|
|
4849
|
-
var
|
|
4889
|
+
var logger2 = createServiceLogger("opencode-auth");
|
|
4850
4890
|
var OPENCODE_CODEX_PLUGIN = "opencode-openai-codex-auth@4.4.0";
|
|
4851
4891
|
var PLUGIN_PACKAGE = "opencode-openai-codex-auth";
|
|
4852
4892
|
function opencodeAuthPath(env) {
|
|
@@ -4893,7 +4933,7 @@ async function ensureAuthEntry(env, seed) {
|
|
|
4893
4933
|
const path = opencodeAuthPath(env);
|
|
4894
4934
|
const store = await readJsonFile(path);
|
|
4895
4935
|
if (!shouldSeed(store.openai, seed)) {
|
|
4896
|
-
|
|
4936
|
+
logger2.info("opencode oauth store is fresher than the seed; leaving it alone");
|
|
4897
4937
|
return;
|
|
4898
4938
|
}
|
|
4899
4939
|
store.openai = {
|
|
@@ -4903,7 +4943,7 @@ async function ensureAuthEntry(env, seed) {
|
|
|
4903
4943
|
expires: seed.expires
|
|
4904
4944
|
};
|
|
4905
4945
|
await writeJsonFile(path, store);
|
|
4906
|
-
|
|
4946
|
+
logger2.info("seeded opencode oauth store entry");
|
|
4907
4947
|
}
|
|
4908
4948
|
async function ensurePluginConfig(env) {
|
|
4909
4949
|
const path = opencodeConfigPath(env);
|
|
@@ -4916,7 +4956,7 @@ async function ensurePluginConfig(env) {
|
|
|
4916
4956
|
const kept = plugins.filter((p) => !isOurs(p));
|
|
4917
4957
|
config.plugin = [...kept, OPENCODE_CODEX_PLUGIN];
|
|
4918
4958
|
await writeJsonFile(path, config);
|
|
4919
|
-
|
|
4959
|
+
logger2.info("ensured opencode codex-auth plugin in config");
|
|
4920
4960
|
}
|
|
4921
4961
|
async function seedOpenCodeOauth(env) {
|
|
4922
4962
|
const seed = parseOauthSeed(env.CONVEYOR_OPENCODE_OAUTH);
|
|
@@ -4925,7 +4965,7 @@ async function seedOpenCodeOauth(env) {
|
|
|
4925
4965
|
await ensureAuthEntry(env, seed);
|
|
4926
4966
|
await ensurePluginConfig(env);
|
|
4927
4967
|
} catch (err) {
|
|
4928
|
-
|
|
4968
|
+
logger2.warn(
|
|
4929
4969
|
`failed to seed opencode oauth store: ${err instanceof Error ? err.message : String(err)}`
|
|
4930
4970
|
);
|
|
4931
4971
|
}
|
|
@@ -5538,14 +5578,14 @@ function resolveTuiAdapter(kind = "claude-code") {
|
|
|
5538
5578
|
|
|
5539
5579
|
// src/harness/pty/stream-server.ts
|
|
5540
5580
|
import net from "net";
|
|
5541
|
-
var
|
|
5581
|
+
var logger3 = createServiceLogger("PtyStreamServer");
|
|
5542
5582
|
var RING_MAX_CHARS = 256 * 1024;
|
|
5543
5583
|
var PtyStreamServer = class {
|
|
5544
5584
|
constructor(options) {
|
|
5545
5585
|
this.options = options;
|
|
5546
5586
|
this.server = net.createServer((socket) => this.handleConnection(socket));
|
|
5547
5587
|
this.server.on("error", (err) => {
|
|
5548
|
-
|
|
5588
|
+
logger3.warn(`PTY stream server error: ${err.message}`);
|
|
5549
5589
|
});
|
|
5550
5590
|
}
|
|
5551
5591
|
options;
|
|
@@ -5586,7 +5626,7 @@ var PtyStreamServer = class {
|
|
|
5586
5626
|
return port;
|
|
5587
5627
|
}
|
|
5588
5628
|
}
|
|
5589
|
-
|
|
5629
|
+
logger3.warn(`PTY stream server could not bind any port in ${base}..${base + attempts - 1}`);
|
|
5590
5630
|
return null;
|
|
5591
5631
|
}
|
|
5592
5632
|
tryListen(port) {
|
|
@@ -5707,7 +5747,7 @@ var PtyStreamServer = class {
|
|
|
5707
5747
|
};
|
|
5708
5748
|
|
|
5709
5749
|
// src/harness/pty/direct-stream.ts
|
|
5710
|
-
var
|
|
5750
|
+
var logger4 = createServiceLogger("PtyDirectStream");
|
|
5711
5751
|
var RELAY_COALESCE_MS = 2e3;
|
|
5712
5752
|
var RELAY_MAX_BUFFER_CHARS = 48 * 1024;
|
|
5713
5753
|
var DirectStreamController = class {
|
|
@@ -5759,7 +5799,7 @@ var DirectStreamController = class {
|
|
|
5759
5799
|
});
|
|
5760
5800
|
void created.listen().then((port) => this.onListening(created, port)).catch((err) => {
|
|
5761
5801
|
this.starting = false;
|
|
5762
|
-
|
|
5802
|
+
logger4.warn(
|
|
5763
5803
|
`PTY stream server failed to start: ${err instanceof Error ? err.message : String(err)}`
|
|
5764
5804
|
);
|
|
5765
5805
|
});
|
|
@@ -5772,7 +5812,7 @@ var DirectStreamController = class {
|
|
|
5772
5812
|
}
|
|
5773
5813
|
this.server = created;
|
|
5774
5814
|
this.reporter.reportPtyStream(port);
|
|
5775
|
-
|
|
5815
|
+
logger4.info(`PTY stream server listening on ${port} (session ${this.reporter.sessionId})`);
|
|
5776
5816
|
}
|
|
5777
5817
|
/** Push the min box across both transports to the pty. */
|
|
5778
5818
|
applyDims() {
|
|
@@ -7110,14 +7150,16 @@ ${context.projectDescription}` : "";
|
|
|
7110
7150
|
parts.push(`
|
|
7111
7151
|
## Project: ${context.projectName}${descLine}`);
|
|
7112
7152
|
}
|
|
7113
|
-
|
|
7153
|
+
const scopePaths = context.subProject ? subProjectScopePaths(context.subProject) : [];
|
|
7154
|
+
if (context.subProject && scopePaths.length > 0) {
|
|
7155
|
+
const folders = scopePaths.map((path) => `\`${path}\``).join(", ");
|
|
7114
7156
|
parts.push(
|
|
7115
7157
|
`
|
|
7116
7158
|
## Sub-project scope: ${context.subProject.name}`,
|
|
7117
|
-
`This task belongs to the "${context.subProject.name}" sub-project, which
|
|
7159
|
+
`This task belongs to the "${context.subProject.name}" sub-project, which allows changes under ${folders}.`,
|
|
7118
7160
|
`- Read and reference the whole repository freely.`,
|
|
7119
|
-
`- Substantive code changes belong under
|
|
7120
|
-
`- If the work genuinely requires substantive changes outside
|
|
7161
|
+
`- Substantive code changes belong under ${folders}. Trivial wiring elsewhere (exports, route registration) is fine when required.`,
|
|
7162
|
+
`- If the work genuinely requires substantive changes outside those folders, do NOT make them here \u2014 create a task on the parent project board (as part of a pack with this one) describing the needed change.`
|
|
7121
7163
|
);
|
|
7122
7164
|
}
|
|
7123
7165
|
if (context.description) {
|
|
@@ -7498,9 +7540,9 @@ var searchFilterFields = {
|
|
|
7498
7540
|
desc: "Only return cards with no assignee (mutually exclusive with assigneeId)"
|
|
7499
7541
|
})
|
|
7500
7542
|
),
|
|
7501
|
-
limit: f.optional(f.number({ desc: "Max results to return (default 20)" }))
|
|
7543
|
+
limit: f.optional(f.number({ desc: "Max results to return (default 20, at most 200)" }))
|
|
7502
7544
|
};
|
|
7503
|
-
var searchResultNote = "Every result lists the tags it carries, so you can see why it matched. Results are relevance-ordered: highest priority first then newest; suggestions-only queries rank by upvote score. Returns summaries \u2014 plan omitted, description truncated; use get_task for full details.";
|
|
7545
|
+
var searchResultNote = "Every result lists the tags it carries, so you can see why it matched. Searches every status, Complete and Cancelled included, unless statusFilters narrows it. Returns at most 200 results. Results are relevance-ordered: highest priority first then newest; suggestions-only queries rank by upvote score. Returns summaries \u2014 plan omitted, description truncated; use get_task for full details.";
|
|
7504
7546
|
var searchTasksContract = defineToolContract({
|
|
7505
7547
|
name: "search_tasks",
|
|
7506
7548
|
agent: {
|
|
@@ -12437,7 +12479,7 @@ function resolvePlaywrightMcpServer(env = process.env) {
|
|
|
12437
12479
|
}
|
|
12438
12480
|
|
|
12439
12481
|
// src/execution/event-handlers.ts
|
|
12440
|
-
var
|
|
12482
|
+
var logger5 = createServiceLogger("event-handlers");
|
|
12441
12483
|
function safeVoid(promise, context) {
|
|
12442
12484
|
if (promise && typeof promise.catch === "function") {
|
|
12443
12485
|
promise.catch((err) => {
|
|
@@ -12639,7 +12681,7 @@ async function emitResultEvent(event, host, context, startTime, lastAssistantUsa
|
|
|
12639
12681
|
}
|
|
12640
12682
|
function handleRateLimitEvent(event, host) {
|
|
12641
12683
|
const { rate_limit_info } = event;
|
|
12642
|
-
|
|
12684
|
+
logger5.info("Rate limit event received", { rate_limit_info });
|
|
12643
12685
|
const status = rate_limit_info.status;
|
|
12644
12686
|
const resetsAt = epochSecondsToISO(rate_limit_info.resetsAt);
|
|
12645
12687
|
const utilization = rate_limit_info.utilization ?? (status === "rejected" ? 1 : void 0);
|
|
@@ -13418,7 +13460,7 @@ function buildCanUseTool(host) {
|
|
|
13418
13460
|
}
|
|
13419
13461
|
|
|
13420
13462
|
// src/execution/query-executor.ts
|
|
13421
|
-
var
|
|
13463
|
+
var logger6 = createServiceLogger("QueryExecutor");
|
|
13422
13464
|
var IMAGE_ERROR_PATTERN2 = /Could not process image/i;
|
|
13423
13465
|
var RETRY_DELAYS_MS = [6e4, 12e4, 18e4, 3e5];
|
|
13424
13466
|
function buildHooks(host) {
|
|
@@ -13503,7 +13545,7 @@ function resolveSessionStart(lineageKey, cwd, tui = process.env.CONVEYOR_TUI) {
|
|
|
13503
13545
|
repairTornSessionFile(path);
|
|
13504
13546
|
if (isResumableTranscript(path)) return { resume: sessionUuid };
|
|
13505
13547
|
rotateSessionFile(cwd, sessionUuid);
|
|
13506
|
-
|
|
13548
|
+
logger6.warn("Rotated a transcript with no conversation aside before resume", { path });
|
|
13507
13549
|
}
|
|
13508
13550
|
markSpawnedTranscript(cwd, sessionUuid);
|
|
13509
13551
|
return { sessionId: sessionUuid };
|
|
@@ -13562,7 +13604,7 @@ function repairTornSessionFile(path) {
|
|
|
13562
13604
|
}
|
|
13563
13605
|
if (keepEnd === content.length) return false;
|
|
13564
13606
|
truncateSync(path, Buffer.byteLength(content.slice(0, keepEnd), "utf8"));
|
|
13565
|
-
|
|
13607
|
+
logger6.warn("Repaired torn transcript before resume", {
|
|
13566
13608
|
path,
|
|
13567
13609
|
trimmedBytes: content.length - keepEnd
|
|
13568
13610
|
});
|
|
@@ -13649,7 +13691,7 @@ function buildQueryOptions(host, context) {
|
|
|
13649
13691
|
disallowedTools: buildDisallowedTools(settings, mode, host.hasExitedPlanMode),
|
|
13650
13692
|
enableFileCheckpointing: settings.enableFileCheckpointing,
|
|
13651
13693
|
stderr: (data) => {
|
|
13652
|
-
|
|
13694
|
+
logger6.warn("Claude Code stderr", { data: data.trimEnd() });
|
|
13653
13695
|
}
|
|
13654
13696
|
};
|
|
13655
13697
|
}
|
|
@@ -14055,7 +14097,7 @@ function rotateSessionFile(cwd, sessionUuid) {
|
|
|
14055
14097
|
renameSync2(path, `${path.replace(/\.jsonl$/, "")}.aborted-${Date.now()}.jsonl`);
|
|
14056
14098
|
return true;
|
|
14057
14099
|
} catch (error) {
|
|
14058
|
-
|
|
14100
|
+
logger6.warn("Could not rotate an aborted transcript aside", {
|
|
14059
14101
|
path,
|
|
14060
14102
|
error: error instanceof Error ? error.message : String(error)
|
|
14061
14103
|
});
|
|
@@ -14119,7 +14161,7 @@ async function handleAuthError(context, host, options) {
|
|
|
14119
14161
|
async function handleStaleSession(context, host, options) {
|
|
14120
14162
|
context.claudeSessionId = null;
|
|
14121
14163
|
host.connection.storeSessionId("");
|
|
14122
|
-
|
|
14164
|
+
logger6.warn("Resume target had no resumable conversation; rotating it aside and starting fresh", {
|
|
14123
14165
|
taskId: context.taskId,
|
|
14124
14166
|
runnerMode: host.config.mode,
|
|
14125
14167
|
agentMode: host.agentMode
|
|
@@ -14309,7 +14351,7 @@ async function runWithRetry(initialQuery, context, host, options) {
|
|
|
14309
14351
|
}
|
|
14310
14352
|
|
|
14311
14353
|
// src/runner/query-bridge.ts
|
|
14312
|
-
var
|
|
14354
|
+
var logger7 = createServiceLogger("QueryBridge");
|
|
14313
14355
|
function resolveHarnessKind() {
|
|
14314
14356
|
if (process.env.CONVEYOR_FORCE_SDK_CARDS === "1") return "sdk";
|
|
14315
14357
|
return "pty";
|
|
@@ -14439,7 +14481,7 @@ var QueryBridge = class {
|
|
|
14439
14481
|
this.freshPrewarmedInitial = warmed;
|
|
14440
14482
|
return warmed;
|
|
14441
14483
|
} catch (err) {
|
|
14442
|
-
|
|
14484
|
+
logger7.warn("TUI prewarm failed; continuing with normal startup", {
|
|
14443
14485
|
error: err instanceof Error ? err.message : String(err)
|
|
14444
14486
|
});
|
|
14445
14487
|
return false;
|
|
@@ -14524,9 +14566,9 @@ var QueryBridge = class {
|
|
|
14524
14566
|
const msg = err instanceof Error ? err.message : String(err);
|
|
14525
14567
|
const isAbort = this._stopped || /abort/i.test(msg);
|
|
14526
14568
|
if (isAbort) {
|
|
14527
|
-
|
|
14569
|
+
logger7.info("Query stopped by user", { error: msg });
|
|
14528
14570
|
} else {
|
|
14529
|
-
|
|
14571
|
+
logger7.error("Query execution failed", { error: msg });
|
|
14530
14572
|
this.connection.sendEvent({ type: "error", message: msg });
|
|
14531
14573
|
}
|
|
14532
14574
|
} finally {
|
|
@@ -14552,9 +14594,9 @@ var QueryBridge = class {
|
|
|
14552
14594
|
const msg = err instanceof Error ? err.message : String(err);
|
|
14553
14595
|
const isAbort = this._stopped || /abort/i.test(msg);
|
|
14554
14596
|
if (isAbort) {
|
|
14555
|
-
|
|
14597
|
+
logger7.info("Passive turn stopped", { error: msg });
|
|
14556
14598
|
} else {
|
|
14557
|
-
|
|
14599
|
+
logger7.error("Passive turn failed", { error: msg });
|
|
14558
14600
|
this.connection.sendEvent({ type: "error", message: msg });
|
|
14559
14601
|
}
|
|
14560
14602
|
} finally {
|
|
@@ -14678,127 +14720,12 @@ var QueryBridge = class {
|
|
|
14678
14720
|
// src/execution/usage-sampler.ts
|
|
14679
14721
|
import { existsSync as existsSync2 } from "fs";
|
|
14680
14722
|
|
|
14681
|
-
// src/usage/
|
|
14682
|
-
|
|
14683
|
-
|
|
14684
|
-
const dtf = new Intl.DateTimeFormat("en-US", {
|
|
14685
|
-
timeZone: tz,
|
|
14686
|
-
hour12: false,
|
|
14687
|
-
year: "numeric",
|
|
14688
|
-
month: "2-digit",
|
|
14689
|
-
day: "2-digit",
|
|
14690
|
-
hour: "2-digit",
|
|
14691
|
-
minute: "2-digit",
|
|
14692
|
-
second: "2-digit"
|
|
14693
|
-
});
|
|
14694
|
-
const map = {};
|
|
14695
|
-
for (const part of dtf.formatToParts(new Date(utcMs))) {
|
|
14696
|
-
if (part.type !== "literal") map[part.type] = part.value;
|
|
14697
|
-
}
|
|
14698
|
-
const hour = map.hour === "24" ? 0 : Number(map.hour);
|
|
14699
|
-
const asUtc = Date.UTC(
|
|
14700
|
-
Number(map.year),
|
|
14701
|
-
Number(map.month) - 1,
|
|
14702
|
-
Number(map.day),
|
|
14703
|
-
hour,
|
|
14704
|
-
Number(map.minute),
|
|
14705
|
-
Number(map.second)
|
|
14706
|
-
);
|
|
14707
|
-
return asUtc - utcMs;
|
|
14708
|
-
}
|
|
14709
|
-
function zonedWallClockToUtc(y, mo, d, h, mi, tz) {
|
|
14710
|
-
if (tz === "UTC" || tz === "Etc/UTC") return Date.UTC(y, mo, d, h, mi);
|
|
14711
|
-
const naiveUtc = Date.UTC(y, mo, d, h, mi);
|
|
14712
|
-
const firstGuess = naiveUtc - tzOffsetMs(naiveUtc, tz);
|
|
14713
|
-
return naiveUtc - tzOffsetMs(firstGuess, tz);
|
|
14714
|
-
}
|
|
14715
|
-
function localTimeZone() {
|
|
14716
|
-
try {
|
|
14717
|
-
return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
14718
|
-
} catch {
|
|
14719
|
-
return "UTC";
|
|
14720
|
-
}
|
|
14721
|
-
}
|
|
14722
|
-
function parseResetInstant(rowText, now, defaultTz = localTimeZone()) {
|
|
14723
|
-
const m = /resets\s+(?:by\s+)?(?:([a-z]{3})\s+(\d{1,2})(?:,|\s+at)?\s+)?(\d{1,2})(?::(\d{2}))?\s*(am|pm)(?:\s*\(([^)]+)\))?/i.exec(
|
|
14724
|
-
rowText
|
|
14725
|
-
);
|
|
14726
|
-
if (!m) return null;
|
|
14727
|
-
const [, monStr, dayStr, hourStr, minStr, meridiem, tzRaw] = m;
|
|
14728
|
-
const tz = tzRaw ? tzRaw.trim() : defaultTz;
|
|
14729
|
-
let hour = Number(hourStr) % 12;
|
|
14730
|
-
if (/pm/i.test(meridiem)) hour += 12;
|
|
14731
|
-
const minute = minStr ? Number(minStr) : 0;
|
|
14732
|
-
const nowDate = new Date(now);
|
|
14733
|
-
try {
|
|
14734
|
-
if (monStr && dayStr) {
|
|
14735
|
-
const month = MONTHS.indexOf(monStr.toLowerCase());
|
|
14736
|
-
if (month < 0) return null;
|
|
14737
|
-
const day = Number(dayStr);
|
|
14738
|
-
let utc2 = zonedWallClockToUtc(nowDate.getUTCFullYear(), month, day, hour, minute, tz);
|
|
14739
|
-
if (utc2 < now - 24 * 60 * 60 * 1e3) {
|
|
14740
|
-
utc2 = zonedWallClockToUtc(nowDate.getUTCFullYear() + 1, month, day, hour, minute, tz);
|
|
14741
|
-
}
|
|
14742
|
-
return new Date(utc2).toISOString();
|
|
14743
|
-
}
|
|
14744
|
-
let utc = zonedWallClockToUtc(
|
|
14745
|
-
nowDate.getUTCFullYear(),
|
|
14746
|
-
nowDate.getUTCMonth(),
|
|
14747
|
-
nowDate.getUTCDate(),
|
|
14748
|
-
hour,
|
|
14749
|
-
minute,
|
|
14750
|
-
tz
|
|
14751
|
-
);
|
|
14752
|
-
if (utc <= now) {
|
|
14753
|
-
utc = zonedWallClockToUtc(
|
|
14754
|
-
nowDate.getUTCFullYear(),
|
|
14755
|
-
nowDate.getUTCMonth(),
|
|
14756
|
-
nowDate.getUTCDate() + 1,
|
|
14757
|
-
hour,
|
|
14758
|
-
minute,
|
|
14759
|
-
tz
|
|
14760
|
-
);
|
|
14761
|
-
}
|
|
14762
|
-
return new Date(utc).toISOString();
|
|
14763
|
-
} catch {
|
|
14764
|
-
return null;
|
|
14765
|
-
}
|
|
14766
|
-
}
|
|
14767
|
-
|
|
14768
|
-
// src/usage/parse-usage.ts
|
|
14769
|
-
var ESC = "\\u001b";
|
|
14770
|
-
var ANSI_CSI = new RegExp(`${ESC}\\[[0-9;?]*[ -/]*[@-~]`, "g");
|
|
14771
|
-
var ANSI_OSC = new RegExp(`${ESC}\\][^\\u0007${ESC}]*(?:\\u0007|${ESC}\\\\)`, "g");
|
|
14772
|
-
var BAR_GLYPHS = /[─-▟]/g;
|
|
14773
|
-
function normalizeUsageText(stdout) {
|
|
14774
|
-
return stdout.replace(ANSI_CSI, "").replace(ANSI_OSC, "").replace(BAR_GLYPHS, " ").replace(/\r/g, "");
|
|
14775
|
-
}
|
|
14776
|
-
function parseUsageGauges(stdout, now = Date.now(), defaultTz) {
|
|
14777
|
-
const text = normalizeUsageText(stdout);
|
|
14778
|
-
const rows = [
|
|
14779
|
-
...text.matchAll(
|
|
14780
|
-
/Current (session|week)\s*(\([^)\n]*\))?[^%\n]*?(\d+(?:\.\d+)?)\s*%(?:\s*used)?([^\n]*?)(?=Current\s+(?:session|week)\b|\n|$)/gi
|
|
14781
|
-
)
|
|
14782
|
-
];
|
|
14783
|
-
const gauges = rows.map((m) => ({
|
|
14784
|
-
label: `Current ${m[1].toLowerCase()}${m[2] ? ` ${m[2]}` : ""}`,
|
|
14785
|
-
utilization: Number(m[3]) / 100,
|
|
14786
|
-
resetsAt: parseResetInstant(m[4] ?? "", now, defaultTz)
|
|
14787
|
-
}));
|
|
14788
|
-
const session = gauges.find((g) => g.label.startsWith("Current session"));
|
|
14789
|
-
const weekly = gauges.filter((g) => g.label.startsWith("Current week"));
|
|
14790
|
-
const weeklyResets = weekly.map((g) => g.resetsAt).filter((r) => r !== null);
|
|
14791
|
-
return {
|
|
14792
|
-
sessionUsage: session ? session.utilization : null,
|
|
14793
|
-
weeklyUsage: weekly.length ? Math.max(...weekly.map((g) => g.utilization)) : null,
|
|
14794
|
-
sessionResetsAt: session?.resetsAt ?? null,
|
|
14795
|
-
// Soonest weekly reset (all weekly rows share one instant in practice).
|
|
14796
|
-
weeklyResetsAt: weeklyResets.length ? new Date(Math.min(...weeklyResets.map((r) => new Date(r).getTime()))).toISOString() : null,
|
|
14797
|
-
gauges
|
|
14798
|
-
};
|
|
14799
|
-
}
|
|
14723
|
+
// src/usage/isolated-probe.ts
|
|
14724
|
+
import { mkdtemp as mkdtemp3, writeFile as writeFile10, copyFile, rm as rm5 } from "fs/promises";
|
|
14725
|
+
import { join as join21 } from "path";
|
|
14800
14726
|
|
|
14801
14727
|
// src/usage/run-probe.ts
|
|
14728
|
+
var logger8 = createServiceLogger("usage-probe");
|
|
14802
14729
|
var PROBE_TIMEOUT_MS = 3e4;
|
|
14803
14730
|
var FIRST_SEND_MS = 5e3;
|
|
14804
14731
|
var RESEND_INTERVAL_MS = 4e3;
|
|
@@ -14815,7 +14742,8 @@ function buildProbeEnv(env = process.env) {
|
|
|
14815
14742
|
return clean;
|
|
14816
14743
|
}
|
|
14817
14744
|
function panelRendering(buf) {
|
|
14818
|
-
|
|
14745
|
+
const text = flattenTerminalText(buf);
|
|
14746
|
+
return /Current\s*session/i.test(text) && /%/.test(text);
|
|
14819
14747
|
}
|
|
14820
14748
|
var UsageProbeRun = class {
|
|
14821
14749
|
constructor(resolve, timing) {
|
|
@@ -14839,7 +14767,7 @@ var UsageProbeRun = class {
|
|
|
14839
14767
|
this.childExited = true;
|
|
14840
14768
|
this.finishBestEffort();
|
|
14841
14769
|
});
|
|
14842
|
-
this.hardTimer = setTimeout(() => this.
|
|
14770
|
+
this.hardTimer = setTimeout(() => this.finishOnTimeout(), timeoutMs);
|
|
14843
14771
|
this.hardTimer.unref?.();
|
|
14844
14772
|
const first = setTimeout(() => this.trySend(), this.timing.firstSendMs);
|
|
14845
14773
|
first.unref?.();
|
|
@@ -14859,6 +14787,15 @@ var UsageProbeRun = class {
|
|
|
14859
14787
|
if (this.settleTimer || !panelRendering(this.buf)) return;
|
|
14860
14788
|
this.settleTimer = setTimeout(() => this.finish(this.buf), this.timing.settleMs);
|
|
14861
14789
|
}
|
|
14790
|
+
finishOnTimeout() {
|
|
14791
|
+
if (!this.settled && !panelRendering(this.buf)) {
|
|
14792
|
+
logger8.info("usage probe timed out before the panel rendered", {
|
|
14793
|
+
bufferLength: this.buf.length,
|
|
14794
|
+
sends: this.sends
|
|
14795
|
+
});
|
|
14796
|
+
}
|
|
14797
|
+
this.finishBestEffort();
|
|
14798
|
+
}
|
|
14862
14799
|
finishBestEffort() {
|
|
14863
14800
|
this.finish(panelRendering(this.buf) ? this.buf : cleanTerminalOutput(this.buf, 500));
|
|
14864
14801
|
}
|
|
@@ -14914,62 +14851,210 @@ async function runUsageProbe(deps = {}) {
|
|
|
14914
14851
|
});
|
|
14915
14852
|
}
|
|
14916
14853
|
|
|
14854
|
+
// src/usage/isolated-probe.ts
|
|
14855
|
+
var logger9 = createServiceLogger("isolated-probe");
|
|
14856
|
+
async function probeUsageIsolated(token, opts = {}) {
|
|
14857
|
+
let dir = null;
|
|
14858
|
+
try {
|
|
14859
|
+
dir = await mkdtemp3(join21(sessionTempBase(), "conveyor-usage-"));
|
|
14860
|
+
const credentials = buildSynthesizedCredentials(token, opts.now ?? Date.now());
|
|
14861
|
+
await writeFile10(join21(dir, ".credentials.json"), credentials, {
|
|
14862
|
+
encoding: "utf8",
|
|
14863
|
+
mode: 384
|
|
14864
|
+
});
|
|
14865
|
+
await copyFile(claudeJsonPath(), join21(dir, ".claude.json")).catch(() => {
|
|
14866
|
+
});
|
|
14867
|
+
return await runUsageProbe({ cwd: opts.cwd, env: { ...process.env, CLAUDE_CONFIG_DIR: dir } });
|
|
14868
|
+
} catch (error) {
|
|
14869
|
+
logger9.info("isolated usage probe failed", {
|
|
14870
|
+
error: error instanceof Error ? error.message : String(error)
|
|
14871
|
+
});
|
|
14872
|
+
return "";
|
|
14873
|
+
} finally {
|
|
14874
|
+
if (dir) await rm5(dir, { recursive: true, force: true }).catch(() => {
|
|
14875
|
+
});
|
|
14876
|
+
}
|
|
14877
|
+
}
|
|
14878
|
+
|
|
14879
|
+
// src/usage/reset-parse.ts
|
|
14880
|
+
var MONTHS = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"];
|
|
14881
|
+
function tzOffsetMs(utcMs, tz) {
|
|
14882
|
+
const dtf = new Intl.DateTimeFormat("en-US", {
|
|
14883
|
+
timeZone: tz,
|
|
14884
|
+
hour12: false,
|
|
14885
|
+
year: "numeric",
|
|
14886
|
+
month: "2-digit",
|
|
14887
|
+
day: "2-digit",
|
|
14888
|
+
hour: "2-digit",
|
|
14889
|
+
minute: "2-digit",
|
|
14890
|
+
second: "2-digit"
|
|
14891
|
+
});
|
|
14892
|
+
const map = {};
|
|
14893
|
+
for (const part of dtf.formatToParts(new Date(utcMs))) {
|
|
14894
|
+
if (part.type !== "literal") map[part.type] = part.value;
|
|
14895
|
+
}
|
|
14896
|
+
const hour = map.hour === "24" ? 0 : Number(map.hour);
|
|
14897
|
+
const asUtc = Date.UTC(
|
|
14898
|
+
Number(map.year),
|
|
14899
|
+
Number(map.month) - 1,
|
|
14900
|
+
Number(map.day),
|
|
14901
|
+
hour,
|
|
14902
|
+
Number(map.minute),
|
|
14903
|
+
Number(map.second)
|
|
14904
|
+
);
|
|
14905
|
+
return asUtc - utcMs;
|
|
14906
|
+
}
|
|
14907
|
+
function zonedWallClockToUtc(y, mo, d, h, mi, tz) {
|
|
14908
|
+
if (tz === "UTC" || tz === "Etc/UTC") return Date.UTC(y, mo, d, h, mi);
|
|
14909
|
+
const naiveUtc = Date.UTC(y, mo, d, h, mi);
|
|
14910
|
+
const firstGuess = naiveUtc - tzOffsetMs(naiveUtc, tz);
|
|
14911
|
+
return naiveUtc - tzOffsetMs(firstGuess, tz);
|
|
14912
|
+
}
|
|
14913
|
+
function localTimeZone() {
|
|
14914
|
+
try {
|
|
14915
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
14916
|
+
} catch {
|
|
14917
|
+
return "UTC";
|
|
14918
|
+
}
|
|
14919
|
+
}
|
|
14920
|
+
function parseResetInstant(rowText, now, defaultTz = localTimeZone()) {
|
|
14921
|
+
const m = /resets\s*(?:by\s*)?(?:([a-z]{3})\s*(\d{1,2})(?:,|\s*at)?\s*)?(\d{1,2})(?::(\d{2}))?\s*(am|pm)(?:\s*\(([^)]+)\))?/i.exec(
|
|
14922
|
+
rowText
|
|
14923
|
+
);
|
|
14924
|
+
if (!m) return null;
|
|
14925
|
+
const [, monStr, dayStr, hourStr, minStr, meridiem, tzRaw] = m;
|
|
14926
|
+
const tz = tzRaw ? tzRaw.trim() : defaultTz;
|
|
14927
|
+
let hour = Number(hourStr) % 12;
|
|
14928
|
+
if (/pm/i.test(meridiem)) hour += 12;
|
|
14929
|
+
const minute = minStr ? Number(minStr) : 0;
|
|
14930
|
+
const nowDate = new Date(now);
|
|
14931
|
+
try {
|
|
14932
|
+
if (monStr && dayStr) {
|
|
14933
|
+
const month = MONTHS.indexOf(monStr.toLowerCase());
|
|
14934
|
+
if (month < 0) return null;
|
|
14935
|
+
const day = Number(dayStr);
|
|
14936
|
+
let utc2 = zonedWallClockToUtc(nowDate.getUTCFullYear(), month, day, hour, minute, tz);
|
|
14937
|
+
if (utc2 < now - 24 * 60 * 60 * 1e3) {
|
|
14938
|
+
utc2 = zonedWallClockToUtc(nowDate.getUTCFullYear() + 1, month, day, hour, minute, tz);
|
|
14939
|
+
}
|
|
14940
|
+
return new Date(utc2).toISOString();
|
|
14941
|
+
}
|
|
14942
|
+
let utc = zonedWallClockToUtc(
|
|
14943
|
+
nowDate.getUTCFullYear(),
|
|
14944
|
+
nowDate.getUTCMonth(),
|
|
14945
|
+
nowDate.getUTCDate(),
|
|
14946
|
+
hour,
|
|
14947
|
+
minute,
|
|
14948
|
+
tz
|
|
14949
|
+
);
|
|
14950
|
+
if (utc <= now) {
|
|
14951
|
+
utc = zonedWallClockToUtc(
|
|
14952
|
+
nowDate.getUTCFullYear(),
|
|
14953
|
+
nowDate.getUTCMonth(),
|
|
14954
|
+
nowDate.getUTCDate() + 1,
|
|
14955
|
+
hour,
|
|
14956
|
+
minute,
|
|
14957
|
+
tz
|
|
14958
|
+
);
|
|
14959
|
+
}
|
|
14960
|
+
return new Date(utc).toISOString();
|
|
14961
|
+
} catch {
|
|
14962
|
+
return null;
|
|
14963
|
+
}
|
|
14964
|
+
}
|
|
14965
|
+
|
|
14966
|
+
// src/usage/parse-usage.ts
|
|
14967
|
+
var BAR_GLYPHS = /[─-▟]/g;
|
|
14968
|
+
function normalizeUsageText(stdout) {
|
|
14969
|
+
return flattenTerminalText(stdout).replace(BAR_GLYPHS, " ").replace(/[\r\n]+/g, " ");
|
|
14970
|
+
}
|
|
14971
|
+
function parseUsageGauges(stdout, now = Date.now(), defaultTz) {
|
|
14972
|
+
const text = normalizeUsageText(stdout);
|
|
14973
|
+
const rows = [
|
|
14974
|
+
...text.matchAll(
|
|
14975
|
+
/Current\s*(session|week)\s*(\([^)\n]*\))?[^%\n]*?(\d+(?:\.\d+)?)\s*%(?:\s*used)?([^\n]*?)(?=Current\s*(?:session|week)|\n|$)/gi
|
|
14976
|
+
)
|
|
14977
|
+
];
|
|
14978
|
+
const gauges = rows.map((m) => ({
|
|
14979
|
+
label: `Current ${m[1].toLowerCase()}${m[2] ? ` ${m[2]}` : ""}`,
|
|
14980
|
+
utilization: Number(m[3]) / 100,
|
|
14981
|
+
resetsAt: parseResetInstant(m[4] ?? "", now, defaultTz)
|
|
14982
|
+
}));
|
|
14983
|
+
const session = gauges.find((g) => g.label.startsWith("Current session"));
|
|
14984
|
+
const weekly = gauges.filter((g) => g.label.startsWith("Current week"));
|
|
14985
|
+
const weeklyResets = weekly.map((g) => g.resetsAt).filter((r) => r !== null);
|
|
14986
|
+
return {
|
|
14987
|
+
sessionUsage: session ? session.utilization : null,
|
|
14988
|
+
weeklyUsage: weekly.length ? Math.max(...weekly.map((g) => g.utilization)) : null,
|
|
14989
|
+
sessionResetsAt: session?.resetsAt ?? null,
|
|
14990
|
+
// Soonest weekly reset (all weekly rows share one instant in practice).
|
|
14991
|
+
weeklyResetsAt: weeklyResets.length ? new Date(Math.min(...weeklyResets.map((r) => new Date(r).getTime()))).toISOString() : null,
|
|
14992
|
+
gauges
|
|
14993
|
+
};
|
|
14994
|
+
}
|
|
14995
|
+
|
|
14917
14996
|
// src/execution/usage-sampler.ts
|
|
14918
|
-
var
|
|
14997
|
+
var logger10 = createServiceLogger("usage-sampler");
|
|
14919
14998
|
var NO_SAMPLES = { samples: [], unmeasurable: null };
|
|
14920
14999
|
function usesNativeUsageReporting(env = process.env) {
|
|
14921
15000
|
return env.CONVEYOR_TUI === "codex";
|
|
14922
15001
|
}
|
|
14923
|
-
function isAttributable(identity
|
|
15002
|
+
function isAttributable(identity) {
|
|
14924
15003
|
if (!identity) return { ok: true };
|
|
14925
15004
|
if (identity.hasRefreshToken && !identity.isConveyorOwned) {
|
|
14926
15005
|
return { ok: false, reason: "manual-login-credentials" };
|
|
14927
15006
|
}
|
|
14928
|
-
if (!(identity.hasRefreshToken && identity.isConveyorOwned) && sessionToken && identity.accessToken && identity.accessToken !== sessionToken) {
|
|
14929
|
-
return { ok: false, reason: "credentials-token-mismatch" };
|
|
14930
|
-
}
|
|
14931
15007
|
return { ok: true };
|
|
14932
15008
|
}
|
|
14933
|
-
|
|
14934
|
-
|
|
15009
|
+
function samplesFromPanel(stdout) {
|
|
15010
|
+
const { sessionUsage, weeklyUsage, sessionResetsAt, weeklyResetsAt, gauges } = parseUsageGauges(stdout);
|
|
15011
|
+
const samples = [];
|
|
15012
|
+
if (sessionUsage !== null) {
|
|
15013
|
+
samples.push({
|
|
15014
|
+
rateLimitType: "five_hour",
|
|
15015
|
+
utilization: sessionUsage,
|
|
15016
|
+
status: "allowed",
|
|
15017
|
+
resetsAt: sessionResetsAt,
|
|
15018
|
+
gauges
|
|
15019
|
+
});
|
|
15020
|
+
}
|
|
15021
|
+
if (weeklyUsage !== null) {
|
|
15022
|
+
samples.push({
|
|
15023
|
+
rateLimitType: "seven_day",
|
|
15024
|
+
utilization: weeklyUsage,
|
|
15025
|
+
status: "allowed",
|
|
15026
|
+
resetsAt: weeklyResetsAt,
|
|
15027
|
+
gauges
|
|
15028
|
+
});
|
|
15029
|
+
}
|
|
15030
|
+
if (samples.length === 0) {
|
|
15031
|
+
logger10.info("usage sample produced no gauges", {
|
|
15032
|
+
stdoutLength: stdout.length,
|
|
15033
|
+
stdoutHead: stdout.slice(0, 200).replaceAll("\n", " ")
|
|
15034
|
+
});
|
|
15035
|
+
}
|
|
15036
|
+
return samples;
|
|
15037
|
+
}
|
|
15038
|
+
async function sampleKeyUsage(opts = {}) {
|
|
15039
|
+
const { token, cwd } = opts;
|
|
15040
|
+
const hasCredentialsFile = opts.hasCredentialsFile ?? (() => existsSync2(claudeCredentialsPath()));
|
|
15041
|
+
if (!token && !hasCredentialsFile()) return NO_SAMPLES;
|
|
14935
15042
|
try {
|
|
14936
|
-
|
|
15043
|
+
if (token) {
|
|
15044
|
+
const probeToken = opts.probeToken ?? ((own) => probeUsageIsolated(own, { cwd }));
|
|
15045
|
+
return { samples: samplesFromPanel(await probeToken(token)), unmeasurable: null };
|
|
15046
|
+
}
|
|
15047
|
+
const attributable = isAttributable(await (opts.readIdentity ?? readCredentialsIdentity)());
|
|
14937
15048
|
if (!attributable.ok) {
|
|
14938
|
-
|
|
15049
|
+
logger10.info("usage sample skipped \u2014 credentials not attributable to this session's key", {
|
|
14939
15050
|
reason: attributable.reason
|
|
14940
15051
|
});
|
|
14941
15052
|
return { samples: [], unmeasurable: { reason: attributable.reason ?? "unattributable" } };
|
|
14942
15053
|
}
|
|
14943
|
-
const
|
|
14944
|
-
|
|
14945
|
-
const samples = [];
|
|
14946
|
-
if (sessionUsage !== null) {
|
|
14947
|
-
samples.push({
|
|
14948
|
-
rateLimitType: "five_hour",
|
|
14949
|
-
utilization: sessionUsage,
|
|
14950
|
-
status: "allowed",
|
|
14951
|
-
resetsAt: sessionResetsAt,
|
|
14952
|
-
gauges
|
|
14953
|
-
});
|
|
14954
|
-
}
|
|
14955
|
-
if (weeklyUsage !== null) {
|
|
14956
|
-
samples.push({
|
|
14957
|
-
rateLimitType: "seven_day",
|
|
14958
|
-
utilization: weeklyUsage,
|
|
14959
|
-
status: "allowed",
|
|
14960
|
-
resetsAt: weeklyResetsAt,
|
|
14961
|
-
gauges
|
|
14962
|
-
});
|
|
14963
|
-
}
|
|
14964
|
-
if (samples.length === 0) {
|
|
14965
|
-
logger7.info("usage sample produced no gauges", {
|
|
14966
|
-
stdoutLength: stdout.length,
|
|
14967
|
-
stdoutHead: stdout.slice(0, 200).replaceAll("\n", " ")
|
|
14968
|
-
});
|
|
14969
|
-
}
|
|
14970
|
-
return { samples, unmeasurable: null };
|
|
15054
|
+
const probeFile = opts.probeCredentialsFile ?? (() => runUsageProbe({ cwd }));
|
|
15055
|
+
return { samples: samplesFromPanel(await probeFile()), unmeasurable: null };
|
|
14971
15056
|
} catch (error) {
|
|
14972
|
-
|
|
15057
|
+
logger10.info("usage sample failed", {
|
|
14973
15058
|
error: error instanceof Error ? error.message : String(error)
|
|
14974
15059
|
});
|
|
14975
15060
|
return NO_SAMPLES;
|
|
@@ -16338,10 +16423,10 @@ var SessionRunner = class _SessionRunner {
|
|
|
16338
16423
|
async sampleAndReportKeyUsage() {
|
|
16339
16424
|
if (this.stopped || usesNativeUsageReporting()) return;
|
|
16340
16425
|
const codingAgentKeyId = process.env.CONVEYOR_CODING_AGENT_KEY_ID;
|
|
16341
|
-
const { samples, unmeasurable } = await sampleKeyUsage(
|
|
16342
|
-
process.env.CLAUDE_CODE_OAUTH_TOKEN,
|
|
16343
|
-
|
|
16344
|
-
);
|
|
16426
|
+
const { samples, unmeasurable } = await sampleKeyUsage({
|
|
16427
|
+
token: process.env.CLAUDE_CODE_OAUTH_TOKEN,
|
|
16428
|
+
cwd: this.config.workspaceDir
|
|
16429
|
+
});
|
|
16345
16430
|
if (unmeasurable) {
|
|
16346
16431
|
this.connection.sendEvent(buildUnmeasurableEvent(unmeasurable.reason, codingAgentKeyId));
|
|
16347
16432
|
return;
|
|
@@ -16868,8 +16953,6 @@ function unshallowRepo(workspaceDir) {
|
|
|
16868
16953
|
|
|
16869
16954
|
export {
|
|
16870
16955
|
isPermissionDeniedError,
|
|
16871
|
-
buildSynthesizedCredentials,
|
|
16872
|
-
claudeJsonPath,
|
|
16873
16956
|
PtyHarness,
|
|
16874
16957
|
resolveTuiKindFromEnv,
|
|
16875
16958
|
resolveTuiAdapter,
|
|
@@ -16878,8 +16961,8 @@ export {
|
|
|
16878
16961
|
buildProjectTools,
|
|
16879
16962
|
resolvePlaywrightMcpServer,
|
|
16880
16963
|
resolveSessionStart,
|
|
16964
|
+
probeUsageIsolated,
|
|
16881
16965
|
parseUsageGauges,
|
|
16882
|
-
runUsageProbe,
|
|
16883
16966
|
usesNativeUsageReporting,
|
|
16884
16967
|
sampleKeyUsage,
|
|
16885
16968
|
buildRateLimitEvents,
|