@rallycry/conveyor-agent 9.0.2 → 9.1.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.
@@ -541,24 +541,23 @@ var AgentConnection = class _AgentConnection {
541
541
  };
542
542
  }
543
543
  // ── Convenience methods (thin wrappers around call / emit) ─────────
544
- async emitStatus(status) {
544
+ async emitStatus(status, reason) {
545
545
  this.lastEmittedStatus = status;
546
546
  await this.flushEvents();
547
+ const payload = {
548
+ sessionId: this.config.sessionId,
549
+ status,
550
+ ...reason ? { reason } : {}
551
+ };
547
552
  const AWAIT_STATUSES = ["idle", "waiting_for_input", "connected"];
548
553
  if (AWAIT_STATUSES.includes(status)) {
549
554
  try {
550
- await this.call("reportAgentStatus", {
551
- sessionId: this.config.sessionId,
552
- status
553
- });
555
+ await this.call("reportAgentStatus", payload);
554
556
  this.lastReportedStatus = status;
555
557
  } catch {
556
558
  }
557
559
  } else {
558
- void this.call("reportAgentStatus", {
559
- sessionId: this.config.sessionId,
560
- status
561
- }).then(() => {
560
+ void this.call("reportAgentStatus", payload).then(() => {
562
561
  this.lastReportedStatus = status;
563
562
  }).catch(() => {
564
563
  });
@@ -1058,7 +1057,18 @@ function restoreWipSnapshot(cwd, branch) {
1058
1057
  stdio: ["ignore", "pipe", "ignore"]
1059
1058
  }).toString().trim();
1060
1059
  const head = execSync("git rev-parse HEAD", { cwd, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
1061
- if (parent !== head) return "stale";
1060
+ if (parent !== head) {
1061
+ try {
1062
+ execSync(`git stash apply ${sha}`, { cwd, stdio: ["ignore", "pipe", "pipe"] });
1063
+ return "applied";
1064
+ } catch {
1065
+ try {
1066
+ execSync("git reset --merge", { cwd, stdio: "ignore" });
1067
+ } catch {
1068
+ }
1069
+ return "stale";
1070
+ }
1071
+ }
1062
1072
  execSync(`git stash apply ${sha}`, { cwd, stdio: ["ignore", "pipe", "pipe"] });
1063
1073
  return "applied";
1064
1074
  } catch {
@@ -1276,6 +1286,7 @@ const net = require("node:net");
1276
1286
  const crypto = require("node:crypto");
1277
1287
 
1278
1288
  const PRE_TOOL_USE_TIMEOUT_MS = 110000;
1289
+ const ASK_USER_QUESTION_TIMEOUT_MS = 5000;
1279
1290
 
1280
1291
  let raw = "";
1281
1292
  process.stdin.setEncoding("utf8");
@@ -1347,17 +1358,26 @@ function preToolUse(payload) {
1347
1358
  );
1348
1359
  process.exit(0);
1349
1360
  };
1350
- const denyUnavailable = () =>
1351
- respond(
1352
- "deny",
1353
- "Conveyor validation is unavailable right now. Wait a moment and call the tool again.",
1354
- );
1361
+ // AskUserQuestion is observe-only (status reporting) \u2014 fail OPEN so a
1362
+ // missing/slow socket never denies the questionnaire. Everything else
1363
+ // (ExitPlanMode) is a Conveyor gate \u2014 fail CLOSED.
1364
+ const failOpen = payload.tool_name === "AskUserQuestion";
1365
+ const respondUnavailable = () =>
1366
+ failOpen
1367
+ ? respond("allow")
1368
+ : respond(
1369
+ "deny",
1370
+ "Conveyor validation is unavailable right now. Wait a moment and call the tool again.",
1371
+ );
1355
1372
  if (!socketPath) {
1356
- denyUnavailable();
1373
+ respondUnavailable();
1357
1374
  return;
1358
1375
  }
1359
1376
  const id = crypto.randomBytes(8).toString("hex");
1360
- const timer = setTimeout(denyUnavailable, PRE_TOOL_USE_TIMEOUT_MS);
1377
+ const timer = setTimeout(
1378
+ respondUnavailable,
1379
+ failOpen ? ASK_USER_QUESTION_TIMEOUT_MS : PRE_TOOL_USE_TIMEOUT_MS,
1380
+ );
1361
1381
  const client = net.connect(socketPath, () => {
1362
1382
  client.write(
1363
1383
  JSON.stringify({
@@ -1392,11 +1412,11 @@ function preToolUse(payload) {
1392
1412
  });
1393
1413
  client.on("error", () => {
1394
1414
  clearTimeout(timer);
1395
- denyUnavailable();
1415
+ respondUnavailable();
1396
1416
  });
1397
1417
  client.on("close", () => {
1398
1418
  clearTimeout(timer);
1399
- denyUnavailable();
1419
+ respondUnavailable();
1400
1420
  });
1401
1421
  }
1402
1422
  `;
@@ -1434,6 +1454,13 @@ async function writeHookSettings(dir) {
1434
1454
  {
1435
1455
  matcher: "ExitPlanMode",
1436
1456
  hooks: [{ type: "command", command: `node ${JSON.stringify(helperPath)}`, timeout: 120 }]
1457
+ },
1458
+ {
1459
+ // Observe-only: lets the session report waiting_for_input the moment
1460
+ // the planning questionnaire renders. The helper fails open for this
1461
+ // tool, so the questionnaire is never blocked by Conveyor.
1462
+ matcher: "AskUserQuestion",
1463
+ hooks: [{ type: "command", command: `node ${JSON.stringify(helperPath)}`, timeout: 30 }]
1437
1464
  }
1438
1465
  ],
1439
1466
  PostToolUse: [
@@ -1671,7 +1698,9 @@ var ConnectAgentRequestSchema = z.object({
1671
1698
  });
1672
1699
  var ReportAgentStatusRequestSchema = z.object({
1673
1700
  sessionId: z.string(),
1674
- status: z.string()
1701
+ status: z.string(),
1702
+ /** Why the agent reports this status (e.g. "user_question" while an AskUserQuestion questionnaire is pending in the TUI). */
1703
+ reason: z.string().optional()
1675
1704
  });
1676
1705
  var NotifyAgentVersionRequestSchema = z.object({
1677
1706
  sessionId: z.string(),
@@ -1986,6 +2015,20 @@ var GetProjectTaskSessionsRequestSchema = z2.object({
1986
2015
  projectId: z2.string(),
1987
2016
  taskId: z2.string()
1988
2017
  });
2018
+ var QueryProjectGcpLogsRequestSchema = z2.object({
2019
+ projectId: z2.string(),
2020
+ env: z2.enum(["prod", "dev", "claudespace"]).optional(),
2021
+ severity: z2.enum(["DEBUG", "INFO", "NOTICE", "WARNING", "ERROR", "CRITICAL", "ALERT", "EMERGENCY"]).optional(),
2022
+ services: z2.array(z2.string().min(1).max(200)).max(25).optional(),
2023
+ sqlInstances: z2.array(z2.string().min(1).max(200)).max(25).optional(),
2024
+ allServices: z2.boolean().optional(),
2025
+ search: z2.string().max(256).optional(),
2026
+ filter: z2.string().max(1e3).optional(),
2027
+ startTime: z2.string().optional(),
2028
+ endTime: z2.string().optional(),
2029
+ limit: z2.number().int().min(1).max(200).optional().default(50),
2030
+ pageToken: z2.string().max(4096).optional()
2031
+ });
1989
2032
  var StartProjectBuildRequestSchema = z2.object({
1990
2033
  projectId: z2.string(),
1991
2034
  taskId: z2.string(),
@@ -2164,6 +2207,7 @@ var CreateProjectSuggestionRequestSchema = z2.object({
2164
2207
  tagNames: z2.array(z2.string()).optional(),
2165
2208
  requestingUserId: z2.string().optional()
2166
2209
  });
2210
+ var AGENT_STATUS_REASON_USER_QUESTION = "user_question";
2167
2211
  var TASK_CHAT_HISTORY_LIMIT = 20;
2168
2212
  var PM_CHAT_HISTORY_LIMIT = 40;
2169
2213
  var AGENT_CHAT_HISTORY_FETCH_LIMIT = Math.max(TASK_CHAT_HISTORY_LIMIT, PM_CHAT_HISTORY_LIMIT) + 10;
@@ -3096,6 +3140,25 @@ async function transcriptSize(path) {
3096
3140
  return 0;
3097
3141
  }
3098
3142
  }
3143
+ function parseUserQuestions(input) {
3144
+ if (!Array.isArray(input.questions)) return [];
3145
+ const questions = [];
3146
+ for (const entry of input.questions) {
3147
+ if (!isRecord2(entry)) continue;
3148
+ if (typeof entry.question !== "string") continue;
3149
+ const options = Array.isArray(entry.options) ? entry.options.filter(isRecord2).filter((o) => typeof o.label === "string").map((o) => ({
3150
+ label: o.label,
3151
+ description: typeof o.description === "string" ? o.description : ""
3152
+ })) : [];
3153
+ questions.push({
3154
+ question: entry.question,
3155
+ header: typeof entry.header === "string" ? entry.header : "",
3156
+ options,
3157
+ ...typeof entry.multiSelect === "boolean" ? { multiSelect: entry.multiSelect } : {}
3158
+ });
3159
+ }
3160
+ return questions;
3161
+ }
3099
3162
  var PtySession = class {
3100
3163
  constructor(prompt, options, resume, bridge) {
3101
3164
  this.prompt = prompt;
@@ -3373,11 +3436,17 @@ var PtySession = class {
3373
3436
  }
3374
3437
  /**
3375
3438
  * PreToolUse request from the hook helper → the runner's `canUseTool`
3376
- * verdict. Only ExitPlanMode is matched by the settings hook, so this is
3377
- * the Conveyor plan-exit gate (validation + identification trigger run
3378
- * inside the callback). Absent callback → allow (mirrors the SDK default).
3439
+ * verdict. ExitPlanMode is the Conveyor plan-exit gate (validation +
3440
+ * identification trigger run inside the callback); AskUserQuestion is
3441
+ * observed and always allowed. Absent callback → allow (mirrors the SDK
3442
+ * default).
3379
3443
  */
3380
3444
  async handlePreToolUse(request) {
3445
+ if (request.tool_name === "AskUserQuestion") {
3446
+ this.disarmSubmitNudge();
3447
+ this.queue.push({ type: "user_question", questions: parseUserQuestions(request.tool_input) });
3448
+ return { decision: "allow" };
3449
+ }
3381
3450
  const canUseTool = this.options.canUseTool;
3382
3451
  let verdict;
3383
3452
  if (canUseTool) {
@@ -6445,6 +6514,44 @@ async function handleResultCase(event, host, context, startTime, isTyping, lastA
6445
6514
  }
6446
6515
 
6447
6516
  // src/execution/event-processor.ts
6517
+ function hasAskUserQuestionBlock(event) {
6518
+ return event.message.content.some((b) => b.type === "tool_use" && b.name === "AskUserQuestion");
6519
+ }
6520
+ async function armQuestionPending(host, state) {
6521
+ state.questionPending = true;
6522
+ await host.connection.emitStatus("waiting_for_input", AGENT_STATUS_REASON_USER_QUESTION);
6523
+ await host.callbacks.onStatusChange("waiting_for_input");
6524
+ }
6525
+ async function clearQuestionPending(host, state, options) {
6526
+ if (!state.questionPending) return;
6527
+ state.questionPending = false;
6528
+ if (options.emitRunning) {
6529
+ await host.connection.emitStatus("running");
6530
+ await host.callbacks.onStatusChange("running");
6531
+ }
6532
+ }
6533
+ async function applyQuestionTransitions(event, host, state) {
6534
+ switch (event.type) {
6535
+ case "user_question":
6536
+ await armQuestionPending(host, state);
6537
+ return;
6538
+ case "assistant":
6539
+ if (!hasAskUserQuestionBlock(event)) {
6540
+ await clearQuestionPending(host, state, { emitRunning: true });
6541
+ }
6542
+ return;
6543
+ case "tool_progress":
6544
+ if (event.tool_name === "AskUserQuestion") {
6545
+ await clearQuestionPending(host, state, { emitRunning: true });
6546
+ }
6547
+ return;
6548
+ case "result":
6549
+ await clearQuestionPending(host, state, { emitRunning: false });
6550
+ return;
6551
+ default:
6552
+ break;
6553
+ }
6554
+ }
6448
6555
  function stopTypingIfNeeded(host, isTyping) {
6449
6556
  if (isTyping) host.connection.sendTypingStop();
6450
6557
  }
@@ -6524,13 +6631,14 @@ async function processEvents(events, context, host) {
6524
6631
  staleSession: void 0,
6525
6632
  authError: void 0,
6526
6633
  lastAssistantUsage: void 0,
6527
- turnToolCalls: []
6634
+ turnToolCalls: [],
6635
+ questionPending: false
6528
6636
  };
6529
6637
  for await (const event of events) {
6530
6638
  if (host.isStopped()) break;
6531
6639
  flushPendingToolCalls(host, state.turnToolCalls);
6532
6640
  const now = Date.now();
6533
- if (now - lastStatusEmit >= STATUS_REEMIT_INTERVAL_MS) {
6641
+ if (now - lastStatusEmit >= STATUS_REEMIT_INTERVAL_MS && !state.questionPending) {
6534
6642
  host.connection.emitStatus("running");
6535
6643
  lastStatusEmit = now;
6536
6644
  }
@@ -6538,6 +6646,7 @@ async function processEvents(events, context, host) {
6538
6646
  stopTypingIfNeeded(host, state.isTyping);
6539
6647
  return { retriable: false, modeRestart: true };
6540
6648
  }
6649
+ await applyQuestionTransitions(event, host, state);
6541
6650
  switch (event.type) {
6542
6651
  case "system":
6543
6652
  await processSystemCase(event, host, context, state);
@@ -8882,4 +8991,4 @@ export {
8882
8991
  runStartCommand,
8883
8992
  unshallowRepo
8884
8993
  };
8885
- //# sourceMappingURL=chunk-5W6MHROF.js.map
8994
+ //# sourceMappingURL=chunk-P3QCTUHC.js.map