@tryarcanist/cli 0.1.172 → 0.1.174

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.
Files changed (3) hide show
  1. package/README.md +13 -0
  2. package/dist/index.js +71 -0
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -226,6 +226,19 @@ Repeatable `--uploaded-file <path>` flags attach local UTF-8 text files to the f
226
226
  JSON mode returns `{sessionId, promptId?}`.
227
227
  With `--wait`, JSON mode also includes best-effort result fields when available: `prUrl?`, `publishedBranch?`, `lastBranch?`.
228
228
 
229
+ ### `arcanist sessions respond <session-id> [answer]`
230
+
231
+ Answers a pending `question` event from `sessions events --follow`.
232
+ Pass the `questionId` from the question event so the answer is durable and safe across reconnects.
233
+
234
+ ```bash
235
+ arcanist sessions respond abc123 "Use PostgreSQL" --question-id q_123
236
+ printf "Use PostgreSQL" | arcanist sessions respond abc123 --question-id q_123 --answer-stdin --json
237
+ ```
238
+
239
+ JSON mode returns the raw respond payload plus `sessionId`.
240
+ If the session is no longer waiting for input, the command exits with conflict and points back to `sessions events --follow --json`.
241
+
229
242
  ### `arcanist sessions stop <session-id>`
230
243
 
231
244
  Stops the active run for a session. Idempotent: if no sandbox is active, the server returns `already_stopped`.
package/dist/index.js CHANGED
@@ -506,6 +506,15 @@ function randomIdempotencyKey() {
506
506
  function assertArcanistSessionMutationAllowed(subcommand) {
507
507
  const role = process.env.ARCANIST_AGENT_ROLE?.trim();
508
508
  if (role !== "verification") return;
509
+ if (subcommand === "respond") {
510
+ throw new CliError(
511
+ "user",
512
+ "`arcanist sessions respond` is disabled inside verification sessions because verification agents cannot mutate the session under review.",
513
+ {
514
+ hint: "Report the unanswered question as a verification blocker instead."
515
+ }
516
+ );
517
+ }
509
518
  throw new CliError(
510
519
  "user",
511
520
  `\`arcanist sessions ${subcommand}\` is disabled inside ${role} sessions because nested Arcanist sessions are not observable to the verifier.`,
@@ -3502,6 +3511,57 @@ async function qaCommand(prUrl, options, command) {
3502
3511
  console.log(`Follow with: arcanist sessions events ${sessionId} --follow --json`);
3503
3512
  }
3504
3513
 
3514
+ // src/commands/respond.ts
3515
+ async function resolveAnswerInput(answerArg, options) {
3516
+ const shouldReadStdin = options.answerStdin === true || answerArg === "-";
3517
+ const answer = shouldReadStdin ? await readStdin() : answerArg;
3518
+ if (!answer || answer.trim().length === 0) {
3519
+ throw new CliError(
3520
+ "user",
3521
+ "Missing answer. Pass an answer argument, use '-' to read stdin, or pass --answer-stdin."
3522
+ );
3523
+ }
3524
+ return answer;
3525
+ }
3526
+ async function respondCommand(sessionId, answerArg, options = {}, command) {
3527
+ assertArcanistSessionMutationAllowed("respond");
3528
+ const questionId = options.questionId?.trim();
3529
+ if (!questionId) {
3530
+ throw new CliError("user", "Missing --question-id for the pending question.", {
3531
+ hint: `Find the question id with: arcanist sessions events ${sessionId} --follow --json`
3532
+ });
3533
+ }
3534
+ const { config } = resolveBusinessContext(command, options);
3535
+ const answer = await resolveAnswerInput(answerArg, options);
3536
+ try {
3537
+ const payload = await apiFetch(config, `/api/sessions/${sessionId}/respond`, {
3538
+ method: "POST",
3539
+ body: JSON.stringify({ answer, questionId })
3540
+ });
3541
+ emit(command, options, { sessionId, ...payload }, () => console.log(`Answer sent to session ${sessionId}.`));
3542
+ } catch (err) {
3543
+ const conflict = parseRespondConflict(err);
3544
+ if (!conflict) throw err;
3545
+ throw new CliError("conflict", conflict.message, {
3546
+ data: { reason: conflict.reason },
3547
+ hint: `Inspect pending questions with: arcanist sessions events ${sessionId} --follow --json`
3548
+ });
3549
+ }
3550
+ }
3551
+ function parseRespondConflict(err) {
3552
+ if (!(err instanceof ApiError) || err.status !== 409) return null;
3553
+ try {
3554
+ const body = JSON.parse(err.body);
3555
+ if (body.error !== "session_not_respondable") return null;
3556
+ return {
3557
+ message: body.reason ? `Session cannot be answered from phase=${body.reason}.` : "Session does not have a pending question.",
3558
+ reason: body.reason ?? "session_not_respondable"
3559
+ };
3560
+ } catch {
3561
+ return null;
3562
+ }
3563
+ }
3564
+
3505
3565
  // src/commands/sandbox.ts
3506
3566
  import { execFileSync } from "child_process";
3507
3567
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
@@ -5009,6 +5069,17 @@ addSendOptions(sessions.command("send").description("Send a message to an existi
5009
5069
  addQaOptions(sessions.command("qa").description("Start a QA verification session for a GitHub pull request")).action(
5010
5070
  (prUrl, options, command) => qaCommand(prUrl, options, command)
5011
5071
  );
5072
+ sessions.command("respond").description("Answer a pending session question").argument("<session-id>", "Session ID").argument("[answer]", "Answer text, or '-' to read stdin").option("--answer-stdin", "Read answer from stdin").requiredOption("--question-id <id>", "Question ID from the question event").addHelpText(
5073
+ "after",
5074
+ `
5075
+ Examples:
5076
+ arcanist sessions respond <session-id> "Use PostgreSQL" --question-id q_123
5077
+ printf "Use PostgreSQL" | arcanist sessions respond <session-id> --question-id q_123 --answer-stdin --json
5078
+
5079
+ JSON:
5080
+ JSON mode returns the raw respond payload plus sessionId.
5081
+ `
5082
+ ).action((sessionId, answer, options, command) => respondCommand(sessionId, answer, options, command));
5012
5083
  sessions.command("stop").description("Stop the active run for a session").argument("<session-id>", "Session ID").addHelpText(
5013
5084
  "after",
5014
5085
  `
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tryarcanist/cli",
3
- "version": "0.1.172",
3
+ "version": "0.1.174",
4
4
  "description": "CLI for Arcanist — create and manage coding agent sessions",
5
5
  "type": "module",
6
6
  "bin": {