@rallycry/conveyor-agent 11.0.18 → 11.0.19
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/{chunk-ZFHN7GO7.js → chunk-N5LEVAGA.js} +127 -16
- package/dist/cli.js +5 -4
- package/dist/index.d.ts +18 -0
- package/dist/index.js +1 -1
- 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
|
@@ -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;
|
|
@@ -6471,13 +6544,16 @@ async function seedOpenCodeOauth(env) {
|
|
|
6471
6544
|
}
|
|
6472
6545
|
|
|
6473
6546
|
// src/harness/openai-model.ts
|
|
6474
|
-
var DEFAULT_OPENAI_CODING_MODEL =
|
|
6547
|
+
var DEFAULT_OPENAI_CODING_MODEL = DEFAULT_CODEX_CODING_MODEL;
|
|
6475
6548
|
function resolveOpenAiCodingModel(model) {
|
|
6476
6549
|
if (!model || model.startsWith("claude-") || model.includes("/") && !model.startsWith("openai/")) {
|
|
6477
6550
|
return DEFAULT_OPENAI_CODING_MODEL;
|
|
6478
6551
|
}
|
|
6479
6552
|
return model.replace(/^openai\//, "");
|
|
6480
6553
|
}
|
|
6554
|
+
function resolveCodexReasoningEffort(...candidates) {
|
|
6555
|
+
return candidates.find(isCodexReasoningEffort);
|
|
6556
|
+
}
|
|
6481
6557
|
|
|
6482
6558
|
// src/harness/opencode/credentials.ts
|
|
6483
6559
|
var PROVIDER_KEY_ENV = {
|
|
@@ -6987,11 +7063,16 @@ var CodexTuiAdapter = class {
|
|
|
6987
7063
|
sandbox_mode: "danger-full-access",
|
|
6988
7064
|
developer_instructions: [
|
|
6989
7065
|
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."
|
|
7066
|
+
"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
7067
|
].filter(Boolean).join("\n\n"),
|
|
6992
7068
|
mcp_servers: codexMcpConfig(input.mcpEntries ?? {}),
|
|
6993
7069
|
projects: { [trustedCwd]: { trust_level: "trusted" } }
|
|
6994
7070
|
};
|
|
7071
|
+
const effort = resolveCodexReasoningEffort(
|
|
7072
|
+
this.env.CONVEYOR_AGENT_REASONING_EFFORT,
|
|
7073
|
+
input.options.codex?.effort
|
|
7074
|
+
);
|
|
7075
|
+
if (effort) config.model_reasoning_effort = effort;
|
|
6995
7076
|
if (input.pluginPath && input.eventsSinkPath) {
|
|
6996
7077
|
const profile = basename(dirname3(input.pluginPath));
|
|
6997
7078
|
const profilePath = join14(env.CODEX_HOME, `${profile}.config.toml`);
|
|
@@ -7009,7 +7090,9 @@ var CodexTuiAdapter = class {
|
|
|
7009
7090
|
if (input.pluginPath) args.push("--profile", basename(dirname3(input.pluginPath)));
|
|
7010
7091
|
for (const [key, value] of Object.entries(config))
|
|
7011
7092
|
args.push("-c", `${key}=${tomlValue(value)}`);
|
|
7012
|
-
const model = resolveOpenAiCodingModel(
|
|
7093
|
+
const model = resolveOpenAiCodingModel(
|
|
7094
|
+
this.env.CONVEYOR_AGENT_MODEL ?? input.options.codex?.model ?? input.options.model
|
|
7095
|
+
);
|
|
7013
7096
|
args.push("--model", model);
|
|
7014
7097
|
if (input.resume) args.push("resume", input.resume);
|
|
7015
7098
|
if (input.initialPrompt) args.push("--", input.initialPrompt);
|
|
@@ -13243,17 +13326,21 @@ function handleRateLimitEvent(event, host) {
|
|
|
13243
13326
|
const { rate_limit_info } = event;
|
|
13244
13327
|
logger4.info("Rate limit event received", { rate_limit_info });
|
|
13245
13328
|
const status = rate_limit_info.status;
|
|
13329
|
+
const resetsAt = epochSecondsToISO(rate_limit_info.resetsAt);
|
|
13246
13330
|
const utilization = rate_limit_info.utilization ?? (status === "rejected" ? 1 : void 0);
|
|
13247
13331
|
if (utilization !== void 0 && rate_limit_info.rateLimitType) {
|
|
13248
13332
|
host.connection.sendEvent({
|
|
13249
13333
|
type: "rate_limit_update",
|
|
13250
13334
|
rateLimitType: rate_limit_info.rateLimitType,
|
|
13251
13335
|
utilization,
|
|
13252
|
-
status
|
|
13336
|
+
status,
|
|
13337
|
+
// The server parses this into the key's sessionResetsAt / weeklyResetsAt;
|
|
13338
|
+
// Codex reports a reset instant with every gauge, so allowed events carry
|
|
13339
|
+
// it too, not only rejections.
|
|
13340
|
+
...resetsAt ? { resetsAt } : {}
|
|
13253
13341
|
});
|
|
13254
13342
|
}
|
|
13255
13343
|
if (status === "rejected") {
|
|
13256
|
-
const resetsAt = epochSecondsToISO(rate_limit_info.resetsAt);
|
|
13257
13344
|
const resetsAtDisplay = resetsAt ?? "unknown";
|
|
13258
13345
|
const message = `Rate limit rejected (type: ${rate_limit_info.rateLimitType ?? "unknown"}, resets at: ${resetsAtDisplay})`;
|
|
13259
13346
|
host.connection.sendEvent({ type: "error", message });
|
|
@@ -14163,6 +14250,7 @@ function buildQueryOptions(host, context) {
|
|
|
14163
14250
|
effort: settings.effort,
|
|
14164
14251
|
thinking: settings.thinking,
|
|
14165
14252
|
betas: settings.betas,
|
|
14253
|
+
codex: settings.codex,
|
|
14166
14254
|
abortController: host.abortController ?? void 0,
|
|
14167
14255
|
disallowedTools: buildDisallowedTools(settings, mode, host.hasExitedPlanMode),
|
|
14168
14256
|
enableFileCheckpointing: settings.enableFileCheckpointing,
|
|
@@ -14318,6 +14406,7 @@ async function* watchForParkedTui(inner, host, opts) {
|
|
|
14318
14406
|
const timer = setTimeout(() => {
|
|
14319
14407
|
parked = true;
|
|
14320
14408
|
host.connection.emitStatus("waiting_for_input");
|
|
14409
|
+
host.callbacks.onSubmittedPtyParked?.();
|
|
14321
14410
|
void host.callbacks.onStatusChange("waiting_for_input");
|
|
14322
14411
|
}, PARKED_TUI_GRACE_MS);
|
|
14323
14412
|
const silenceTimeoutMs = resolveTurnSilenceTimeoutMs();
|
|
@@ -15373,6 +15462,9 @@ async function runUsageProbe(deps = {}) {
|
|
|
15373
15462
|
// src/execution/usage-sampler.ts
|
|
15374
15463
|
var logger7 = createServiceLogger("usage-sampler");
|
|
15375
15464
|
var NO_SAMPLES = { samples: [], unmeasurable: null };
|
|
15465
|
+
function usesNativeUsageReporting(env = process.env) {
|
|
15466
|
+
return env.CONVEYOR_TUI === "codex";
|
|
15467
|
+
}
|
|
15376
15468
|
function isAttributable(identity, sessionToken) {
|
|
15377
15469
|
if (!identity) return { ok: true };
|
|
15378
15470
|
if (identity.hasRefreshToken && !identity.isConveyorOwned) {
|
|
@@ -16606,7 +16698,7 @@ var SessionRunner = class _SessionRunner {
|
|
|
16606
16698
|
* `selectBestKey` rotation honest. Best-effort — never throws, no-op when the
|
|
16607
16699
|
* pod has no OAuth token (e.g. API-key projects). */
|
|
16608
16700
|
async sampleAndReportKeyUsage() {
|
|
16609
|
-
if (this.stopped) return;
|
|
16701
|
+
if (this.stopped || usesNativeUsageReporting()) return;
|
|
16610
16702
|
const codingAgentKeyId = process.env.CONVEYOR_CODING_AGENT_KEY_ID;
|
|
16611
16703
|
const { samples, unmeasurable } = await sampleKeyUsage(
|
|
16612
16704
|
process.env.CLAUDE_CODE_OAUTH_TOKEN,
|
|
@@ -16754,16 +16846,8 @@ var SessionRunner = class _SessionRunner {
|
|
|
16754
16846
|
isAuto: this.config.isAuto
|
|
16755
16847
|
};
|
|
16756
16848
|
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
|
-
},
|
|
16849
|
+
onStatusChange: (status) => this.handleQueryStatus(status),
|
|
16850
|
+
onSubmittedPtyParked: () => this.applySubmittedPtyParkedStatus(),
|
|
16767
16851
|
onEvent: (event) => {
|
|
16768
16852
|
if (!this.agentLiveReported) {
|
|
16769
16853
|
this.agentLiveReported = true;
|
|
@@ -17016,6 +17100,32 @@ ${outcome.failures.join("\n")}
|
|
|
17016
17100
|
await this.connection.emitStatus(status);
|
|
17017
17101
|
await this.callbacks.onStatusChange(status);
|
|
17018
17102
|
}
|
|
17103
|
+
/**
|
|
17104
|
+
* A submitted PTY query can park on an unexpected terminal dialog without
|
|
17105
|
+
* returning its turn. Keep the runner's local state (which drives heartbeat
|
|
17106
|
+
* liveness) in sync with that specific watchdog signal, but do not emit it
|
|
17107
|
+
* again: QueryBridge already sent it.
|
|
17108
|
+
*
|
|
17109
|
+
* Retry delays and pending questionnaires also report `waiting_for_input`,
|
|
17110
|
+
* but they retain their existing bounded liveness holds and must not change
|
|
17111
|
+
* `_state` here.
|
|
17112
|
+
*/
|
|
17113
|
+
applySubmittedPtyParkedStatus() {
|
|
17114
|
+
this._state = "waiting_for_input";
|
|
17115
|
+
this.refreshLoopStatus();
|
|
17116
|
+
}
|
|
17117
|
+
/** Forward an ordinary query status without changing liveness for transient
|
|
17118
|
+
* waiting states. The submitted-PTY watchdog calls its dedicated handler. */
|
|
17119
|
+
handleQueryStatus(status) {
|
|
17120
|
+
if (status === "running") {
|
|
17121
|
+
this.beginRunningEpisode();
|
|
17122
|
+
if (this._state === "waiting_for_input") {
|
|
17123
|
+
this._state = "running";
|
|
17124
|
+
this.lifecycle.cancelIdleTimer();
|
|
17125
|
+
}
|
|
17126
|
+
}
|
|
17127
|
+
return this.callbacks.onStatusChange(status);
|
|
17128
|
+
}
|
|
17019
17129
|
/** Mark a fresh turn without emitting a duplicate status. QueryExecutor's
|
|
17020
17130
|
* prefilled PTY callback already emitted `running` before it reaches here. */
|
|
17021
17131
|
beginRunningEpisode() {
|
|
@@ -17121,6 +17231,7 @@ export {
|
|
|
17121
17231
|
resolveSessionStart,
|
|
17122
17232
|
parseUsageGauges,
|
|
17123
17233
|
runUsageProbe,
|
|
17234
|
+
usesNativeUsageReporting,
|
|
17124
17235
|
sampleKeyUsage,
|
|
17125
17236
|
buildRateLimitEvents,
|
|
17126
17237
|
buildUnmeasurableEvent,
|
package/dist/cli.js
CHANGED
|
@@ -26,8 +26,9 @@ import {
|
|
|
26
26
|
resolveTuiAdapter,
|
|
27
27
|
resolveTuiKindFromEnv,
|
|
28
28
|
runUsageProbe,
|
|
29
|
-
sampleKeyUsage
|
|
30
|
-
|
|
29
|
+
sampleKeyUsage,
|
|
30
|
+
usesNativeUsageReporting
|
|
31
|
+
} from "./chunk-N5LEVAGA.js";
|
|
31
32
|
import "./chunk-SR66HQKB.js";
|
|
32
33
|
import {
|
|
33
34
|
inheritedEnv,
|
|
@@ -236,7 +237,7 @@ var ProjectSessionRunner = class {
|
|
|
236
237
|
/** Report the running subscription key's rate-limit utilization (same path as
|
|
237
238
|
* the task runner). Best-effort — never throws, no-op without an OAuth token. */
|
|
238
239
|
async sampleAndReportKeyUsage() {
|
|
239
|
-
if (this.stopped) return;
|
|
240
|
+
if (this.stopped || usesNativeUsageReporting()) return;
|
|
240
241
|
const codingAgentKeyId = process.env.CONVEYOR_CODING_AGENT_KEY_ID;
|
|
241
242
|
const { samples, unmeasurable } = await sampleKeyUsage(
|
|
242
243
|
process.env.CLAUDE_CODE_OAUTH_TOKEN,
|
|
@@ -575,7 +576,7 @@ var AdhocSessionRunner = class {
|
|
|
575
576
|
}
|
|
576
577
|
/** Single-key fallback: sample only the key this pod booted under. */
|
|
577
578
|
async sampleAndReportOwnKey() {
|
|
578
|
-
if (this.stopped) return;
|
|
579
|
+
if (this.stopped || usesNativeUsageReporting()) return;
|
|
579
580
|
const codingAgentKeyId = process.env.CONVEYOR_CODING_AGENT_KEY_ID;
|
|
580
581
|
const { samples, unmeasurable } = await sampleKeyUsage(
|
|
581
582
|
process.env.CLAUDE_CODE_OAUTH_TOKEN,
|
package/dist/index.d.ts
CHANGED
|
@@ -1107,6 +1107,20 @@ declare class SessionRunner {
|
|
|
1107
1107
|
* the tool starts work that outlives the turn. */
|
|
1108
1108
|
private noteToolUseForBackgroundWork;
|
|
1109
1109
|
private setState;
|
|
1110
|
+
/**
|
|
1111
|
+
* A submitted PTY query can park on an unexpected terminal dialog without
|
|
1112
|
+
* returning its turn. Keep the runner's local state (which drives heartbeat
|
|
1113
|
+
* liveness) in sync with that specific watchdog signal, but do not emit it
|
|
1114
|
+
* again: QueryBridge already sent it.
|
|
1115
|
+
*
|
|
1116
|
+
* Retry delays and pending questionnaires also report `waiting_for_input`,
|
|
1117
|
+
* but they retain their existing bounded liveness holds and must not change
|
|
1118
|
+
* `_state` here.
|
|
1119
|
+
*/
|
|
1120
|
+
private applySubmittedPtyParkedStatus;
|
|
1121
|
+
/** Forward an ordinary query status without changing liveness for transient
|
|
1122
|
+
* waiting states. The submitted-PTY watchdog calls its dedicated handler. */
|
|
1123
|
+
private handleQueryStatus;
|
|
1110
1124
|
/** Mark a fresh turn without emitting a duplicate status. QueryExecutor's
|
|
1111
1125
|
* prefilled PTY callback already emitted `running` before it reaches here. */
|
|
1112
1126
|
private beginRunningEpisode;
|
|
@@ -1165,6 +1179,10 @@ type UserQuestionSignal = {
|
|
|
1165
1179
|
interface AgentRunnerCallbacks {
|
|
1166
1180
|
onEvent: (event: Record<string, unknown>) => void | Promise<void>;
|
|
1167
1181
|
onStatusChange: (status: string) => void | Promise<void>;
|
|
1182
|
+
/** Fired only when a submitted PTY query stays silent past the parked-TUI
|
|
1183
|
+
* grace window. This differs from ordinary `waiting_for_input` reports:
|
|
1184
|
+
* retry delays and questionnaires keep the runner locally live. */
|
|
1185
|
+
onSubmittedPtyParked?: () => void;
|
|
1168
1186
|
/** Optional — fired on question-pending transitions (see UserQuestionSignal). */
|
|
1169
1187
|
onUserQuestion?: (signal: UserQuestionSignal) => void;
|
|
1170
1188
|
}
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rallycry/conveyor-agent",
|
|
3
|
-
"version": "11.0.
|
|
3
|
+
"version": "11.0.19",
|
|
4
4
|
"description": "Conveyor Agent Runner v10 - PTY harness for the task chat (SDK harness for audit/project-chat). Agent-as-User architecture with BaseService patterns. Works locally too.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -38,6 +38,42 @@ out in an **Environment** note — never assume; check which one you are in.
|
|
|
38
38
|
serially. Parallel fan-out is a deliberate choice the user makes by pressing
|
|
39
39
|
Build on the parent, not something a build session opts into.
|
|
40
40
|
|
|
41
|
+
## Goal and finish line
|
|
42
|
+
|
|
43
|
+
Which goal applies depends on the route below. State it once, then hold it: a
|
|
44
|
+
run ends when its finish line is true, not earlier.
|
|
45
|
+
|
|
46
|
+
**Task path goal:** a pull request for this card.
|
|
47
|
+
**Finish line:** the card in ReviewPR, the PR open, CI started. Then aftercare
|
|
48
|
+
per `## Aftercare` — in a pod you stay wake-able; standalone locally you stop.
|
|
49
|
+
**A task turn may end only when:** a harness-tracked background gate is in
|
|
50
|
+
flight (Claude Code only); you are blocked on a human with the question posted;
|
|
51
|
+
or the finish line is true.
|
|
52
|
+
|
|
53
|
+
**Pack path goal:** the WHOLE pack, not one child. Per child the goal covers
|
|
54
|
+
the full cycle: plan reviewed and promoted → implemented on the pack branch →
|
|
55
|
+
gated → reviewer-of-record pass → merged (locally) or
|
|
56
|
+
`mcp__conveyor__update_task` → ReviewDev (pod) → base synced. Then
|
|
57
|
+
cross-reference, then the finale.
|
|
58
|
+
**Finish line:** every child is ReviewDev, Complete, or Cancelled AND the
|
|
59
|
+
parent's finale PR into the base branch is open with CI started. One child done
|
|
60
|
+
is not the finish line. A turn that ends with a ready child and no wake armed
|
|
61
|
+
is the failure this section exists to prevent.
|
|
62
|
+
**A pack turn may end only when:** a harness-tracked background gate is in
|
|
63
|
+
flight (Claude Code only — Codex holds the gate's command session open
|
|
64
|
+
instead); you are blocked on a human with the parked protocol posted; or the
|
|
65
|
+
finale PR is open and green-and-quiet. Nothing else. Anything else is a stalled
|
|
66
|
+
run, not a paused one.
|
|
67
|
+
|
|
68
|
+
**Runtime:** *Codex CLI* — before the first step (Setup step 1 on the pack
|
|
69
|
+
path), create a thread goal with your goal tool whose objective is the finish
|
|
70
|
+
line sentence for this route, naming the card slug. Call `update_goal` with
|
|
71
|
+
`complete` only when that finish line is true (for a pack: the finale PR is
|
|
72
|
+
open); `blocked` only per the parked protocol; never because the budget is
|
|
73
|
+
low. *Claude Code* — there is no goal tool; the pacing rule in
|
|
74
|
+
[references/pack-path.md](references/pack-path.md) is the loop, and its pack
|
|
75
|
+
heartbeat is the fallback.
|
|
76
|
+
|
|
41
77
|
## Environment
|
|
42
78
|
|
|
43
79
|
Resolve which one you are in before touching git or the filesystem.
|
|
@@ -96,18 +96,39 @@ End a turn with children remaining ONLY when a wake is guaranteed:
|
|
|
96
96
|
|
|
97
97
|
- **A background gate is in flight.** Its completion notification re-invokes
|
|
98
98
|
you. (Launch it with the harness's `run_in_background`; hand-rolled
|
|
99
|
-
backgrounding wakes nothing.)
|
|
99
|
+
backgrounding wakes nothing.) *Codex CLI substitution:* Codex has no
|
|
100
|
+
background gate. Hold the gate's command session open until it returns an
|
|
101
|
+
exit result, then continue in the same turn — ending the turn is not how a
|
|
102
|
+
Codex runner waits.
|
|
100
103
|
- **You are blocked on a human** — the parked protocol below. The user's chat
|
|
101
104
|
reply is the wake.
|
|
102
105
|
- **The finale PR is open and quiet** — the Babysit tier. Green and quiet is
|
|
103
106
|
the end.
|
|
104
107
|
|
|
105
|
-
Any other end-of-turn
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
pack
|
|
108
|
+
Any other end-of-turn is runtime-specific:
|
|
109
|
+
|
|
110
|
+
**Claude Code.** Arm `ScheduleWakeup` first: 60–90s, prompt restating which
|
|
111
|
+
child comes next. That is the pod-sanctioned mechanism (the pod prompt allows
|
|
112
|
+
`ScheduleWakeup` exactly when nothing else will notify you), and it is a rare
|
|
113
|
+
exception, not the cadence. On top of it, run a **pack heartbeat**: at Setup,
|
|
114
|
+
before the first child, arm ONE long fallback wake (`ScheduleWakeup`,
|
|
115
|
+
1200–1500s) whose prompt is the pack continuation — "re-derive with
|
|
116
|
+
`mcp__conveyor__list_subtasks` and take the next ready child; if nothing is
|
|
117
|
+
ready and no gate is in flight, stop" — and re-arm it at every child boundary
|
|
118
|
+
so exactly one is outstanding. A heartbeat that fires mid-work re-derives
|
|
119
|
+
state and is a no-op. This is NOT the "insurance wakeup" the pod prompt
|
|
120
|
+
forbids: that rule is about a background job's own completion notification,
|
|
121
|
+
which does fire. Nothing at all notifies a pack between children, so the
|
|
122
|
+
heartbeat is the only guaranteed wake a Claude pack has. Do not delete it.
|
|
123
|
+
|
|
124
|
+
**Codex CLI.** There is no `ScheduleWakeup` and no `/loop`. The thread goal
|
|
125
|
+
created per the SKILL.md *Goal and finish line* section is the continuation:
|
|
126
|
+
an idle thread is re-prompted to keep working toward it. The only way to leave
|
|
127
|
+
a child ready is therefore to keep working in the same turn — merge, next
|
|
128
|
+
child, merge. Mark the goal complete only when the finale PR is open.
|
|
129
|
+
|
|
130
|
+
Never end a turn "to report progress" — in either runtime, chat is where
|
|
131
|
+
progress goes, and posting there does not end the pack.
|
|
111
132
|
|
|
112
133
|
**Pre-exit invariant.** Before ending ANY turn, re-run
|
|
113
134
|
`mcp__conveyor__list_subtasks`. A child that is `Open` with its dependencies
|
|
@@ -19,6 +19,34 @@ identification decides.** Moving the card to Open fires identification
|
|
|
19
19
|
automatically (story points, icon, agent, and tags if you set none). Never set
|
|
20
20
|
icon or story points yourself, and never `start_task` unless the user asks.
|
|
21
21
|
|
|
22
|
+
## Goal and finish line
|
|
23
|
+
|
|
24
|
+
**Goal:** a plan on this card that a context-free executor can build, or one
|
|
25
|
+
batched round of questions for a human when scope is genuinely ambiguous.
|
|
26
|
+
**Finish line:** the plan is saved with `mcp__conveyor__update_task` AND the
|
|
27
|
+
recommendation is posted with `mcp__conveyor__post_to_chat` and
|
|
28
|
+
`milestone: "plan_ready"`. In a pod, story points and risk are also set
|
|
29
|
+
(`mcp__conveyor__update_task_properties`) and plan mode is exited.
|
|
30
|
+
**A turn may end only when:** (a) the finish line above is true, or (b) an
|
|
31
|
+
`AskUserQuestion` is pending. Nothing else. Anything else is a stalled run, not
|
|
32
|
+
a paused one.
|
|
33
|
+
|
|
34
|
+
**Never ask whether to start building, and never offer to.** Once the plan is
|
|
35
|
+
saved, the handoff is not yours: in the cloud the system spawns a separate
|
|
36
|
+
Builder session for the card, and locally a human runs `/conveyor-build` when
|
|
37
|
+
they are ready. Until one of those happens, every further turn on this card is
|
|
38
|
+
a plan iteration — read the feedback, revise, re-save, re-post `plan_ready`.
|
|
39
|
+
Do not claim the card, do not touch the tree, and do not invoke
|
|
40
|
+
`/conveyor-build` unprompted. (Locally the user may ask for it in the same
|
|
41
|
+
session; that request is the only trigger.)
|
|
42
|
+
|
|
43
|
+
**Runtime:** *Codex CLI* — before Phase 0, create a thread goal with your goal
|
|
44
|
+
tool whose objective is: "Save a plan on card <slug> that a context-free
|
|
45
|
+
builder can execute, or ask the blocking questions; do not start
|
|
46
|
+
implementation." Mark it complete only when the finish line is true, never
|
|
47
|
+
because the budget is low. *Claude Code* — there is no goal tool; the finish
|
|
48
|
+
line and the two turn endings above are the loop.
|
|
49
|
+
|
|
22
50
|
## Phase 0 — Resolve context
|
|
23
51
|
|
|
24
52
|
1. `mcp__conveyor__get_connection_context` (all Conveyor tools fully-qualified;
|
|
@@ -135,7 +163,10 @@ touching Conveyor, unless they asked you to just ship it.
|
|
|
135
163
|
> does not exist in a pod. In a pod, the card exists and you are on it: save
|
|
136
164
|
> the plan with `mcp__conveyor__update_task` (`plan`, and `description` if it
|
|
137
165
|
> needs sharpening), post the same recommendation message to chat with
|
|
138
|
-
> `milestone: "plan_ready"`, and stop
|
|
166
|
+
> `milestone: "plan_ready"`, and stop. "Stop" means end the turn with no
|
|
167
|
+
> question about starting work — per *Goal and finish line*, the Builder is a
|
|
168
|
+
> separate session the system spawns, and every later turn here is a plan
|
|
169
|
+
> iteration. A pod does not promote its own card to
|
|
139
170
|
> Open. A parked plan is never `blocked`: that milestone means you cannot
|
|
140
171
|
> continue until a human acts, and it pages nobody. To page a person, ask them
|
|
141
172
|
> with AskUserQuestion. If the pod also has to leave
|
|
@@ -26,6 +26,30 @@ process, this skill loses: a project's own rules outrank a general one.
|
|
|
26
26
|
PR touches are not this PR's job — note them in chat if they matter, or file
|
|
27
27
|
a suggestion, but do not block on them.
|
|
28
28
|
|
|
29
|
+
## Goal and finish line
|
|
30
|
+
|
|
31
|
+
**Goal:** one verdict on this PR.
|
|
32
|
+
**Finish line:** the environment's verdict tool returned success —
|
|
33
|
+
`mcp__conveyor__approve_code_review` / `mcp__conveyor__request_code_changes`
|
|
34
|
+
in a pod, `mcp__conveyor__approve_task` / `mcp__conveyor__request_changes`
|
|
35
|
+
locally — OR a blocked note is posted with the specific blocker and no verdict
|
|
36
|
+
(the `## Blocked` case).
|
|
37
|
+
**A turn may end only when:** (a) the verdict call succeeded — read the
|
|
38
|
+
response; a "superseded by a newer review cycle" error is not success; (b) the
|
|
39
|
+
blocked note is posted; or (c) you pushed your own fix and are waiting for CI
|
|
40
|
+
before approving. Nothing else. Findings posted with no verdict called is a
|
|
41
|
+
stalled review, not a paused one.
|
|
42
|
+
|
|
43
|
+
**Reviewer of record for a pack child** has no verdict tool: its finish line is
|
|
44
|
+
the merge (locally) or the `mcp__conveyor__update_task` → ReviewDev write
|
|
45
|
+
(pod), and the pack path's goal wording in conveyor-build governs instead.
|
|
46
|
+
|
|
47
|
+
**Runtime:** *Codex CLI* — before reading the change, create a thread goal with
|
|
48
|
+
your goal tool: "Render exactly one review verdict on PR <n> for card <slug>."
|
|
49
|
+
Mark it complete only after the verdict call succeeded, never because the
|
|
50
|
+
budget is low. *Claude Code* — there is no goal tool; the finish line above is
|
|
51
|
+
the loop.
|
|
52
|
+
|
|
29
53
|
## Read the change
|
|
30
54
|
|
|
31
55
|
1. `mcp__conveyor__get_task` for the plan — you cannot judge "does this do what
|
|
@@ -142,8 +166,10 @@ is the reviewer, and the independent review happens later on the pack's PR into
|
|
|
142
166
|
|
|
143
167
|
- Apply the same criteria above. The absence of an automated pass makes this
|
|
144
168
|
review more load-bearing, not less.
|
|
145
|
-
- There is **no verdict tool** — merging the child PR
|
|
146
|
-
|
|
169
|
+
- There is **no verdict tool** — merging the child PR (locally) or the
|
|
170
|
+
`mcp__conveyor__update_task` → ReviewDev write (pod) is the approval, and
|
|
171
|
+
that write or merge is this review's finish line; the pack path's goal
|
|
172
|
+
wording applies. Say what you checked in chat so the record exists.
|
|
147
173
|
- You wrote this code, which makes self-review the weak point. An independent
|
|
148
174
|
reviewer with the diff and no memory of writing it catches what you cannot.
|
|
149
175
|
|