@rallycry/conveyor-agent 11.0.17 → 11.0.18

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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  startWorkbenchServer
3
- } from "./chunk-7TSQXIN3.js";
3
+ } from "./chunk-EIHZRSYI.js";
4
4
  import {
5
5
  TOMBSTONE_MESSAGE,
6
6
  isLegacyEntrypointLaunch
@@ -12,7 +12,7 @@ import {
12
12
  refreshSkillsAfterCheckout,
13
13
  runPreReadyBinds
14
14
  } from "./chunk-SR66HQKB.js";
15
- import "./chunk-QLMNQSJL.js";
15
+ import "./chunk-W5INK3NE.js";
16
16
  import {
17
17
  GitPrepJob,
18
18
  defaultGit,
@@ -20,6 +20,8 @@ import {
20
20
  reportBootMilestone
21
21
  } from "./chunk-GL2DIQEQ.js";
22
22
  import "./chunk-W4LZ7R6Z.js";
23
+ import "./chunk-372R6E4C.js";
24
+ import "./chunk-SQM2BQ7H.js";
23
25
  import {
24
26
  workbenchPort
25
27
  } from "./chunk-KMB3BU4S.js";
@@ -0,0 +1,147 @@
1
+ import {
2
+ getWorkbenchClient
3
+ } from "./chunk-SQM2BQ7H.js";
4
+ import {
5
+ sharedDir,
6
+ workbenchEnabled
7
+ } from "./chunk-KMB3BU4S.js";
8
+
9
+ // src/runner/heavy-gate.ts
10
+ import { existsSync, readFileSync, readdirSync, statSync } from "fs";
11
+ import path from "path";
12
+ var GATE_KEYS = ["heavy", "test", "typecheck", "build"];
13
+ var GATES_DIR_NAME = "gates";
14
+ function runDir() {
15
+ return process.env.CONVEYOR_RUN_DIR ?? "/tmp/conveyor-run";
16
+ }
17
+ function gatesDir() {
18
+ return path.join(sharedDir() ?? runDir(), GATES_DIR_NAME);
19
+ }
20
+ function pidAlive(pid) {
21
+ if (process.platform === "linux" && pidIsZombie(pid)) return false;
22
+ try {
23
+ process.kill(pid, 0);
24
+ return true;
25
+ } catch {
26
+ return false;
27
+ }
28
+ }
29
+ function pidIsZombie(pid) {
30
+ try {
31
+ const stat = readFileSync(`/proc/${pid}/stat`, "utf8");
32
+ const closingParen = stat.lastIndexOf(")");
33
+ return closingParen >= 0 && stat.slice(closingParen + 1).trimStart().startsWith("Z");
34
+ } catch {
35
+ return false;
36
+ }
37
+ }
38
+ function parsePid(raw) {
39
+ const pid = Number.parseInt(raw.trim(), 10);
40
+ return Number.isInteger(pid) && pid > 0 ? pid : null;
41
+ }
42
+ function singletonPidFiles() {
43
+ return GATE_KEYS.map((key) => path.join(runDir(), `${key}.pid`));
44
+ }
45
+ var splitGenericGateStatus = null;
46
+ var splitGenericGateStatusRefresh = null;
47
+ function getLocalGenericGateStatus() {
48
+ const activeLabels = [];
49
+ const abandonedReceipts = [];
50
+ let entries;
51
+ try {
52
+ entries = readdirSync(gatesDir());
53
+ } catch {
54
+ return { activeLabels, abandonedReceipts };
55
+ }
56
+ for (const entry of entries) {
57
+ if (!entry.endsWith(".pid")) continue;
58
+ const pidPath = path.join(gatesDir(), entry);
59
+ const label = entry.slice(0, -".pid".length);
60
+ try {
61
+ const pid = parsePid(readFileSync(pidPath, "utf8"));
62
+ if (pid === null) continue;
63
+ if (pidAlive(pid)) {
64
+ activeLabels.push(label);
65
+ } else if (!existsSync(path.join(gatesDir(), `${label}.exit`))) {
66
+ abandonedReceipts.push({ path: pidPath, label, pid, mtimeMs: statSync(pidPath).mtimeMs });
67
+ }
68
+ } catch {
69
+ }
70
+ }
71
+ abandonedReceipts.sort((a, b) => a.mtimeMs - b.mtimeMs);
72
+ return { activeLabels, abandonedReceipts };
73
+ }
74
+ function genericGateStatus() {
75
+ if (!workbenchEnabled()) return getLocalGenericGateStatus();
76
+ return splitGenericGateStatus ?? { activeLabels: ["unknown"], abandonedReceipts: [] };
77
+ }
78
+ function refreshGenericGateStatus() {
79
+ if (!workbenchEnabled()) return Promise.resolve(getLocalGenericGateStatus());
80
+ if (splitGenericGateStatusRefresh) return splitGenericGateStatusRefresh;
81
+ splitGenericGateStatusRefresh = getWorkbenchClient().gateStatus().then((status) => {
82
+ splitGenericGateStatus = {
83
+ activeLabels: status.activeLabels,
84
+ abandonedReceipts: status.abandonedReceipts.map((receipt) => ({
85
+ ...receipt,
86
+ path: path.join(gatesDir(), `${receipt.label}.pid`)
87
+ }))
88
+ };
89
+ return splitGenericGateStatus;
90
+ }).catch(() => {
91
+ splitGenericGateStatus = null;
92
+ return genericGateStatus();
93
+ }).finally(() => {
94
+ splitGenericGateStatusRefresh = null;
95
+ });
96
+ return splitGenericGateStatusRefresh;
97
+ }
98
+ function listAbandonedGateReceipts() {
99
+ return genericGateStatus().abandonedReceipts;
100
+ }
101
+ function listGateExitSentinels() {
102
+ const sentinels = [];
103
+ let entries;
104
+ try {
105
+ entries = readdirSync(gatesDir());
106
+ } catch {
107
+ return sentinels;
108
+ }
109
+ for (const entry of entries) {
110
+ if (!entry.endsWith(".exit")) continue;
111
+ const file = path.join(gatesDir(), entry);
112
+ try {
113
+ const mtimeMs = statSync(file).mtimeMs;
114
+ let code = null;
115
+ try {
116
+ const parsed = JSON.parse(readFileSync(file, "utf8"));
117
+ const value = parsed?.code;
118
+ if (typeof value === "number") code = value;
119
+ } catch {
120
+ }
121
+ sentinels.push({ path: file, label: entry.slice(0, -".exit".length), code, mtimeMs });
122
+ } catch {
123
+ }
124
+ }
125
+ sentinels.sort((a, b) => a.mtimeMs - b.mtimeMs);
126
+ return sentinels;
127
+ }
128
+ function isHeavyGateActive() {
129
+ if (genericGateStatus().activeLabels.length > 0) return true;
130
+ for (const file of singletonPidFiles()) {
131
+ try {
132
+ const pid = parsePid(readFileSync(file, "utf8"));
133
+ if (pid !== null && pidAlive(pid)) return true;
134
+ } catch {
135
+ }
136
+ }
137
+ return false;
138
+ }
139
+
140
+ export {
141
+ gatesDir,
142
+ getLocalGenericGateStatus,
143
+ refreshGenericGateStatus,
144
+ listAbandonedGateReceipts,
145
+ listGateExitSentinels,
146
+ isHeavyGateActive
147
+ };
@@ -1,9 +1,12 @@
1
1
  import {
2
2
  loadPtySpawn
3
- } from "./chunk-QLMNQSJL.js";
3
+ } from "./chunk-W5INK3NE.js";
4
4
  import {
5
5
  terminateProcessGroup
6
6
  } from "./chunk-W4LZ7R6Z.js";
7
+ import {
8
+ getLocalGenericGateStatus
9
+ } from "./chunk-372R6E4C.js";
7
10
  import {
8
11
  FrameReader,
9
12
  timingSafeTokenEqual,
@@ -112,21 +115,25 @@ async function runRefreshSkills(socket, opts) {
112
115
  }
113
116
  socket.end();
114
117
  }
118
+ function immediateResponse(req, opts) {
119
+ if (req.op === "ping") return { t: "pong", version: opts.version };
120
+ if (req.op === "gitStatus") return opts.getGitStatus?.() ?? { t: "gitStatus", state: "ready" };
121
+ if (req.op === "gateStatus") return { t: "gateStatus", ...getLocalGenericGateStatus() };
122
+ return null;
123
+ }
115
124
  function dispatch(socket, req, sink, opts, liveOps) {
116
125
  if (!req || typeof req !== "object" || !timingSafeTokenEqual(req.token ?? "", opts.token)) {
117
126
  writeFrame(socket, { t: "error", message: "unauthorized", code: "unauthorized" });
118
127
  socket.destroy();
119
128
  return;
120
129
  }
130
+ const immediate = immediateResponse(req, opts);
131
+ if (immediate) {
132
+ writeFrame(socket, immediate);
133
+ socket.end();
134
+ return;
135
+ }
121
136
  switch (req.op) {
122
- case "ping":
123
- writeFrame(socket, { t: "pong", version: opts.version });
124
- socket.end();
125
- return;
126
- case "gitStatus":
127
- writeFrame(socket, opts.getGitStatus?.() ?? { t: "gitStatus", state: "ready" });
128
- socket.end();
129
- return;
130
137
  case "refreshSkills":
131
138
  void runRefreshSkills(socket, opts);
132
139
  return;
@@ -5,7 +5,7 @@ import {
5
5
  readWorkspaceBytes,
6
6
  statWorkspacePath,
7
7
  workspacePathExists
8
- } from "./chunk-M4SD2ESQ.js";
8
+ } from "./chunk-WS7QRB37.js";
9
9
  import {
10
10
  reportBootMilestone
11
11
  } from "./chunk-GL2DIQEQ.js";
@@ -16,7 +16,7 @@ import {
16
16
  } from "./chunk-W4LZ7R6Z.js";
17
17
  import {
18
18
  getWorkbenchClient
19
- } from "./chunk-B54XFOXL.js";
19
+ } from "./chunk-SQM2BQ7H.js";
20
20
  import {
21
21
  workbenchEnabled
22
22
  } from "./chunk-KMB3BU4S.js";
@@ -37,7 +37,31 @@ var RemoteProcessHandle = class extends EventEmitter {
37
37
  }
38
38
  };
39
39
 
40
+ // src/workbench/gate-status.ts
41
+ function readGateStatus(open, timeoutMs) {
42
+ return new Promise((resolve, reject) => {
43
+ let status = null;
44
+ let timer = null;
45
+ const socket = open(
46
+ (frame) => {
47
+ if (frame.t === "gateStatus") status = frame;
48
+ },
49
+ (error) => {
50
+ if (timer) clearTimeout(timer);
51
+ if (status) resolve(status);
52
+ else reject(error ?? new WorkbenchError("connection closed before gateStatus"));
53
+ }
54
+ );
55
+ timer = setTimeout(
56
+ () => socket.destroy(new WorkbenchError("gateStatus timed out", "workbench_timeout")),
57
+ timeoutMs
58
+ );
59
+ timer.unref();
60
+ });
61
+ }
62
+
40
63
  // src/workbench/client.ts
64
+ var DEFAULT_GATE_STATUS_TIMEOUT_MS = 5e3;
41
65
  var WORKBENCH_COMMAND_ENV_DENYLIST = /* @__PURE__ */ new Set([
42
66
  "POD_BOOTSTRAP_TOKEN",
43
67
  "CONVEYOR_BOOTSTRAP_TOKEN",
@@ -107,10 +131,12 @@ var WorkbenchClient = class {
107
131
  port;
108
132
  host;
109
133
  token;
134
+ gateStatusTimeoutMs;
110
135
  constructor(options = {}) {
111
136
  this.port = options.port ?? workbenchPort() ?? DEFAULT_WORKBENCH_PORT;
112
137
  this.host = options.host ?? "127.0.0.1";
113
138
  this.token = options.token ?? workbenchToken();
139
+ this.gateStatusTimeoutMs = options.gateStatusTimeoutMs ?? DEFAULT_GATE_STATUS_TIMEOUT_MS;
114
140
  }
115
141
  /** Open a connection, send the request, route response frames. */
116
142
  open(request, onFrame, onClose) {
@@ -168,6 +194,12 @@ var WorkbenchClient = class {
168
194
  );
169
195
  });
170
196
  }
197
+ gateStatus() {
198
+ return readGateStatus(
199
+ (onFrame, onClose) => this.open({ op: "gateStatus" }, onFrame, onClose),
200
+ this.gateStatusTimeoutMs
201
+ );
202
+ }
171
203
  /** Ask the workbench — which owns the repo filesystem in split mode — to
172
204
  * re-run the skill floor after the agent's authoritative task-branch
173
205
  * checkout. A daemon without a provider answers an empty result. */
@@ -200,7 +200,7 @@ async function loadPtySpawn() {
200
200
  async function resolvePtySpawn() {
201
201
  const { workbenchEnabled } = await import("./mode-ZJSOSLGU.js");
202
202
  if (!workbenchEnabled()) return loadPtySpawn();
203
- const { getWorkbenchClient } = await import("./client-DVVHMIOO.js");
203
+ const { getWorkbenchClient } = await import("./client-VIFYMBT2.js");
204
204
  return (file, args, options) => getWorkbenchClient().spawnPty(file, args, options);
205
205
  }
206
206
  function sessionTempBase() {
@@ -20,7 +20,7 @@ import {
20
20
  import {
21
21
  WorkbenchError,
22
22
  getWorkbenchClient
23
- } from "./chunk-B54XFOXL.js";
23
+ } from "./chunk-SQM2BQ7H.js";
24
24
  import {
25
25
  workbenchEnabled
26
26
  } from "./chunk-KMB3BU4S.js";
@@ -29,7 +29,7 @@ import {
29
29
  spawnOptionsFingerprint,
30
30
  transcriptSize,
31
31
  turnOptionsFrom
32
- } from "./chunk-QLMNQSJL.js";
32
+ } from "./chunk-W5INK3NE.js";
33
33
  import {
34
34
  AgentConnection,
35
35
  CodespacePortVisibility,
@@ -59,7 +59,7 @@ import {
59
59
  statWorkspacePath,
60
60
  updateRemoteToken,
61
61
  verifyGitCredential
62
- } from "./chunk-M4SD2ESQ.js";
62
+ } from "./chunk-WS7QRB37.js";
63
63
  import {
64
64
  registerBootMilestoneSocketFallback,
65
65
  reportBootMilestone
@@ -72,15 +72,17 @@ import {
72
72
  } from "./chunk-W4LZ7R6Z.js";
73
73
  import {
74
74
  isHeavyGateActive,
75
- listGateExitSentinels
76
- } from "./chunk-7P6QZHXZ.js";
75
+ listAbandonedGateReceipts,
76
+ listGateExitSentinels,
77
+ refreshGenericGateStatus
78
+ } from "./chunk-372R6E4C.js";
77
79
  import {
78
80
  LoopLagMonitor,
79
81
  loopStatusForRunnerStatus
80
82
  } from "./chunk-IA45XHOA.js";
81
83
  import {
82
84
  getWorkbenchClient
83
- } from "./chunk-B54XFOXL.js";
85
+ } from "./chunk-SQM2BQ7H.js";
84
86
  import {
85
87
  workbenchEnabled
86
88
  } from "./chunk-KMB3BU4S.js";
@@ -2181,6 +2183,10 @@ var CATALOG = {
2181
2183
  // rejects (media_type_header_exception), breaking search/audit indexing
2182
2184
  // in pods. 512m heap matches the project compose sizing.
2183
2185
  image: "docker.elastic.co/elasticsearch/elasticsearch:9.4.0",
2186
+ mirror: {
2187
+ src: "docker.elastic.co/elasticsearch/elasticsearch:9.4.0",
2188
+ dest: "mirror-elasticsearch:9.4.0"
2189
+ },
2184
2190
  ports: [9200],
2185
2191
  // Baked service images are `docker commit`s of a recently-running ES, so
2186
2192
  // they carry a stale data-dir node.lock; ES 9 hard-fails on it at boot
@@ -15688,7 +15694,7 @@ var SessionRunner = class _SessionRunner {
15688
15694
  onHeartbeat: () => {
15689
15695
  const loopStatus = this.refreshLoopStatus();
15690
15696
  this.connection.sendHeartbeat(this.loopLag.takeMaxLagMs(), loopStatus);
15691
- this.maybeWakeForOrphanedBackgroundWork();
15697
+ void this.refreshGateStatusAndMaybeWake();
15692
15698
  },
15693
15699
  onIdleTimeout: () => {
15694
15700
  if (this.deferShutdownForLiveChild("idle")) return;
@@ -15829,6 +15835,10 @@ var SessionRunner = class _SessionRunner {
15829
15835
  /** One-shot latch: at most one watchdog wake per idle episode. Reset when a
15830
15836
  * turn runs. */
15831
15837
  backgroundWakeFiredThisIdle = false;
15838
+ /** Start of the last active episode. A gate can be launched shortly before
15839
+ * the agent declares completion, so idle-start freshness would reject its
15840
+ * terminal receipt as stale. */
15841
+ activeEpisodeStartedAt = Date.now();
15832
15842
  /** When the current idle episode began — an exit sentinel older than this
15833
15843
  * appeared while a turn could still have consumed it, so it never wakes. */
15834
15844
  idleEpisodeStartedAt = 0;
@@ -15843,22 +15853,30 @@ var SessionRunner = class _SessionRunner {
15843
15853
  * evidence of orphaned background work — a tracked launch with no live gate
15844
15854
  * process continuously past `ORPHAN_WAKE_GRACE_MS`, or an unconsumed
15845
15855
  * `gates/<label>.exit` sentinel that appeared this idle episode — never on
15846
- * "the agent seems stuck". A `completed` agent is never woken (the
15847
- * completion guard holds), and a live pid always suppresses the wake.
15856
+ * "the agent seems stuck". A `completed` agent remains parked unless a
15857
+ * current-episode generic receipt proves a required gate died without an
15858
+ * exit record, and a live pid always suppresses the wake.
15848
15859
  */
15849
15860
  maybeWakeForOrphanedBackgroundWork() {
15850
15861
  try {
15851
- if (this._state !== "idle" || this.stopped || this.completedThisTurn) {
15862
+ if (this._state !== "idle" || this.stopped) {
15852
15863
  this.orphanGraceStartedAt = null;
15853
15864
  return;
15854
15865
  }
15855
15866
  if (this.backgroundWakeFiredThisIdle) return;
15856
15867
  if (!this.inputResolver) return;
15868
+ const abandoned = listAbandonedGateReceipts().find(
15869
+ (receipt) => receipt.mtimeMs >= this.activeEpisodeStartedAt
15870
+ );
15871
+ if (this.completedThisTurn && !abandoned) {
15872
+ this.orphanGraceStartedAt = null;
15873
+ return;
15874
+ }
15857
15875
  if (isHeavyGateActive()) {
15858
15876
  this.orphanGraceStartedAt = null;
15859
15877
  return;
15860
15878
  }
15861
- const evidence = this.findOrphanEvidence(Date.now());
15879
+ const evidence = abandoned ? this.abandonedReceiptEvidence(abandoned) : this.findOrphanEvidence(Date.now());
15862
15880
  if (!evidence) return;
15863
15881
  process.stderr.write(
15864
15882
  `[conveyor-agent] Background-work watchdog: waking agent \u2014 ${evidence.description}
@@ -15867,11 +15885,16 @@ var SessionRunner = class _SessionRunner {
15867
15885
  this.backgroundWakeFiredThisIdle = true;
15868
15886
  this.orphanGraceStartedAt = null;
15869
15887
  if (evidence.sentinel) rmSync(evidence.sentinel.path, { force: true });
15888
+ if (evidence.abandonedReceipt) rmSync(evidence.abandonedReceipt.path, { force: true });
15870
15889
  this.backgroundWork.clear();
15890
+ if (evidence.abandonedReceipt) {
15891
+ this.completedThisTurn = false;
15892
+ this.dormantDeadline = null;
15893
+ }
15871
15894
  const resolver = this.inputResolver;
15872
15895
  this.inputResolver = null;
15873
15896
  resolver({
15874
- content: `A background job you started appears to have finished or died without delivering its completion notification (${evidence.description}). Read the job's output \u2014 its log file, or the corresponding file under $CONVEYOR_RUN_DIR/gates/ \u2014 to determine the real result, then continue your plan. Do not assume the job succeeded; verify its output first, and rerun it if it was killed.`,
15897
+ content: `A required gate you started appears to have finished or died without delivering its completion notification (${evidence.description}). Read the job's output \u2014 its log file, or the corresponding file under $CONVEYOR_SHARED_DIR/gates on split pods \u2014 to determine the real result, then continue your plan. Do not assume the job succeeded; verify its output first. If it died, rerun it in the foreground without shell '&' and retain the command session until its exit result.`,
15875
15898
  userId: "system",
15876
15899
  source: "background_work_check"
15877
15900
  });
@@ -15902,6 +15925,25 @@ var SessionRunner = class _SessionRunner {
15902
15925
  description: `${this.backgroundWork.pendingCount()} tracked background launch(es) with no live gate process for ${Math.round(ORPHAN_WAKE_GRACE_MS / 6e4)}+ minutes`
15903
15926
  };
15904
15927
  }
15928
+ abandonedReceiptEvidence(receipt) {
15929
+ return {
15930
+ abandonedReceipt: receipt,
15931
+ description: `gate '${receipt.label}' stopped before writing an exit receipt (pid ${receipt.pid})`
15932
+ };
15933
+ }
15934
+ /** Refresh PID liveness in the owning workbench namespace before checking
15935
+ * terminal receipts. The local path is immediate; socket failures preserve
15936
+ * the conservative cached state and never interrupt the heartbeat. */
15937
+ async refreshGateStatusAndMaybeWake() {
15938
+ try {
15939
+ await refreshGenericGateStatus();
15940
+ this.refreshLoopStatus();
15941
+ this.maybeWakeForOrphanedBackgroundWork();
15942
+ } catch (err) {
15943
+ process.stderr.write(`[conveyor-agent] Gate status refresh failed: ${err}
15944
+ `);
15945
+ }
15946
+ }
15905
15947
  get sessionId() {
15906
15948
  return this.connection.sessionId;
15907
15949
  }
@@ -16713,9 +16755,12 @@ var SessionRunner = class _SessionRunner {
16713
16755
  };
16714
16756
  const bridge = new QueryBridge(this.connection, this.mode, runnerConfig, {
16715
16757
  onStatusChange: (status) => {
16716
- if (status === "running" && this._state === "waiting_for_input") {
16717
- this._state = "running";
16718
- this.lifecycle.cancelIdleTimer();
16758
+ if (status === "running") {
16759
+ this.beginRunningEpisode();
16760
+ if (this._state === "waiting_for_input") {
16761
+ this._state = "running";
16762
+ this.lifecycle.cancelIdleTimer();
16763
+ }
16719
16764
  }
16720
16765
  return this.callbacks.onStatusChange(status);
16721
16766
  },
@@ -16963,8 +17008,7 @@ ${outcome.failures.join("\n")}
16963
17008
  }
16964
17009
  async setState(status) {
16965
17010
  if (status === "running") {
16966
- this.backgroundDeferStartedAt = null;
16967
- this.backgroundWakeFiredThisIdle = false;
17011
+ this.beginRunningEpisode();
16968
17012
  }
16969
17013
  if (status === "idle" && this._state !== "idle") this.idleEpisodeStartedAt = Date.now();
16970
17014
  this._state = status;
@@ -16972,6 +17016,13 @@ ${outcome.failures.join("\n")}
16972
17016
  await this.connection.emitStatus(status);
16973
17017
  await this.callbacks.onStatusChange(status);
16974
17018
  }
17019
+ /** Mark a fresh turn without emitting a duplicate status. QueryExecutor's
17020
+ * prefilled PTY callback already emitted `running` before it reaches here. */
17021
+ beginRunningEpisode() {
17022
+ this.activeEpisodeStartedAt = Date.now();
17023
+ this.backgroundDeferStartedAt = null;
17024
+ this.backgroundWakeFiredThisIdle = false;
17025
+ }
16975
17026
  async shutdown(finalState) {
16976
17027
  process.stderr.write(`[conveyor-agent] Shutdown: reason=${finalState}
16977
17028
  `);
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  WorkspaceCommandSupervisor,
8
8
  startWorkspaceCommandsAfterConnect,
9
9
  stopWorkspaceCommands
10
- } from "./chunk-X2MKHJBZ.js";
10
+ } from "./chunk-GZRZGBIK.js";
11
11
  import {
12
12
  DEFAULT_SONNET_MODEL,
13
13
  PtyHarness,
@@ -27,13 +27,13 @@ import {
27
27
  resolveTuiKindFromEnv,
28
28
  runUsageProbe,
29
29
  sampleKeyUsage
30
- } from "./chunk-GVDQQZCO.js";
30
+ } from "./chunk-ZFHN7GO7.js";
31
31
  import "./chunk-SR66HQKB.js";
32
32
  import {
33
33
  inheritedEnv,
34
34
  resolvePtySpawn,
35
35
  sessionTempBase
36
- } from "./chunk-QLMNQSJL.js";
36
+ } from "./chunk-W5INK3NE.js";
37
37
  import {
38
38
  AgentConnection,
39
39
  CodespacePortVisibility,
@@ -44,12 +44,12 @@ import {
44
44
  createServiceLogger,
45
45
  fetchBootstrap,
46
46
  loadConveyorConfig
47
- } from "./chunk-M4SD2ESQ.js";
47
+ } from "./chunk-WS7QRB37.js";
48
48
  import "./chunk-GL2DIQEQ.js";
49
49
  import "./chunk-W4LZ7R6Z.js";
50
- import "./chunk-7P6QZHXZ.js";
50
+ import "./chunk-372R6E4C.js";
51
51
  import "./chunk-IA45XHOA.js";
52
- import "./chunk-B54XFOXL.js";
52
+ import "./chunk-SQM2BQ7H.js";
53
53
  import "./chunk-KMB3BU4S.js";
54
54
  import "./chunk-6Q6LQBWO.js";
55
55
 
@@ -1205,7 +1205,7 @@ function wireSpawnChildren(mode, connection, supervisors, logger7) {
1205
1205
 
1206
1206
  // src/cli.ts
1207
1207
  if (process.argv[2] === "boot") {
1208
- const { runBoot } = await import("./boot-CCRZMY2P.js");
1208
+ const { runBoot } = await import("./boot-4ZNOCKFZ.js");
1209
1209
  process.exit(await runBoot(process.argv.slice(3)));
1210
1210
  }
1211
1211
  if (isLegacyEntrypointLaunch(process.env)) {
@@ -1307,7 +1307,7 @@ process.on("unhandledRejection", (reason) => {
1307
1307
  process.exit(1);
1308
1308
  });
1309
1309
  if (process.env.CONVEYOR_MODE === "workbench") {
1310
- const { startWorkbenchServer } = await import("./server-WM23BC5C.js");
1310
+ const { startWorkbenchServer } = await import("./server-L5CQ5EJB.js");
1311
1311
  const { oomWatchdogOptionsFromEnv } = await import("./oom-watchdog-PAC5OJJG.js");
1312
1312
  const { DEFAULT_WORKBENCH_PORT } = await import("./protocol-QBCYO4GI.js");
1313
1313
  const port = Number(process.env.CONVEYOR_WORKBENCH_PORT) || DEFAULT_WORKBENCH_PORT;
@@ -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-VJR6J5PL.js");
1461
+ const { runServingSession } = await import("./serve-boot-RU5BYIK4.js");
1462
1462
  exitContext.runnerMode = "serving";
1463
1463
  exitContext.sessionId = process.env.CONVEYOR_SESSION_ID ?? exitContext.sessionId;
1464
1464
  const outcome = await runServingSession({
@@ -4,7 +4,7 @@ import {
4
4
  WorkbenchError,
5
5
  getWorkbenchClient,
6
6
  resetWorkbenchClient
7
- } from "./chunk-B54XFOXL.js";
7
+ } from "./chunk-SQM2BQ7H.js";
8
8
  import "./chunk-KMB3BU4S.js";
9
9
  import "./chunk-6Q6LQBWO.js";
10
10
  export {
package/dist/gate-cli.js CHANGED
@@ -1,7 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  gatesDir
4
- } from "./chunk-7P6QZHXZ.js";
4
+ } from "./chunk-372R6E4C.js";
5
+ import "./chunk-SQM2BQ7H.js";
6
+ import "./chunk-KMB3BU4S.js";
7
+ import "./chunk-6Q6LQBWO.js";
5
8
 
6
9
  // src/runner/gate-wrapper.ts
7
10
  import { mkdirSync, rmSync, writeFileSync } from "fs";
package/dist/index.d.ts CHANGED
@@ -810,6 +810,10 @@ declare class SessionRunner {
810
810
  /** One-shot latch: at most one watchdog wake per idle episode. Reset when a
811
811
  * turn runs. */
812
812
  private backgroundWakeFiredThisIdle;
813
+ /** Start of the last active episode. A gate can be launched shortly before
814
+ * the agent declares completion, so idle-start freshness would reject its
815
+ * terminal receipt as stale. */
816
+ private activeEpisodeStartedAt;
813
817
  /** When the current idle episode began — an exit sentinel older than this
814
818
  * appeared while a turn could still have consumed it, so it never wakes. */
815
819
  private idleEpisodeStartedAt;
@@ -824,13 +828,19 @@ declare class SessionRunner {
824
828
  * evidence of orphaned background work — a tracked launch with no live gate
825
829
  * process continuously past `ORPHAN_WAKE_GRACE_MS`, or an unconsumed
826
830
  * `gates/<label>.exit` sentinel that appeared this idle episode — never on
827
- * "the agent seems stuck". A `completed` agent is never woken (the
828
- * completion guard holds), and a live pid always suppresses the wake.
831
+ * "the agent seems stuck". A `completed` agent remains parked unless a
832
+ * current-episode generic receipt proves a required gate died without an
833
+ * exit record, and a live pid always suppresses the wake.
829
834
  */
830
835
  private maybeWakeForOrphanedBackgroundWork;
831
836
  /** Orphan evidence that has held past the grace, or null. Maintains the
832
837
  * grace clock for the tracked-work condition as a side effect. */
833
838
  private findOrphanEvidence;
839
+ private abandonedReceiptEvidence;
840
+ /** Refresh PID liveness in the owning workbench namespace before checking
841
+ * terminal receipts. The local path is immediate; socket failures preserve
842
+ * the conservative cached state and never interrupt the heartbeat. */
843
+ private refreshGateStatusAndMaybeWake;
834
844
  get sessionId(): string;
835
845
  get isStopped(): boolean;
836
846
  /** Wire the boot supervisor handle post-construction — cli.ts constructs it
@@ -1097,6 +1107,9 @@ declare class SessionRunner {
1097
1107
  * the tool starts work that outlives the turn. */
1098
1108
  private noteToolUseForBackgroundWork;
1099
1109
  private setState;
1110
+ /** Mark a fresh turn without emitting a duplicate status. QueryExecutor's
1111
+ * prefilled PTY callback already emitted `running` before it reaches here. */
1112
+ private beginRunningEpisode;
1100
1113
  private shutdown;
1101
1114
  private _finalState;
1102
1115
  /** The final status after run() completes. Use to determine exit code. */
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  SessionRunner,
3
3
  unshallowRepo
4
- } from "./chunk-GVDQQZCO.js";
4
+ } from "./chunk-ZFHN7GO7.js";
5
5
  import "./chunk-SR66HQKB.js";
6
- import "./chunk-QLMNQSJL.js";
6
+ import "./chunk-W5INK3NE.js";
7
7
  import {
8
8
  AgentConnection,
9
9
  GIT_TIMEOUT_MS,
@@ -17,18 +17,18 @@ import {
17
17
  stageAndCommit,
18
18
  updateRemoteToken,
19
19
  workspacePathExists
20
- } from "./chunk-M4SD2ESQ.js";
20
+ } from "./chunk-WS7QRB37.js";
21
21
  import "./chunk-GL2DIQEQ.js";
22
22
  import {
23
23
  runAuthTokenCommand,
24
24
  runSetupCommand,
25
25
  runStartCommand
26
26
  } from "./chunk-W4LZ7R6Z.js";
27
- import "./chunk-7P6QZHXZ.js";
27
+ import "./chunk-372R6E4C.js";
28
28
  import "./chunk-IA45XHOA.js";
29
29
  import {
30
30
  getWorkbenchClient
31
- } from "./chunk-B54XFOXL.js";
31
+ } from "./chunk-SQM2BQ7H.js";
32
32
  import {
33
33
  workbenchEnabled
34
34
  } from "./chunk-KMB3BU4S.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  WorkspaceCommandSupervisor
3
- } from "./chunk-X2MKHJBZ.js";
3
+ } from "./chunk-GZRZGBIK.js";
4
4
  import {
5
5
  AgentConnection,
6
6
  CodespacePortVisibility,
@@ -11,11 +11,11 @@ import {
11
11
  createServiceLogger,
12
12
  ensureOnTaskBranch,
13
13
  loadConveyorConfig
14
- } from "./chunk-M4SD2ESQ.js";
14
+ } from "./chunk-WS7QRB37.js";
15
15
  import "./chunk-GL2DIQEQ.js";
16
16
  import "./chunk-W4LZ7R6Z.js";
17
17
  import "./chunk-IA45XHOA.js";
18
- import "./chunk-B54XFOXL.js";
18
+ import "./chunk-SQM2BQ7H.js";
19
19
  import "./chunk-KMB3BU4S.js";
20
20
  import "./chunk-6Q6LQBWO.js";
21
21
 
@@ -1,8 +1,11 @@
1
1
  import {
2
2
  startWorkbenchServer
3
- } from "./chunk-7TSQXIN3.js";
4
- import "./chunk-QLMNQSJL.js";
3
+ } from "./chunk-EIHZRSYI.js";
4
+ import "./chunk-W5INK3NE.js";
5
5
  import "./chunk-W4LZ7R6Z.js";
6
+ import "./chunk-372R6E4C.js";
7
+ import "./chunk-SQM2BQ7H.js";
8
+ import "./chunk-KMB3BU4S.js";
6
9
  import "./chunk-6Q6LQBWO.js";
7
10
  import "./chunk-6W6UZ4SJ.js";
8
11
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rallycry/conveyor-agent",
3
- "version": "11.0.17",
3
+ "version": "11.0.18",
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",
@@ -1,76 +0,0 @@
1
- // src/runner/heavy-gate.ts
2
- import { readdirSync, readFileSync, statSync } from "fs";
3
- import path from "path";
4
- var GATE_KEYS = ["heavy", "test", "typecheck", "build"];
5
- var GATES_DIR_NAME = "gates";
6
- function runDir() {
7
- return process.env.CONVEYOR_RUN_DIR ?? "/tmp/conveyor-run";
8
- }
9
- function gatesDir() {
10
- return path.join(runDir(), GATES_DIR_NAME);
11
- }
12
- function pidAlive(pid) {
13
- try {
14
- process.kill(pid, 0);
15
- return true;
16
- } catch {
17
- return false;
18
- }
19
- }
20
- function parsePid(raw) {
21
- const pid = Number.parseInt(raw.trim(), 10);
22
- return Number.isInteger(pid) && pid > 0 ? pid : null;
23
- }
24
- function gatePidFiles() {
25
- const files = GATE_KEYS.map((key) => path.join(runDir(), `${key}.pid`));
26
- try {
27
- for (const entry of readdirSync(gatesDir())) {
28
- if (entry.endsWith(".pid")) files.push(path.join(gatesDir(), entry));
29
- }
30
- } catch {
31
- }
32
- return files;
33
- }
34
- function listGateExitSentinels() {
35
- const sentinels = [];
36
- let entries;
37
- try {
38
- entries = readdirSync(gatesDir());
39
- } catch {
40
- return sentinels;
41
- }
42
- for (const entry of entries) {
43
- if (!entry.endsWith(".exit")) continue;
44
- const file = path.join(gatesDir(), entry);
45
- try {
46
- const mtimeMs = statSync(file).mtimeMs;
47
- let code = null;
48
- try {
49
- const parsed = JSON.parse(readFileSync(file, "utf8"));
50
- const value = parsed?.code;
51
- if (typeof value === "number") code = value;
52
- } catch {
53
- }
54
- sentinels.push({ path: file, label: entry.slice(0, -".exit".length), code, mtimeMs });
55
- } catch {
56
- }
57
- }
58
- sentinels.sort((a, b) => a.mtimeMs - b.mtimeMs);
59
- return sentinels;
60
- }
61
- function isHeavyGateActive() {
62
- for (const file of gatePidFiles()) {
63
- try {
64
- const pid = parsePid(readFileSync(file, "utf8"));
65
- if (pid !== null && pidAlive(pid)) return true;
66
- } catch {
67
- }
68
- }
69
- return false;
70
- }
71
-
72
- export {
73
- gatesDir,
74
- listGateExitSentinels,
75
- isHeavyGateActive
76
- };