@rallycry/conveyor-agent 8.5.0 → 8.6.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.
@@ -12,7 +12,7 @@ import {
12
12
  ensureClaudeCredentials,
13
13
  removeConveyorCredentials,
14
14
  sessionTranscriptPath
15
- } from "./chunk-63FTZOL5.js";
15
+ } from "./chunk-6B545CHM.js";
16
16
 
17
17
  // src/setup/bootstrap.ts
18
18
  var BOOTSTRAP_TIMEOUT_MS = 3e4;
@@ -134,11 +134,13 @@ function applyBootstrapToEnv(config) {
134
134
  import { io } from "socket.io-client";
135
135
  var EVENT_BATCH_MS = 500;
136
136
  var MAX_EVENT_BUFFER = 5e3;
137
+ var TOKEN_REFRESH_INTERVAL_MS = 6 * 60 * 60 * 1e3;
137
138
  var AgentConnection = class _AgentConnection {
138
139
  socket = null;
139
140
  config;
140
141
  eventBuffer = [];
141
142
  flushTimer = null;
143
+ tokenRefreshTimer = null;
142
144
  lastEmittedStatus = null;
143
145
  lastReportedStatus = null;
144
146
  droppedEventCount = 0;
@@ -202,6 +204,7 @@ var AgentConnection = class _AgentConnection {
202
204
  if (!this.config.apiUrl) {
203
205
  return Promise.reject(new Error("Cannot connect: apiUrl is empty"));
204
206
  }
207
+ this.startProactiveTokenRefresh();
205
208
  return new Promise((resolve2, reject) => {
206
209
  let settled = false;
207
210
  let attempts = 0;
@@ -316,6 +319,7 @@ var AgentConnection = class _AgentConnection {
316
319
  });
317
320
  }
318
321
  disconnect() {
322
+ this.stopProactiveTokenRefresh();
319
323
  void this.flushEvents();
320
324
  if (this.socket) {
321
325
  this.socket.io.reconnection(false);
@@ -399,7 +403,32 @@ var AgentConnection = class _AgentConnection {
399
403
  }
400
404
  }
401
405
  looksLikeAuthError(message) {
402
- return /unauthor|forbid|auth|token/i.test(message);
406
+ return /unauthor|forbid|auth|token|session (?:not found|expired|invalid)|invalid session/i.test(
407
+ message
408
+ );
409
+ }
410
+ // ── Proactive task-token refresh ────────────────────────────────────────
411
+ //
412
+ // Socket.IO only re-presents the taskToken on a (re)connect handshake, and
413
+ // the server only re-validates the JWT then. So a token that expires while
414
+ // the socket stays connected goes unnoticed until the next RPC fails. Re-mint
415
+ // periodically from the bootstrap endpoint — refreshFromBootstrap() updates
416
+ // both this.config.taskToken and socket.auth.taskToken, so any later
417
+ // reconnect carries a fresh token. No-ops for project mode / missing
418
+ // codespace env, and is rate-limited to once/60s inside refreshFromBootstrap.
419
+ startProactiveTokenRefresh() {
420
+ if (this.tokenRefreshTimer) return;
421
+ this.tokenRefreshTimer = setInterval(() => {
422
+ void this.refreshTaskTokenFromBootstrap().catch(() => {
423
+ });
424
+ }, TOKEN_REFRESH_INTERVAL_MS);
425
+ this.tokenRefreshTimer.unref?.();
426
+ }
427
+ stopProactiveTokenRefresh() {
428
+ if (this.tokenRefreshTimer) {
429
+ clearInterval(this.tokenRefreshTimer);
430
+ this.tokenRefreshTimer = null;
431
+ }
403
432
  }
404
433
  drainPendingMessages(messages) {
405
434
  for (const msg of messages) {
@@ -1487,6 +1516,15 @@ var SetManualTestsRequestSchema = z.object({
1487
1516
  sessionId: z.string(),
1488
1517
  items: z.array(z.object({ title: z.string().min(1) })).min(1)
1489
1518
  });
1519
+ var EditManualTestRequestSchema = z.object({
1520
+ sessionId: z.string(),
1521
+ title: z.string().min(1),
1522
+ newTitle: z.string().min(1)
1523
+ });
1524
+ var RemoveManualTestRequestSchema = z.object({
1525
+ sessionId: z.string(),
1526
+ title: z.string().min(1)
1527
+ });
1490
1528
  var TrackSpendingRequestSchema = z.object({
1491
1529
  sessionId: z.string(),
1492
1530
  inputTokens: z.number().int().nonnegative(),
@@ -1808,6 +1846,9 @@ var PostProjectAgentMessageRequestSchema = z2.object({
1808
1846
  content: z2.string().min(1),
1809
1847
  chatId: z2.string().optional()
1810
1848
  });
1849
+ var ListAccessibleProjectsRequestSchema = z2.object({
1850
+ pageSize: z2.number().int().positive().max(100).optional().default(100)
1851
+ });
1811
1852
  var ListProjectTasksRequestSchema = z2.object({
1812
1853
  projectId: z2.string(),
1813
1854
  status: z2.string().optional(),
@@ -1936,6 +1977,11 @@ var StopProjectBuildRequestSchema = z2.object({
1936
1977
  taskId: z2.string(),
1937
1978
  requestingUserId: z2.string().optional()
1938
1979
  });
1980
+ var CreateProjectReleaseRequestSchema = z2.object({
1981
+ projectId: z2.string(),
1982
+ taskIds: z2.array(z2.string()).optional(),
1983
+ requestingUserId: z2.string().optional()
1984
+ });
1939
1985
  var ApproveProjectMergePRRequestSchema = z2.object({
1940
1986
  projectId: z2.string(),
1941
1987
  childTaskId: z2.string(),
@@ -2011,6 +2057,22 @@ var GetProjectAttachmentRequestSchema = z2.object({
2011
2057
  /** Max bytes of text content to return from `offset`. Server default applies. */
2012
2058
  maxBytes: z2.number().int().positive().optional()
2013
2059
  });
2060
+ var RequestProjectFileUploadRequestSchema = z2.object({
2061
+ projectId: z2.string(),
2062
+ taskId: z2.string(),
2063
+ fileName: z2.string().min(1).max(255),
2064
+ mimeType: z2.string().min(1).max(128),
2065
+ fileSize: z2.number().int().positive().max(MAX_FILE_SIZE_BYTES),
2066
+ requestingUserId: z2.string().optional()
2067
+ });
2068
+ var ConfirmProjectFileUploadRequestSchema = z2.object({
2069
+ projectId: z2.string(),
2070
+ taskId: z2.string(),
2071
+ fileId: z2.string(),
2072
+ /** When set, the attachment is also posted to the task chat with this text. */
2073
+ comment: z2.string().max(2e3).optional(),
2074
+ requestingUserId: z2.string().optional()
2075
+ });
2014
2076
  var CreateProjectPullRequestRequestSchema = z2.object({
2015
2077
  projectId: z2.string(),
2016
2078
  taskId: z2.string(),
@@ -2035,6 +2097,29 @@ var RemoveProjectTaskReviewerRequestSchema = z2.object({
2035
2097
  userId: z2.string(),
2036
2098
  requestingUserId: z2.string().optional()
2037
2099
  });
2100
+ var ListProjectManualTestsRequestSchema = z2.object({
2101
+ projectId: z2.string(),
2102
+ taskId: z2.string()
2103
+ });
2104
+ var SetProjectManualTestsRequestSchema = z2.object({
2105
+ projectId: z2.string(),
2106
+ taskId: z2.string(),
2107
+ items: z2.array(z2.object({ title: z2.string().min(1) })).min(1),
2108
+ requestingUserId: z2.string().optional()
2109
+ });
2110
+ var EditProjectManualTestRequestSchema = z2.object({
2111
+ projectId: z2.string(),
2112
+ taskId: z2.string(),
2113
+ title: z2.string().min(1),
2114
+ newTitle: z2.string().min(1),
2115
+ requestingUserId: z2.string().optional()
2116
+ });
2117
+ var RemoveProjectManualTestRequestSchema = z2.object({
2118
+ projectId: z2.string(),
2119
+ taskId: z2.string(),
2120
+ title: z2.string().min(1),
2121
+ requestingUserId: z2.string().optional()
2122
+ });
2038
2123
  var StartTaskAuditRequestSchema = z3.object({
2039
2124
  projectId: z3.string(),
2040
2125
  taskIds: z3.array(z3.string()).min(1)
@@ -4064,9 +4149,15 @@ function buildListManualTestsTool(connection) {
4064
4149
  sessionId: connection.sessionId
4065
4150
  });
4066
4151
  if (items.length === 0) return textResult("No manual tests recorded for this task.");
4067
- const lines = items.map((item, i) => {
4152
+ const lines = items.flatMap((item, i) => {
4068
4153
  const checked = item.checked ? "[x]" : "[ ]";
4069
- return `${i + 1}. ${checked} ${item.title}`;
4154
+ const row = [`${i + 1}. ${checked} ${item.title}`];
4155
+ for (const f of item.failures ?? []) {
4156
+ const who = f.userName ?? "Someone";
4157
+ const reason = f.reason ?? "(no message)";
4158
+ row.push(` \u26A0 Failed (${who}): ${reason}`);
4159
+ }
4160
+ return row;
4070
4161
  });
4071
4162
  return textResult(lines.join("\n"));
4072
4163
  } catch {
@@ -4099,6 +4190,50 @@ function buildSetManualTestsTool(connection) {
4099
4190
  }
4100
4191
  );
4101
4192
  }
4193
+ function buildEditManualTestTool(connection) {
4194
+ return defineTool(
4195
+ "edit_manual_test",
4196
+ "Rename an existing manual test step. Identify the test by its current title (case-insensitive); pass the new title to replace it. Use to correct or refine a recorded manual verification step.",
4197
+ {
4198
+ title: z8.string().min(1).describe("The current title of the manual test to edit"),
4199
+ newTitle: z8.string().min(1).describe("The new title for the manual test")
4200
+ },
4201
+ async ({ title, newTitle }) => {
4202
+ try {
4203
+ await connection.call("editManualTest", {
4204
+ sessionId: connection.sessionId,
4205
+ title,
4206
+ newTitle
4207
+ });
4208
+ return textResult(`Updated manual test to "${newTitle}".`);
4209
+ } catch (error) {
4210
+ const msg = error instanceof Error ? error.message : "Unknown error";
4211
+ return textResult(`Failed to edit manual test: ${msg}`);
4212
+ }
4213
+ }
4214
+ );
4215
+ }
4216
+ function buildRemoveManualTestTool(connection) {
4217
+ return defineTool(
4218
+ "remove_manual_test",
4219
+ "Remove an existing manual test step from the task checklist. Identify the test by its title (case-insensitive). Use to delete a stale or incorrect manual verification step.",
4220
+ {
4221
+ title: z8.string().min(1).describe("The title of the manual test to remove")
4222
+ },
4223
+ async ({ title }) => {
4224
+ try {
4225
+ await connection.call("removeManualTest", {
4226
+ sessionId: connection.sessionId,
4227
+ title
4228
+ });
4229
+ return textResult(`Removed manual test "${title}".`);
4230
+ } catch (error) {
4231
+ const msg = error instanceof Error ? error.message : "Unknown error";
4232
+ return textResult(`Failed to remove manual test: ${msg}`);
4233
+ }
4234
+ }
4235
+ );
4236
+ }
4102
4237
 
4103
4238
  // src/tools/common-tools.ts
4104
4239
  function buildCommonTools(connection, config) {
@@ -4108,6 +4243,8 @@ function buildCommonTools(connection, config) {
4108
4243
  buildGetSuggestionsTool(connection),
4109
4244
  buildListManualTestsTool(connection),
4110
4245
  buildSetManualTestsTool(connection),
4246
+ buildEditManualTestTool(connection),
4247
+ buildRemoveManualTestTool(connection),
4111
4248
  ...buildMutationTools(connection, config),
4112
4249
  buildUploadAttachmentTool(connection, config)
4113
4250
  ];
@@ -8200,7 +8337,10 @@ var SessionRunner = class _SessionRunner {
8200
8337
  `);
8201
8338
  this.connection.sendEvent({ type: "shutdown", reason: finalState });
8202
8339
  this.lifecycle.destroy();
8203
- await this.setState(finalState);
8340
+ try {
8341
+ await this.setState(finalState);
8342
+ } catch {
8343
+ }
8204
8344
  this.connection.disconnect();
8205
8345
  this._finalState = finalState;
8206
8346
  }
@@ -8259,20 +8399,31 @@ var ProjectConnection = class {
8259
8399
  return this.socket?.connected ?? false;
8260
8400
  }
8261
8401
  // ── Typed service method call ──────────────────────────────────────────
8262
- call(method, payload) {
8402
+ call(method, payload, opts) {
8263
8403
  const socket = this.requireSocket();
8264
8404
  return new Promise((resolve2, reject) => {
8265
- socket.emit(
8266
- `agentSessionService:${String(method)}`,
8267
- payload,
8268
- (response) => {
8269
- if (response.success && response.data !== void 0) {
8270
- resolve2(response.data);
8271
- } else {
8272
- reject(new Error(response.error ?? `Service call failed: ${String(method)}`));
8273
- }
8405
+ const handleResponse = (response) => {
8406
+ if (response.success && response.data !== void 0) {
8407
+ resolve2(response.data);
8408
+ } else {
8409
+ reject(new Error(response.error ?? `Service call failed: ${String(method)}`));
8274
8410
  }
8275
- );
8411
+ };
8412
+ if (opts?.timeoutMs) {
8413
+ socket.timeout(opts.timeoutMs).emit(
8414
+ `agentSessionService:${String(method)}`,
8415
+ payload,
8416
+ (err, response) => {
8417
+ if (err) {
8418
+ reject(new Error(`Service call timed out: ${String(method)}`));
8419
+ } else {
8420
+ handleResponse(response);
8421
+ }
8422
+ }
8423
+ );
8424
+ return;
8425
+ }
8426
+ socket.emit(`agentSessionService:${String(method)}`, payload, handleResponse);
8276
8427
  });
8277
8428
  }
8278
8429
  // ── Connection lifecycle ───────────────────────────────────────────────
@@ -8330,8 +8481,13 @@ var ProjectConnection = class {
8330
8481
  this.flushTimer = null;
8331
8482
  }
8332
8483
  this.flushEvents();
8333
- this.socket?.disconnect();
8334
- this.socket = null;
8484
+ if (this.socket) {
8485
+ this.socket.io.reconnection(false);
8486
+ this.socket.removeAllListeners();
8487
+ this.socket.disconnect();
8488
+ this.socket = null;
8489
+ }
8490
+ this.reconnectCallbacks = [];
8335
8491
  }
8336
8492
  onReconnect(callback) {
8337
8493
  this.reconnectCallbacks.push(callback);
@@ -9928,6 +10084,10 @@ function spawnChildAgent(assignment, workDir) {
9928
10084
  CONVEYOR_MODE: mode,
9929
10085
  CONVEYOR_WORKSPACE: workDir,
9930
10086
  CONVEYOR_USE_WORKTREE: "false",
10087
+ // The project runner flushes git for every child working tree after the
10088
+ // kill (see ProjectRunner.stop), so the child skips its own shutdown
10089
+ // flush — keeps SIGTERM teardown well inside the SIGKILL window.
10090
+ CONVEYOR_SKIP_SHUTDOWN_GIT_FLUSH: "true",
9931
10091
  CONVEYOR_AGENT_MODE: effectiveAgentMode,
9932
10092
  CONVEYOR_IS_AUTO: isAuto ? "true" : "false",
9933
10093
  CONVEYOR_USE_SANDBOX: useSandbox === true ? "true" : "false",
@@ -10052,6 +10212,8 @@ function handleStopTask(taskId, activeAgents, projectDir) {
10052
10212
  // src/runner/project-runner.ts
10053
10213
  var logger10 = createServiceLogger("ProjectRunner");
10054
10214
  var HEARTBEAT_INTERVAL_MS = 3e4;
10215
+ var DISCONNECT_ACK_TIMEOUT_MS = 5e3;
10216
+ var SHUTDOWN_WATCHDOG_MS = 9e4;
10055
10217
  var ProjectRunner = class {
10056
10218
  connection;
10057
10219
  projectDir;
@@ -10135,11 +10297,17 @@ var ProjectRunner = class {
10135
10297
  }
10136
10298
  /** Best-effort WIP commit + push of every tracked working tree on shutdown.
10137
10299
  * Iterates the main project dir plus any active agent worktrees so nothing
10138
- * pending is lost when the pod is killed. Never throws. */
10139
- async flushGitOnShutdown() {
10140
- const dirs = /* @__PURE__ */ new Set([this.projectDir]);
10141
- for (const agent of this.activeAgents.values()) {
10142
- if (agent.worktreePath) dirs.add(agent.worktreePath);
10300
+ * pending is lost when the pod is killed. Never throws.
10301
+ *
10302
+ * Callers that kill child agents first must snapshot the worktree dirs
10303
+ * BEFORE the kill the child exit handler removes entries from
10304
+ * `activeAgents`, so reading it after the kill misses the worktrees. */
10305
+ async flushGitOnShutdown(dirs) {
10306
+ if (!dirs) {
10307
+ dirs = /* @__PURE__ */ new Set([this.projectDir]);
10308
+ for (const agent of this.activeAgents.values()) {
10309
+ if (agent.worktreePath) dirs.add(agent.worktreePath);
10310
+ }
10143
10311
  }
10144
10312
  for (const dir of dirs) {
10145
10313
  try {
@@ -10163,38 +10331,62 @@ var ProjectRunner = class {
10163
10331
  if (this.stopping) return;
10164
10332
  this.stopping = true;
10165
10333
  logger10.info("Shutting down");
10166
- this.commitWatcher.stop();
10167
- await killStartCommand(this.startCmd);
10168
- if (this.heartbeatTimer) {
10169
- clearInterval(this.heartbeatTimer);
10170
- this.heartbeatTimer = null;
10334
+ const watchdog = setTimeout(() => {
10335
+ logger10.warn("Shutdown watchdog fired, forcing lifecycle resolution");
10336
+ this.finishShutdown();
10337
+ }, SHUTDOWN_WATCHDOG_MS);
10338
+ watchdog.unref();
10339
+ const flushDirs = /* @__PURE__ */ new Set([this.projectDir]);
10340
+ for (const agent of this.activeAgents.values()) {
10341
+ if (agent.worktreePath) flushDirs.add(agent.worktreePath);
10171
10342
  }
10172
- const stopPromises = [...this.activeAgents.keys()].map(
10173
- (key) => new Promise((resolve2) => {
10174
- const agent = this.activeAgents.get(key);
10175
- if (!agent) {
10176
- resolve2();
10177
- return;
10178
- }
10179
- agent.process.on("exit", () => resolve2());
10180
- handleStopTask(key, this.activeAgents, this.projectDir);
10181
- })
10182
- );
10183
- await Promise.race([
10184
- Promise.all(stopPromises),
10185
- new Promise((resolve2) => {
10186
- setTimeout(resolve2, STOP_TIMEOUT_MS + 5e3);
10187
- })
10188
- ]);
10189
- await this.flushGitOnShutdown();
10190
10343
  try {
10191
- await this.connection.call("disconnectProjectRunner", {
10192
- projectId: this.connection.projectId
10193
- });
10344
+ this.commitWatcher.stop();
10345
+ await killStartCommand(this.startCmd);
10346
+ if (this.heartbeatTimer) {
10347
+ clearInterval(this.heartbeatTimer);
10348
+ this.heartbeatTimer = null;
10349
+ }
10350
+ const stopPromises = [...this.activeAgents.keys()].map(
10351
+ (key) => new Promise((resolve2) => {
10352
+ const agent = this.activeAgents.get(key);
10353
+ if (!agent) {
10354
+ resolve2();
10355
+ return;
10356
+ }
10357
+ agent.process.on("exit", () => resolve2());
10358
+ handleStopTask(key, this.activeAgents, this.projectDir);
10359
+ })
10360
+ );
10361
+ await Promise.race([
10362
+ Promise.all(stopPromises),
10363
+ new Promise((resolve2) => {
10364
+ setTimeout(resolve2, STOP_TIMEOUT_MS + 5e3);
10365
+ })
10366
+ ]);
10367
+ await this.flushGitOnShutdown(flushDirs);
10368
+ try {
10369
+ await this.connection.call(
10370
+ "disconnectProjectRunner",
10371
+ { projectId: this.connection.projectId },
10372
+ { timeoutMs: DISCONNECT_ACK_TIMEOUT_MS }
10373
+ );
10374
+ } catch {
10375
+ }
10376
+ logger10.info("Shutdown complete");
10377
+ } finally {
10378
+ clearTimeout(watchdog);
10379
+ this.finishShutdown();
10380
+ }
10381
+ }
10382
+ /** Idempotent final step: tear down the socket and resolve the lifecycle
10383
+ * promise so start() returns. Called from stop()'s finally and from the
10384
+ * shutdown watchdog. */
10385
+ finishShutdown() {
10386
+ try {
10387
+ this.connection.disconnect();
10194
10388
  } catch {
10195
10389
  }
10196
- this.connection.disconnect();
10197
- logger10.info("Shutdown complete");
10198
10390
  if (this.resolveLifecycle) {
10199
10391
  this.resolveLifecycle();
10200
10392
  this.resolveLifecycle = null;
@@ -10268,7 +10460,7 @@ var ProjectRunner = class {
10268
10460
  async handleAuditTags(request) {
10269
10461
  this.connection.emitStatus("busy");
10270
10462
  try {
10271
- const { handleTagAudit } = await import("./tag-audit-handler-PKYLDJHH.js");
10463
+ const { handleTagAudit } = await import("./tag-audit-handler-3IFB7YDV.js");
10272
10464
  await handleTagAudit(request, this.connection, this.projectDir);
10273
10465
  } catch (error) {
10274
10466
  const msg = parseErrorMessage(error);
@@ -10291,7 +10483,7 @@ var ProjectRunner = class {
10291
10483
  async handleAuditTasks(request) {
10292
10484
  this.connection.emitStatus("busy");
10293
10485
  try {
10294
- const { handleTaskAudit } = await import("./task-audit-handler-WINYLZMU.js");
10486
+ const { handleTaskAudit } = await import("./task-audit-handler-U5Q52YT2.js");
10295
10487
  await handleTaskAudit(request, this.connection, this.projectDir);
10296
10488
  } catch (error) {
10297
10489
  const msg = parseErrorMessage(error);
@@ -10432,4 +10624,4 @@ export {
10432
10624
  loadConveyorConfig,
10433
10625
  unshallowRepo
10434
10626
  };
10435
- //# sourceMappingURL=chunk-3T4SJEH6.js.map
10627
+ //# sourceMappingURL=chunk-HMSNJRVO.js.map