@botbuddy/cli 1.19.1 → 1.19.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbuddy/cli",
3
- "version": "1.19.1",
3
+ "version": "1.19.2",
4
4
  "description": "BotBuddy — Swarm coordination CLI for multi-agent workflows",
5
5
  "type": "module",
6
6
  "bin": {
package/src/wait-core.mjs CHANGED
@@ -576,13 +576,27 @@ export function truncateReceipt(receipt, maxBytes = 10240) {
576
576
  clone.detail_truncated = true;
577
577
  }
578
578
  if (Buffer.byteLength(JSON.stringify(clone)) > maxBytes && clone.outcome === "error") {
579
- return {
579
+ const base = {
580
580
  schema_version: clone.schema_version,
581
581
  outcome: "error",
582
582
  error: typeof clone.error === "string" ? clone.error.slice(0, 48) : "receipt_truncated",
583
583
  truncated: true,
584
584
  ...(clone.client ? { client: clone.client } : {}),
585
585
  };
586
+ // BOT-1590: the recovery hint is the whole point of an error receipt, so keep
587
+ // it (trimmed to whatever still fits the cap) rather than dropping it — a
588
+ // caller at the documented minimum cap must still get its one actionable line.
589
+ if (typeof clone.recovery === "string" && clone.recovery.length > 0) {
590
+ const room = maxBytes - Buffer.byteLength(JSON.stringify({ ...base, recovery: "" }));
591
+ if (room > 0) {
592
+ let recovery = clone.recovery;
593
+ while (recovery.length > 0 && Buffer.byteLength(recovery, "utf8") > room) {
594
+ recovery = recovery.slice(0, -1);
595
+ }
596
+ if (recovery.length > 0) base.recovery = recovery;
597
+ }
598
+ }
599
+ return base;
586
600
  }
587
601
  return clone;
588
602
  }
package/src/wait.mjs CHANGED
@@ -14,7 +14,7 @@
14
14
  // Run botbuddy wait --help for the condition grammar.
15
15
 
16
16
  import { EXIT, parseConditions, parseSseFrames, runWaitLoop, normalizeSince, truncateReceipt, formatPrReviewSnapshotWarnings } from "./wait-core.mjs";
17
- import { resolveAgentProfile, withPrincipalReceipt } from "./wait-profile.mjs";
17
+ import { resolveAgentProfile, withPrincipalReceipt, PROFILE_FILE } from "./wait-profile.mjs";
18
18
  import { VERSION } from "./version.mjs";
19
19
  import { fileURLToPath } from "node:url";
20
20
  import { latestPublicCliCommand } from "./public-invocation.mjs";
@@ -643,10 +643,52 @@ function profileErrorReceipt(profile, error) {
643
643
  schema_version: 1,
644
644
  outcome: "error",
645
645
  error,
646
- recovery: profileRecovery(profile),
646
+ recovery: profileCredentialRecovery(profile),
647
647
  }, profile, { sessionTenant: profile.tenant, agentId: null });
648
648
  }
649
649
 
650
+ // BOT-1590: canonical one-line recoveries for every setup/auth/parameter error.
651
+ // Each names the exact command to run and the source of any missing/invalid
652
+ // value, so an agent (or a human) can self-serve without opening the docs. The
653
+ // SAME string is written to stderr and the receipt `recovery` field that /waits
654
+ // renders. Keep each to one line and never embed a secret value — names, slots,
655
+ // and env-var names only.
656
+ const RECOVERY = Object.freeze({
657
+ // $BOTBUDDY_SESSION_TOKEN (bb_sess_+64hex) is minted by register_agent; the
658
+ // harness exports it at session start.
659
+ sessionToken: "register_agent → export BOTBUDDY_SESSION_TOKEN=<session_token> (the harness exports it at session start)",
660
+ // $BOTBUDDY_SESSION_ID is the work-graph session id returned by register_agent.
661
+ sessionId: "register_agent → export BOTBUDDY_SESSION_ID=<session_id> (the work-graph session id from register_agent)",
662
+ // A --token / session-token contradiction: the session token is the whole
663
+ // credential, so drop --token (never advise also setting a session id here).
664
+ sessionTokenConflict: "unset --token — $BOTBUDDY_SESSION_TOKEN is the whole credential (protocol 3)",
665
+ // Grammar/condition errors point at the help and the canonical doc.
666
+ conditions: "check the condition grammar: botbuddy wait --help / docs/agent-wait.md",
667
+ });
668
+
669
+ const KNOWN_PROFILES = "botbuddy-dev, supplyguard-dev";
670
+
671
+ // profile_required / unknown_profile: name the file, a minimal example, and the
672
+ // known profile names so the fix is a single edit (AC-3).
673
+ function profileResolutionRecovery(errorCode) {
674
+ return errorCode === "unknown_profile"
675
+ ? `set a known profile in ${PROFILE_FILE} or pass --profile <name> — known profiles: ${KNOWN_PROFILES}`
676
+ : `add ${PROFILE_FILE} ({"schema_version":1,"profile":"botbuddy-dev"}) or pass --profile <name> — known profiles: ${KNOWN_PROFILES}`;
677
+ }
678
+
679
+ // A profile-credential error (missing/revoked/wrong-tenant): name the exact
680
+ // setup command and the credential's real source, then rule out human PATs,
681
+ // which are never valid for waits (AC-4). `botbuddy profile setup` stores the
682
+ // key in the macOS Keychain under the profile env-var's name (it REQUIRES the
683
+ // Keychain — ensureProfileCredentialBackend); on a non-Keychain host the
684
+ // supported path is exporting that same profile env var directly, which
685
+ // resolveAgentProfile reads. (The 0600 config.json store is the owner login
686
+ // token's, not a profile credential's — a wait cannot consume it.)
687
+ const HUMAN_PAT_NOTE = "human PATs (BOTBUDDY_AGENT_KEY / BOTBUDDY_AGENT_API_KEY) are not valid for waits";
688
+ function profileCredentialRecovery(profile) {
689
+ return `${profileRecovery(profile)} (stores $${profile.tokenEnv} in the macOS Keychain; on a non-Keychain host export $${profile.tokenEnv} directly); ${HUMAN_PAT_NOTE}`;
690
+ }
691
+
650
692
  export async function runWait(argv) {
651
693
  const opts = parseArgv(argv);
652
694
  const emitReceipt = (receipt, options = {}) => emit(receipt, {
@@ -671,7 +713,8 @@ export async function runWait(argv) {
671
713
  const { conditions, errors } = parseConditions(opts.conditions);
672
714
  if (errors.length > 0) {
673
715
  for (const e of errors) process.stderr.write(`bb-wait: invalid condition '${e.spec}': ${e.message}\n`);
674
- emitReceipt({ schema_version: 1, outcome: "error", error: "invalid_conditions", errors });
716
+ process.stderr.write(`bb-wait: ${RECOVERY.conditions}\n`);
717
+ emitReceipt({ schema_version: 1, outcome: "error", error: "invalid_conditions", errors, recovery: RECOVERY.conditions });
675
718
  process.exit(EXIT.INVALID);
676
719
  }
677
720
 
@@ -696,13 +739,13 @@ export async function runWait(argv) {
696
739
  // is a contradiction and is rejected.
697
740
  if (needsRelay && opts.sessionToken) {
698
741
  if (!SESSION_TOKEN_RE.test(opts.sessionToken)) {
699
- process.stderr.write("botbuddy wait: $BOTBUDDY_SESSION_TOKEN must match bb_sess_<64 hex>\n");
700
- emitReceipt({ schema_version: 1, outcome: "error", error: "invalid_session_token" });
742
+ process.stderr.write(`botbuddy wait: $BOTBUDDY_SESSION_TOKEN must match bb_sess_<64 hex>; ${RECOVERY.sessionToken}\n`);
743
+ emitReceipt({ schema_version: 1, outcome: "error", error: "invalid_session_token", recovery: RECOVERY.sessionToken });
701
744
  process.exit(EXIT.INVALID);
702
745
  }
703
746
  if (opts.token && opts.token !== opts.sessionToken) {
704
- process.stderr.write("botbuddy wait: --token conflicts with $BOTBUDDY_SESSION_TOKEN; pass only one\n");
705
- emitReceipt({ schema_version: 1, outcome: "error", error: "session_token_conflict" });
747
+ process.stderr.write(`botbuddy wait: --token conflicts with $BOTBUDDY_SESSION_TOKEN; ${RECOVERY.sessionTokenConflict}\n`);
748
+ emitReceipt({ schema_version: 1, outcome: "error", error: "session_token_conflict", recovery: RECOVERY.sessionTokenConflict });
706
749
  process.exit(EXIT.INVALID);
707
750
  }
708
751
  opts.useSessionToken = true;
@@ -714,19 +757,19 @@ export async function runWait(argv) {
714
757
  // timer-only wait needs no relay and no session id (handled by !needsRelay).
715
758
  if (!opts.sessionId) {
716
759
  process.stderr.write(
717
- "botbuddy wait: --session-id is required (or set $BOTBUDDY_SESSION_ID) — the work-graph session id returned by register_agent\n",
760
+ `botbuddy wait: --session-id is required (or set $BOTBUDDY_SESSION_ID) — the work-graph session id returned by register_agent; ${RECOVERY.sessionId}\n`,
718
761
  );
719
762
  emitReceipt({
720
763
  schema_version: 1,
721
764
  outcome: "error",
722
765
  error: "session_id_required",
723
- recovery: "register_agent → export BOTBUDDY_SESSION_ID=<session_id>",
766
+ recovery: RECOVERY.sessionId,
724
767
  });
725
768
  process.exit(EXIT.INVALID);
726
769
  }
727
770
  if (!SESSION_UUID.test(opts.sessionId)) {
728
- process.stderr.write(`botbuddy wait: --session-id must be a uuid (got '${opts.sessionId}')\n`);
729
- emitReceipt({ schema_version: 1, outcome: "error", error: "invalid_session_agent", detail: "session_id must be a uuid" });
771
+ process.stderr.write(`botbuddy wait: --session-id must be a uuid (got '${opts.sessionId}'); ${RECOVERY.sessionId}\n`);
772
+ emitReceipt({ schema_version: 1, outcome: "error", error: "invalid_session_agent", detail: "session_id must be a uuid", recovery: RECOVERY.sessionId });
730
773
  process.exit(EXIT.INVALID);
731
774
  }
732
775
  try {
@@ -735,14 +778,16 @@ export async function runWait(argv) {
735
778
  explicitToken: opts.token,
736
779
  });
737
780
  } catch (err) {
738
- process.stderr.write(`bb-wait: ${err.message}\n`);
739
- emitReceipt({ schema_version: 1, outcome: "error", error: err.code || "invalid_profile" });
781
+ const code = err.code || "invalid_profile";
782
+ const recovery = profileResolutionRecovery(code);
783
+ process.stderr.write(`bb-wait: ${err.message}; ${recovery}\n`);
784
+ emitReceipt({ schema_version: 1, outcome: "error", error: code, recovery });
740
785
  process.exit(EXIT.INVALID);
741
786
  }
742
787
  opts.token = opts.agentProfile.token;
743
788
  if (!opts.token) {
744
789
  process.stderr.write(
745
- `botbuddy wait: profile '${opts.agentProfile.name}' has no tenant-bound agent credential; run '${profileRecovery(opts.agentProfile)}'\n`,
790
+ `botbuddy wait: profile '${opts.agentProfile.name}' has no tenant-bound agent credential; run ${profileCredentialRecovery(opts.agentProfile)}\n`,
746
791
  );
747
792
  emitReceipt(profileErrorReceipt(opts.agentProfile, "profile_required"));
748
793
  process.exit(EXIT.AUTH);
@@ -897,7 +942,7 @@ export async function runWait(argv) {
897
942
  process.exit(EXIT.AUTH);
898
943
  }
899
944
  const error = typedProfileError(err.errorCode);
900
- process.stderr.write(`botbuddy wait: profile authentication failed (${error}); run '${profileRecovery(opts.agentProfile)}'\n`);
945
+ process.stderr.write(`botbuddy wait: profile authentication failed (${error}); run ${profileCredentialRecovery(opts.agentProfile)}\n`);
901
946
  emitReceipt(profileErrorReceipt(opts.agentProfile, error));
902
947
  process.exit(EXIT.AUTH);
903
948
  }