@rallycry/conveyor-agent 10.2.5 → 10.3.0
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-CFELRV35.js → chunk-AMPYOJJC.js} +207 -59
- package/dist/chunk-AMPYOJJC.js.map +1 -0
- package/dist/cli.js +14 -39
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +18 -16
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/runtime/entrypoint.sh +7 -1
- package/dist/chunk-CFELRV35.js.map +0 -1
|
@@ -2035,6 +2035,10 @@ var SessionStopRequestSchema = z.object({
|
|
|
2035
2035
|
sessionId: z.string(),
|
|
2036
2036
|
reason: z.string().optional()
|
|
2037
2037
|
});
|
|
2038
|
+
var EndReviewSessionRequestSchema = z.object({
|
|
2039
|
+
sessionId: z.string(),
|
|
2040
|
+
reason: z.enum(["approved", "changes_requested", "finished"]).optional()
|
|
2041
|
+
});
|
|
2038
2042
|
var ConnectAgentRequestSchema = z.object({
|
|
2039
2043
|
sessionId: z.string()
|
|
2040
2044
|
});
|
|
@@ -2647,9 +2651,15 @@ var AGENT_STATUS_REASON_USER_QUESTION = "user_question";
|
|
|
2647
2651
|
var TASK_CHAT_HISTORY_LIMIT = 20;
|
|
2648
2652
|
var PM_CHAT_HISTORY_LIMIT = 40;
|
|
2649
2653
|
var AGENT_CHAT_HISTORY_FETCH_LIMIT = Math.max(TASK_CHAT_HISTORY_LIMIT, PM_CHAT_HISTORY_LIMIT) + 10;
|
|
2654
|
+
var PRE_BUILD_TASK_STATUSES = /* @__PURE__ */ new Set(["Planning", "Open"]);
|
|
2655
|
+
function hasTaskPlan(plan) {
|
|
2656
|
+
return !!plan?.trim();
|
|
2657
|
+
}
|
|
2658
|
+
function taskNeedsPlanning(task) {
|
|
2659
|
+
return !hasTaskPlan(task.plan) && PRE_BUILD_TASK_STATUSES.has(task.status) && !task.githubPRUrl;
|
|
2660
|
+
}
|
|
2650
2661
|
|
|
2651
2662
|
// src/runner/mode-controller.ts
|
|
2652
|
-
var FRESH_AUTO_STATUSES = /* @__PURE__ */ new Set(["Planning", "Open"]);
|
|
2653
2663
|
var ModeController = class {
|
|
2654
2664
|
_mode;
|
|
2655
2665
|
_hasExitedPlanMode = false;
|
|
@@ -2734,28 +2744,28 @@ var ModeController = class {
|
|
|
2734
2744
|
return this._mode;
|
|
2735
2745
|
}
|
|
2736
2746
|
/**
|
|
2737
|
-
*
|
|
2738
|
-
*
|
|
2739
|
-
*
|
|
2740
|
-
*
|
|
2741
|
-
*
|
|
2747
|
+
* An auto task that still NEEDS PLANNING (no plan saved, pre-build status, no
|
|
2748
|
+
* PR — see `taskNeedsPlanning` in @project/shared) runs the read-only plan turn
|
|
2749
|
+
* first (`--permission-mode plan`): resolveInitialMode leaves it in `auto` with
|
|
2750
|
+
* `_hasExitedPlanMode = false`, and once the agent finishes it calls
|
|
2751
|
+
* ExitPlanMode → building. Everything else bypasses planning and builds
|
|
2752
|
+
* immediately with `--dangerously-skip-permissions`:
|
|
2742
2753
|
*
|
|
2743
|
-
* - a
|
|
2744
|
-
* - a
|
|
2745
|
-
* -
|
|
2746
|
-
* - a
|
|
2754
|
+
* - a plan already on the card (e.g. a Planning card planned by a human/PM),
|
|
2755
|
+
* - a build already underway (status past `Planning`/`Open` — releases booted
|
|
2756
|
+
* `InProgress`, pod restarts mid-build where DB `agentMode` stays `"auto"`),
|
|
2757
|
+
* - a PR already open (safety net if status lags).
|
|
2747
2758
|
*
|
|
2748
2759
|
* Runtime Build-after-Discovery never reaches this gate as `auto` — the server's
|
|
2749
|
-
* `resolveModeToSend` rewrites it to `"building"` for any
|
|
2760
|
+
* `resolveModeToSend` rewrites it to `"building"` for any task with a plan.
|
|
2750
2761
|
*/
|
|
2751
2762
|
canBypassPlanning(context) {
|
|
2752
2763
|
if (this._mode !== "auto") return false;
|
|
2753
|
-
|
|
2754
|
-
return !!context.githubPRUrl;
|
|
2764
|
+
return !taskNeedsPlanning(context);
|
|
2755
2765
|
}
|
|
2756
2766
|
// ── Mode transitions ───────────────────────────────────────────────
|
|
2757
2767
|
/** Handle mode change from server */
|
|
2758
|
-
handleModeChange(newMode) {
|
|
2768
|
+
handleModeChange(newMode, context) {
|
|
2759
2769
|
if (newMode === this._mode) return { type: "noop" };
|
|
2760
2770
|
if (this._runnerMode === "task" && newMode === "review") {
|
|
2761
2771
|
this._mode = newMode;
|
|
@@ -2770,7 +2780,7 @@ var ModeController = class {
|
|
|
2770
2780
|
if (this._runnerMode === "task" && newMode === "auto") {
|
|
2771
2781
|
this._mode = newMode;
|
|
2772
2782
|
this._isAuto = true;
|
|
2773
|
-
this._hasExitedPlanMode =
|
|
2783
|
+
this._hasExitedPlanMode = !context || !taskNeedsPlanning(context);
|
|
2774
2784
|
return { type: "restart_query", newMode: "auto" };
|
|
2775
2785
|
}
|
|
2776
2786
|
if (this._mode === "auto" && this._hasExitedPlanMode && newMode === "building") {
|
|
@@ -2941,6 +2951,11 @@ var Lifecycle = class {
|
|
|
2941
2951
|
};
|
|
2942
2952
|
|
|
2943
2953
|
// src/harness/types.ts
|
|
2954
|
+
function isExternalMcpStdioServer(value) {
|
|
2955
|
+
if (typeof value !== "object" || value === null) return false;
|
|
2956
|
+
const v = value;
|
|
2957
|
+
return v.type === "stdio" && typeof v.command === "string";
|
|
2958
|
+
}
|
|
2944
2959
|
function defineTool(name, description, schema, handler, options) {
|
|
2945
2960
|
return {
|
|
2946
2961
|
name,
|
|
@@ -3905,6 +3920,15 @@ async function startToolServers(mcpServers, tempDir) {
|
|
|
3905
3920
|
const servers = [];
|
|
3906
3921
|
const config = {};
|
|
3907
3922
|
for (const [name, handle] of Object.entries(mcpServers)) {
|
|
3923
|
+
if (isExternalMcpStdioServer(handle)) {
|
|
3924
|
+
config[name] = {
|
|
3925
|
+
type: "stdio",
|
|
3926
|
+
command: handle.command,
|
|
3927
|
+
...handle.args ? { args: handle.args } : {},
|
|
3928
|
+
...handle.env ? { env: handle.env } : {}
|
|
3929
|
+
};
|
|
3930
|
+
continue;
|
|
3931
|
+
}
|
|
3908
3932
|
const tools = handle instanceof PtyMcpServer ? handle.tools : [];
|
|
3909
3933
|
if (tools.length === 0) continue;
|
|
3910
3934
|
const server = new PtyToolServer(name, tools);
|
|
@@ -4164,11 +4188,14 @@ var PLAN_DIALOG_INTERVAL_MS = 1500;
|
|
|
4164
4188
|
var PLAN_DIALOG_SLOW_INTERVAL_MS = 5e3;
|
|
4165
4189
|
var PLAN_DIALOG_FAST_WINDOW_MS = 1e4;
|
|
4166
4190
|
var PLAN_DIALOG_WINDOW_MS = 9e4;
|
|
4191
|
+
function envMs(name, fallback) {
|
|
4192
|
+
const raw = Number(process.env[name]);
|
|
4193
|
+
return Number.isFinite(raw) && raw > 0 ? raw : fallback;
|
|
4194
|
+
}
|
|
4195
|
+
function resolveSubmitSettleMs() {
|
|
4196
|
+
return envMs("CONVEYOR_PTY_SUBMIT_SETTLE_MS", SUBMIT_SETTLE_MS);
|
|
4197
|
+
}
|
|
4167
4198
|
function resolveSubmitNudgeTiming() {
|
|
4168
|
-
const envMs = (name, fallback) => {
|
|
4169
|
-
const raw = Number(process.env[name]);
|
|
4170
|
-
return Number.isFinite(raw) && raw > 0 ? raw : fallback;
|
|
4171
|
-
};
|
|
4172
4199
|
return {
|
|
4173
4200
|
intervalMs: envMs("CONVEYOR_PTY_NUDGE_INTERVAL_MS", SUBMIT_NUDGE_INTERVAL_MS),
|
|
4174
4201
|
slowIntervalMs: envMs("CONVEYOR_PTY_NUDGE_SLOW_INTERVAL_MS", SUBMIT_NUDGE_SLOW_INTERVAL_MS),
|
|
@@ -4176,6 +4203,15 @@ function resolveSubmitNudgeTiming() {
|
|
|
4176
4203
|
windowMs: envMs("CONVEYOR_PTY_NUDGE_WINDOW_MS", SUBMIT_NUDGE_WINDOW_MS)
|
|
4177
4204
|
};
|
|
4178
4205
|
}
|
|
4206
|
+
function resolvePlanDialogTiming() {
|
|
4207
|
+
return {
|
|
4208
|
+
firstPressMs: envMs("CONVEYOR_PTY_PLAN_DIALOG_FIRST_PRESS_MS", PLAN_DIALOG_FIRST_PRESS_MS),
|
|
4209
|
+
intervalMs: envMs("CONVEYOR_PTY_PLAN_DIALOG_INTERVAL_MS", PLAN_DIALOG_INTERVAL_MS),
|
|
4210
|
+
slowIntervalMs: envMs("CONVEYOR_PTY_PLAN_DIALOG_SLOW_INTERVAL_MS", PLAN_DIALOG_SLOW_INTERVAL_MS),
|
|
4211
|
+
fastWindowMs: envMs("CONVEYOR_PTY_PLAN_DIALOG_FAST_WINDOW_MS", PLAN_DIALOG_FAST_WINDOW_MS),
|
|
4212
|
+
windowMs: envMs("CONVEYOR_PTY_PLAN_DIALOG_WINDOW_MS", PLAN_DIALOG_WINDOW_MS)
|
|
4213
|
+
};
|
|
4214
|
+
}
|
|
4179
4215
|
function turnOptionsFrom(options) {
|
|
4180
4216
|
return {
|
|
4181
4217
|
canUseTool: options.canUseTool,
|
|
@@ -4727,7 +4763,7 @@ var PtySession = class {
|
|
|
4727
4763
|
if (text === "" && !this.adapter.capabilities.prefill) return;
|
|
4728
4764
|
this.writeStdin(this.adapter.encodePromptBytes(text));
|
|
4729
4765
|
if (this.turn.promptDelivery === "prefill") return;
|
|
4730
|
-
await sleep3(
|
|
4766
|
+
await sleep3(resolveSubmitSettleMs());
|
|
4731
4767
|
if (this._toreDown) return;
|
|
4732
4768
|
this.writeStdin("\r");
|
|
4733
4769
|
if (this.adapter.capabilities.structuredEvents) this.armSubmitNudge();
|
|
@@ -4844,10 +4880,11 @@ var PtySession = class {
|
|
|
4844
4880
|
if (this.pendingPlanApproval) return;
|
|
4845
4881
|
this.pendingPlanApproval = true;
|
|
4846
4882
|
const startedAt = Date.now();
|
|
4883
|
+
const { firstPressMs, intervalMs, slowIntervalMs, fastWindowMs, windowMs } = resolvePlanDialogTiming();
|
|
4847
4884
|
const press = () => {
|
|
4848
4885
|
if (this._toreDown || !this.pendingPlanApproval) return;
|
|
4849
4886
|
const elapsed = Date.now() - startedAt;
|
|
4850
|
-
if (elapsed >=
|
|
4887
|
+
if (elapsed >= windowMs) {
|
|
4851
4888
|
process.stderr.write(
|
|
4852
4889
|
"[PtySession] plan-dialog auto-accept window expired without ExitPlanMode executing \u2014 the approval dialog may still be parked\n"
|
|
4853
4890
|
);
|
|
@@ -4855,10 +4892,10 @@ var PtySession = class {
|
|
|
4855
4892
|
return;
|
|
4856
4893
|
}
|
|
4857
4894
|
this.writeStdin("\r");
|
|
4858
|
-
const interval = elapsed <
|
|
4895
|
+
const interval = elapsed < fastWindowMs ? intervalMs : slowIntervalMs;
|
|
4859
4896
|
this.planApprovalTimer = setTimeout(press, interval);
|
|
4860
4897
|
};
|
|
4861
|
-
this.planApprovalTimer = setTimeout(press,
|
|
4898
|
+
this.planApprovalTimer = setTimeout(press, firstPressMs);
|
|
4862
4899
|
}
|
|
4863
4900
|
disarmPlanDialogAutoAccept() {
|
|
4864
4901
|
this.pendingPlanApproval = false;
|
|
@@ -5141,7 +5178,7 @@ function buildPackRunnerSystemPrompt(context, config, setupLog) {
|
|
|
5141
5178
|
` - "ReviewDev" / "Complete": Already done. Skip.`,
|
|
5142
5179
|
` - "Planning": Not ready. If blocking progress, notify team.`,
|
|
5143
5180
|
``,
|
|
5144
|
-
`3. Fire ALL ready "Open" tasks whose dependencies are met, not just one
|
|
5181
|
+
`3. Fire ALL ready "Open" tasks whose dependencies are met, not just one \u2014 independent tasks run in parallel. There is a concurrency limit: if start_child_cloud_build returns a PACK_CHILD_LIMIT error, that is backpressure, not a failure. Merge or wait on in-flight children, then start more as slots free up.`,
|
|
5145
5182
|
``,
|
|
5146
5183
|
`4. After merging a PR: run \`git pull origin ${context.baseBranch}\` then re-check list_subtasks \u2014 previously blocked tasks may now be ready.`,
|
|
5147
5184
|
``,
|
|
@@ -5150,9 +5187,9 @@ function buildPackRunnerSystemPrompt(context, config, setupLog) {
|
|
|
5150
5187
|
`6. When ALL children are in "ReviewDev" or "Complete" (no "Open", "InProgress", or "ReviewPR" remaining): do a final review, summarize results in chat, and mark this parent task complete with force_update_task_status("Complete").`,
|
|
5151
5188
|
``,
|
|
5152
5189
|
`## Important Rules`,
|
|
5153
|
-
`- When dependencies are set on children, use them to determine execution order. Fire all ready tasks in parallel.`,
|
|
5190
|
+
`- When dependencies are set on children, use them to determine execution order. Fire all ready tasks in parallel (up to the PACK_CHILD_LIMIT backpressure \u2014 see the loop above).`,
|
|
5154
5191
|
`- When NO dependencies are set on any children, fall back to ordinal order (one at a time). This preserves backward compatibility.`,
|
|
5155
|
-
`- After firing builds OR when waiting on CI, explicitly state you are going idle.
|
|
5192
|
+
`- After firing builds OR when waiting on CI, explicitly state you are going idle. Go idle when waiting \u2014 the system wakes you (or relaunches this environment) when a child changes status.`,
|
|
5156
5193
|
`- Do NOT attempt to write code yourself. Your role is coordination only.`,
|
|
5157
5194
|
`- If a child is stuck in "InProgress" for an unusually long time, use get_execution_logs(childTaskId) to check its logs and escalate to the team if it appears stuck.`,
|
|
5158
5195
|
`- You can use get_task(childTaskId) to get a child's full details including PR URL and branch.`,
|
|
@@ -6064,7 +6101,7 @@ Git safety \u2014 STRICT rules:`,
|
|
|
6064
6101
|
function buildSystemPrompt(mode, context, config, setupLog, agentMode) {
|
|
6065
6102
|
const isPm = mode === "pm";
|
|
6066
6103
|
const isPmActive = isPm && agentMode === "building";
|
|
6067
|
-
const isPackRunner = isPm && !!config.isAuto && !!context.isParentTask;
|
|
6104
|
+
const isPackRunner = mode === "pack" || isPm && !!config.isAuto && !!context.isParentTask;
|
|
6068
6105
|
if (isPackRunner) {
|
|
6069
6106
|
return buildPackRunnerSystemPrompt(context, config, setupLog);
|
|
6070
6107
|
}
|
|
@@ -6393,7 +6430,7 @@ Address the requested changes directly. Do NOT re-investigate the codebase from
|
|
|
6393
6430
|
return parts;
|
|
6394
6431
|
}
|
|
6395
6432
|
function buildIdleRelaunchInstructions(context, isPm, agentMode, isAuto) {
|
|
6396
|
-
if (isPm && agentMode === "auto" && context.status
|
|
6433
|
+
if (isPm && agentMode === "auto" && PRE_BUILD_TASK_STATUSES.has(context.status ?? "")) {
|
|
6397
6434
|
if (context.plan?.trim()) {
|
|
6398
6435
|
return [
|
|
6399
6436
|
`You were relaunched in auto mode. A plan already exists for this task.`,
|
|
@@ -6455,7 +6492,7 @@ function buildInstructions(mode, context, scenario, agentMode, isAuto) {
|
|
|
6455
6492
|
return parts;
|
|
6456
6493
|
}
|
|
6457
6494
|
async function buildInitialPrompt(mode, context, isAuto, agentMode) {
|
|
6458
|
-
const isPackRunner = mode === "pm" && !!isAuto && !!context.isParentTask;
|
|
6495
|
+
const isPackRunner = mode === "pack" || mode === "pm" && !!isAuto && !!context.isParentTask;
|
|
6459
6496
|
if (!isPackRunner) {
|
|
6460
6497
|
const sessionRelaunch = buildRelaunchWithSession(mode, context, agentMode, isAuto);
|
|
6461
6498
|
if (sessionRelaunch) return sessionRelaunch;
|
|
@@ -7562,6 +7599,12 @@ function buildDiscoveryTools(connection) {
|
|
|
7562
7599
|
|
|
7563
7600
|
// src/tools/code-review-tools.ts
|
|
7564
7601
|
import { z as z10 } from "zod";
|
|
7602
|
+
async function endReviewSession(connection, reason) {
|
|
7603
|
+
await connection.call("endReviewSession", {
|
|
7604
|
+
sessionId: connection.sessionId,
|
|
7605
|
+
reason
|
|
7606
|
+
});
|
|
7607
|
+
}
|
|
7565
7608
|
function buildCodeReviewTools(connection) {
|
|
7566
7609
|
return [
|
|
7567
7610
|
defineTool(
|
|
@@ -7584,6 +7627,7 @@ ${summary}`;
|
|
|
7584
7627
|
result: "approved",
|
|
7585
7628
|
summary
|
|
7586
7629
|
});
|
|
7630
|
+
await endReviewSession(connection, "approved");
|
|
7587
7631
|
return textResult("Code review approved. Exiting.");
|
|
7588
7632
|
}
|
|
7589
7633
|
),
|
|
@@ -7622,6 +7666,7 @@ ${issueLines}`;
|
|
|
7622
7666
|
summary,
|
|
7623
7667
|
issues
|
|
7624
7668
|
});
|
|
7669
|
+
await endReviewSession(connection, "changes_requested");
|
|
7625
7670
|
return textResult("Code review complete \u2014 changes requested. Exiting.");
|
|
7626
7671
|
}
|
|
7627
7672
|
)
|
|
@@ -7636,6 +7681,9 @@ function getTaskModeTools(agentMode, connection) {
|
|
|
7636
7681
|
return [];
|
|
7637
7682
|
}
|
|
7638
7683
|
function getModeTools(agentMode, connection, config, context) {
|
|
7684
|
+
if (config.mode === "pack") {
|
|
7685
|
+
return buildPmTools(connection, { includePackTools: true });
|
|
7686
|
+
}
|
|
7639
7687
|
if (config.mode === "task") return getTaskModeTools(agentMode, connection);
|
|
7640
7688
|
switch (agentMode) {
|
|
7641
7689
|
case "building":
|
|
@@ -7667,6 +7715,49 @@ function createConveyorMcpServer(harness, connection, config, context, agentMode
|
|
|
7667
7715
|
});
|
|
7668
7716
|
}
|
|
7669
7717
|
|
|
7718
|
+
// src/harness/pty/adapters/types.ts
|
|
7719
|
+
import { accessSync, constants, statSync } from "fs";
|
|
7720
|
+
import { join as join6 } from "path";
|
|
7721
|
+
var TuiUnavailableError = class extends Error {
|
|
7722
|
+
constructor(tui, message) {
|
|
7723
|
+
super(message);
|
|
7724
|
+
this.tui = tui;
|
|
7725
|
+
this.name = "TuiUnavailableError";
|
|
7726
|
+
}
|
|
7727
|
+
tui;
|
|
7728
|
+
};
|
|
7729
|
+
function isExecutable(path4) {
|
|
7730
|
+
try {
|
|
7731
|
+
if (!statSync(path4).isFile()) return false;
|
|
7732
|
+
accessSync(path4, constants.X_OK);
|
|
7733
|
+
return true;
|
|
7734
|
+
} catch {
|
|
7735
|
+
return false;
|
|
7736
|
+
}
|
|
7737
|
+
}
|
|
7738
|
+
function findOnPath(binary, env = process.env) {
|
|
7739
|
+
if (binary.includes("/")) {
|
|
7740
|
+
return isExecutable(binary) ? binary : null;
|
|
7741
|
+
}
|
|
7742
|
+
for (const dir of (env.PATH ?? "").split(":")) {
|
|
7743
|
+
if (!dir) continue;
|
|
7744
|
+
const candidate = join6(dir, binary);
|
|
7745
|
+
if (isExecutable(candidate)) return candidate;
|
|
7746
|
+
}
|
|
7747
|
+
return null;
|
|
7748
|
+
}
|
|
7749
|
+
|
|
7750
|
+
// src/execution/playwright-mcp.ts
|
|
7751
|
+
var PLAYWRIGHT_MCP_BINARIES = ["playwright-mcp", "mcp-server-playwright"];
|
|
7752
|
+
var PLAYWRIGHT_MCP_ARGS = ["--browser", "chromium", "--headless", "--no-sandbox", "--isolated"];
|
|
7753
|
+
function resolvePlaywrightMcpServer(env = process.env) {
|
|
7754
|
+
for (const binary of PLAYWRIGHT_MCP_BINARIES) {
|
|
7755
|
+
const command = findOnPath(binary, env);
|
|
7756
|
+
if (command) return { type: "stdio", command, args: [...PLAYWRIGHT_MCP_ARGS] };
|
|
7757
|
+
}
|
|
7758
|
+
return null;
|
|
7759
|
+
}
|
|
7760
|
+
|
|
7670
7761
|
// src/execution/event-handlers.ts
|
|
7671
7762
|
var logger = createServiceLogger("event-handlers");
|
|
7672
7763
|
function safeVoid(promise, context) {
|
|
@@ -8560,7 +8651,13 @@ function buildQueryOptions(host, context) {
|
|
|
8560
8651
|
planDialogAutoAccept: mode === "auto" && !host.hasExitedPlanMode,
|
|
8561
8652
|
tools: { type: "preset", preset: "claude_code" },
|
|
8562
8653
|
mcpServers: {
|
|
8563
|
-
conveyor: createConveyorMcpServer(host.harness, host.connection, host.config, context, mode)
|
|
8654
|
+
conveyor: createConveyorMcpServer(host.harness, host.connection, host.config, context, mode),
|
|
8655
|
+
// Baked-in browser automation (claudespace pods) — omitted when the
|
|
8656
|
+
// binary isn't on PATH (dev machines, legacy images).
|
|
8657
|
+
...(() => {
|
|
8658
|
+
const playwright = resolvePlaywrightMcpServer();
|
|
8659
|
+
return playwright ? { playwright } : {};
|
|
8660
|
+
})()
|
|
8564
8661
|
},
|
|
8565
8662
|
sandbox: context.useSandbox ? { enabled: true } : { enabled: false },
|
|
8566
8663
|
hooks: buildHooks(host),
|
|
@@ -9337,7 +9434,7 @@ var QueryBridge = class {
|
|
|
9337
9434
|
|
|
9338
9435
|
// src/runner/session-runner-helpers.ts
|
|
9339
9436
|
import { readFileSync as readFileSync2 } from "fs";
|
|
9340
|
-
import { dirname as dirname2, join as
|
|
9437
|
+
import { dirname as dirname2, join as join7 } from "path";
|
|
9341
9438
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9342
9439
|
function mapChatHistory(messages) {
|
|
9343
9440
|
if (!messages) return [];
|
|
@@ -9366,7 +9463,7 @@ function readAgentVersion() {
|
|
|
9366
9463
|
const here = dirname2(fileURLToPath2(import.meta.url));
|
|
9367
9464
|
for (const rel of ["../package.json", "../../package.json"]) {
|
|
9368
9465
|
try {
|
|
9369
|
-
const pkg = JSON.parse(readFileSync2(
|
|
9466
|
+
const pkg = JSON.parse(readFileSync2(join7(here, rel), "utf-8"));
|
|
9370
9467
|
if (pkg.version) return pkg.version;
|
|
9371
9468
|
} catch {
|
|
9372
9469
|
}
|
|
@@ -9454,8 +9551,22 @@ async function sampleKeyUsage(token) {
|
|
|
9454
9551
|
import { readFile as readFile4 } from "fs/promises";
|
|
9455
9552
|
import { execFile as execFile3 } from "child_process";
|
|
9456
9553
|
var PROC_TCP_LISTEN_STATE = "0A";
|
|
9554
|
+
function isLoopbackHexAddress(hex) {
|
|
9555
|
+
const addr = hex.toUpperCase();
|
|
9556
|
+
if (addr.length === 8) {
|
|
9557
|
+
return addr.slice(6, 8) === "7F";
|
|
9558
|
+
}
|
|
9559
|
+
if (addr.length === 32) {
|
|
9560
|
+
if (addr === "00000000000000000000000001000000") return true;
|
|
9561
|
+
if (addr.slice(0, 16) === "0000000000000000" && addr.slice(16, 24) === "FFFF0000") {
|
|
9562
|
+
return addr.slice(30, 32) === "7F";
|
|
9563
|
+
}
|
|
9564
|
+
return false;
|
|
9565
|
+
}
|
|
9566
|
+
return false;
|
|
9567
|
+
}
|
|
9457
9568
|
function parseProcNetTcpListeners(content) {
|
|
9458
|
-
const
|
|
9569
|
+
const sockets = [];
|
|
9459
9570
|
const lines = content.split("\n");
|
|
9460
9571
|
for (let i = 1; i < lines.length; i++) {
|
|
9461
9572
|
const line = lines[i];
|
|
@@ -9464,26 +9575,40 @@ function parseProcNetTcpListeners(content) {
|
|
|
9464
9575
|
if (cols.length < 4 || cols[3] !== PROC_TCP_LISTEN_STATE) continue;
|
|
9465
9576
|
const local = cols[1];
|
|
9466
9577
|
if (!local) continue;
|
|
9467
|
-
const portHex = local.split(":")
|
|
9468
|
-
if (!portHex) continue;
|
|
9578
|
+
const [addrHex, portHex] = local.split(":");
|
|
9579
|
+
if (!addrHex || !portHex) continue;
|
|
9469
9580
|
const port = Number.parseInt(portHex, 16);
|
|
9470
|
-
if (Number.isInteger(port)
|
|
9581
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) continue;
|
|
9582
|
+
sockets.push({ port, loopback: isLoopbackHexAddress(addrHex) });
|
|
9583
|
+
}
|
|
9584
|
+
return sockets;
|
|
9585
|
+
}
|
|
9586
|
+
function collectScan(sockets) {
|
|
9587
|
+
const ports = /* @__PURE__ */ new Set();
|
|
9588
|
+
const hasExternal = /* @__PURE__ */ new Set();
|
|
9589
|
+
for (const { port, loopback } of sockets) {
|
|
9590
|
+
ports.add(port);
|
|
9591
|
+
if (!loopback) hasExternal.add(port);
|
|
9592
|
+
}
|
|
9593
|
+
const loopbackOnly = /* @__PURE__ */ new Set();
|
|
9594
|
+
for (const port of ports) {
|
|
9595
|
+
if (!hasExternal.has(port)) loopbackOnly.add(port);
|
|
9471
9596
|
}
|
|
9472
|
-
return ports;
|
|
9597
|
+
return { ports, loopbackOnly };
|
|
9473
9598
|
}
|
|
9474
9599
|
var DEFAULT_PROC_PATHS = ["/proc/net/tcp", "/proc/net/tcp6"];
|
|
9475
9600
|
async function readProcListeningPorts(procPaths = DEFAULT_PROC_PATHS) {
|
|
9476
|
-
const
|
|
9601
|
+
const sockets = [];
|
|
9477
9602
|
let readable = false;
|
|
9478
9603
|
for (const path4 of procPaths) {
|
|
9479
9604
|
try {
|
|
9480
9605
|
const content = await readFile4(path4, "utf8");
|
|
9481
9606
|
readable = true;
|
|
9482
|
-
|
|
9607
|
+
sockets.push(...parseProcNetTcpListeners(content));
|
|
9483
9608
|
} catch {
|
|
9484
9609
|
}
|
|
9485
9610
|
}
|
|
9486
|
-
return readable ?
|
|
9611
|
+
return readable ? collectScan(sockets) : null;
|
|
9487
9612
|
}
|
|
9488
9613
|
async function readNetstatListeningPorts() {
|
|
9489
9614
|
const output = await new Promise((resolve) => {
|
|
@@ -9492,17 +9617,21 @@ async function readNetstatListeningPorts() {
|
|
|
9492
9617
|
});
|
|
9493
9618
|
});
|
|
9494
9619
|
if (output === null) return null;
|
|
9495
|
-
const
|
|
9620
|
+
const sockets = [];
|
|
9496
9621
|
for (const line of output.split("\n")) {
|
|
9497
9622
|
if (!line.includes("LISTEN")) continue;
|
|
9498
9623
|
const cols = line.trim().split(/\s+/);
|
|
9499
9624
|
const local = cols[3];
|
|
9500
9625
|
if (!local) continue;
|
|
9501
|
-
const
|
|
9502
|
-
|
|
9503
|
-
|
|
9626
|
+
const lastDot = local.lastIndexOf(".");
|
|
9627
|
+
if (lastDot < 0) continue;
|
|
9628
|
+
const host = local.slice(0, lastDot);
|
|
9629
|
+
const port = Number(local.slice(lastDot + 1));
|
|
9630
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) continue;
|
|
9631
|
+
const loopback = host.startsWith("127.") || host === "::1" || host === "localhost";
|
|
9632
|
+
sockets.push({ port, loopback });
|
|
9504
9633
|
}
|
|
9505
|
-
return
|
|
9634
|
+
return collectScan(sockets);
|
|
9506
9635
|
}
|
|
9507
9636
|
async function readListeningPorts() {
|
|
9508
9637
|
const proc = await readProcListeningPorts();
|
|
@@ -9526,6 +9655,8 @@ var PortDiscovery = class {
|
|
|
9526
9655
|
log;
|
|
9527
9656
|
baseline = null;
|
|
9528
9657
|
tracked = /* @__PURE__ */ new Map();
|
|
9658
|
+
/** Loopback-only candidates already warned about (once per port). */
|
|
9659
|
+
warnedLoopback = /* @__PURE__ */ new Set();
|
|
9529
9660
|
timer = null;
|
|
9530
9661
|
ticking = false;
|
|
9531
9662
|
disabled = false;
|
|
@@ -9553,7 +9684,7 @@ var PortDiscovery = class {
|
|
|
9553
9684
|
this.log("Port discovery disabled: no listening-socket source available");
|
|
9554
9685
|
return;
|
|
9555
9686
|
}
|
|
9556
|
-
this.baseline = baseline;
|
|
9687
|
+
this.baseline = baseline.ports;
|
|
9557
9688
|
this.timer = setInterval(() => void this.tick(), this.intervalMs);
|
|
9558
9689
|
this.timer.unref?.();
|
|
9559
9690
|
}
|
|
@@ -9590,8 +9721,21 @@ var PortDiscovery = class {
|
|
|
9590
9721
|
return true;
|
|
9591
9722
|
}
|
|
9592
9723
|
updateTracking(current) {
|
|
9593
|
-
|
|
9724
|
+
const reachable = /* @__PURE__ */ new Set();
|
|
9725
|
+
for (const port of current.ports) {
|
|
9594
9726
|
if (!this.isCandidate(port)) continue;
|
|
9727
|
+
if (current.loopbackOnly.has(port)) {
|
|
9728
|
+
if (!this.warnedLoopback.has(port)) {
|
|
9729
|
+
this.warnedLoopback.add(port);
|
|
9730
|
+
this.log(
|
|
9731
|
+
`Port ${port} is listening on loopback only and cannot be previewed \u2014 bind 0.0.0.0 (or the pod IP) to make it reachable through the preview proxy`
|
|
9732
|
+
);
|
|
9733
|
+
}
|
|
9734
|
+
continue;
|
|
9735
|
+
}
|
|
9736
|
+
reachable.add(port);
|
|
9737
|
+
}
|
|
9738
|
+
for (const port of reachable) {
|
|
9595
9739
|
const entry = this.tracked.get(port);
|
|
9596
9740
|
if (!entry) {
|
|
9597
9741
|
this.tracked.set(port, { seen: 1, missed: 0, confirmed: false, detectedAt: "" });
|
|
@@ -9605,7 +9749,7 @@ var PortDiscovery = class {
|
|
|
9605
9749
|
}
|
|
9606
9750
|
}
|
|
9607
9751
|
for (const [port, entry] of this.tracked) {
|
|
9608
|
-
if (
|
|
9752
|
+
if (reachable.has(port)) continue;
|
|
9609
9753
|
entry.missed += 1;
|
|
9610
9754
|
entry.seen = 0;
|
|
9611
9755
|
if (entry.missed >= CONFIRM_SCANS || !entry.confirmed) this.tracked.delete(port);
|
|
@@ -9907,7 +10051,7 @@ var SessionRunner = class _SessionRunner {
|
|
|
9907
10051
|
}
|
|
9908
10052
|
this.mode.applyServerMode(this.fullContext?.agentMode, this.fullContext?.isAuto);
|
|
9909
10053
|
this.mode.resolveInitialMode(this.taskContext);
|
|
9910
|
-
if (this.fullContext?.isAuto && this.taskContext.status
|
|
10054
|
+
if (this.fullContext?.isAuto && PRE_BUILD_TASK_STATUSES.has(this.taskContext.status) && this.mode.isBuildCapable) {
|
|
9911
10055
|
void this.connection.triggerIdentification().catch(() => {
|
|
9912
10056
|
});
|
|
9913
10057
|
}
|
|
@@ -10378,11 +10522,12 @@ var SessionRunner = class _SessionRunner {
|
|
|
10378
10522
|
* help) it's waiting on a human, not silently stuck.
|
|
10379
10523
|
*
|
|
10380
10524
|
* - "pr": In Progress with no open PR (build finished without shipping).
|
|
10381
|
-
* - "planning": still
|
|
10382
|
-
* agent that finishes its plan turn
|
|
10383
|
-
* → clean-exit → its session Ends
|
|
10384
|
-
* before a plan ever landed. Nudging
|
|
10385
|
-
* keeps the session Active so the pod
|
|
10525
|
+
* - "planning": still in a pre-build status (Planning/Open) without
|
|
10526
|
+
* (identified + saved plan). A fresh auto agent that finishes its plan turn
|
|
10527
|
+
* WITHOUT ExitPlanMode would otherwise idle → clean-exit → its session Ends
|
|
10528
|
+
* → the workspace is reaped "agent_gone" before a plan ever landed. Nudging
|
|
10529
|
+
* (and, on exhaustion, going dormant) keeps the session Active so the pod
|
|
10530
|
+
* is never prematurely slept.
|
|
10386
10531
|
*/
|
|
10387
10532
|
autoStuckKind() {
|
|
10388
10533
|
if (!this.mode.isAuto || this.stopped) return null;
|
|
@@ -10392,7 +10537,7 @@ var SessionRunner = class _SessionRunner {
|
|
|
10392
10537
|
if (!this.taskContext) return null;
|
|
10393
10538
|
const { status, storyPointId, plan, githubPRUrl } = this.taskContext;
|
|
10394
10539
|
if (status === "InProgress" && !githubPRUrl) return "pr";
|
|
10395
|
-
if (status
|
|
10540
|
+
if (PRE_BUILD_TASK_STATUSES.has(status) && !(storyPointId !== null && !!plan?.trim())) {
|
|
10396
10541
|
return "planning";
|
|
10397
10542
|
}
|
|
10398
10543
|
return null;
|
|
@@ -10564,7 +10709,7 @@ var SessionRunner = class _SessionRunner {
|
|
|
10564
10709
|
this.connection.onSoftStop(() => this.softStop());
|
|
10565
10710
|
this.connection.onReconnected = () => this.queryBridge?.forceRepaint();
|
|
10566
10711
|
this.connection.onModeChange((data) => {
|
|
10567
|
-
const action = this.mode.handleModeChange(data.agentMode);
|
|
10712
|
+
const action = this.mode.handleModeChange(data.agentMode, this.taskContext);
|
|
10568
10713
|
if (action.type === "start_auto") {
|
|
10569
10714
|
this.connection.emitModeChanged(this.mode.effectiveMode);
|
|
10570
10715
|
this.softStop();
|
|
@@ -10721,12 +10866,12 @@ var SessionRunner = class _SessionRunner {
|
|
|
10721
10866
|
|
|
10722
10867
|
// src/setup/config.ts
|
|
10723
10868
|
import { readFile as readFile5 } from "fs/promises";
|
|
10724
|
-
import { join as
|
|
10869
|
+
import { join as join8 } from "path";
|
|
10725
10870
|
var DEVCONTAINER_PATH = ".devcontainer/conveyor/devcontainer.json";
|
|
10726
10871
|
var DEVCONTAINER_PORT_DENY_LIST = /* @__PURE__ */ new Set([5432, 6379, 9200]);
|
|
10727
10872
|
async function loadForwardPorts(workspaceDir) {
|
|
10728
10873
|
try {
|
|
10729
|
-
const raw = await readFile5(
|
|
10874
|
+
const raw = await readFile5(join8(workspaceDir, DEVCONTAINER_PATH), "utf-8");
|
|
10730
10875
|
const parsed = JSON.parse(raw);
|
|
10731
10876
|
const ports = (parsed.forwardPorts ?? []).filter(
|
|
10732
10877
|
(p) => typeof p === "number" && !DEVCONTAINER_PORT_DENY_LIST.has(p)
|
|
@@ -10860,6 +11005,9 @@ export {
|
|
|
10860
11005
|
updateRemoteToken,
|
|
10861
11006
|
flushPendingChanges,
|
|
10862
11007
|
pushToOrigin,
|
|
11008
|
+
TuiUnavailableError,
|
|
11009
|
+
findOnPath,
|
|
11010
|
+
resolvePlaywrightMcpServer,
|
|
10863
11011
|
resolveSessionStart,
|
|
10864
11012
|
sampleKeyUsage,
|
|
10865
11013
|
awaitGitReady,
|
|
@@ -10878,4 +11026,4 @@ export {
|
|
|
10878
11026
|
runStartCommand,
|
|
10879
11027
|
unshallowRepo
|
|
10880
11028
|
};
|
|
10881
|
-
//# sourceMappingURL=chunk-
|
|
11029
|
+
//# sourceMappingURL=chunk-AMPYOJJC.js.map
|