@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 +1 -1
- package/src/wait-core.mjs +15 -1
- package/src/wait.mjs +60 -15
package/package.json
CHANGED
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
|
-
|
|
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:
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
739
|
-
|
|
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
|
|
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
|
|
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
|
}
|