@rallycry/conveyor-agent 10.13.3 → 10.13.9

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/index.d.ts CHANGED
@@ -3,6 +3,11 @@ import { RunnerMode, AgentSessionServiceMethods, AgentMode, PtyChatEventPayload,
3
3
  export * from '@project/shared';
4
4
  import { ChildProcess } from 'node:child_process';
5
5
 
6
+ /**
7
+ * The milestones an agent may declare — narrower than the server's full slug
8
+ * set, which also covers server-generated review verdicts.
9
+ */
10
+ type AgentMilestone = NonNullable<AgentSessionServiceMethods["postAgentMessage"]["payload"]["milestone"]>;
6
11
  interface AgentConnectionConfig {
7
12
  apiUrl: string;
8
13
  taskToken: string;
@@ -90,6 +95,8 @@ declare class AgentConnection {
90
95
  private earlySpawnReviews;
91
96
  private spawnTuiCallback;
92
97
  private earlySpawnTuis;
98
+ private probeUsageCallback;
99
+ private earlyProbeUsage;
93
100
  private ptyInputCallback;
94
101
  private ptyResizeCallback;
95
102
  constructor(config: AgentConnectionConfig);
@@ -148,6 +155,9 @@ declare class AgentConnection {
148
155
  */
149
156
  reportReviewSpawnFailure(reviewSessionId: string, error?: string): void;
150
157
  onSpawnTui(callback: (data: SpawnTuiData) => void): void;
158
+ /** Register the on-demand usage-refresh handler; drains an early-buffered
159
+ * `session:probeUsage` that arrived before the runner was ready. */
160
+ onProbeUsage(callback: () => void): void;
151
161
  /**
152
162
  * Report that a same-pod TUI/shell child failed to spawn (fire-and-forget).
153
163
  * The server Ends the orphaned session — no fallback pod (unlike review).
@@ -183,8 +193,8 @@ declare class AgentConnection {
183
193
  /** Subscribe to relayed (reconciled) terminal resizes. Returns an unsubscribe fn. */
184
194
  onPtyResize(handler: (cols: number, rows: number) => void): () => void;
185
195
  emitStatus(status: string, reason?: string, questionText?: string): Promise<void>;
186
- postChatMessage(content: string): void;
187
- postChatMessageAwait(content: string): Promise<void>;
196
+ postChatMessage(content: string, milestone?: AgentMilestone): void;
197
+ postChatMessageAwait(content: string, milestone?: AgentMilestone): Promise<void>;
188
198
  private suppressIfDuplicate;
189
199
  checkAndTrackDuplicate(content: string): {
190
200
  duplicate: false;
@@ -237,6 +247,15 @@ declare class AgentConnection {
237
247
  triggerIdentification(): Promise<{
238
248
  identified: boolean;
239
249
  }>;
250
+ handoffToImplementer(payload: {
251
+ storyPoints?: number;
252
+ message?: string;
253
+ }): Promise<{
254
+ handedOff: boolean;
255
+ agentName?: string;
256
+ model?: string;
257
+ reason?: string;
258
+ }>;
240
259
  refreshAuthToken(): Promise<boolean>;
241
260
  /**
242
261
  * Refresh the in-process `CONVEYOR_TASK_TOKEN` from the bootstrap endpoint.
package/dist/index.js CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  unshallowRepo,
22
22
  updateRemoteToken,
23
23
  uploadSnapshotToGcs
24
- } from "./chunk-P4VWWR5O.js";
24
+ } from "./chunk-PXQJ4NVO.js";
25
25
  import "./chunk-7TQO4ZF4.js";
26
26
 
27
27
  // src/runner/worktree.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rallycry/conveyor-agent",
3
- "version": "10.13.3",
3
+ "version": "10.13.9",
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",
@@ -118,6 +118,31 @@ refresh_agent_version() {
118
118
  fi
119
119
  }
120
120
 
121
+ # ── Reclaim any root-owned $HOME config/state entries (bake defense-in-depth) ──
122
+ # Bake steps that run as root while ENV HOME points at /home/conveyor can leave
123
+ # root-owned entries under ~/.config / ~/.local (a uv install receipt in
124
+ # ~/.config once bricked every pod boot fleet-wide). This reclaims them so a
125
+ # baked ownership slip can't EACCES the entrypoint under `set -e`.
126
+ #
127
+ # CONDITIONAL, not `chown -R`: a `find … ! -user conveyor` walk is stat-only when
128
+ # the tree is already clean (the common case since the bake-side `HOME=/root`
129
+ # fix in 9815c8915 — current images carry ZERO root-owned files here), so it
130
+ # triggers no overlayfs copy-up. The former unconditional `chown -R` copied up
131
+ # ALL ~3.9k baked files (~63MB, dominated by ~/.config/opencode's 3,652 files)
132
+ # on EVERY boot even when nothing was root-owned — ~16s of I/O-bound
133
+ # uninterruptible-disk-sleep that reclaimed nothing, and the true owner of the
134
+ # "GCS-FUSE" boot gap. Runs in the PRE-BIND phase below (payload-independent, off
135
+ # the time-to-agent critical path) and before any mount symlinks exist, so it
136
+ # never follows into the FUSE mount. See
137
+ # docs/investigations/gcs-fuse-user-home-boot-cost-2026-07-15.md.
138
+ reclaim_home_ownership() {
139
+ local d
140
+ for d in /home/conveyor/.config /home/conveyor/.local; do
141
+ [ -d "${d}" ] || continue
142
+ sudo -n find "${d}" ! -user conveyor -exec chown conveyor:conveyor {} + 2>/dev/null || true
143
+ done
144
+ }
145
+
121
146
  start_workspace_sshd
122
147
 
123
148
  # ── Required env vars (injected by pod spec — v3: exactly these two) ──
@@ -134,6 +159,10 @@ export CLAUDESPACE_NAME="${INSTANCE_NAME}"
134
159
  # ═══════════════════════════════════════════════════════════════════════════
135
160
  PREWARM_DEV_LOOP_PID=""
136
161
  refresh_agent_version
162
+ # Reclaim root-owned $HOME entries here, during standby, so the common (clean)
163
+ # case costs a stat-only walk off the critical path instead of a ~16s copy-up
164
+ # `chown -R` after the bundle arrives.
165
+ reclaim_home_ownership
137
166
 
138
167
  # ═══════════════════════════════════════════════════════════════════════════
139
168
  # PULL-BASED BOOTSTRAP — poll until the reconciler binds this pod to a
@@ -271,6 +300,27 @@ if [ -n "${ENV_KEYS}" ]; then
271
300
  done <<< "${ENV_KEYS}"
272
301
  echo "[boot] Injected ${ENV_COUNT} env vars from bootstrap bundle"
273
302
  fi
303
+
304
+ # ── Boot timing instrumentation (env-inject → agent-launch breakdown) ──
305
+ # A ~16s window between "Injected env vars" and "Linking Claude state" was
306
+ # root-caused (2026-07-16) to the unconditional root-owned-$HOME `chown -R` that
307
+ # used to run here — NOT GCS-FUSE (the mount is idle during the gap). That chown
308
+ # is now a conditional pre-bind reclaim (see reclaim_home_ownership). These marks
309
+ # stay as a regression probe: they attribute the gap to each sub-step —
310
+ # mount-readiness stat vs first metadata write vs symlink wiring — so if the
311
+ # total ever balloons again a canary boot shows WHERE. EPOCHREALTIME is
312
+ # bash-native (no per-mark subprocess); the `//[.,]/` strip tolerates
313
+ # comma-decimal locales. Grep the boot log for `[boot-timing]`. See
314
+ # docs/investigations/gcs-fuse-user-home-boot-cost-2026-07-15.md.
315
+ BOOT_MARK_T0_US="${EPOCHREALTIME//[.,]/}"
316
+ boot_mark() {
317
+ if [ -z "${EPOCHREALTIME:-}" ]; then echo "[boot-timing] $1"; return 0; fi
318
+ local now_us="${EPOCHREALTIME//[.,]/}"
319
+ local delta_us=$(( now_us - BOOT_MARK_T0_US ))
320
+ printf '[boot-timing] +%d.%03ds %s\n' "$(( delta_us / 1000000 ))" "$(( (delta_us / 1000) % 1000 ))" "$1"
321
+ }
322
+ boot_mark "env injected — entering user-home wiring"
323
+
274
324
  export ANTHROPIC_API_KEY=$(echo "${BOOTSTRAP_JSON}" | jq -r '.anthropicKey // empty')
275
325
  export CLOUDSDK_AUTH_ACCESS_TOKEN=$(echo "${BOOTSTRAP_JSON}" | jq -r '.gcpToken // empty')
276
326
  # WorkPreservation GCS tier (spec §Durability & snapshots): the upload URL is
@@ -333,17 +383,22 @@ seed_claude_json() {
333
383
  USER_HOME_MOUNT="/mnt/conveyor-users"
334
384
  USER_HOME_PROJECT_ID="${CONVEYOR_PROJECT_ID_FROM_BUNDLE:-${PROJECT_ID:-}}"
335
385
 
336
- # Bake steps that run as root while ENV HOME points at /home/conveyor can leave
337
- # root-owned $HOME entries (a uv install receipt in ~/.config once bricked every
338
- # pod boot fleet-wide); reclaim them before the symlink work below so a baked
339
- # ownership slip cannot EACCES the entrypoint under `set -e`. Runs before any
340
- # symlinks exist, so there is nothing to follow into the FUSE mount.
341
- sudo -n chown -R conveyor:conveyor /home/conveyor/.config /home/conveyor/.local 2>/dev/null || true
386
+ # Root-owned $HOME reclaim moved OFF this critical path into the pre-bind phase
387
+ # (reclaim_home_ownership, called after refresh_agent_version) and made
388
+ # conditional the former unconditional `chown -R` here cost ~16s of copy-up on
389
+ # every boot and was the real owner of this "GCS-FUSE" gap. See
390
+ # docs/investigations/gcs-fuse-user-home-boot-cost-2026-07-15.md.
342
391
 
392
+ # The `[ -d "${USER_HOME_MOUNT}" ]` test below is the first stat against the FUSE
393
+ # mount — if the gcsfuse sidecar is still handshaking it blocks here, so the
394
+ # mark straddles the readiness probe.
395
+ boot_mark "probing user-home mount presence"
343
396
  if [ -n "${CONVEYOR_USER_ID}" ] && [ -n "${USER_HOME_PROJECT_ID}" ] && [ -d "${USER_HOME_MOUNT}" ]; then
344
397
  USER_HOME_ROOT="${USER_HOME_MOUNT}/users/${CONVEYOR_USER_ID}/${USER_HOME_PROJECT_ID}"
398
+ boot_mark "user-home mount ready — linking Claude state"
345
399
  echo "[pool] Linking Claude state to ${USER_HOME_ROOT}"
346
400
  mkdir -p "${USER_HOME_ROOT}/.claude" "${USER_HOME_ROOT}/.config/claude" 2>/dev/null || true
401
+ boot_mark "mkdir into mount done (first metadata write)"
347
402
 
348
403
  # Replace any image defaults with live symlinks into the mount for the SHARED,
349
404
  # append-mostly Claude state: transcripts/plans/memory under ~/.claude and
@@ -365,6 +420,7 @@ if [ -n "${CONVEYOR_USER_ID}" ] && [ -n "${USER_HOME_PROJECT_ID}" ] && [ -d "${U
365
420
  mkdir -p /home/conveyor/.config 2>/dev/null || true
366
421
  ln -sfn "${USER_HOME_ROOT}/.claude" /home/conveyor/.claude
367
422
  ln -sfn "${USER_HOME_ROOT}/.config/claude" /home/conveyor/.config/claude
423
+ boot_mark "~/.claude + ~/.config/claude symlinks wired"
368
424
 
369
425
  # Pod-local ~/.claude.json: clear any symlink/file a prior boot or the image
370
426
  # baked in, then seed a fresh real file on the pod's OWN disk (local
@@ -394,6 +450,7 @@ if [ -n "${CONVEYOR_USER_ID}" ] && [ -n "${USER_HOME_PROJECT_ID}" ] && [ -d "${U
394
450
  fi
395
451
  ln -sfn "${CRED_POD_LOCAL}" "${CRED_SHARED}" 2>/dev/null || true
396
452
  chmod 600 "${CRED_POD_LOCAL}" 2>/dev/null || true
453
+ boot_mark "~/.claude.json seeded + credentials repointed"
397
454
 
398
455
  # OpenCode state (sessions + config) persists the same way as ~/.claude.
399
456
  mkdir -p "${USER_HOME_ROOT}/.local/share/opencode" "${USER_HOME_ROOT}/.config/opencode" 2>/dev/null || true
@@ -401,11 +458,13 @@ if [ -n "${CONVEYOR_USER_ID}" ] && [ -n "${USER_HOME_PROJECT_ID}" ] && [ -d "${U
401
458
  mkdir -p /home/conveyor/.local/share 2>/dev/null || true
402
459
  ln -sfn "${USER_HOME_ROOT}/.local/share/opencode" /home/conveyor/.local/share/opencode
403
460
  ln -sfn "${USER_HOME_ROOT}/.config/opencode" /home/conveyor/.config/opencode
461
+ boot_mark "opencode state linked — user-home wiring complete"
404
462
  else
405
463
  echo "[pool] Skipping user-home symlink (userId='${CONVEYOR_USER_ID}', projectId='${USER_HOME_PROJECT_ID}', mount present: $([ -d "${USER_HOME_MOUNT}" ] && echo yes || echo no))"
406
464
  # No persistent home — the CLI reads the pod-local config; seed it there so
407
465
  # non-FUSE pods get the same first-run gate suppression as the mounted path.
408
466
  seed_claude_json /home/conveyor/.claude.json
467
+ boot_mark "no user-home mount — pod-local seed only"
409
468
  fi
410
469
 
411
470
  # ── Wire published graphify bundles from the shared user-home mount ──
@@ -842,6 +901,7 @@ export CONVEYOR_WORKSPACE=/workspaces/repo
842
901
  # Exit 0 = clean shutdown (idle timeout, task complete) — pod dies.
843
902
  # Non-zero = crash — retry after a brief pause.
844
903
  cd /workspaces/repo
904
+ boot_mark "launching agent (env-inject → agent-launch total)"
845
905
  echo "[pool] Launching agent..."
846
906
  set +e
847
907
  # Belt-and-braces alongside the pod's preStop hook (which pkills the agent