@rallycry/conveyor-agent 11.0.18 → 11.0.20
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-4ZNOCKFZ.js → boot-Z3EBJ7GX.js} +2 -2
- package/dist/{chunk-SR66HQKB.js → chunk-37J5MMQT.js} +1 -1
- package/dist/{chunk-WS7QRB37.js → chunk-JBGPARLG.js} +1 -1
- package/dist/{chunk-ZFHN7GO7.js → chunk-POK3QCGN.js} +465 -114
- package/dist/{chunk-GL2DIQEQ.js → chunk-Q4FQOJ7D.js} +23 -4
- package/dist/{chunk-GZRZGBIK.js → chunk-SKTNJNXQ.js} +2 -2
- package/dist/cli.js +11 -10
- package/dist/index.d.ts +18 -0
- package/dist/index.js +4 -4
- package/dist/{serve-boot-RU5BYIK4.js → serve-boot-4BUN4U44.js} +3 -3
- package/package.json +1 -1
- package/skills/conveyor-build/SKILL.md +36 -0
- package/skills/conveyor-build/references/pack-path.md +28 -7
- package/skills/conveyor-plan/SKILL.md +32 -1
- package/skills/conveyor-review/SKILL.md +28 -2
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
mapChatHistory,
|
|
3
3
|
readAgentVersion,
|
|
4
4
|
refreshSkillsAfterCheckout
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-37J5MMQT.js";
|
|
6
6
|
import {
|
|
7
7
|
MAX_BETWEEN_TURN_BUFFER,
|
|
8
8
|
MAX_DIAGNOSTIC_OUTPUT,
|
|
@@ -59,11 +59,11 @@ import {
|
|
|
59
59
|
statWorkspacePath,
|
|
60
60
|
updateRemoteToken,
|
|
61
61
|
verifyGitCredential
|
|
62
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-JBGPARLG.js";
|
|
63
63
|
import {
|
|
64
64
|
registerBootMilestoneSocketFallback,
|
|
65
65
|
reportBootMilestone
|
|
66
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-Q4FQOJ7D.js";
|
|
67
67
|
import {
|
|
68
68
|
describeTokenFile,
|
|
69
69
|
ghHostsExternallyOwned,
|
|
@@ -1416,6 +1416,7 @@ var ListProjectSessionGroupsRequestSchema = z5.object({
|
|
|
1416
1416
|
projectId: z5.string()
|
|
1417
1417
|
});
|
|
1418
1418
|
var ListMyLiveSessionsAcrossProjectsRequestSchema = z5.object({});
|
|
1419
|
+
var ListSessionGroupsAcrossProjectsRequestSchema = z5.object({});
|
|
1419
1420
|
var GetProjectAvailableTuisRequestSchema = z5.object({
|
|
1420
1421
|
projectId: z5.string()
|
|
1421
1422
|
});
|
|
@@ -2042,6 +2043,11 @@ var ANTHROPIC_CATALOG = [
|
|
|
2042
2043
|
anthropicEntry(DEFAULT_HAIKU_MODEL, "Haiku 4.5", 1, 5, { supportsEffort: false }),
|
|
2043
2044
|
anthropicEntry(FABLE_MODEL, "Fable 5.1", 10, 50)
|
|
2044
2045
|
];
|
|
2046
|
+
var CODEX_REASONING_EFFORTS = ["low", "medium", "high", "xhigh", "max"];
|
|
2047
|
+
var DEFAULT_CODEX_CODING_MODEL = "gpt-5.6-terra";
|
|
2048
|
+
function isCodexReasoningEffort(value) {
|
|
2049
|
+
return typeof value === "string" && CODEX_REASONING_EFFORTS.includes(value);
|
|
2050
|
+
}
|
|
2045
2051
|
var HUMAN_PROSE_WRITING_STYLE = `## Writing style for humans
|
|
2046
2052
|
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):
|
|
2047
2053
|
- Use active voice. Say who does what ("The API rejects the request", not "the request is rejected").
|
|
@@ -3251,6 +3257,13 @@ var JsonlTailer = class {
|
|
|
3251
3257
|
};
|
|
3252
3258
|
|
|
3253
3259
|
// src/harness/codex/event-source.ts
|
|
3260
|
+
var USAGE_CAP_PATTERN = /usage_limit_(?:reached|exceeded)|rate_limit_reached|UsageLimitReached|hit your usage limit/i;
|
|
3261
|
+
var WEEKLY_WINDOW_MINUTES = 24 * 60;
|
|
3262
|
+
function isUsageCap(error) {
|
|
3263
|
+
return [error.codex_error_info, error.code, error.type, error.message].some(
|
|
3264
|
+
(value) => typeof value === "string" && USAGE_CAP_PATTERN.test(value)
|
|
3265
|
+
);
|
|
3266
|
+
}
|
|
3254
3267
|
var CodexEventSource = class {
|
|
3255
3268
|
constructor(emit, onSession, chat) {
|
|
3256
3269
|
this.emit = emit;
|
|
@@ -3263,6 +3276,8 @@ var CodexEventSource = class {
|
|
|
3263
3276
|
sessionId = null;
|
|
3264
3277
|
completed = /* @__PURE__ */ new Set();
|
|
3265
3278
|
transcript = null;
|
|
3279
|
+
/** Last reported reading per rate-limit window, keyed by `five_hour` / `seven_day`. */
|
|
3280
|
+
windows = /* @__PURE__ */ new Map();
|
|
3266
3281
|
handleRecord(raw) {
|
|
3267
3282
|
if (!raw || typeof raw !== "object") return;
|
|
3268
3283
|
const e = raw;
|
|
@@ -3317,11 +3332,16 @@ var CodexEventSource = class {
|
|
|
3317
3332
|
if (record.type !== "event_msg" || !record.payload || typeof record.payload !== "object")
|
|
3318
3333
|
return;
|
|
3319
3334
|
const event = record.payload;
|
|
3335
|
+
if (event.type === "token_count") {
|
|
3336
|
+
this.handleTokenCount(event);
|
|
3337
|
+
return;
|
|
3338
|
+
}
|
|
3320
3339
|
if (event.type !== "task_complete" || !event.error || typeof event.error !== "object") return;
|
|
3321
3340
|
const turn = event.turn_id;
|
|
3322
3341
|
if (typeof turn !== "string" || this.completed.has(turn)) return;
|
|
3323
3342
|
this.rememberTurn(turn);
|
|
3324
3343
|
const error = event.error;
|
|
3344
|
+
if (isUsageCap(error)) this.emitUsageCap();
|
|
3325
3345
|
this.emit({
|
|
3326
3346
|
type: "result",
|
|
3327
3347
|
subtype: "error",
|
|
@@ -3329,6 +3349,59 @@ var CodexEventSource = class {
|
|
|
3329
3349
|
});
|
|
3330
3350
|
this.chat({ kind: "turn_end" });
|
|
3331
3351
|
}
|
|
3352
|
+
/**
|
|
3353
|
+
* `token_count` carries the account's rate-limit gauges on every turn:
|
|
3354
|
+
* `rate_limits.primary` (the 5-hour window) and `.secondary` (weekly), each
|
|
3355
|
+
* with `used_percent`, `window_minutes` and `resets_at` in epoch seconds.
|
|
3356
|
+
* Classified by window length rather than by name, and reported only when a
|
|
3357
|
+
* window's numbers changed — the event fires per turn, and every report
|
|
3358
|
+
* rewrites the key row and fans out `project:codingAgentKeyUpdated`.
|
|
3359
|
+
*/
|
|
3360
|
+
handleTokenCount(event) {
|
|
3361
|
+
const limits = event.rate_limits;
|
|
3362
|
+
if (!limits || typeof limits !== "object") return;
|
|
3363
|
+
for (const name of ["primary", "secondary"]) {
|
|
3364
|
+
const window = limits[name];
|
|
3365
|
+
if (!window || typeof window !== "object") continue;
|
|
3366
|
+
const reading = window;
|
|
3367
|
+
if (typeof reading.used_percent !== "number") continue;
|
|
3368
|
+
const minutes = typeof reading.window_minutes === "number" ? reading.window_minutes : name === "secondary" ? WEEKLY_WINDOW_MINUTES : 0;
|
|
3369
|
+
const rateLimitType = minutes >= WEEKLY_WINDOW_MINUTES ? "seven_day" : "five_hour";
|
|
3370
|
+
const resetsAt = typeof reading.resets_at === "number" ? reading.resets_at : void 0;
|
|
3371
|
+
const last = this.windows.get(rateLimitType);
|
|
3372
|
+
if (last && last.usedPercent === reading.used_percent && last.resetsAt === resetsAt) continue;
|
|
3373
|
+
this.windows.set(rateLimitType, { usedPercent: reading.used_percent, resetsAt });
|
|
3374
|
+
this.emit({
|
|
3375
|
+
type: "rate_limit_event",
|
|
3376
|
+
rate_limit_info: {
|
|
3377
|
+
status: "allowed",
|
|
3378
|
+
rateLimitType,
|
|
3379
|
+
utilization: Math.max(0, Math.min(1, reading.used_percent / 100)),
|
|
3380
|
+
resetsAt
|
|
3381
|
+
}
|
|
3382
|
+
});
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3385
|
+
/**
|
|
3386
|
+
* The CLI does not say which window capped. The fullest window last reported
|
|
3387
|
+
* is the one that did; with no reading at all, the 5-hour window is the
|
|
3388
|
+
* conservative guess (its fallback pause is the shorter one).
|
|
3389
|
+
*/
|
|
3390
|
+
emitUsageCap() {
|
|
3391
|
+
let rateLimitType = "five_hour";
|
|
3392
|
+
let resetsAt;
|
|
3393
|
+
let fullest = -Infinity;
|
|
3394
|
+
for (const [type, window] of this.windows) {
|
|
3395
|
+
if (window.usedPercent <= fullest) continue;
|
|
3396
|
+
fullest = window.usedPercent;
|
|
3397
|
+
rateLimitType = type;
|
|
3398
|
+
resetsAt = window.resetsAt;
|
|
3399
|
+
}
|
|
3400
|
+
this.emit({
|
|
3401
|
+
type: "rate_limit_event",
|
|
3402
|
+
rate_limit_info: { status: "rejected", rateLimitType, resetsAt }
|
|
3403
|
+
});
|
|
3404
|
+
}
|
|
3332
3405
|
rememberTurn(turn) {
|
|
3333
3406
|
this.completed.add(turn);
|
|
3334
3407
|
const oldest = this.completed.values().next().value;
|
|
@@ -4774,7 +4847,8 @@ var ClaudeTuiAdapter = class {
|
|
|
4774
4847
|
structuredEvents: true,
|
|
4775
4848
|
prefill: true,
|
|
4776
4849
|
passiveTurns: true,
|
|
4777
|
-
rawPromptGate: false
|
|
4850
|
+
rawPromptGate: false,
|
|
4851
|
+
prewarm: true
|
|
4778
4852
|
};
|
|
4779
4853
|
resolveBinary(env = process.env) {
|
|
4780
4854
|
return env.CONVEYOR_CLAUDE_BIN ?? "claude";
|
|
@@ -4869,7 +4943,6 @@ var PtySession = class {
|
|
|
4869
4943
|
// Raw-TUI input detection (see awaitRawTuiInputLive). `probeWindow`, when
|
|
4870
4944
|
// non-null, accumulates raw output so a probe can look for its own sentinel
|
|
4871
4945
|
// coming back.
|
|
4872
|
-
spawnedAt = 0;
|
|
4873
4946
|
// Set once the child writes a DEC private mode — it owns the tty from then on,
|
|
4874
4947
|
// so the kernel no longer echoes our keystrokes and a sentinel coming back is
|
|
4875
4948
|
// attributable to the app's own repaint.
|
|
@@ -4929,6 +5002,8 @@ var PtySession = class {
|
|
|
4929
5002
|
turn;
|
|
4930
5003
|
codexPromptInArgv = false;
|
|
4931
5004
|
codexInitialPrompt;
|
|
5005
|
+
/** A fresh CLI intentionally started without input for checkout overlap. */
|
|
5006
|
+
prewarmed = false;
|
|
4932
5007
|
codexSource = null;
|
|
4933
5008
|
adapterCleanupPaths = [];
|
|
4934
5009
|
onIdle(listener) {
|
|
@@ -5022,7 +5097,7 @@ var PtySession = class {
|
|
|
5022
5097
|
* never set) keeps the Claude semantics byte-identical.
|
|
5023
5098
|
*/
|
|
5024
5099
|
canReuse(resume, fingerprint) {
|
|
5025
|
-
const resumeMatches = resume === void 0 ? this.reportedOpenCodeId !== null : resume === this.sessionUuid;
|
|
5100
|
+
const resumeMatches = resume === void 0 ? this.reportedOpenCodeId !== null || this.prewarmed : resume === this.sessionUuid;
|
|
5026
5101
|
return !this._toreDown && !this.exited && this.activeQueue === null && // A parked questionnaire owns the terminal — a fed prompt would type
|
|
5027
5102
|
// into the dialog. Force teardown + respawn instead.
|
|
5028
5103
|
!this.questionnairePending && resumeMatches && fingerprint === this.spawnFingerprint;
|
|
@@ -5052,6 +5127,7 @@ var PtySession = class {
|
|
|
5052
5127
|
if (this.betweenTurnBuffer.length > MAX_BETWEEN_TURN_BUFFER) {
|
|
5053
5128
|
this.betweenTurnBuffer.shift();
|
|
5054
5129
|
}
|
|
5130
|
+
if (this.prewarmed) return;
|
|
5055
5131
|
if (!this.passiveSignaled) {
|
|
5056
5132
|
this.passiveSignaled = true;
|
|
5057
5133
|
this.passiveListener?.();
|
|
@@ -5063,8 +5139,10 @@ var PtySession = class {
|
|
|
5063
5139
|
* the prompt into the live pty. The caller must have verified canReuse().
|
|
5064
5140
|
*/
|
|
5065
5141
|
async beginTurn(prompt, options) {
|
|
5142
|
+
this.prewarmed = false;
|
|
5066
5143
|
this.turnPrompt = prompt;
|
|
5067
5144
|
this.turn = turnOptionsFrom(options);
|
|
5145
|
+
this.attachInput();
|
|
5068
5146
|
this.resetForTurn();
|
|
5069
5147
|
this.betweenTurnBuffer = [];
|
|
5070
5148
|
this.activeQueue = new AsyncEventQueue();
|
|
@@ -5155,13 +5233,24 @@ var PtySession = class {
|
|
|
5155
5233
|
}
|
|
5156
5234
|
}
|
|
5157
5235
|
async start() {
|
|
5236
|
+
await this.startInternal(true);
|
|
5237
|
+
}
|
|
5238
|
+
/** Start the CLI and its event plumbing without allocating a model turn. */
|
|
5239
|
+
async startParked() {
|
|
5240
|
+
this.prewarmed = true;
|
|
5241
|
+
await this.startInternal(false);
|
|
5242
|
+
}
|
|
5243
|
+
// oxlint-disable-next-line complexity -- adapter startup and turn gating share one teardown boundary
|
|
5244
|
+
async startInternal(feedPrompt) {
|
|
5158
5245
|
await this.collectCodexPrompt();
|
|
5159
5246
|
const sessionId = this.resume ?? this.options.sessionId;
|
|
5160
5247
|
if (!sessionId) {
|
|
5161
5248
|
throw new Error("PtySession requires options.sessionId or a resume target");
|
|
5162
5249
|
}
|
|
5163
|
-
|
|
5164
|
-
|
|
5250
|
+
if (feedPrompt) {
|
|
5251
|
+
this.activeQueue = new AsyncEventQueue();
|
|
5252
|
+
this.turnStream = this.activeQueue.drain();
|
|
5253
|
+
}
|
|
5165
5254
|
const signal = this.turn.abortController?.signal;
|
|
5166
5255
|
if (signal?.aborted) {
|
|
5167
5256
|
await this.teardown();
|
|
@@ -5187,7 +5276,7 @@ var PtySession = class {
|
|
|
5187
5276
|
model: this.options.model
|
|
5188
5277
|
});
|
|
5189
5278
|
}
|
|
5190
|
-
if (signal) {
|
|
5279
|
+
if (signal && feedPrompt) {
|
|
5191
5280
|
this.abortHandler = () => {
|
|
5192
5281
|
void this.teardown();
|
|
5193
5282
|
};
|
|
@@ -5198,10 +5287,10 @@ var PtySession = class {
|
|
|
5198
5287
|
}
|
|
5199
5288
|
}
|
|
5200
5289
|
if (this.bridge) {
|
|
5201
|
-
|
|
5290
|
+
if (feedPrompt) this.attachInput();
|
|
5202
5291
|
this.unsubResize = this.bridge.onResize((cols, rows) => this.resizePty(cols, rows));
|
|
5203
5292
|
}
|
|
5204
|
-
await this.feedPrompt();
|
|
5293
|
+
if (feedPrompt) await this.feedPrompt();
|
|
5205
5294
|
} catch (err) {
|
|
5206
5295
|
await this.teardown();
|
|
5207
5296
|
throw err;
|
|
@@ -5241,7 +5330,11 @@ var PtySession = class {
|
|
|
5241
5330
|
session_id: id,
|
|
5242
5331
|
model: this.options.model
|
|
5243
5332
|
});
|
|
5244
|
-
this.sendChatEvent({
|
|
5333
|
+
this.sendChatEvent({
|
|
5334
|
+
kind: "init",
|
|
5335
|
+
model: this.options.model,
|
|
5336
|
+
claudeSessionId: id
|
|
5337
|
+
});
|
|
5245
5338
|
},
|
|
5246
5339
|
(event) => this.sendChatEvent(event)
|
|
5247
5340
|
);
|
|
@@ -5279,7 +5372,11 @@ var PtySession = class {
|
|
|
5279
5372
|
session_id: id,
|
|
5280
5373
|
model: this.options.model
|
|
5281
5374
|
});
|
|
5282
|
-
this.sendChatEvent({
|
|
5375
|
+
this.sendChatEvent({
|
|
5376
|
+
kind: "init",
|
|
5377
|
+
model: this.options.model,
|
|
5378
|
+
claudeSessionId: id
|
|
5379
|
+
});
|
|
5283
5380
|
},
|
|
5284
5381
|
(event) => this.sendChatEvent(event)
|
|
5285
5382
|
);
|
|
@@ -5393,9 +5490,19 @@ var PtySession = class {
|
|
|
5393
5490
|
this.pendingSyntheticQuestionIds = [];
|
|
5394
5491
|
this.questionResultRemap.clear();
|
|
5395
5492
|
for (const toolUseId of orphaned) {
|
|
5396
|
-
this.sendChatEvent({
|
|
5493
|
+
this.sendChatEvent({
|
|
5494
|
+
kind: "tool_result",
|
|
5495
|
+
toolUseId,
|
|
5496
|
+
output: "",
|
|
5497
|
+
isError: false
|
|
5498
|
+
});
|
|
5397
5499
|
}
|
|
5398
5500
|
}
|
|
5501
|
+
/** Attach relay keystrokes only once a Git-gated turn is allowed to accept them. */
|
|
5502
|
+
attachInput() {
|
|
5503
|
+
if (!this.bridge || this.unsubInput) return;
|
|
5504
|
+
this.unsubInput = this.bridge.onInput((data) => this.writeStdin(data));
|
|
5505
|
+
}
|
|
5399
5506
|
writeStdin(text) {
|
|
5400
5507
|
this.pty?.write(text);
|
|
5401
5508
|
}
|
|
@@ -5562,7 +5669,6 @@ var PtySession = class {
|
|
|
5562
5669
|
pty.onExit((event) => {
|
|
5563
5670
|
void this.finalizeOnExit(event.exitCode);
|
|
5564
5671
|
});
|
|
5565
|
-
this.spawnedAt = Date.now();
|
|
5566
5672
|
this.sawTerminalSetup = false;
|
|
5567
5673
|
this.probeWindow = null;
|
|
5568
5674
|
this.wroteToProcess = false;
|
|
@@ -5606,7 +5712,7 @@ var PtySession = class {
|
|
|
5606
5712
|
if (!needsRawReadyGate(this.adapter.capabilities)) return;
|
|
5607
5713
|
const timing = resolveRawTuiProbeTiming();
|
|
5608
5714
|
const { sentinel } = timing;
|
|
5609
|
-
const start =
|
|
5715
|
+
const start = Date.now();
|
|
5610
5716
|
const setupDeadline = start + timing.firstOutputMaxMs;
|
|
5611
5717
|
while (!this._toreDown && Date.now() < setupDeadline) {
|
|
5612
5718
|
if (this.sawTerminalSetup) break;
|
|
@@ -5890,7 +5996,12 @@ var PtySession = class {
|
|
|
5890
5996
|
}
|
|
5891
5997
|
if (!this.adapter.capabilities.structuredEvents) {
|
|
5892
5998
|
if (exitCode === 0) {
|
|
5893
|
-
this.pushEvent({
|
|
5999
|
+
this.pushEvent({
|
|
6000
|
+
type: "result",
|
|
6001
|
+
subtype: "success",
|
|
6002
|
+
result: "",
|
|
6003
|
+
total_cost_usd: 0
|
|
6004
|
+
});
|
|
5894
6005
|
} else {
|
|
5895
6006
|
this.pushEvent({
|
|
5896
6007
|
type: "result",
|
|
@@ -6051,6 +6162,9 @@ var PtyHarness = class _PtyHarness {
|
|
|
6051
6162
|
authNoticeSent = false;
|
|
6052
6163
|
/** Once-per-process guard on the pod-recycle escalation (see escalateToPodRecycle). */
|
|
6053
6164
|
recycleRequested = false;
|
|
6165
|
+
disposed = false;
|
|
6166
|
+
prewarming = null;
|
|
6167
|
+
prewarmInFlight = null;
|
|
6054
6168
|
/**
|
|
6055
6169
|
* Wiggle the live pty's size so the CLI repaints its whole screen. No-op on
|
|
6056
6170
|
* the SDK harness. Falls back to the parked session so an API reconnect while
|
|
@@ -6111,6 +6225,60 @@ var PtyHarness = class _PtyHarness {
|
|
|
6111
6225
|
yield* this.drain(session);
|
|
6112
6226
|
yield* this.recoverFailedDelivery(session, options, want);
|
|
6113
6227
|
}
|
|
6228
|
+
/**
|
|
6229
|
+
* Prepare a fresh TUI while another boot task runs. This deliberately does
|
|
6230
|
+
* not send stdin or create an active event queue, so no model/tool work can
|
|
6231
|
+
* begin before the runner releases its checkout and WIP gates.
|
|
6232
|
+
*/
|
|
6233
|
+
async prewarm(options) {
|
|
6234
|
+
if (this.prewarmInFlight) return this.prewarmInFlight;
|
|
6235
|
+
const pending = this.startPrewarm(options);
|
|
6236
|
+
this.prewarmInFlight = pending;
|
|
6237
|
+
try {
|
|
6238
|
+
return await pending;
|
|
6239
|
+
} finally {
|
|
6240
|
+
this.prewarmInFlight = null;
|
|
6241
|
+
}
|
|
6242
|
+
}
|
|
6243
|
+
async startPrewarm(options) {
|
|
6244
|
+
if (this.disposed || !this.adapter.capabilities.prewarm) return false;
|
|
6245
|
+
const startedAt = Date.now();
|
|
6246
|
+
const prepared = ensureSessionTarget(options, void 0);
|
|
6247
|
+
if (prepared !== options) {
|
|
6248
|
+
_PtyHarness.log.warn("prewarm had no session target \u2014 minted one", {
|
|
6249
|
+
sessionId: prepared.sessionId
|
|
6250
|
+
});
|
|
6251
|
+
}
|
|
6252
|
+
const fingerprint = this.fingerprintOf(prepared);
|
|
6253
|
+
if (this.parked?.canReuse(void 0, fingerprint) && !await this.parkedHomeDied(prepared)) {
|
|
6254
|
+
_PtyHarness.log.info("TUI prewarm reused parked process", {
|
|
6255
|
+
elapsedMs: Date.now() - startedAt
|
|
6256
|
+
});
|
|
6257
|
+
return true;
|
|
6258
|
+
}
|
|
6259
|
+
if (this.parked) {
|
|
6260
|
+
const stale = this.parked;
|
|
6261
|
+
this.parked = null;
|
|
6262
|
+
this.cancelEndedTimer();
|
|
6263
|
+
await stale.teardown();
|
|
6264
|
+
}
|
|
6265
|
+
if (this.disposed) return false;
|
|
6266
|
+
_PtyHarness.log.info("TUI prewarm starting fresh process");
|
|
6267
|
+
const session = await this.spawnSession("", prepared, void 0, true, (created) => {
|
|
6268
|
+
this.prewarming = created;
|
|
6269
|
+
});
|
|
6270
|
+
this.prewarming = null;
|
|
6271
|
+
if (this.disposed) {
|
|
6272
|
+
await session.teardown();
|
|
6273
|
+
this.notifyEnded();
|
|
6274
|
+
return false;
|
|
6275
|
+
}
|
|
6276
|
+
this.park(session);
|
|
6277
|
+
_PtyHarness.log.info("TUI prewarm process started", {
|
|
6278
|
+
elapsedMs: Date.now() - startedAt
|
|
6279
|
+
});
|
|
6280
|
+
return true;
|
|
6281
|
+
}
|
|
6114
6282
|
/**
|
|
6115
6283
|
* Did the shared `~/.claude` mount die while this session sat parked?
|
|
6116
6284
|
*
|
|
@@ -6147,8 +6315,9 @@ var PtyHarness = class _PtyHarness {
|
|
|
6147
6315
|
* environment preparation (config-home health, credential synthesis,
|
|
6148
6316
|
* auth-readiness warning) and registering the parked-death watch.
|
|
6149
6317
|
*/
|
|
6150
|
-
async spawnSession(prompt, options, want) {
|
|
6318
|
+
async spawnSession(prompt, options, want, parked = false, onAllocated) {
|
|
6151
6319
|
const session = new PtySession(prompt, options, want, this.bridge, this.adapter);
|
|
6320
|
+
onAllocated?.(session);
|
|
6152
6321
|
if (this.ownsClaudeConfigHome) {
|
|
6153
6322
|
await ensureUsableClaudeConfigHome(options.cwd, _PtyHarness.log);
|
|
6154
6323
|
}
|
|
@@ -6157,7 +6326,8 @@ var PtyHarness = class _PtyHarness {
|
|
|
6157
6326
|
await this.warnIfAuthNotReady();
|
|
6158
6327
|
}
|
|
6159
6328
|
session.onExit(() => this.handleSessionExit(session));
|
|
6160
|
-
await session.
|
|
6329
|
+
if (parked) await session.startParked();
|
|
6330
|
+
else await session.start();
|
|
6161
6331
|
return session;
|
|
6162
6332
|
}
|
|
6163
6333
|
/**
|
|
@@ -6329,10 +6499,21 @@ var PtyHarness = class _PtyHarness {
|
|
|
6329
6499
|
* the Connected-TUI tab hides promptly.
|
|
6330
6500
|
*/
|
|
6331
6501
|
async dispose() {
|
|
6502
|
+
this.disposed = true;
|
|
6332
6503
|
this.cancelEndedTimer();
|
|
6333
|
-
|
|
6504
|
+
try {
|
|
6505
|
+
await this.prewarmInFlight;
|
|
6506
|
+
} catch (error) {
|
|
6507
|
+
_PtyHarness.log.warn("TUI prewarm failed during disposal", {
|
|
6508
|
+
error: error instanceof Error ? error.message : String(error)
|
|
6509
|
+
});
|
|
6510
|
+
}
|
|
6511
|
+
const toKill = [.../* @__PURE__ */ new Set([this.parked, this.activeSession, this.prewarming])].filter(
|
|
6512
|
+
(s) => s !== null
|
|
6513
|
+
);
|
|
6334
6514
|
this.parked = null;
|
|
6335
6515
|
this.activeSession = null;
|
|
6516
|
+
this.prewarming = null;
|
|
6336
6517
|
for (const session of toKill) {
|
|
6337
6518
|
try {
|
|
6338
6519
|
await session.teardown();
|
|
@@ -6471,13 +6652,16 @@ async function seedOpenCodeOauth(env) {
|
|
|
6471
6652
|
}
|
|
6472
6653
|
|
|
6473
6654
|
// src/harness/openai-model.ts
|
|
6474
|
-
var DEFAULT_OPENAI_CODING_MODEL =
|
|
6655
|
+
var DEFAULT_OPENAI_CODING_MODEL = DEFAULT_CODEX_CODING_MODEL;
|
|
6475
6656
|
function resolveOpenAiCodingModel(model) {
|
|
6476
6657
|
if (!model || model.startsWith("claude-") || model.includes("/") && !model.startsWith("openai/")) {
|
|
6477
6658
|
return DEFAULT_OPENAI_CODING_MODEL;
|
|
6478
6659
|
}
|
|
6479
6660
|
return model.replace(/^openai\//, "");
|
|
6480
6661
|
}
|
|
6662
|
+
function resolveCodexReasoningEffort(...candidates) {
|
|
6663
|
+
return candidates.find(isCodexReasoningEffort);
|
|
6664
|
+
}
|
|
6481
6665
|
|
|
6482
6666
|
// src/harness/opencode/credentials.ts
|
|
6483
6667
|
var PROVIDER_KEY_ENV = {
|
|
@@ -6771,7 +6955,8 @@ var OpenCodeTuiAdapter = class {
|
|
|
6771
6955
|
// opencode silently discards early stdin while the TUI paints; the pasted
|
|
6772
6956
|
// text itself is lost, so the readiness probe must gate the first write
|
|
6773
6957
|
// even though structured events exist now.
|
|
6774
|
-
rawPromptGate: true
|
|
6958
|
+
rawPromptGate: true,
|
|
6959
|
+
prewarm: true
|
|
6775
6960
|
};
|
|
6776
6961
|
resolveBinary(env = this.env) {
|
|
6777
6962
|
const override = env.CONVEYOR_OPENCODE_BIN;
|
|
@@ -6935,7 +7120,8 @@ var CodexTuiAdapter = class {
|
|
|
6935
7120
|
structuredEvents: true,
|
|
6936
7121
|
prefill: true,
|
|
6937
7122
|
passiveTurns: true,
|
|
6938
|
-
rawPromptGate: true
|
|
7123
|
+
rawPromptGate: true,
|
|
7124
|
+
prewarm: true
|
|
6939
7125
|
};
|
|
6940
7126
|
resolveBinary(env = this.env) {
|
|
6941
7127
|
const path = findOnPath(env.CONVEYOR_CODEX_BIN ?? "codex", env);
|
|
@@ -6987,11 +7173,16 @@ var CodexTuiAdapter = class {
|
|
|
6987
7173
|
sandbox_mode: "danger-full-access",
|
|
6988
7174
|
developer_instructions: [
|
|
6989
7175
|
input.options.appendSystemPrompt,
|
|
6990
|
-
"Conveyor workflow skills are stored in .claude/skills in the workspace. When instructed to run a /skill-name skill, read .claude/skills/skill-name/SKILL.md and follow its instructions. This is a file-based workflow, not a native slash command."
|
|
7176
|
+
"Conveyor workflow skills are stored in .claude/skills in the workspace. When instructed to run a /skill-name skill, read .claude/skills/skill-name/SKILL.md and follow its instructions. This is a file-based workflow, not a native slash command. When a skill's Goal and finish line section tells you to create a thread goal, do so with your goal tool before the first step; the objective is that section's finish line."
|
|
6991
7177
|
].filter(Boolean).join("\n\n"),
|
|
6992
7178
|
mcp_servers: codexMcpConfig(input.mcpEntries ?? {}),
|
|
6993
7179
|
projects: { [trustedCwd]: { trust_level: "trusted" } }
|
|
6994
7180
|
};
|
|
7181
|
+
const effort = resolveCodexReasoningEffort(
|
|
7182
|
+
this.env.CONVEYOR_AGENT_REASONING_EFFORT,
|
|
7183
|
+
input.options.codex?.effort
|
|
7184
|
+
);
|
|
7185
|
+
if (effort) config.model_reasoning_effort = effort;
|
|
6995
7186
|
if (input.pluginPath && input.eventsSinkPath) {
|
|
6996
7187
|
const profile = basename(dirname3(input.pluginPath));
|
|
6997
7188
|
const profilePath = join14(env.CODEX_HOME, `${profile}.config.toml`);
|
|
@@ -7009,7 +7200,9 @@ var CodexTuiAdapter = class {
|
|
|
7009
7200
|
if (input.pluginPath) args.push("--profile", basename(dirname3(input.pluginPath)));
|
|
7010
7201
|
for (const [key, value] of Object.entries(config))
|
|
7011
7202
|
args.push("-c", `${key}=${tomlValue(value)}`);
|
|
7012
|
-
const model = resolveOpenAiCodingModel(
|
|
7203
|
+
const model = resolveOpenAiCodingModel(
|
|
7204
|
+
this.env.CONVEYOR_AGENT_MODEL ?? input.options.codex?.model ?? input.options.model
|
|
7205
|
+
);
|
|
7013
7206
|
args.push("--model", model);
|
|
7014
7207
|
if (input.resume) args.push("resume", input.resume);
|
|
7015
7208
|
if (input.initialPrompt) args.push("--", input.initialPrompt);
|
|
@@ -8103,6 +8296,7 @@ function buildParentReviewPrompt() {
|
|
|
8103
8296
|
var TYPE_PRIORITY = { rule: 0, doc: 1, file: 2, folder: 3 };
|
|
8104
8297
|
var SUMMARY_SCAN_CHARS = 4e3;
|
|
8105
8298
|
var SUMMARY_MAX_CHARS = 160;
|
|
8299
|
+
var CONTEXT_READ_CONCURRENCY = 4;
|
|
8106
8300
|
var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
8107
8301
|
".png",
|
|
8108
8302
|
".jpg",
|
|
@@ -8205,24 +8399,61 @@ async function readFolderListing(folderPath) {
|
|
|
8205
8399
|
return null;
|
|
8206
8400
|
}
|
|
8207
8401
|
}
|
|
8208
|
-
|
|
8209
|
-
|
|
8210
|
-
|
|
8211
|
-
|
|
8212
|
-
|
|
8213
|
-
|
|
8214
|
-
|
|
8215
|
-
|
|
8216
|
-
|
|
8402
|
+
var ContextReadCoordinator = class {
|
|
8403
|
+
availableSlots = CONTEXT_READ_CONCURRENCY;
|
|
8404
|
+
waitingReaders = [];
|
|
8405
|
+
pendingSummaries = /* @__PURE__ */ new Map();
|
|
8406
|
+
async resolveEntry(entry) {
|
|
8407
|
+
const result = {
|
|
8408
|
+
type: entry.type,
|
|
8409
|
+
path: entry.path,
|
|
8410
|
+
label: entry.label,
|
|
8411
|
+
summary: null
|
|
8412
|
+
};
|
|
8413
|
+
if (entry.label) {
|
|
8414
|
+
result.summary = truncateSummary(entry.label);
|
|
8415
|
+
return result;
|
|
8416
|
+
}
|
|
8417
|
+
result.summary = await this.readSummary(entry);
|
|
8217
8418
|
return result;
|
|
8218
8419
|
}
|
|
8219
|
-
|
|
8220
|
-
|
|
8221
|
-
|
|
8420
|
+
readSummary(entry) {
|
|
8421
|
+
const kind = entry.type === "folder" ? "folder" : "file";
|
|
8422
|
+
const key = `${kind}:${entry.path}`;
|
|
8423
|
+
const pending = this.pendingSummaries.get(key);
|
|
8424
|
+
if (pending) return pending;
|
|
8425
|
+
const read = this.limit(
|
|
8426
|
+
() => kind === "folder" ? readFolderListing(entry.path) : readFileSummary(entry.path)
|
|
8427
|
+
);
|
|
8428
|
+
this.pendingSummaries.set(key, read);
|
|
8429
|
+
return read;
|
|
8222
8430
|
}
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
|
|
8431
|
+
async limit(read) {
|
|
8432
|
+
await this.acquireSlot();
|
|
8433
|
+
try {
|
|
8434
|
+
return await read();
|
|
8435
|
+
} finally {
|
|
8436
|
+
this.releaseSlot();
|
|
8437
|
+
}
|
|
8438
|
+
}
|
|
8439
|
+
async acquireSlot() {
|
|
8440
|
+
if (this.availableSlots > 0) {
|
|
8441
|
+
this.availableSlots -= 1;
|
|
8442
|
+
return;
|
|
8443
|
+
}
|
|
8444
|
+
await new Promise((resolve) => {
|
|
8445
|
+
this.waitingReaders.push(resolve);
|
|
8446
|
+
});
|
|
8447
|
+
}
|
|
8448
|
+
releaseSlot() {
|
|
8449
|
+
const nextReader = this.waitingReaders.shift();
|
|
8450
|
+
if (nextReader) {
|
|
8451
|
+
nextReader();
|
|
8452
|
+
return;
|
|
8453
|
+
}
|
|
8454
|
+
this.availableSlots += 1;
|
|
8455
|
+
}
|
|
8456
|
+
};
|
|
8226
8457
|
function formatEntry(entry) {
|
|
8227
8458
|
const suffix = entry.summary ? ` \u2014 ${entry.summary}` : "";
|
|
8228
8459
|
return `- \`${entry.path}\`${suffix}`;
|
|
@@ -8277,39 +8508,33 @@ function formatResolvedTags(resolved, subProject, mentioned, runnerMode) {
|
|
|
8277
8508
|
}
|
|
8278
8509
|
return parts.join("\n");
|
|
8279
8510
|
}
|
|
8280
|
-
|
|
8281
|
-
if (!contextPaths?.length) return [];
|
|
8511
|
+
function resolveEntries(contextPaths, coordinator) {
|
|
8512
|
+
if (!contextPaths?.length) return Promise.resolve([]);
|
|
8282
8513
|
const sorted = [...contextPaths].sort(
|
|
8283
8514
|
(a, b) => (TYPE_PRIORITY[a.type] ?? 99) - (TYPE_PRIORITY[b.type] ?? 99)
|
|
8284
8515
|
);
|
|
8285
|
-
|
|
8286
|
-
for (const entry of sorted) {
|
|
8287
|
-
results.push(await resolveEntry(entry));
|
|
8288
|
-
}
|
|
8289
|
-
return results;
|
|
8516
|
+
return Promise.all(sorted.map((entry) => coordinator.resolveEntry(entry)));
|
|
8290
8517
|
}
|
|
8291
8518
|
function countResolved(entries) {
|
|
8292
8519
|
const injected = entries.filter((e) => e.summary !== null).length;
|
|
8293
8520
|
return { injected, skipped: entries.length - injected };
|
|
8294
8521
|
}
|
|
8295
|
-
async function resolveAssignedTags(assignedTags) {
|
|
8296
|
-
const resolved =
|
|
8297
|
-
|
|
8298
|
-
let skipped = 0;
|
|
8299
|
-
for (const tag of assignedTags) {
|
|
8300
|
-
const entries = await resolveEntries(tag.contextPaths);
|
|
8301
|
-
const counts = countResolved(entries);
|
|
8302
|
-
injected += counts.injected;
|
|
8303
|
-
skipped += counts.skipped;
|
|
8304
|
-
resolved.push({
|
|
8522
|
+
async function resolveAssignedTags(assignedTags, coordinator) {
|
|
8523
|
+
const resolved = await Promise.all(
|
|
8524
|
+
assignedTags.map(async (tag) => ({
|
|
8305
8525
|
tagName: tag.name,
|
|
8306
8526
|
description: tag.description,
|
|
8307
|
-
entries,
|
|
8527
|
+
entries: await resolveEntries(tag.contextPaths, coordinator),
|
|
8308
8528
|
hasOverview: tag.hasOverview,
|
|
8309
8529
|
overviewPath: tag.overviewPath ?? null
|
|
8310
|
-
})
|
|
8311
|
-
|
|
8312
|
-
|
|
8530
|
+
}))
|
|
8531
|
+
);
|
|
8532
|
+
const counts = resolved.map((tag) => countResolved(tag.entries));
|
|
8533
|
+
return {
|
|
8534
|
+
resolved,
|
|
8535
|
+
injected: counts.reduce((total, current) => total + current.injected, 0),
|
|
8536
|
+
skipped: counts.reduce((total, current) => total + current.skipped, 0)
|
|
8537
|
+
};
|
|
8313
8538
|
}
|
|
8314
8539
|
async function resolveTagContext(projectTags, taskTagIds, _model, _betas, runnerMode, subProject, mentionedTagIds) {
|
|
8315
8540
|
const taskTagIdSet = new Set(taskTagIds);
|
|
@@ -8323,30 +8548,26 @@ async function resolveTagContext(projectTags, taskTagIds, _model, _betas, runner
|
|
|
8323
8548
|
if (!hasTagPaths && !hasSubProjectPaths && mentionedTags.length === 0) {
|
|
8324
8549
|
return { injectedSection: "", stats: { injected: 0, skipped: 0 } };
|
|
8325
8550
|
}
|
|
8326
|
-
const
|
|
8327
|
-
|
|
8328
|
-
|
|
8329
|
-
|
|
8330
|
-
|
|
8331
|
-
|
|
8332
|
-
|
|
8333
|
-
|
|
8334
|
-
|
|
8335
|
-
if (subProject && hasSubProjectPaths) {
|
|
8336
|
-
const entries = await resolveEntries(subProject.contextPaths);
|
|
8337
|
-
const counts = countResolved(entries);
|
|
8338
|
-
subInjected = counts.injected;
|
|
8339
|
-
subSkipped = counts.skipped;
|
|
8340
|
-
subProjectResolved = { name: subProject.name, entries };
|
|
8341
|
-
}
|
|
8551
|
+
const coordinator = new ContextReadCoordinator();
|
|
8552
|
+
const subProjectEntries = subProject && hasSubProjectPaths ? resolveEntries(subProject.contextPaths, coordinator) : Promise.resolve(null);
|
|
8553
|
+
const [assigned, mentioned, subProjectResolvedEntries] = await Promise.all([
|
|
8554
|
+
resolveAssignedTags(assignedTags, coordinator),
|
|
8555
|
+
resolveAssignedTags(mentionedTags, coordinator),
|
|
8556
|
+
subProjectEntries
|
|
8557
|
+
]);
|
|
8558
|
+
const subProjectResolved = subProject && subProjectResolvedEntries ? { name: subProject.name, entries: subProjectResolvedEntries } : null;
|
|
8559
|
+
const subProjectCounts = subProjectResolvedEntries ? countResolved(subProjectResolvedEntries) : { injected: 0, skipped: 0 };
|
|
8342
8560
|
return {
|
|
8343
8561
|
injectedSection: formatResolvedTags(
|
|
8344
|
-
resolved,
|
|
8562
|
+
assigned.resolved,
|
|
8345
8563
|
subProjectResolved,
|
|
8346
|
-
|
|
8564
|
+
mentioned.resolved,
|
|
8347
8565
|
runnerMode
|
|
8348
8566
|
),
|
|
8349
|
-
stats: {
|
|
8567
|
+
stats: {
|
|
8568
|
+
injected: assigned.injected + subProjectCounts.injected,
|
|
8569
|
+
skipped: assigned.skipped + subProjectCounts.skipped
|
|
8570
|
+
}
|
|
8350
8571
|
};
|
|
8351
8572
|
}
|
|
8352
8573
|
|
|
@@ -13243,17 +13464,21 @@ function handleRateLimitEvent(event, host) {
|
|
|
13243
13464
|
const { rate_limit_info } = event;
|
|
13244
13465
|
logger4.info("Rate limit event received", { rate_limit_info });
|
|
13245
13466
|
const status = rate_limit_info.status;
|
|
13467
|
+
const resetsAt = epochSecondsToISO(rate_limit_info.resetsAt);
|
|
13246
13468
|
const utilization = rate_limit_info.utilization ?? (status === "rejected" ? 1 : void 0);
|
|
13247
13469
|
if (utilization !== void 0 && rate_limit_info.rateLimitType) {
|
|
13248
13470
|
host.connection.sendEvent({
|
|
13249
13471
|
type: "rate_limit_update",
|
|
13250
13472
|
rateLimitType: rate_limit_info.rateLimitType,
|
|
13251
13473
|
utilization,
|
|
13252
|
-
status
|
|
13474
|
+
status,
|
|
13475
|
+
// The server parses this into the key's sessionResetsAt / weeklyResetsAt;
|
|
13476
|
+
// Codex reports a reset instant with every gauge, so allowed events carry
|
|
13477
|
+
// it too, not only rejections.
|
|
13478
|
+
...resetsAt ? { resetsAt } : {}
|
|
13253
13479
|
});
|
|
13254
13480
|
}
|
|
13255
13481
|
if (status === "rejected") {
|
|
13256
|
-
const resetsAt = epochSecondsToISO(rate_limit_info.resetsAt);
|
|
13257
13482
|
const resetsAtDisplay = resetsAt ?? "unknown";
|
|
13258
13483
|
const message = `Rate limit rejected (type: ${rate_limit_info.rateLimitType ?? "unknown"}, resets at: ${resetsAtDisplay})`;
|
|
13259
13484
|
host.connection.sendEvent({ type: "error", message });
|
|
@@ -14120,7 +14345,11 @@ function buildQueryOptions(host, context) {
|
|
|
14120
14345
|
const systemPromptText = buildSystemPrompt(
|
|
14121
14346
|
host.config.mode,
|
|
14122
14347
|
context,
|
|
14123
|
-
{
|
|
14348
|
+
{
|
|
14349
|
+
...host.config,
|
|
14350
|
+
isAuto: host.isAuto,
|
|
14351
|
+
runtimeTui: process.env.CONVEYOR_TUI
|
|
14352
|
+
},
|
|
14124
14353
|
host.setupLog,
|
|
14125
14354
|
mode
|
|
14126
14355
|
);
|
|
@@ -14163,6 +14392,7 @@ function buildQueryOptions(host, context) {
|
|
|
14163
14392
|
effort: settings.effort,
|
|
14164
14393
|
thinking: settings.thinking,
|
|
14165
14394
|
betas: settings.betas,
|
|
14395
|
+
codex: settings.codex,
|
|
14166
14396
|
abortController: host.abortController ?? void 0,
|
|
14167
14397
|
disallowedTools: buildDisallowedTools(settings, mode, host.hasExitedPlanMode),
|
|
14168
14398
|
enableFileCheckpointing: settings.enableFileCheckpointing,
|
|
@@ -14180,7 +14410,11 @@ function buildMultimodalPrompt(textPrompt, context, skipImages = false) {
|
|
|
14180
14410
|
for (const msg of context.chatHistory) {
|
|
14181
14411
|
for (const f2 of msg.files ?? []) {
|
|
14182
14412
|
if (f2.content && f2.contentEncoding === "base64") {
|
|
14183
|
-
chatImages.push({
|
|
14413
|
+
chatImages.push({
|
|
14414
|
+
fileName: f2.fileName,
|
|
14415
|
+
mimeType: f2.mimeType,
|
|
14416
|
+
content: f2.content
|
|
14417
|
+
});
|
|
14184
14418
|
}
|
|
14185
14419
|
}
|
|
14186
14420
|
}
|
|
@@ -14195,14 +14429,24 @@ function buildMultimodalPrompt(textPrompt, context, skipImages = false) {
|
|
|
14195
14429
|
data: file.content ?? ""
|
|
14196
14430
|
}
|
|
14197
14431
|
});
|
|
14198
|
-
blocks.push({
|
|
14432
|
+
blocks.push({
|
|
14433
|
+
type: "text",
|
|
14434
|
+
text: `[Attached image: ${file.fileName} (${file.mimeType})]`
|
|
14435
|
+
});
|
|
14199
14436
|
}
|
|
14200
14437
|
for (const file of chatImages) {
|
|
14201
14438
|
blocks.push({
|
|
14202
14439
|
type: "image",
|
|
14203
|
-
source: {
|
|
14440
|
+
source: {
|
|
14441
|
+
type: "base64",
|
|
14442
|
+
media_type: file.mimeType,
|
|
14443
|
+
data: file.content
|
|
14444
|
+
}
|
|
14445
|
+
});
|
|
14446
|
+
blocks.push({
|
|
14447
|
+
type: "text",
|
|
14448
|
+
text: `[Chat image: ${file.fileName} (${file.mimeType})]`
|
|
14204
14449
|
});
|
|
14205
|
-
blocks.push({ type: "text", text: `[Chat image: ${file.fileName} (${file.mimeType})]` });
|
|
14206
14450
|
}
|
|
14207
14451
|
return blocks;
|
|
14208
14452
|
}
|
|
@@ -14318,6 +14562,7 @@ async function* watchForParkedTui(inner, host, opts) {
|
|
|
14318
14562
|
const timer = setTimeout(() => {
|
|
14319
14563
|
parked = true;
|
|
14320
14564
|
host.connection.emitStatus("waiting_for_input");
|
|
14565
|
+
host.callbacks.onSubmittedPtyParked?.();
|
|
14321
14566
|
void host.callbacks.onStatusChange("waiting_for_input");
|
|
14322
14567
|
}, PARKED_TUI_GRACE_MS);
|
|
14323
14568
|
const silenceTimeoutMs = resolveTurnSilenceTimeoutMs();
|
|
@@ -14359,7 +14604,7 @@ async function* watchForParkedTui(inner, host, opts) {
|
|
|
14359
14604
|
function takesPlannerOpeningTurn(host, mode) {
|
|
14360
14605
|
return host.config.mode === "plan" && mode === "discovery";
|
|
14361
14606
|
}
|
|
14362
|
-
async function runSdkQuery(host, context, followUpContent, promptDeliveryOverride) {
|
|
14607
|
+
async function runSdkQuery(host, context, followUpContent, promptDeliveryOverride, freshPrewarmed = false) {
|
|
14363
14608
|
if (host.isStopped()) return;
|
|
14364
14609
|
const mode = host.agentMode;
|
|
14365
14610
|
const isDiscoveryLike = mode === "discovery" || mode === "help";
|
|
@@ -14368,7 +14613,7 @@ async function runSdkQuery(host, context, followUpContent, promptDeliveryOverrid
|
|
|
14368
14613
|
host.config.workspaceDir,
|
|
14369
14614
|
host.config.mode === "code-review" && process.env.CONVEYOR_TUI !== "codex" ? "claude-code" : process.env.CONVEYOR_TUI
|
|
14370
14615
|
);
|
|
14371
|
-
const hasExistingSession = !!sessionStart.resume;
|
|
14616
|
+
const hasExistingSession = !!sessionStart.resume && !freshPrewarmed;
|
|
14372
14617
|
const promptDelivery = promptDeliveryOverride ?? resolvePromptDelivery({
|
|
14373
14618
|
harnessKind: host.harnessKind,
|
|
14374
14619
|
runnerMode: host.config.mode,
|
|
@@ -14390,11 +14635,49 @@ async function runSdkQuery(host, context, followUpContent, promptDeliveryOverrid
|
|
|
14390
14635
|
if (isDiscoveryLike && host.harnessKind === "sdk") {
|
|
14391
14636
|
return;
|
|
14392
14637
|
}
|
|
14393
|
-
if (isDiscoveryLike && resume && !takesPlannerOpeningTurn(host, mode)) {
|
|
14638
|
+
if (isDiscoveryLike && resume && !freshPrewarmed && !takesPlannerOpeningTurn(host, mode)) {
|
|
14394
14639
|
return;
|
|
14395
14640
|
}
|
|
14396
14641
|
await runInitialQuery(host, context, options, resume, promptDelivery);
|
|
14397
14642
|
}
|
|
14643
|
+
async function prewarmInitialTuiQuery(host, context) {
|
|
14644
|
+
if (host.harnessKind !== "pty" || !host.harness.prewarm) return false;
|
|
14645
|
+
const mode = host.agentMode;
|
|
14646
|
+
const sessionStart = resolveSessionStart(
|
|
14647
|
+
sessionLineageKey(context.taskId, mode, host.config.mode),
|
|
14648
|
+
host.config.workspaceDir,
|
|
14649
|
+
host.config.mode === "code-review" && process.env.CONVEYOR_TUI !== "codex" ? "claude-code" : process.env.CONVEYOR_TUI
|
|
14650
|
+
);
|
|
14651
|
+
if (sessionStart.resume) return false;
|
|
14652
|
+
const promptDelivery = resolvePromptDelivery({
|
|
14653
|
+
harnessKind: host.harnessKind,
|
|
14654
|
+
runnerMode: host.config.mode,
|
|
14655
|
+
isAuto: host.isAuto,
|
|
14656
|
+
agentMode: mode,
|
|
14657
|
+
isFollowUp: false,
|
|
14658
|
+
hasExistingSession: false
|
|
14659
|
+
});
|
|
14660
|
+
const options = {
|
|
14661
|
+
...buildQueryOptions(host, context),
|
|
14662
|
+
promptDelivery,
|
|
14663
|
+
...sessionStart.sessionId ? { sessionId: sessionStart.sessionId } : {}
|
|
14664
|
+
};
|
|
14665
|
+
const initialPrompt = await buildInitialPrompt(
|
|
14666
|
+
host.config.mode,
|
|
14667
|
+
context,
|
|
14668
|
+
host.isAuto,
|
|
14669
|
+
host.agentMode,
|
|
14670
|
+
host.config.packExecution
|
|
14671
|
+
);
|
|
14672
|
+
const { appendSystemPrompt } = selectInitialPromptInput(
|
|
14673
|
+
promptDelivery,
|
|
14674
|
+
initialPrompt,
|
|
14675
|
+
context,
|
|
14676
|
+
options.appendSystemPrompt,
|
|
14677
|
+
host.harnessKind
|
|
14678
|
+
);
|
|
14679
|
+
return host.harness.prewarm({ ...options, appendSystemPrompt });
|
|
14680
|
+
}
|
|
14398
14681
|
async function runFollowUpQuery(host, context, options, resume, followUpContent) {
|
|
14399
14682
|
if (options.promptDelivery === "prefill") {
|
|
14400
14683
|
await runPrefilledFollowUp(host, context, options, resume, followUpContent);
|
|
@@ -14720,7 +15003,10 @@ function handleRetryError(error, context, host, options, prevImageError) {
|
|
|
14720
15003
|
return handleAuthError(context, host, options);
|
|
14721
15004
|
}
|
|
14722
15005
|
if (!isRetriableError(error)) throw error;
|
|
14723
|
-
return {
|
|
15006
|
+
return {
|
|
15007
|
+
action: "continue",
|
|
15008
|
+
lastErrorWasImage: classifyImageError(error) || prevImageError
|
|
15009
|
+
};
|
|
14724
15010
|
}
|
|
14725
15011
|
function handleProcessResult(result, context, host, options) {
|
|
14726
15012
|
if (result.modeRestart || host.isStopped()) return { action: "return" };
|
|
@@ -14737,10 +15023,16 @@ function handleProcessResult(result, context, host, options) {
|
|
|
14737
15023
|
};
|
|
14738
15024
|
}
|
|
14739
15025
|
if (result.staleSession && context.claudeSessionId) {
|
|
14740
|
-
return {
|
|
15026
|
+
return {
|
|
15027
|
+
action: "return_promise",
|
|
15028
|
+
promise: handleStaleSession(context, host, options)
|
|
15029
|
+
};
|
|
14741
15030
|
}
|
|
14742
15031
|
if (result.authError) {
|
|
14743
|
-
return {
|
|
15032
|
+
return {
|
|
15033
|
+
action: "return_promise",
|
|
15034
|
+
promise: handleAuthError(context, host, options)
|
|
15035
|
+
};
|
|
14744
15036
|
}
|
|
14745
15037
|
if (!result.retriable) return { action: "return" };
|
|
14746
15038
|
return {
|
|
@@ -14848,6 +15140,7 @@ var QueryBridge = class {
|
|
|
14848
15140
|
_apiOutageDetail = null;
|
|
14849
15141
|
_keyCycleCount = 0;
|
|
14850
15142
|
_abortController = null;
|
|
15143
|
+
freshPrewarmedInitial = false;
|
|
14851
15144
|
/** Called by SessionRunner when ExitPlanMode triggers a mode transition. */
|
|
14852
15145
|
onModeTransition;
|
|
14853
15146
|
/** Called by tool handlers to soft-stop (abort query, keep session alive). */
|
|
@@ -14903,6 +15196,19 @@ var QueryBridge = class {
|
|
|
14903
15196
|
forceRepaint() {
|
|
14904
15197
|
this.harness.forceRepaint?.();
|
|
14905
15198
|
}
|
|
15199
|
+
/** Warm a fresh interactive task TUI only. It receives no input until SessionRunner's Git gate releases. */
|
|
15200
|
+
async prewarmInitial(context) {
|
|
15201
|
+
try {
|
|
15202
|
+
const warmed = await prewarmInitialTuiQuery(this.buildHost(), context);
|
|
15203
|
+
this.freshPrewarmedInitial = warmed;
|
|
15204
|
+
return warmed;
|
|
15205
|
+
} catch (err) {
|
|
15206
|
+
logger6.warn("TUI prewarm failed; continuing with normal startup", {
|
|
15207
|
+
error: err instanceof Error ? err.message : String(err)
|
|
15208
|
+
});
|
|
15209
|
+
return false;
|
|
15210
|
+
}
|
|
15211
|
+
}
|
|
14906
15212
|
stop() {
|
|
14907
15213
|
this._stopped = true;
|
|
14908
15214
|
this._abortController?.abort();
|
|
@@ -14952,7 +15258,7 @@ var QueryBridge = class {
|
|
|
14952
15258
|
isAuto: this.mode.isAuto,
|
|
14953
15259
|
agentMode: this.mode.effectiveMode,
|
|
14954
15260
|
isFollowUp: false,
|
|
14955
|
-
hasExistingSession: hasExistingSessionFile(context.taskId, this.runnerConfig.workspaceDir, {
|
|
15261
|
+
hasExistingSession: !this.freshPrewarmedInitial && hasExistingSessionFile(context.taskId, this.runnerConfig.workspaceDir, {
|
|
14956
15262
|
agentMode: this.mode.effectiveMode,
|
|
14957
15263
|
runnerMode: this.runnerConfig.mode
|
|
14958
15264
|
})
|
|
@@ -14975,7 +15281,9 @@ var QueryBridge = class {
|
|
|
14975
15281
|
this._abortController = new AbortController();
|
|
14976
15282
|
const host = this.buildHost();
|
|
14977
15283
|
try {
|
|
14978
|
-
|
|
15284
|
+
const freshPrewarmed = !followUpContent && this.freshPrewarmedInitial;
|
|
15285
|
+
this.freshPrewarmedInitial = false;
|
|
15286
|
+
await runSdkQuery(host, context, followUpContent, promptDelivery, freshPrewarmed);
|
|
14979
15287
|
} catch (err) {
|
|
14980
15288
|
const msg = err instanceof Error ? err.message : String(err);
|
|
14981
15289
|
const isAbort = this._stopped || /abort/i.test(msg);
|
|
@@ -15373,6 +15681,9 @@ async function runUsageProbe(deps = {}) {
|
|
|
15373
15681
|
// src/execution/usage-sampler.ts
|
|
15374
15682
|
var logger7 = createServiceLogger("usage-sampler");
|
|
15375
15683
|
var NO_SAMPLES = { samples: [], unmeasurable: null };
|
|
15684
|
+
function usesNativeUsageReporting(env = process.env) {
|
|
15685
|
+
return env.CONVEYOR_TUI === "codex";
|
|
15686
|
+
}
|
|
15376
15687
|
function isAttributable(identity, sessionToken) {
|
|
15377
15688
|
if (!identity) return { ok: true };
|
|
15378
15689
|
if (identity.hasRefreshToken && !identity.isConveyorOwned) {
|
|
@@ -15689,7 +16000,10 @@ var SessionRunner = class _SessionRunner {
|
|
|
15689
16000
|
});
|
|
15690
16001
|
const initialMode = config.agentMode ?? (config.runnerMode === "pm" ? config.isAuto ? "auto" : "discovery" : "building");
|
|
15691
16002
|
this.mode = new ModeController(initialMode, config.runnerMode, config.isAuto);
|
|
15692
|
-
const lifecycleConfig = {
|
|
16003
|
+
const lifecycleConfig = {
|
|
16004
|
+
...DEFAULT_LIFECYCLE_CONFIG,
|
|
16005
|
+
...config.lifecycle
|
|
16006
|
+
};
|
|
15693
16007
|
this.lifecycle = new Lifecycle(lifecycleConfig, {
|
|
15694
16008
|
onHeartbeat: () => {
|
|
15695
16009
|
const loopStatus = this.refreshLoopStatus();
|
|
@@ -16048,6 +16362,10 @@ var SessionRunner = class _SessionRunner {
|
|
|
16048
16362
|
await this.shutdown("error");
|
|
16049
16363
|
return;
|
|
16050
16364
|
}
|
|
16365
|
+
this.mode.applyServerMode(this.fullContext?.agentMode, this.fullContext?.isAuto);
|
|
16366
|
+
this.mode.resolveInitialMode(this.taskContext);
|
|
16367
|
+
this.queryBridge = this.createQueryBridge();
|
|
16368
|
+
const prewarmInitial = this.queryBridge.prewarmInitial(this.fullContext);
|
|
16051
16369
|
const gitState = await awaitGitReady({
|
|
16052
16370
|
onLog: (m) => process.stderr.write(`[conveyor-agent] ${m}
|
|
16053
16371
|
`)
|
|
@@ -16056,6 +16374,7 @@ var SessionRunner = class _SessionRunner {
|
|
|
16056
16374
|
const message = gitState === "failed" ? "Workspace git preparation failed (see pod logs)" : "Workspace git preparation timed out (see pod logs)";
|
|
16057
16375
|
this.connection.sendEvent({ type: "error", message });
|
|
16058
16376
|
await this.callbacks.onEvent({ type: "error", message });
|
|
16377
|
+
await this.queryBridge.dispose();
|
|
16059
16378
|
await this.shutdown("error");
|
|
16060
16379
|
return;
|
|
16061
16380
|
}
|
|
@@ -16069,8 +16388,8 @@ var SessionRunner = class _SessionRunner {
|
|
|
16069
16388
|
process.stderr.write("[conveyor-agent] WARNING: task-branch checkout failed\n");
|
|
16070
16389
|
}
|
|
16071
16390
|
if (ok) void reportBootMilestone({ key: "branch_ready" });
|
|
16072
|
-
await this.refreshSkillsForCheckout();
|
|
16073
16391
|
}
|
|
16392
|
+
if (this.fullContext?.githubBranch) await this.refreshSkillsForCheckout();
|
|
16074
16393
|
if (!this.stopped) {
|
|
16075
16394
|
this.lifecycle.startGitFlush();
|
|
16076
16395
|
}
|
|
@@ -16085,13 +16404,15 @@ var SessionRunner = class _SessionRunner {
|
|
|
16085
16404
|
}
|
|
16086
16405
|
}
|
|
16087
16406
|
this.workspaceCommands?.notifyWorkspaceReady();
|
|
16088
|
-
this.mode.applyServerMode(this.fullContext?.agentMode, this.fullContext?.isAuto);
|
|
16089
|
-
this.mode.resolveInitialMode(this.taskContext);
|
|
16090
16407
|
if (this.fullContext?.isAuto && PRE_BUILD_TASK_STATUSES.has(this.taskContext.status) && this.mode.isBuildCapable && hasTaskPlan(this.fullContext.plan)) {
|
|
16091
16408
|
void this.connection.triggerIdentification().catch(() => {
|
|
16092
16409
|
});
|
|
16093
16410
|
}
|
|
16094
|
-
|
|
16411
|
+
await prewarmInitial;
|
|
16412
|
+
if (this.stopped) {
|
|
16413
|
+
await this.queryBridge.dispose();
|
|
16414
|
+
return;
|
|
16415
|
+
}
|
|
16095
16416
|
this.logInitialization();
|
|
16096
16417
|
const staleBatch = [...this.pendingMessages];
|
|
16097
16418
|
const didExecuteInitialQuery = await this.executeInitialMode();
|
|
@@ -16284,7 +16605,11 @@ var SessionRunner = class _SessionRunner {
|
|
|
16284
16605
|
}
|
|
16285
16606
|
if (delivery === "prefill") {
|
|
16286
16607
|
await this.setState("waiting_for_input");
|
|
16287
|
-
await this.callbacks.onEvent({
|
|
16608
|
+
await this.callbacks.onEvent({
|
|
16609
|
+
type: "execute_mode",
|
|
16610
|
+
mode: effectiveMode,
|
|
16611
|
+
delivery
|
|
16612
|
+
});
|
|
16288
16613
|
if (this.pendingMessages.length > 0) {
|
|
16289
16614
|
if (!this.stopped) await this.setState("idle");
|
|
16290
16615
|
return false;
|
|
@@ -16297,7 +16622,10 @@ var SessionRunner = class _SessionRunner {
|
|
|
16297
16622
|
}
|
|
16298
16623
|
} else {
|
|
16299
16624
|
await this.setState("running");
|
|
16300
|
-
await this.callbacks.onEvent({
|
|
16625
|
+
await this.callbacks.onEvent({
|
|
16626
|
+
type: "execute_mode",
|
|
16627
|
+
mode: effectiveMode
|
|
16628
|
+
});
|
|
16301
16629
|
await this.executeQuery(void 0, delivery);
|
|
16302
16630
|
await this.requeueWedgedInitialQuery(delivery);
|
|
16303
16631
|
}
|
|
@@ -16606,7 +16934,7 @@ var SessionRunner = class _SessionRunner {
|
|
|
16606
16934
|
* `selectBestKey` rotation honest. Best-effort — never throws, no-op when the
|
|
16607
16935
|
* pod has no OAuth token (e.g. API-key projects). */
|
|
16608
16936
|
async sampleAndReportKeyUsage() {
|
|
16609
|
-
if (this.stopped) return;
|
|
16937
|
+
if (this.stopped || usesNativeUsageReporting()) return;
|
|
16610
16938
|
const codingAgentKeyId = process.env.CONVEYOR_CODING_AGENT_KEY_ID;
|
|
16611
16939
|
const { samples, unmeasurable } = await sampleKeyUsage(
|
|
16612
16940
|
process.env.CLAUDE_CODE_OAUTH_TOKEN,
|
|
@@ -16754,16 +17082,8 @@ var SessionRunner = class _SessionRunner {
|
|
|
16754
17082
|
isAuto: this.config.isAuto
|
|
16755
17083
|
};
|
|
16756
17084
|
const bridge = new QueryBridge(this.connection, this.mode, runnerConfig, {
|
|
16757
|
-
onStatusChange: (status) =>
|
|
16758
|
-
|
|
16759
|
-
this.beginRunningEpisode();
|
|
16760
|
-
if (this._state === "waiting_for_input") {
|
|
16761
|
-
this._state = "running";
|
|
16762
|
-
this.lifecycle.cancelIdleTimer();
|
|
16763
|
-
}
|
|
16764
|
-
}
|
|
16765
|
-
return this.callbacks.onStatusChange(status);
|
|
16766
|
-
},
|
|
17085
|
+
onStatusChange: (status) => this.handleQueryStatus(status),
|
|
17086
|
+
onSubmittedPtyParked: () => this.applySubmittedPtyParkedStatus(),
|
|
16767
17087
|
onEvent: (event) => {
|
|
16768
17088
|
if (!this.agentLiveReported) {
|
|
16769
17089
|
this.agentLiveReported = true;
|
|
@@ -16788,7 +17108,11 @@ var SessionRunner = class _SessionRunner {
|
|
|
16788
17108
|
const oldMode = this.mode.effectiveMode;
|
|
16789
17109
|
process.stderr.write(`[conveyor-agent] Mode transition: ${oldMode} \u2192 ${newMode}
|
|
16790
17110
|
`);
|
|
16791
|
-
this.connection.sendEvent({
|
|
17111
|
+
this.connection.sendEvent({
|
|
17112
|
+
type: "mode_transition",
|
|
17113
|
+
from: oldMode,
|
|
17114
|
+
to: newMode
|
|
17115
|
+
});
|
|
16792
17116
|
this.mode.pendingModeRestart = true;
|
|
16793
17117
|
this.connection.emitModeChanged(newMode);
|
|
16794
17118
|
this.softStop();
|
|
@@ -17016,6 +17340,32 @@ ${outcome.failures.join("\n")}
|
|
|
17016
17340
|
await this.connection.emitStatus(status);
|
|
17017
17341
|
await this.callbacks.onStatusChange(status);
|
|
17018
17342
|
}
|
|
17343
|
+
/**
|
|
17344
|
+
* A submitted PTY query can park on an unexpected terminal dialog without
|
|
17345
|
+
* returning its turn. Keep the runner's local state (which drives heartbeat
|
|
17346
|
+
* liveness) in sync with that specific watchdog signal, but do not emit it
|
|
17347
|
+
* again: QueryBridge already sent it.
|
|
17348
|
+
*
|
|
17349
|
+
* Retry delays and pending questionnaires also report `waiting_for_input`,
|
|
17350
|
+
* but they retain their existing bounded liveness holds and must not change
|
|
17351
|
+
* `_state` here.
|
|
17352
|
+
*/
|
|
17353
|
+
applySubmittedPtyParkedStatus() {
|
|
17354
|
+
this._state = "waiting_for_input";
|
|
17355
|
+
this.refreshLoopStatus();
|
|
17356
|
+
}
|
|
17357
|
+
/** Forward an ordinary query status without changing liveness for transient
|
|
17358
|
+
* waiting states. The submitted-PTY watchdog calls its dedicated handler. */
|
|
17359
|
+
handleQueryStatus(status) {
|
|
17360
|
+
if (status === "running") {
|
|
17361
|
+
this.beginRunningEpisode();
|
|
17362
|
+
if (this._state === "waiting_for_input") {
|
|
17363
|
+
this._state = "running";
|
|
17364
|
+
this.lifecycle.cancelIdleTimer();
|
|
17365
|
+
}
|
|
17366
|
+
}
|
|
17367
|
+
return this.callbacks.onStatusChange(status);
|
|
17368
|
+
}
|
|
17019
17369
|
/** Mark a fresh turn without emitting a duplicate status. QueryExecutor's
|
|
17020
17370
|
* prefilled PTY callback already emitted `running` before it reaches here. */
|
|
17021
17371
|
beginRunningEpisode() {
|
|
@@ -17121,6 +17471,7 @@ export {
|
|
|
17121
17471
|
resolveSessionStart,
|
|
17122
17472
|
parseUsageGauges,
|
|
17123
17473
|
runUsageProbe,
|
|
17474
|
+
usesNativeUsageReporting,
|
|
17124
17475
|
sampleKeyUsage,
|
|
17125
17476
|
buildRateLimitEvents,
|
|
17126
17477
|
buildUnmeasurableEvent,
|