@rallycry/conveyor-agent 11.0.28 → 11.0.29
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-ND5INHM4.js → chunk-FF5JML7W.js} +112 -12
- package/dist/{chunk-GHNYP67H.js → chunk-KLM6ZOUX.js} +96 -15
- package/dist/{chunk-DMTBXIA4.js → chunk-V5X3JCAZ.js} +7 -2
- package/dist/cli.js +4 -4
- package/dist/index.d.ts +28 -6
- package/dist/index.js +2 -2
- package/dist/{serve-boot-CYVW7Z27.js → serve-boot-N5XKECWC.js} +2 -2
- package/package.json +1 -1
- package/skills/conveyor-workflows/SKILL.md +11 -0
|
@@ -95,7 +95,7 @@ import {
|
|
|
95
95
|
statWorkspacePath,
|
|
96
96
|
updateRemoteToken,
|
|
97
97
|
verifyGitCredential
|
|
98
|
-
} from "./chunk-
|
|
98
|
+
} from "./chunk-KLM6ZOUX.js";
|
|
99
99
|
import {
|
|
100
100
|
registerBootMilestoneSocketFallback,
|
|
101
101
|
reportBootMilestone
|
|
@@ -550,6 +550,8 @@ var MAX_TRACKED_MESSAGES = 200;
|
|
|
550
550
|
var CHAT_TEXT_MAX = 16e3;
|
|
551
551
|
var CHAT_TOOL_INPUT_MAX = 1900;
|
|
552
552
|
var CHAT_TOOL_OUTPUT_MAX = 1900;
|
|
553
|
+
var OPENCODE_LIVENESS_THROTTLE_MS = 5e3;
|
|
554
|
+
var MAX_TRACKED_CHILD_SESSIONS = 100;
|
|
553
555
|
function truncate(text, max) {
|
|
554
556
|
return text.length > max ? `${text.slice(0, max)}\u2026` : text;
|
|
555
557
|
}
|
|
@@ -585,6 +587,9 @@ var OpenCodeEventSource = class {
|
|
|
585
587
|
relayedTextParts = /* @__PURE__ */ new Set();
|
|
586
588
|
relayedToolUses = /* @__PURE__ */ new Set();
|
|
587
589
|
relayedToolResults = /* @__PURE__ */ new Set();
|
|
590
|
+
/** Session ids opencode reported with a `parentID` — subagent sessions. */
|
|
591
|
+
childSessions = /* @__PURE__ */ new Set();
|
|
592
|
+
lastLivenessAt = Number.NEGATIVE_INFINITY;
|
|
588
593
|
/** The opencode-assigned session id, once any event has carried it. */
|
|
589
594
|
get sessionId() {
|
|
590
595
|
return this.latchedSessionId;
|
|
@@ -598,11 +603,21 @@ var OpenCodeEventSource = class {
|
|
|
598
603
|
handleRecord(raw) {
|
|
599
604
|
if (typeof raw !== "object" || raw === null) return;
|
|
600
605
|
const event = raw;
|
|
606
|
+
this.trackChildSession(event);
|
|
601
607
|
this.latchSessionId(event);
|
|
608
|
+
if (this.isForeignSession(event)) {
|
|
609
|
+
if (this.active) this.emitLiveness();
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
602
612
|
const info = busInfoOf(event);
|
|
603
613
|
if (info?.id && info.role) this.trackRole(info.id, info.role);
|
|
604
614
|
if (isBusyEvent(event)) {
|
|
605
615
|
if (!this.active) this.beginBusTurn();
|
|
616
|
+
this.emitLiveness();
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
if (isRetryEvent(event)) {
|
|
620
|
+
if (this.active) this.emitLiveness();
|
|
606
621
|
return;
|
|
607
622
|
}
|
|
608
623
|
if (isIdleEvent(event)) {
|
|
@@ -629,15 +644,58 @@ var OpenCodeEventSource = class {
|
|
|
629
644
|
this.relayAssistantPart(part);
|
|
630
645
|
accumulateUsage({ part }, this.usage);
|
|
631
646
|
const mapped = mapOpenCodeEvent({ part });
|
|
632
|
-
if (!mapped)
|
|
647
|
+
if (!mapped) {
|
|
648
|
+
this.emitLiveness();
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
633
651
|
this.emit(mapped);
|
|
634
652
|
if (mapped.type === "assistant") {
|
|
635
653
|
const block = mapped.message.content[0];
|
|
636
654
|
if (block?.type === "text" && block.text) this.assistantText += block.text;
|
|
637
655
|
}
|
|
638
656
|
}
|
|
657
|
+
/**
|
|
658
|
+
* Local-only proof the turn is live. Throttled per turn; `beginBusTurn`
|
|
659
|
+
* resets the floor so every turn's `busy` gets through.
|
|
660
|
+
*/
|
|
661
|
+
emitLiveness() {
|
|
662
|
+
const now = Date.now();
|
|
663
|
+
if (now - this.lastLivenessAt < OPENCODE_LIVENESS_THROTTLE_MS) return;
|
|
664
|
+
this.lastLivenessAt = now;
|
|
665
|
+
this.emit({ type: "tool_progress", tool_name: "OpenCode", elapsed_time_seconds: 0 });
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* A record from another session. Known children (a `parentID` was seen)
|
|
669
|
+
* always are; an unknown session counts as foreign only mid-turn, where
|
|
670
|
+
* the only other session on the bus is a subagent. Outside a turn it is
|
|
671
|
+
* processed as before, and one that goes `busy` becomes the new root — a
|
|
672
|
+
* human's `/new` in the parked TUI opens a passive turn and later spawns
|
|
673
|
+
* resume into it.
|
|
674
|
+
*/
|
|
675
|
+
isForeignSession(event) {
|
|
676
|
+
const id = busSessionIdOf(event);
|
|
677
|
+
if (!id || !this.latchedSessionId || id === this.latchedSessionId) return false;
|
|
678
|
+
if (this.childSessions.has(id) || this.active) return true;
|
|
679
|
+
if (isBusyEvent(event)) {
|
|
680
|
+
this.latchedSessionId = id;
|
|
681
|
+
this.onSessionId?.(id);
|
|
682
|
+
}
|
|
683
|
+
return false;
|
|
684
|
+
}
|
|
685
|
+
trackChildSession(event) {
|
|
686
|
+
if (event.type !== "session.created" && event.type !== "session.updated") return;
|
|
687
|
+
const info = event.properties?.info;
|
|
688
|
+
if (typeof info?.id !== "string" || typeof info.parentID !== "string") return;
|
|
689
|
+
if (!info.parentID) return;
|
|
690
|
+
this.childSessions.add(info.id);
|
|
691
|
+
if (this.childSessions.size > MAX_TRACKED_CHILD_SESSIONS) {
|
|
692
|
+
const oldest = this.childSessions.values().next().value;
|
|
693
|
+
if (oldest !== void 0) this.childSessions.delete(oldest);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
639
696
|
beginBusTurn() {
|
|
640
697
|
this.active = true;
|
|
698
|
+
this.lastLivenessAt = Number.NEGATIVE_INFINITY;
|
|
641
699
|
this.usage = { inputTokens: 0, outputTokens: 0, totalCostUsd: 0 };
|
|
642
700
|
this.assistantText = "";
|
|
643
701
|
this.relayedTextParts.clear();
|
|
@@ -705,7 +763,7 @@ var OpenCodeEventSource = class {
|
|
|
705
763
|
latchSessionId(event) {
|
|
706
764
|
if (this.latchedSessionId) return;
|
|
707
765
|
const id = busSessionIdOf(event);
|
|
708
|
-
if (!id) return;
|
|
766
|
+
if (!id || this.childSessions.has(id)) return;
|
|
709
767
|
this.latchedSessionId = id;
|
|
710
768
|
this.onSessionId?.(id);
|
|
711
769
|
}
|
|
@@ -717,6 +775,9 @@ var OpenCodeEventSource = class {
|
|
|
717
775
|
}
|
|
718
776
|
}
|
|
719
777
|
};
|
|
778
|
+
function isRetryEvent(event) {
|
|
779
|
+
return event.type === "session.status" && event.properties?.status?.type === "retry";
|
|
780
|
+
}
|
|
720
781
|
function describeSessionError(event) {
|
|
721
782
|
const error = event.properties?.error;
|
|
722
783
|
if (error && typeof error.message === "string") return error.message;
|
|
@@ -1008,6 +1069,7 @@ var JsonlTailer = class {
|
|
|
1008
1069
|
|
|
1009
1070
|
// src/harness/codex/event-source.ts
|
|
1010
1071
|
var USAGE_CAP_PATTERN = /usage_limit_(?:reached|exceeded)|rate_limit_reached|UsageLimitReached|hit your usage limit/i;
|
|
1072
|
+
var CODEX_LIVENESS_THROTTLE_MS = 5e3;
|
|
1011
1073
|
var WEEKLY_WINDOW_MINUTES = 24 * 60;
|
|
1012
1074
|
function isUsageCap(error) {
|
|
1013
1075
|
return [error.codex_error_info, error.code, error.type, error.message].some(
|
|
@@ -1024,6 +1086,9 @@ var CodexEventSource = class {
|
|
|
1024
1086
|
onSession;
|
|
1025
1087
|
chat;
|
|
1026
1088
|
sessionId = null;
|
|
1089
|
+
/** UserPromptSubmit seen and no completion/interrupt since. */
|
|
1090
|
+
turnOpen = false;
|
|
1091
|
+
lastLivenessAt = Number.NEGATIVE_INFINITY;
|
|
1027
1092
|
completed = /* @__PURE__ */ new Set();
|
|
1028
1093
|
transcript = null;
|
|
1029
1094
|
/** Last reported reading per rate-limit window, keyed by `five_hour` / `seven_day`. */
|
|
@@ -1037,13 +1102,18 @@ var CodexEventSource = class {
|
|
|
1037
1102
|
this.sessionId = id;
|
|
1038
1103
|
this.onSession(id);
|
|
1039
1104
|
}
|
|
1040
|
-
if (id !== this.sessionId)
|
|
1105
|
+
if (id !== this.sessionId) {
|
|
1106
|
+
this.emitLiveness();
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1041
1109
|
if (e.hook_event_name === "SessionStart") this.watchTranscript(e);
|
|
1042
1110
|
if (e.type === "agent-turn-complete") {
|
|
1043
1111
|
this.finish(e);
|
|
1044
1112
|
return;
|
|
1045
1113
|
}
|
|
1046
1114
|
if (e.hook_event_name === "UserPromptSubmit") {
|
|
1115
|
+
this.turnOpen = true;
|
|
1116
|
+
this.lastLivenessAt = Date.now();
|
|
1047
1117
|
this.emit({ type: "tool_progress", tool_name: "Codex", elapsed_time_seconds: 0 });
|
|
1048
1118
|
if (typeof e.prompt === "string")
|
|
1049
1119
|
this.chat({ kind: "user_text", text: e.prompt.slice(0, 16e3) });
|
|
@@ -1056,6 +1126,7 @@ var CodexEventSource = class {
|
|
|
1056
1126
|
elapsed_time_seconds: 0
|
|
1057
1127
|
});
|
|
1058
1128
|
} else if (e.hook_event_name === "Interrupt") {
|
|
1129
|
+
this.turnOpen = false;
|
|
1059
1130
|
this.emit({ type: "result", subtype: "error", errors: ["Codex turn interrupted"] });
|
|
1060
1131
|
this.chat({ kind: "turn_end" });
|
|
1061
1132
|
}
|
|
@@ -1065,6 +1136,14 @@ var CodexEventSource = class {
|
|
|
1065
1136
|
await this.transcript?.flush();
|
|
1066
1137
|
this.transcript = null;
|
|
1067
1138
|
}
|
|
1139
|
+
/** Throttled local-only liveness; silent unless a turn is open. */
|
|
1140
|
+
emitLiveness() {
|
|
1141
|
+
if (!this.turnOpen) return;
|
|
1142
|
+
const now = Date.now();
|
|
1143
|
+
if (now - this.lastLivenessAt < CODEX_LIVENESS_THROTTLE_MS) return;
|
|
1144
|
+
this.lastLivenessAt = now;
|
|
1145
|
+
this.emit({ type: "tool_progress", tool_name: "Codex", elapsed_time_seconds: 0 });
|
|
1146
|
+
}
|
|
1068
1147
|
watchTranscript(e) {
|
|
1069
1148
|
if (this.transcript || typeof e.transcript_path !== "string" || typeof e.transcript_offset !== "number" || e.transcript_offset < 0)
|
|
1070
1149
|
return;
|
|
@@ -1079,9 +1158,10 @@ var CodexEventSource = class {
|
|
|
1079
1158
|
handleTranscriptRecord(raw) {
|
|
1080
1159
|
if (!raw || typeof raw !== "object") return;
|
|
1081
1160
|
const record = raw;
|
|
1082
|
-
if (
|
|
1083
|
-
return;
|
|
1161
|
+
if (!record.payload || typeof record.payload !== "object") return;
|
|
1084
1162
|
const event = record.payload;
|
|
1163
|
+
if (event.type !== "task_complete") this.emitLiveness();
|
|
1164
|
+
if (record.type !== "event_msg") return;
|
|
1085
1165
|
if (event.type === "token_count") {
|
|
1086
1166
|
this.handleTokenCount(event);
|
|
1087
1167
|
return;
|
|
@@ -1090,6 +1170,7 @@ var CodexEventSource = class {
|
|
|
1090
1170
|
const turn = event.turn_id;
|
|
1091
1171
|
if (typeof turn !== "string" || this.completed.has(turn)) return;
|
|
1092
1172
|
this.rememberTurn(turn);
|
|
1173
|
+
this.turnOpen = false;
|
|
1093
1174
|
const error = event.error;
|
|
1094
1175
|
if (isUsageCap(error)) this.emitUsageCap();
|
|
1095
1176
|
this.emit({
|
|
@@ -1178,6 +1259,7 @@ var CodexEventSource = class {
|
|
|
1178
1259
|
const turn = e["turn-id"];
|
|
1179
1260
|
if (typeof turn !== "string" || this.completed.has(turn)) return;
|
|
1180
1261
|
this.rememberTurn(turn);
|
|
1262
|
+
this.turnOpen = false;
|
|
1181
1263
|
const text = typeof e["last-assistant-message"] === "string" ? e["last-assistant-message"] : "";
|
|
1182
1264
|
if (text) {
|
|
1183
1265
|
this.emit({
|
|
@@ -2643,6 +2725,7 @@ var ClaudeTuiAdapter = class {
|
|
|
2643
2725
|
};
|
|
2644
2726
|
|
|
2645
2727
|
// src/harness/pty/session.ts
|
|
2728
|
+
var SUBMIT_PROVING_PROGRESS_ADAPTERS = /* @__PURE__ */ new Set(["codex", "opencode"]);
|
|
2646
2729
|
var PtySession = class {
|
|
2647
2730
|
constructor(prompt, options, resume, bridge, adapter = new ClaudeTuiAdapter()) {
|
|
2648
2731
|
this.options = options;
|
|
@@ -3702,7 +3785,7 @@ var PtySession = class {
|
|
|
3702
3785
|
if (this.pendingPlanApproval && event.type === "result") {
|
|
3703
3786
|
this.disarmPlanDialogAutoAccept();
|
|
3704
3787
|
}
|
|
3705
|
-
if (this.pendingSubmitNudge && (event.type === "assistant" || event.type === "result" || this.adapter.id
|
|
3788
|
+
if (this.pendingSubmitNudge && (event.type === "assistant" || event.type === "result" || SUBMIT_PROVING_PROGRESS_ADAPTERS.has(this.adapter.id) && event.type === "tool_progress")) {
|
|
3706
3789
|
this.disarmSubmitNudge();
|
|
3707
3790
|
}
|
|
3708
3791
|
this.synthesizeRateLimitFromBanner(event);
|
|
@@ -4919,6 +5002,13 @@ var CodexTuiAdapter = class {
|
|
|
4919
5002
|
// The pod image owns CLI upgrades. An interactive update would try to
|
|
4920
5003
|
// replace the root-owned global package from the conveyor user account.
|
|
4921
5004
|
check_for_update_on_startup: false,
|
|
5005
|
+
// Acknowledge 0.157.0's model promotions so unattended sessions start.
|
|
5006
|
+
// This dismisses the notices; --model below still selects the saved model.
|
|
5007
|
+
"notice.model_migrations": {
|
|
5008
|
+
"gpt-5.6-terra": "gpt-6-sol",
|
|
5009
|
+
"gpt-5.6-sol": "gpt-6-sol",
|
|
5010
|
+
"gpt-5.6-luna": "gpt-6-luna"
|
|
5011
|
+
},
|
|
4922
5012
|
sqlite_home: codexSqliteHome(this.env),
|
|
4923
5013
|
approval_policy: "never",
|
|
4924
5014
|
// Always full access, plan mode included. Codex's own read-only mode is
|
|
@@ -5217,7 +5307,10 @@ var DirectStreamController = class {
|
|
|
5217
5307
|
this.starting = true;
|
|
5218
5308
|
const created = new PtyStreamServer({
|
|
5219
5309
|
sessionId: this.reporter.sessionId,
|
|
5220
|
-
onInput: (data) =>
|
|
5310
|
+
onInput: (data) => {
|
|
5311
|
+
this.reporter.notePtyInput();
|
|
5312
|
+
this.inputHandler?.(data);
|
|
5313
|
+
},
|
|
5221
5314
|
onResize: (cols, rows) => {
|
|
5222
5315
|
this.directDims = { cols, rows };
|
|
5223
5316
|
this.applyDims();
|
|
@@ -8597,7 +8690,7 @@ var waitForChecksContract = defineToolContract({
|
|
|
8597
8690
|
var ciWaitContracts = [waitForChecksContract];
|
|
8598
8691
|
var DESCRIPTION = [
|
|
8599
8692
|
"Ask typed questions about text and get probabilities back, never text: yes/no (noul \u2192 probability of yes), pick-one (choice \u2192 the chosen option, per-option probabilities, and a confidence), or a graded scale (score \u2192 the level, per-level probabilities, and a confidence).",
|
|
8600
|
-
"Requires
|
|
8693
|
+
"Requires Jev to be switched on under Project Settings \u2192 AI Provider Keys (a TypeSafe key, or an OpenCode Zen key); without it the tool returns an error saying so.",
|
|
8601
8694
|
"The state is data, not a guardrail: adversarial text inside it can move the answers, so never use a result to authorize an action.",
|
|
8602
8695
|
"Ask one simple judgment per question and split compound questions; accuracy falls when the state carries irrelevant text.",
|
|
8603
8696
|
"Pass items to judge up to 100 separate texts with the same questions in one call; answers come back in item order.",
|
|
@@ -12926,13 +13019,17 @@ async function runPassiveTurn(host, context) {
|
|
|
12926
13019
|
host.activeQuery = null;
|
|
12927
13020
|
}
|
|
12928
13021
|
}
|
|
13022
|
+
function parkedTuiWatchOptions(harness, cwd) {
|
|
13023
|
+
if (harness.emitsStructuredEvents === false) return { disableWedgeAbort: true };
|
|
13024
|
+
if (harness.ownsClaudeConfigHome === false) return {};
|
|
13025
|
+
return { probeMountDead: () => isConfigHomeMountDead(cwd) };
|
|
13026
|
+
}
|
|
12929
13027
|
async function trackAndRun(host, context, options, agentQuery) {
|
|
12930
13028
|
if (host.harnessKind === "pty" && options.promptDelivery !== "prefill") {
|
|
12931
|
-
const rawRelay = host.harness.emitsStructuredEvents === false;
|
|
12932
13029
|
agentQuery = watchForParkedTui(
|
|
12933
13030
|
agentQuery,
|
|
12934
13031
|
host,
|
|
12935
|
-
|
|
13032
|
+
parkedTuiWatchOptions(host.harness, options.cwd)
|
|
12936
13033
|
);
|
|
12937
13034
|
}
|
|
12938
13035
|
host.activeQuery = agentQuery;
|
|
@@ -14320,6 +14417,7 @@ var SessionRunner = class _SessionRunner {
|
|
|
14320
14417
|
onGitFlush: () => void this.periodicGitFlush(),
|
|
14321
14418
|
onUsageSample: () => void this.sampleAndReportKeyUsage()
|
|
14322
14419
|
});
|
|
14420
|
+
this.connection.onPtyInputActivity(() => this.lifecycle.touchIdleTimer());
|
|
14323
14421
|
}
|
|
14324
14422
|
get state() {
|
|
14325
14423
|
return this._state;
|
|
@@ -15656,7 +15754,9 @@ ${outcome.failures.join("\n")}
|
|
|
15656
15754
|
* turn: a background launch, or an armed `ScheduleWakeup`. */
|
|
15657
15755
|
noteToolUseForBackgroundWork(event) {
|
|
15658
15756
|
if (event.type !== "tool_use" || typeof event.tool !== "string") return;
|
|
15659
|
-
this.
|
|
15757
|
+
if (resolveCardTui(this.config.runnerMode) === "claude-code") {
|
|
15758
|
+
this.backgroundWork.noteToolUse(event.tool, event.input);
|
|
15759
|
+
}
|
|
15660
15760
|
this.scheduledWakeup.noteToolUse(event.tool, event.input);
|
|
15661
15761
|
}
|
|
15662
15762
|
async setState(status) {
|
|
@@ -244,6 +244,7 @@ function syncBundleGithubToken(token) {
|
|
|
244
244
|
var logger = createServiceLogger("agent-connection");
|
|
245
245
|
var EVENT_BATCH_MS = 500;
|
|
246
246
|
var MAX_EVENT_BUFFER = 5e3;
|
|
247
|
+
var PTY_INPUT_REPORT_INTERVAL_MS = 3e4;
|
|
247
248
|
var TOKEN_REFRESH_INTERVAL_MS = 45 * 60 * 1e3;
|
|
248
249
|
var AgentConnection = class _AgentConnection {
|
|
249
250
|
socket = null;
|
|
@@ -291,6 +292,9 @@ var AgentConnection = class _AgentConnection {
|
|
|
291
292
|
* question-grace expiry reads it to defer un-parking while a human is
|
|
292
293
|
* actively typing into the Connected-TUI (they may be mid-answer). */
|
|
293
294
|
_lastPtyInputAt = null;
|
|
295
|
+
/** Runner-side subscribers to keystrokes from EITHER transport. */
|
|
296
|
+
ptyInputActivityListeners = /* @__PURE__ */ new Set();
|
|
297
|
+
lastReportedDirectInputAt = Number.NEGATIVE_INFINITY;
|
|
294
298
|
constructor(config) {
|
|
295
299
|
this.config = config;
|
|
296
300
|
}
|
|
@@ -479,7 +483,7 @@ var AgentConnection = class _AgentConnection {
|
|
|
479
483
|
});
|
|
480
484
|
this.socket.on("pty:input", (data) => {
|
|
481
485
|
if (data.sessionId && data.sessionId !== this.config.sessionId) return;
|
|
482
|
-
this.
|
|
486
|
+
this.notePtyInput("relay");
|
|
483
487
|
this.ptyInputCallback?.(data.data);
|
|
484
488
|
});
|
|
485
489
|
this.socket.on("pty:resize", (data) => {
|
|
@@ -946,7 +950,32 @@ var AgentConnection = class _AgentConnection {
|
|
|
946
950
|
}).catch(() => {
|
|
947
951
|
});
|
|
948
952
|
}
|
|
949
|
-
/**
|
|
953
|
+
/**
|
|
954
|
+
* A human typed into the TUI. Stamps `lastPtyInputAt` and notifies the
|
|
955
|
+
* runner's input-activity listeners for both transports. Direct-stream
|
|
956
|
+
* keystrokes never cross the API, so for those this also tells the server
|
|
957
|
+
* (`reportPtyInput`, throttled, fire-and-forget) — the relay path's server
|
|
958
|
+
* already bumps in `relayPtyInput`. An old API that doesn't know the method
|
|
959
|
+
* rejects harmlessly.
|
|
960
|
+
*/
|
|
961
|
+
notePtyInput(origin = "direct") {
|
|
962
|
+
const now = Date.now();
|
|
963
|
+
this._lastPtyInputAt = now;
|
|
964
|
+
for (const listener of this.ptyInputActivityListeners) listener();
|
|
965
|
+
if (origin !== "direct" || !this.socket) return;
|
|
966
|
+
if (now - this.lastReportedDirectInputAt < PTY_INPUT_REPORT_INTERVAL_MS) return;
|
|
967
|
+
this.lastReportedDirectInputAt = now;
|
|
968
|
+
void this.call("reportPtyInput", { sessionId: this.config.sessionId }).catch(() => {
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
/** Subscribe to keystroke activity from either transport. Returns an unsubscribe fn. */
|
|
972
|
+
onPtyInputActivity(listener) {
|
|
973
|
+
this.ptyInputActivityListeners.add(listener);
|
|
974
|
+
return () => {
|
|
975
|
+
this.ptyInputActivityListeners.delete(listener);
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
/** Epoch ms of the last TUI keystroke (relay or direct), or null before any. */
|
|
950
979
|
get lastPtyInputAt() {
|
|
951
980
|
return this._lastPtyInputAt;
|
|
952
981
|
}
|
|
@@ -1446,7 +1475,7 @@ ${q.question}${q.options.length ? "\n" + q.options.map((o) => `- ${o.label}: ${o
|
|
|
1446
1475
|
}
|
|
1447
1476
|
};
|
|
1448
1477
|
|
|
1449
|
-
// ../shared/dist/chunk-
|
|
1478
|
+
// ../shared/dist/chunk-3D2OVVIS.js
|
|
1450
1479
|
import { z } from "zod";
|
|
1451
1480
|
import { z as z2 } from "zod";
|
|
1452
1481
|
var DEFAULT_SONNET_MODEL = "claude-sonnet-5";
|
|
@@ -1455,6 +1484,15 @@ var DEFAULT_HAIKU_MODEL = "claude-haiku-4-5-20251001";
|
|
|
1455
1484
|
var FABLE_MODEL = "claude-fable-5-1";
|
|
1456
1485
|
var PREVIOUS_SONNET_MODEL = "claude-sonnet-4-6";
|
|
1457
1486
|
var PREVIOUS_OPUS_MODEL = "claude-opus-5";
|
|
1487
|
+
function formatModelId(provider, model) {
|
|
1488
|
+
return `${provider}/${model}`;
|
|
1489
|
+
}
|
|
1490
|
+
var JEV_MODEL = "jev-1.13.0";
|
|
1491
|
+
var JEV_MODEL_ID = formatModelId("typesafe", JEV_MODEL);
|
|
1492
|
+
var JEV_ZEN_MODEL = "jev-1.13";
|
|
1493
|
+
var JEV_ZEN_MODEL_ID = formatModelId("zen", JEV_ZEN_MODEL);
|
|
1494
|
+
var JEV_INPUT_PRICE_PER_TOKEN = 0.042 / 1e6;
|
|
1495
|
+
var JEV_STATE_MAX_CHARS = 1e5;
|
|
1458
1496
|
var PTY_STREAM_PORT_BASE = 7420;
|
|
1459
1497
|
var PTY_STREAM_PORT_ATTEMPTS = 8;
|
|
1460
1498
|
function encodePtyStreamFrame(frame) {
|
|
@@ -1508,6 +1546,11 @@ var PREVIEW_PORT_DENY_LIST = [
|
|
|
1508
1546
|
9200,
|
|
1509
1547
|
...Array.from({ length: PTY_STREAM_PORT_ATTEMPTS }, (_, i) => PTY_STREAM_PORT_BASE + i)
|
|
1510
1548
|
];
|
|
1549
|
+
function isAllowablePreviewPort(port) {
|
|
1550
|
+
if (typeof port !== "number" || !Number.isInteger(port)) return false;
|
|
1551
|
+
if (port < 1 || port > 65535) return false;
|
|
1552
|
+
return !PREVIEW_PORT_DENY_LIST.includes(port);
|
|
1553
|
+
}
|
|
1511
1554
|
function normalizeCheckpointPath(value) {
|
|
1512
1555
|
let normalized = value.trim().replace(/\/{2,}/g, "/");
|
|
1513
1556
|
normalized = normalized.split("/").filter((segment) => segment !== ".").join("/");
|
|
@@ -1973,7 +2016,9 @@ var KnownAgentEventSchema = z22.discriminatedUnion("type", [
|
|
|
1973
2016
|
startCommandRunning: z22.boolean().optional(),
|
|
1974
2017
|
startCommandConfigured: z22.boolean().optional(),
|
|
1975
2018
|
// Sanitized server-side by sanitizeSessionPreviewPorts — stays unknown.
|
|
1976
|
-
previewPorts: z22.unknown().optional()
|
|
2019
|
+
previewPorts: z22.unknown().optional(),
|
|
2020
|
+
// Sanitized server-side by sanitizeSessionPreviewLinks — stays unknown.
|
|
2021
|
+
previewLinks: z22.unknown().optional()
|
|
1977
2022
|
}).passthrough(),
|
|
1978
2023
|
z22.object({ type: z22.literal("setup_error"), message: z22.string() }).passthrough(),
|
|
1979
2024
|
z22.object({ type: z22.literal("start_command_started") }).passthrough(),
|
|
@@ -2451,6 +2496,9 @@ var ReportPtyStreamRequestSchema = z32.object({
|
|
|
2451
2496
|
sessionId: z32.string(),
|
|
2452
2497
|
port: z32.number().int().positive().max(65535).nullable()
|
|
2453
2498
|
});
|
|
2499
|
+
var ReportPtyInputRequestSchema = z32.object({
|
|
2500
|
+
sessionId: z32.string()
|
|
2501
|
+
});
|
|
2454
2502
|
var GetPtyStreamEndpointRequestSchema = z32.object({
|
|
2455
2503
|
sessionId: z32.string()
|
|
2456
2504
|
});
|
|
@@ -3275,6 +3323,30 @@ var CRITICAL_AUTOMATED_SOURCES = /* @__PURE__ */ new Set([
|
|
|
3275
3323
|
// clear the completion guard.
|
|
3276
3324
|
"ci_result"
|
|
3277
3325
|
]);
|
|
3326
|
+
var MAX_PREVIEW_LINKS = 8;
|
|
3327
|
+
var MAX_PREVIEW_LINK_LABEL = 64;
|
|
3328
|
+
var MAX_PREVIEW_LINK_PATH = 512;
|
|
3329
|
+
function isSafePreviewPath(path) {
|
|
3330
|
+
if (!path.startsWith("/") || path.startsWith("//")) return false;
|
|
3331
|
+
if (path.length > MAX_PREVIEW_LINK_PATH) return false;
|
|
3332
|
+
return !/[\s\\\u0000-\u001f\u007f]/.test(path);
|
|
3333
|
+
}
|
|
3334
|
+
function sanitizeSessionPreviewLinks(raw) {
|
|
3335
|
+
if (!Array.isArray(raw)) return [];
|
|
3336
|
+
const out = [];
|
|
3337
|
+
for (const entry of raw) {
|
|
3338
|
+
if (out.length >= MAX_PREVIEW_LINKS) break;
|
|
3339
|
+
if (!entry || typeof entry !== "object") continue;
|
|
3340
|
+
const { label, port, path } = entry;
|
|
3341
|
+
if (typeof label !== "string") continue;
|
|
3342
|
+
const trimmed = label.trim().slice(0, MAX_PREVIEW_LINK_LABEL);
|
|
3343
|
+
if (trimmed.length === 0 || !isAllowablePreviewPort(port)) continue;
|
|
3344
|
+
const resolvedPath = path === void 0 ? "/" : path;
|
|
3345
|
+
if (typeof resolvedPath !== "string" || !isSafePreviewPath(resolvedPath)) continue;
|
|
3346
|
+
out.push({ label: trimmed, port, path: resolvedPath });
|
|
3347
|
+
}
|
|
3348
|
+
return out;
|
|
3349
|
+
}
|
|
3278
3350
|
var DECISION_STATUS_LABELS = ["Open", "Decided", "Cancelled"];
|
|
3279
3351
|
var DECISION_OPTION_KINDS = ["current", "planned", "proposed"];
|
|
3280
3352
|
var DECISION_VOTE_SOURCES = ["web", "slack", "discord", "agent"];
|
|
@@ -3448,13 +3520,6 @@ var UpdateProjectDecisionRequestSchema = z10.object({
|
|
|
3448
3520
|
status: z10.enum(["Cancelled", "Open"]).optional(),
|
|
3449
3521
|
requestingUserId: z10.string().optional()
|
|
3450
3522
|
});
|
|
3451
|
-
function formatModelId(provider, model) {
|
|
3452
|
-
return `${provider}/${model}`;
|
|
3453
|
-
}
|
|
3454
|
-
var JEV_MODEL = "jev-1.13.0";
|
|
3455
|
-
var JEV_MODEL_ID = formatModelId("typesafe", JEV_MODEL);
|
|
3456
|
-
var JEV_INPUT_PRICE_PER_TOKEN = 0.042 / 1e6;
|
|
3457
|
-
var JEV_STATE_MAX_CHARS = 1e5;
|
|
3458
3523
|
var EVALUATE_MAX_QUESTIONS = 32;
|
|
3459
3524
|
var EVALUATE_MAX_ITEMS = 100;
|
|
3460
3525
|
var EVALUATE_ITEM_MAX_CHARS = 8e3;
|
|
@@ -4624,21 +4689,30 @@ async function loadForwardPorts(workspaceDir) {
|
|
|
4624
4689
|
if (value.visibility === "public" || value.visibility === "private") {
|
|
4625
4690
|
entry.visibility = value.visibility;
|
|
4626
4691
|
}
|
|
4692
|
+
if (value.onAutoForward === "ignore") entry.hidden = true;
|
|
4627
4693
|
attributes[key] = entry;
|
|
4628
4694
|
}
|
|
4629
|
-
|
|
4695
|
+
const links = sanitizeSessionPreviewLinks(parsed.customizations?.conveyor?.previewLinks);
|
|
4696
|
+
return { ports, attributes, links };
|
|
4630
4697
|
} catch {
|
|
4631
|
-
return { ports: [], attributes: {} };
|
|
4698
|
+
return { ports: [], attributes: {}, links: [] };
|
|
4632
4699
|
}
|
|
4633
4700
|
}
|
|
4634
4701
|
function buildSessionPreviewPorts(result) {
|
|
4635
|
-
|
|
4702
|
+
const toEntry = (port) => {
|
|
4636
4703
|
const attr = result.attributes[String(port)];
|
|
4637
4704
|
const entry = { port };
|
|
4638
4705
|
if (attr?.label) entry.label = attr.label;
|
|
4639
4706
|
if (attr?.visibility) entry.visibility = attr.visibility;
|
|
4707
|
+
if (attr?.hidden) entry.hidden = true;
|
|
4640
4708
|
return entry;
|
|
4641
|
-
}
|
|
4709
|
+
};
|
|
4710
|
+
const forwarded = result.ports.filter((port) => !DEVCONTAINER_PORT_DENY_LIST.has(port));
|
|
4711
|
+
const listed = new Set(forwarded);
|
|
4712
|
+
const hiddenOnly = Object.entries(result.attributes).filter(([, attr]) => attr.hidden).map(([key]) => Number(key)).filter(
|
|
4713
|
+
(port) => Number.isInteger(port) && port >= 1 && port <= 65535 && !listed.has(port) && !DEVCONTAINER_PORT_DENY_LIST.has(port)
|
|
4714
|
+
);
|
|
4715
|
+
return [...forwarded, ...hiddenOnly].map(toEntry);
|
|
4642
4716
|
}
|
|
4643
4717
|
function loadConveyorConfig() {
|
|
4644
4718
|
const envStart = process.env.CONVEYOR_START_COMMAND;
|
|
@@ -4741,12 +4815,19 @@ var Lifecycle = class {
|
|
|
4741
4815
|
this.clearIdleTimers();
|
|
4742
4816
|
const delay2 = Math.max(0, overrideMs ?? this.config.idleTimeoutMs);
|
|
4743
4817
|
this.idleTimer = setTimeout(() => {
|
|
4818
|
+
this.idleTimer = null;
|
|
4744
4819
|
this.callbacks.onIdleTimeout();
|
|
4745
4820
|
}, delay2);
|
|
4746
4821
|
}
|
|
4747
4822
|
cancelIdleTimer() {
|
|
4748
4823
|
this.clearIdleTimers();
|
|
4749
4824
|
}
|
|
4825
|
+
/** Restart an ARMED idle timer with the full window; a no-op when none is
|
|
4826
|
+
* armed. Human activity uses this, so a keystroke during a running turn
|
|
4827
|
+
* (timer cancelled) can never arm one. */
|
|
4828
|
+
touchIdleTimer() {
|
|
4829
|
+
if (this.idleTimer) this.startIdleTimer();
|
|
4830
|
+
}
|
|
4750
4831
|
// ── Dormant timer ──────────────────────────────────────────────────
|
|
4751
4832
|
/** Start (or restart) the dormant timer.
|
|
4752
4833
|
* @param overrideMs Optional custom delay in ms. When provided, the timer
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
readWorkspaceBytes,
|
|
9
9
|
statWorkspacePath,
|
|
10
10
|
workspacePathExists
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-KLM6ZOUX.js";
|
|
12
12
|
import {
|
|
13
13
|
reportBootMilestone
|
|
14
14
|
} from "./chunk-Q4FQOJ7D.js";
|
|
@@ -583,6 +583,10 @@ var WorkspaceCommandSupervisor = class {
|
|
|
583
583
|
this.reportBootMilestoneFn("start_command_launched");
|
|
584
584
|
const forwardPorts = await this.loadForwardPortsFn(this.workspaceDir);
|
|
585
585
|
if (this.stopped) return;
|
|
586
|
+
this.reportSetupComplete(startCommandRunning, forwardPorts);
|
|
587
|
+
}
|
|
588
|
+
/** Send `setup_complete` with the devcontainer preview hints and links. */
|
|
589
|
+
reportSetupComplete(startCommandRunning, forwardPorts) {
|
|
586
590
|
const previewPorts = buildSessionPreviewPorts(forwardPorts);
|
|
587
591
|
this.connection.sendEvent({
|
|
588
592
|
type: "setup_complete",
|
|
@@ -590,7 +594,8 @@ var WorkspaceCommandSupervisor = class {
|
|
|
590
594
|
// Reported separately so the server can tell "nothing to run" apart from
|
|
591
595
|
// "the dev server failed to launch" — both report startCommandRunning:false.
|
|
592
596
|
startCommandConfigured: Boolean(this.config?.startCommand),
|
|
593
|
-
...previewPorts.length > 0 ? { previewPorts } : {}
|
|
597
|
+
...previewPorts.length > 0 ? { previewPorts } : {},
|
|
598
|
+
...forwardPorts.links.length > 0 ? { previewLinks: forwardPorts.links } : {}
|
|
594
599
|
});
|
|
595
600
|
}
|
|
596
601
|
/** Run the dependency sync, forwarding its output as setup output so it lands
|
package/dist/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
WorkspaceCommandSupervisor,
|
|
8
8
|
startWorkspaceCommandsAfterConnect,
|
|
9
9
|
stopWorkspaceCommands
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-V5X3JCAZ.js";
|
|
11
11
|
import {
|
|
12
12
|
PtyHarness,
|
|
13
13
|
SessionRunner,
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
runUsageProbe,
|
|
26
26
|
sampleKeyUsage,
|
|
27
27
|
usesNativeUsageReporting
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-FF5JML7W.js";
|
|
29
29
|
import "./chunk-37J5MMQT.js";
|
|
30
30
|
import {
|
|
31
31
|
inheritedEnv,
|
|
@@ -44,7 +44,7 @@ import {
|
|
|
44
44
|
createServiceLogger,
|
|
45
45
|
fetchBootstrap,
|
|
46
46
|
loadConveyorConfig
|
|
47
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-KLM6ZOUX.js";
|
|
48
48
|
import "./chunk-Q4FQOJ7D.js";
|
|
49
49
|
import "./chunk-W4LZ7R6Z.js";
|
|
50
50
|
import "./chunk-372R6E4C.js";
|
|
@@ -1458,7 +1458,7 @@ if (!RUNNER_MODES.includes(CONVEYOR_MODE)) {
|
|
|
1458
1458
|
process.exit(1);
|
|
1459
1459
|
}
|
|
1460
1460
|
if (CONVEYOR_MODE === "serving") {
|
|
1461
|
-
const { runServingSession } = await import("./serve-boot-
|
|
1461
|
+
const { runServingSession } = await import("./serve-boot-N5XKECWC.js");
|
|
1462
1462
|
exitContext.runnerMode = "serving";
|
|
1463
1463
|
exitContext.sessionId = process.env.CONVEYOR_SESSION_ID ?? exitContext.sessionId;
|
|
1464
1464
|
const outcome = await runServingSession({
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _project_shared from '@project/shared';
|
|
2
|
-
import { RunnerMode, AgentSessionServiceMethods, AgentMode, PtyChatEventPayload, WorkspaceDiscoveredPort, AgentQuestion, AgentRunnerStatus } from '@project/shared';
|
|
2
|
+
import { RunnerMode, AgentSessionServiceMethods, AgentMode, PtyChatEventPayload, WorkspaceDiscoveredPort, AgentQuestion, AgentRunnerStatus, SessionPreviewLink } from '@project/shared';
|
|
3
3
|
export * from '@project/shared';
|
|
4
4
|
import { ChildProcess } from 'node:child_process';
|
|
5
5
|
|
|
@@ -135,6 +135,9 @@ declare class AgentConnection {
|
|
|
135
135
|
* question-grace expiry reads it to defer un-parking while a human is
|
|
136
136
|
* actively typing into the Connected-TUI (they may be mid-answer). */
|
|
137
137
|
private _lastPtyInputAt;
|
|
138
|
+
/** Runner-side subscribers to keystrokes from EITHER transport. */
|
|
139
|
+
private readonly ptyInputActivityListeners;
|
|
140
|
+
private lastReportedDirectInputAt;
|
|
138
141
|
constructor(config: AgentConnectionConfig);
|
|
139
142
|
get sessionId(): string;
|
|
140
143
|
get connected(): boolean;
|
|
@@ -294,7 +297,18 @@ declare class AgentConnection {
|
|
|
294
297
|
* just stays on the relay transport.
|
|
295
298
|
*/
|
|
296
299
|
reportPtyStream(port: number | null): void;
|
|
297
|
-
/**
|
|
300
|
+
/**
|
|
301
|
+
* A human typed into the TUI. Stamps `lastPtyInputAt` and notifies the
|
|
302
|
+
* runner's input-activity listeners for both transports. Direct-stream
|
|
303
|
+
* keystrokes never cross the API, so for those this also tells the server
|
|
304
|
+
* (`reportPtyInput`, throttled, fire-and-forget) — the relay path's server
|
|
305
|
+
* already bumps in `relayPtyInput`. An old API that doesn't know the method
|
|
306
|
+
* rejects harmlessly.
|
|
307
|
+
*/
|
|
308
|
+
notePtyInput(origin?: "relay" | "direct"): void;
|
|
309
|
+
/** Subscribe to keystroke activity from either transport. Returns an unsubscribe fn. */
|
|
310
|
+
onPtyInputActivity(listener: () => void): () => void;
|
|
311
|
+
/** Epoch ms of the last TUI keystroke (relay or direct), or null before any. */
|
|
298
312
|
get lastPtyInputAt(): number | null;
|
|
299
313
|
/** Subscribe to relayed keystrokes. Returns an unsubscribe fn. */
|
|
300
314
|
onPtyInput(handler: (data: string) => void): () => void;
|
|
@@ -620,6 +634,10 @@ declare class Lifecycle {
|
|
|
620
634
|
* window every time it defers. */
|
|
621
635
|
startIdleTimer(overrideMs?: number): void;
|
|
622
636
|
cancelIdleTimer(): void;
|
|
637
|
+
/** Restart an ARMED idle timer with the full window; a no-op when none is
|
|
638
|
+
* armed. Human activity uses this, so a keystroke during a running turn
|
|
639
|
+
* (timer cancelled) can never arm one. */
|
|
640
|
+
touchIdleTimer(): void;
|
|
623
641
|
/** Start (or restart) the dormant timer.
|
|
624
642
|
* @param overrideMs Optional custom delay in ms. When provided, the timer
|
|
625
643
|
* fires after exactly that delay instead of `dormantTimeoutMs`. SessionRunner
|
|
@@ -1294,12 +1312,16 @@ declare function removeWorktree(projectDir: string, taskId: string): Promise<voi
|
|
|
1294
1312
|
interface ConveyorConfig {
|
|
1295
1313
|
startCommand?: string;
|
|
1296
1314
|
}
|
|
1315
|
+
type PortAttributes = {
|
|
1316
|
+
label?: string;
|
|
1317
|
+
visibility?: "public" | "private";
|
|
1318
|
+
hidden?: boolean;
|
|
1319
|
+
};
|
|
1297
1320
|
interface ForwardPortsResult {
|
|
1298
1321
|
ports: number[];
|
|
1299
|
-
attributes: Record<string,
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
}>;
|
|
1322
|
+
attributes: Record<string, PortAttributes>;
|
|
1323
|
+
/** `customizations.conveyor.previewLinks`, sanitized. */
|
|
1324
|
+
links: SessionPreviewLink[];
|
|
1303
1325
|
}
|
|
1304
1326
|
declare function loadForwardPorts(workspaceDir: string): Promise<ForwardPortsResult>;
|
|
1305
1327
|
/** Load config from env vars (project-level settings injected via bootstrap).
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SessionRunner,
|
|
3
3
|
unshallowRepo
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-FF5JML7W.js";
|
|
5
5
|
import "./chunk-37J5MMQT.js";
|
|
6
6
|
import "./chunk-WCJ6HRVU.js";
|
|
7
7
|
import {
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
stageAndCommit,
|
|
18
18
|
updateRemoteToken,
|
|
19
19
|
workspacePathExists
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-KLM6ZOUX.js";
|
|
21
21
|
import "./chunk-Q4FQOJ7D.js";
|
|
22
22
|
import {
|
|
23
23
|
runAuthTokenCommand,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
WorkspaceCommandSupervisor
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-V5X3JCAZ.js";
|
|
4
4
|
import {
|
|
5
5
|
AgentConnection,
|
|
6
6
|
CodespacePortVisibility,
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
createServiceLogger,
|
|
12
12
|
ensureOnTaskBranch,
|
|
13
13
|
loadConveyorConfig
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-KLM6ZOUX.js";
|
|
15
15
|
import "./chunk-Q4FQOJ7D.js";
|
|
16
16
|
import "./chunk-W4LZ7R6Z.js";
|
|
17
17
|
import "./chunk-IA45XHOA.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rallycry/conveyor-agent",
|
|
3
|
-
"version": "11.0.
|
|
3
|
+
"version": "11.0.29",
|
|
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",
|
|
@@ -151,6 +151,17 @@ labels. Each tag carries a `description` (≤255 — the summary), an `overview`
|
|
|
151
151
|
work. The loop idles on `conveyor-wait`, a CLI in `@rallycry/conveyor-mcp`
|
|
152
152
|
that blocks until a card becomes claimable, so an idle loop wakes on a board
|
|
153
153
|
event instead of a timer.
|
|
154
|
+
- **From an app, with a project token (no MCP, no user)**: an external service
|
|
155
|
+
holding an incident-scoped project token files AND starts a card in one
|
|
156
|
+
call — `POST /api/incidents/report` with `type: "task"`, a `plan`, and
|
|
157
|
+
`start: true` (optionally `tags: [<glossary names>]`; an unknown name is a
|
|
158
|
+
400). The build starts through the same path as `start_task`, as the token's
|
|
159
|
+
owner, and the response carries `{ taskId, slug, url, status, buildStarted,
|
|
160
|
+
buildError? }`. A failed start still returns 201 with `buildStarted: false`
|
|
161
|
+
and leaves the card for a human — do not retry the report, that files a
|
|
162
|
+
second card. Poll `GET /api/incidents/tasks/<taskId>` with the same token for
|
|
163
|
+
`{ status, githubPRUrl, githubPRNumber, agentRunnerStatus }`. `start` is
|
|
164
|
+
rate-limited to 30 per token per 10 minutes.
|
|
154
165
|
- **Reserved-branch trap**: a card with an assigned agent may have a reserved
|
|
155
166
|
`githubBranch`. Check `get_task` before pushing: if set, push to THAT
|
|
156
167
|
branch; a PR from any other branch gets auto-closed and unlinked.
|