@indigoai-us/hq-cli 5.345.48 → 5.345.50
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/CHANGELOG.md +17 -0
- package/README.md +2 -1
- package/dist/command-catalog.generated.d.ts +64 -11
- package/dist/command-catalog.generated.js +77 -11
- package/dist/commands/anywhere-runtime-flag.js +5 -3
- package/dist/commands/db-destroy.js +13 -11
- package/dist/commands/db-dump.js +3 -3
- package/dist/commands/db-remote-deps.d.ts +3 -0
- package/dist/commands/db-remote-deps.js +13 -0
- package/dist/commands/db-sql.js +4 -3
- package/dist/commands/db-usage.js +4 -3
- package/dist/commands/dm-forward-flag.d.ts +4 -0
- package/dist/commands/dm-forward-flag.js +7 -0
- package/dist/commands/dm.d.ts +8 -0
- package/dist/commands/dm.js +219 -27
- package/dist/commands/feedback.d.ts +10 -0
- package/dist/commands/feedback.js +45 -0
- package/dist/commands/lanes-message.js +2 -2
- package/dist/commands/lanes.js +2 -2
- package/dist/commands/mcp-serve.js +17 -4
- package/dist/lib/anywhere-person-setting.d.ts +1 -0
- package/dist/lib/anywhere-person-setting.js +12 -0
- package/dist/lib/anywhere-runtime-fast-flag.js +5 -3
- package/dist/lib/anywhere-runtime-flag-key.d.ts +3 -1
- package/dist/lib/anywhere-runtime-flag-key.js +6 -1
- package/dist/lib/daemon/flag.d.ts +1 -1
- package/dist/lib/daemon/flag.js +20 -9
- package/dist/lib/daemon/hq-cloud.d.ts +2 -2
- package/dist/lib/daemon/hq-cloud.js +38 -9
- package/dist/lib/daemon/run.js +45 -19
- package/dist/lib/daemon/server.d.ts +1 -1
- package/dist/lib/db/app-ref.d.ts +21 -0
- package/dist/lib/db/app-ref.js +45 -0
- package/dist/lib/lanes/keep-alive-audit.d.ts +5 -1
- package/dist/lib/lanes/keep-alive-audit.js +85 -3
- package/dist/lib/lanes/reconcile.js +6 -4
- package/dist/lib/lanes/registry-flag.d.ts +11 -1
- package/dist/lib/lanes/registry-flag.js +68 -29
- package/dist/lib/lanes/watcher.js +63 -2
- package/dist/utils/resident-package-preload.js +4 -1
- package/dist/utils/self-update.js +6 -5
- package/dist/utils/version-gate.d.ts +11 -4
- package/dist/utils/version-gate.js +132 -41
- package/docs/anywhere-runtime.md +5 -3
- package/package.json +1 -1
package/dist/commands/dm.js
CHANGED
|
@@ -5,6 +5,7 @@ import { vaultApiFetch } from "../utils/vault-api.js";
|
|
|
5
5
|
import { isMachineIdentity } from "@indigoai-us/hq-cloud";
|
|
6
6
|
import { peekIdToken } from "../utils/id-token.js";
|
|
7
7
|
import { EMAIL_PATTERN, RECIPIENT_UID_PATTERN } from "../lib/notify-recipient.js";
|
|
8
|
+
import { dmForwardCliEnabled } from "./dm-forward-flag.js";
|
|
8
9
|
/**
|
|
9
10
|
* Classify a recipient arg as an email or a person/agent uid. Mirrors the
|
|
10
11
|
* email/uid heuristic used by `hq members`. Returns null for neither.
|
|
@@ -346,6 +347,32 @@ function friendlyDmError(status, code, fallback) {
|
|
|
346
347
|
return `Server error: ${fallback}`;
|
|
347
348
|
return fallback;
|
|
348
349
|
}
|
|
350
|
+
function friendlyForwardError(code) {
|
|
351
|
+
switch (code) {
|
|
352
|
+
case "FORWARD_SOURCE_NOT_FOUND":
|
|
353
|
+
return "Source message not found or you cannot read it.";
|
|
354
|
+
case "INVALID_FORWARD_OF":
|
|
355
|
+
return "Invalid source message reference; provide a valid eventId and --from value.";
|
|
356
|
+
case "INVALID_FILE_ACCESS":
|
|
357
|
+
return "Invalid file access choice; use --grant-files or --omit-files.";
|
|
358
|
+
case "FORWARD_FILE_ACCESS_REQUIRED":
|
|
359
|
+
return "Some forwarded files are not readable by the recipient.";
|
|
360
|
+
case "FORWARD_FILES_NOT_ALLOWED":
|
|
361
|
+
return "Files cannot be granted for this channel or cross-company forward.";
|
|
362
|
+
case "CROSS_COMPANY_ACK_REQUIRED":
|
|
363
|
+
return "Warning: this forward crosses company boundaries; review the destination and retry with --cross-company.";
|
|
364
|
+
case "CROSS_COMPANY_FORBIDDEN":
|
|
365
|
+
return "Cross-company forwarding requires owner or admin access in both companies.";
|
|
366
|
+
case "FORWARD_FILE_SHARE_FORBIDDEN":
|
|
367
|
+
return "You do not have permission to share one or more forwarded files.";
|
|
368
|
+
case "FORWARD_FILE_GRANT_FAILED":
|
|
369
|
+
return "The recipient could not be granted access to a forwarded file; no message was sent.";
|
|
370
|
+
case "RECIPIENT_NOT_FOUND":
|
|
371
|
+
return "Recipient not found or not reachable.";
|
|
372
|
+
default:
|
|
373
|
+
return "Message forwarding failed; check the source and destination, then try again.";
|
|
374
|
+
}
|
|
375
|
+
}
|
|
349
376
|
/**
|
|
350
377
|
* Resolve a connection request from a pending-requests list by a caller-supplied
|
|
351
378
|
* identifier (email, personUid, or pairKey). Pure → unit-testable.
|
|
@@ -529,6 +556,21 @@ export async function fetchChannelMembers(token, channelId) {
|
|
|
529
556
|
const data = (await res.json());
|
|
530
557
|
return data.members ?? [];
|
|
531
558
|
}
|
|
559
|
+
/** Resolve structured mentions in a channel message body, failing before post on invalid names. */
|
|
560
|
+
async function resolveChannelBodyMentions(token, channelId, body) {
|
|
561
|
+
const tokens = parseMentionTokens(body);
|
|
562
|
+
if (tokens.length === 0)
|
|
563
|
+
return { ok: true, body, mentions: [] };
|
|
564
|
+
const members = await fetchChannelMembers(token, channelId);
|
|
565
|
+
const resolved = resolveMentions(tokens, members);
|
|
566
|
+
if (!resolved.ok)
|
|
567
|
+
return resolved;
|
|
568
|
+
return {
|
|
569
|
+
ok: true,
|
|
570
|
+
body: stripMentionQuotes(body),
|
|
571
|
+
mentions: resolved.mentions,
|
|
572
|
+
};
|
|
573
|
+
}
|
|
532
574
|
/** Fetch a channel history page (GET /v1/notify/channels/{id}/messages). Newest-first. */
|
|
533
575
|
async function fetchChannelMessages(token, channelId, query = {}) {
|
|
534
576
|
const res = await vaultApiFetch({
|
|
@@ -546,9 +588,9 @@ async function fetchChannelMessages(token, channelId, query = {}) {
|
|
|
546
588
|
/**
|
|
547
589
|
* Channel DM path: `hq dm vyg-dev "msg"`, `hq dm '#vyg-dev' "msg"`, or
|
|
548
590
|
* `hq dm --channel vyg-dev "msg"`. Resolves the caller's channel by name via
|
|
549
|
-
* GET /v1/notify/channels, then posts the message into it. Scheduling
|
|
550
|
-
*
|
|
551
|
-
*
|
|
591
|
+
* GET /v1/notify/channels, then posts the message into it. Scheduling remains
|
|
592
|
+
* a 1:1-DM feature; prompt and details are supported for channel sends while
|
|
593
|
+
* the dm.forward-cli kill switch is enabled.
|
|
552
594
|
*/
|
|
553
595
|
async function runChannelSend(channelName, message, opts) {
|
|
554
596
|
try {
|
|
@@ -557,12 +599,17 @@ async function runChannelSend(channelName, message, opts) {
|
|
|
557
599
|
console.error(chalk.red(`A message body is required: hq dm ${channelName} "<message>" (or hq dm --channel ${channelName} "<message>").`));
|
|
558
600
|
process.exit(1);
|
|
559
601
|
}
|
|
560
|
-
const
|
|
561
|
-
opts.
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
602
|
+
const hasChannelContext = opts.prompt !== undefined || opts.promptFile !== undefined ||
|
|
603
|
+
opts.details !== undefined || opts.detailsFile !== undefined;
|
|
604
|
+
if (hasChannelContext && !dmForwardCliEnabled()) {
|
|
605
|
+
console.error(chalk.red("Channel --details/--prompt support is disabled by the dm.forward-cli kill switch."));
|
|
606
|
+
process.exit(1);
|
|
607
|
+
}
|
|
608
|
+
if (opts.promptFile === "" || opts.detailsFile === "") {
|
|
609
|
+
console.error(chalk.red("--prompt-file and --details-file require a non-empty path."));
|
|
610
|
+
process.exit(1);
|
|
611
|
+
}
|
|
612
|
+
const unsupported = [opts.at ? "--at" : null, opts.in ? "--in" : null].filter(Boolean);
|
|
566
613
|
if (unsupported.length > 0) {
|
|
567
614
|
console.error(chalk.red(`${unsupported.join(", ")} ${unsupported.length === 1 ? "is" : "are"} only supported for 1:1 DMs, not channel messages.`));
|
|
568
615
|
process.exit(1);
|
|
@@ -599,21 +646,16 @@ async function runChannelSend(channelName, message, opts) {
|
|
|
599
646
|
// and posts the text literally.
|
|
600
647
|
let mentions = [];
|
|
601
648
|
if (opts.mentions !== false) {
|
|
602
|
-
const
|
|
603
|
-
if (
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
console.error(chalk.red(resolved.error));
|
|
608
|
-
process.exit(1);
|
|
609
|
-
return;
|
|
610
|
-
}
|
|
611
|
-
mentions = resolved.mentions;
|
|
649
|
+
const resolved = await resolveChannelBodyMentions(token, channel.channelId, body);
|
|
650
|
+
if (!resolved.ok) {
|
|
651
|
+
console.error(chalk.red(resolved.error));
|
|
652
|
+
process.exit(1);
|
|
653
|
+
return;
|
|
612
654
|
}
|
|
655
|
+
body = resolved.body;
|
|
656
|
+
mentions = resolved.mentions;
|
|
613
657
|
}
|
|
614
658
|
// Send the human-readable form: the quotes only existed for parsing.
|
|
615
|
-
if (mentions.length > 0)
|
|
616
|
-
body = stripMentionQuotes(body);
|
|
617
659
|
let audience;
|
|
618
660
|
if (opts.audience !== undefined && opts.audience !== "") {
|
|
619
661
|
if (!DM_AUDIENCE_VALUES.includes(opts.audience)) {
|
|
@@ -622,6 +664,12 @@ async function runChannelSend(channelName, message, opts) {
|
|
|
622
664
|
audience = opts.audience;
|
|
623
665
|
}
|
|
624
666
|
const payload = { body };
|
|
667
|
+
const prompt = opts.promptFile ? readFileSync(opts.promptFile, "utf8") : opts.prompt;
|
|
668
|
+
const details = opts.detailsFile ? readFileSync(opts.detailsFile, "utf8") : opts.details;
|
|
669
|
+
if (prompt !== undefined)
|
|
670
|
+
payload.prompt = prompt;
|
|
671
|
+
if (details !== undefined)
|
|
672
|
+
payload.details = details;
|
|
625
673
|
if (mentions.length > 0)
|
|
626
674
|
payload.mentions = mentions;
|
|
627
675
|
if (rootEventId)
|
|
@@ -736,6 +784,130 @@ async function runDmSend(recipient, message, opts) {
|
|
|
736
784
|
process.exit(1);
|
|
737
785
|
}
|
|
738
786
|
}
|
|
787
|
+
async function resolveForwardSourceConversation(token, source) {
|
|
788
|
+
if (source.includes(",")) {
|
|
789
|
+
throw new Error("--from accepts one person or channel, not a group recipient list.");
|
|
790
|
+
}
|
|
791
|
+
const channelName = parseChannelName(source);
|
|
792
|
+
if (channelName !== null) {
|
|
793
|
+
const channel = await resolveChannel(token, channelName);
|
|
794
|
+
return { conversationId: channel.channelId };
|
|
795
|
+
}
|
|
796
|
+
const recipient = detectRecipient(source);
|
|
797
|
+
if (!recipient) {
|
|
798
|
+
throw new Error("--from must be a person email, person/agent UID, or channel name/ID.");
|
|
799
|
+
}
|
|
800
|
+
if (recipient.toPersonUid)
|
|
801
|
+
return { conversationId: recipient.toPersonUid };
|
|
802
|
+
const res = await vaultApiFetch({ token, path: "/v1/notify/contacts" });
|
|
803
|
+
if (!res.ok)
|
|
804
|
+
throw new Error("Could not resolve the source person; use their person UID.");
|
|
805
|
+
const data = (await res.json());
|
|
806
|
+
const peer = (data.contacts ?? []).find((contact) => contact.email?.trim().toLowerCase() === recipient.toEmail);
|
|
807
|
+
if (!peer?.personUid)
|
|
808
|
+
throw new Error("Source person not found in your contacts; use their person UID.");
|
|
809
|
+
return { conversationId: peer.personUid };
|
|
810
|
+
}
|
|
811
|
+
async function runDmForward(eventId, opts) {
|
|
812
|
+
try {
|
|
813
|
+
if (!dmForwardCliEnabled()) {
|
|
814
|
+
console.error(chalk.red("Message forwarding is disabled by the dm.forward-cli kill switch."));
|
|
815
|
+
process.exit(1);
|
|
816
|
+
}
|
|
817
|
+
if (opts.grantFiles && opts.omitFiles) {
|
|
818
|
+
console.error(chalk.red("Choose only one of --grant-files or --omit-files."));
|
|
819
|
+
process.exit(1);
|
|
820
|
+
}
|
|
821
|
+
if (opts.note !== undefined && opts.noteFile !== undefined) {
|
|
822
|
+
console.error(chalk.red("Choose only one of --note or --note-file."));
|
|
823
|
+
process.exit(1);
|
|
824
|
+
}
|
|
825
|
+
if (!eventId.trim()) {
|
|
826
|
+
console.error(chalk.red("A source eventId is required."));
|
|
827
|
+
process.exit(1);
|
|
828
|
+
}
|
|
829
|
+
if (opts.to.includes(",")) {
|
|
830
|
+
console.error(chalk.red("--to accepts one recipient or channel, not a group recipient list."));
|
|
831
|
+
process.exit(1);
|
|
832
|
+
}
|
|
833
|
+
const note = opts.noteFile !== undefined
|
|
834
|
+
? readFileSync(opts.noteFile, "utf8")
|
|
835
|
+
: opts.note;
|
|
836
|
+
const token = await ensureCognitoToken();
|
|
837
|
+
const source = await resolveForwardSourceConversation(token, opts.from);
|
|
838
|
+
const destinationChannelName = parseChannelName(opts.to);
|
|
839
|
+
const fileAccess = opts.grantFiles ? "grant" : opts.omitFiles ? "omit" : undefined;
|
|
840
|
+
const shared = {
|
|
841
|
+
...(note !== undefined ? { body: note } : {}),
|
|
842
|
+
forwardOf: { conversationId: source.conversationId, eventId: eventId.trim() },
|
|
843
|
+
...(fileAccess ? { fileAccess } : {}),
|
|
844
|
+
...(opts.crossCompany ? { acknowledgeCrossCompany: true } : {}),
|
|
845
|
+
};
|
|
846
|
+
let path;
|
|
847
|
+
let payload;
|
|
848
|
+
let destinationLabel;
|
|
849
|
+
if (destinationChannelName !== null) {
|
|
850
|
+
const channel = await resolveChannel(token, destinationChannelName);
|
|
851
|
+
path = `/v1/notify/channels/${encodeURIComponent(channel.channelId)}/messages`;
|
|
852
|
+
payload = { ...shared };
|
|
853
|
+
if (note !== undefined) {
|
|
854
|
+
const resolved = await resolveChannelBodyMentions(token, channel.channelId, note);
|
|
855
|
+
if (!resolved.ok) {
|
|
856
|
+
console.error(chalk.red(resolved.error));
|
|
857
|
+
process.exit(1);
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
payload.body = resolved.body;
|
|
861
|
+
if (resolved.mentions.length > 0)
|
|
862
|
+
payload.mentions = resolved.mentions;
|
|
863
|
+
}
|
|
864
|
+
destinationLabel = `#${channel.name ?? destinationChannelName}`;
|
|
865
|
+
}
|
|
866
|
+
else {
|
|
867
|
+
const recipient = detectRecipient(opts.to);
|
|
868
|
+
if (!recipient) {
|
|
869
|
+
console.error(chalk.red("--to must be a person email, person/agent UID, or channel name/ID."));
|
|
870
|
+
process.exit(1);
|
|
871
|
+
}
|
|
872
|
+
path = "/v1/notify/dm";
|
|
873
|
+
payload = { ...recipient, ...shared };
|
|
874
|
+
destinationLabel = opts.to;
|
|
875
|
+
}
|
|
876
|
+
const res = await vaultApiFetch({ token, path, method: "POST", body: payload });
|
|
877
|
+
if (!res.ok) {
|
|
878
|
+
const err = (await res.json().catch(() => ({})));
|
|
879
|
+
const code = typeof err.code === "string" ? err.code : undefined;
|
|
880
|
+
if (code === "FORWARD_FILE_ACCESS_REQUIRED") {
|
|
881
|
+
console.error(chalk.yellow(friendlyForwardError(code)));
|
|
882
|
+
const files = Array.isArray(err.files) ? err.files : [];
|
|
883
|
+
for (const item of files) {
|
|
884
|
+
if (!item || typeof item !== "object")
|
|
885
|
+
continue;
|
|
886
|
+
const file = item;
|
|
887
|
+
const label = typeof file.name === "string" ? file.name : "forwarded file";
|
|
888
|
+
const location = typeof file.vaultPath === "string" ? file.vaultPath :
|
|
889
|
+
typeof file.path === "string" ? file.path : undefined;
|
|
890
|
+
console.error(chalk.dim(` - ${label}${location ? ` (${location})` : ""}`));
|
|
891
|
+
}
|
|
892
|
+
console.error(chalk.dim("Retry with --grant-files or --omit-files."));
|
|
893
|
+
}
|
|
894
|
+
else {
|
|
895
|
+
console.error(chalk.red(friendlyForwardError(code)));
|
|
896
|
+
}
|
|
897
|
+
process.exit(1);
|
|
898
|
+
}
|
|
899
|
+
const data = (await res.json().catch(() => ({})));
|
|
900
|
+
if (typeof data.eventId !== "string" || !data.eventId.trim()) {
|
|
901
|
+
console.error(chalk.red("Forward succeeded without returning an eventId."));
|
|
902
|
+
process.exit(1);
|
|
903
|
+
}
|
|
904
|
+
console.log(chalk.green(`Message forwarded to ${destinationLabel}. New eventId: ${data.eventId}`));
|
|
905
|
+
}
|
|
906
|
+
catch (err) {
|
|
907
|
+
console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
|
|
908
|
+
process.exit(1);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
739
911
|
async function runDmRequests() {
|
|
740
912
|
try {
|
|
741
913
|
const token = await ensureCognitoToken();
|
|
@@ -871,7 +1043,11 @@ export function formatThreadMessage(m, nowMs) {
|
|
|
871
1043
|
const who = m.direction === "out" ? "you" : senderLabel(m);
|
|
872
1044
|
const when = chalk.dim(formatRelativeTime(m.createdAt, nowMs));
|
|
873
1045
|
const audienceTag = m.audience === "agent" ? chalk.dim(" [agent]") : "";
|
|
874
|
-
|
|
1046
|
+
const id = m.eventId ? ` ${chalk.dim(m.eventId)}` : "";
|
|
1047
|
+
const forwarded = m.forwardedFrom?.senderName
|
|
1048
|
+
? `\n ${chalk.dim(`Forwarded from ${m.forwardedFrom.senderName}`)}`
|
|
1049
|
+
: "";
|
|
1050
|
+
return `${arrow} ${chalk.bold(who)} ${when}${audienceTag}${id}${forwarded}\n ${firstLine(m.body)}${formatAttachments(m)}`;
|
|
875
1051
|
}
|
|
876
1052
|
/** Render one channel/group message line. */
|
|
877
1053
|
export function formatChannelMessage(m, nowMs, opts = {}) {
|
|
@@ -886,7 +1062,10 @@ export function formatChannelMessage(m, nowMs, opts = {}) {
|
|
|
886
1062
|
(senderLabel(m).toLowerCase() === "hq"
|
|
887
1063
|
? "System notification (no text provided)."
|
|
888
1064
|
: "(no message text)");
|
|
889
|
-
|
|
1065
|
+
const forwarded = m.forwardedFrom?.senderName
|
|
1066
|
+
? `${bodyIndent}${chalk.dim(`Forwarded from ${m.forwardedFrom.senderName}`)}\n`
|
|
1067
|
+
: "";
|
|
1068
|
+
return `${indent}${who} ${when}${idPart}\n${forwarded}${bodyIndent}${readableBody}${formatAttachments(m, bodyIndent)}`;
|
|
890
1069
|
}
|
|
891
1070
|
/**
|
|
892
1071
|
* Read the caller's person/agent uid and email from a JWT we already hold.
|
|
@@ -1330,16 +1509,29 @@ export function registerDmCommand(program) {
|
|
|
1330
1509
|
.option("--channel <name>", "Post the message to one of your DM channels by name (e.g. --channel vyg-dev)")
|
|
1331
1510
|
.option("--no-mentions", "Do not resolve `@name` in a channel message into structured mentions — post the text literally")
|
|
1332
1511
|
.option("--thread <eventId>", 'Reply in a channel thread. Pass a root eventId from `hq dm channel`, or `last` for your most recent top-level message in that channel')
|
|
1333
|
-
.option("--prompt <text>", "Agent-context prompt the recipient can one-click copy into their agent (1:1 DMs
|
|
1334
|
-
.option("--prompt-file <path>", "Read the agent prompt from a file (1:1 DMs
|
|
1335
|
-
.option("--details <text>", "Longer detail shown in the recipient's DM detail window (1:1 DMs
|
|
1336
|
-
.option("--details-file <path>", "Read the details from a file (1:1 DMs
|
|
1512
|
+
.option("--prompt <text>", "Agent-context prompt the recipient can one-click copy into their agent (1:1 DMs; channel sends also supported)")
|
|
1513
|
+
.option("--prompt-file <path>", "Read the agent prompt from a file (1:1 DMs; channel sends also supported)")
|
|
1514
|
+
.option("--details <text>", "Longer detail shown in the recipient's DM detail window (1:1 DMs; channel sends also supported)")
|
|
1515
|
+
.option("--details-file <path>", "Read the details from a file (1:1 DMs; channel sends also supported)")
|
|
1337
1516
|
.option("--at <iso>", "Schedule delivery at an ISO8601 time (store-and-forward; delivered within ~60s of the time; 1:1 DMs only)")
|
|
1338
1517
|
.option("--in <duration>", "Schedule delivery after a relative delay: 30s, 10m, 2h, 1d (1:1 DMs only)")
|
|
1339
1518
|
.option("--audience <human|agent|both>", "Tag who this message is for: human (a person must read it), agent (progress/status, hidden from humans by default), or both")
|
|
1340
1519
|
.action(async (recipient, message, opts) => {
|
|
1341
1520
|
await runDmSend(recipient, message, opts);
|
|
1342
1521
|
});
|
|
1522
|
+
dm
|
|
1523
|
+
.command("forward <eventId>")
|
|
1524
|
+
.description("Forward a DM or channel message through the existing send routes.")
|
|
1525
|
+
.requiredOption("--from <person|channel>", "Source person email/UID or channel name/ID")
|
|
1526
|
+
.requiredOption("--to <person|agent|channel>", "Destination person email/UID, agent UID, or channel name/ID")
|
|
1527
|
+
.option("--note <text>", "Optional note to add above the forwarded message")
|
|
1528
|
+
.option("--note-file <path>", "Read the optional forward note from a file")
|
|
1529
|
+
.option("--grant-files", "Grant the recipient access to files they cannot currently open")
|
|
1530
|
+
.option("--omit-files", "Omit files the recipient cannot currently open")
|
|
1531
|
+
.option("--cross-company", "Confirm forwarding across company boundaries")
|
|
1532
|
+
.action(async (eventId, opts) => {
|
|
1533
|
+
await runDmForward(eventId, opts);
|
|
1534
|
+
});
|
|
1343
1535
|
dm
|
|
1344
1536
|
.command("inbox")
|
|
1345
1537
|
.description("List your recent incoming direct messages.")
|
|
@@ -4,6 +4,8 @@ import { collectFeedbackLogs, feedbackLogsEnabled } from "../utils/feedback-logs
|
|
|
4
4
|
import { type LogBundleRef } from "../utils/feedback-log-bundle.js";
|
|
5
5
|
export declare const FEEDBACK_LATEST_VERSION_FLAG = "feedback.latest-version";
|
|
6
6
|
export declare const FEEDBACK_LATEST_VERSION_FLAG_FALLBACK = false;
|
|
7
|
+
export declare const FEEDBACK_REPORTER_KIND_FLAG = "feedback.reporter-kind";
|
|
8
|
+
export declare const FEEDBACK_REPORTER_KIND_FLAG_FALLBACK = true;
|
|
7
9
|
export declare const BODY_MAX_BYTES: number;
|
|
8
10
|
/** Validate fields that must be valid before feedback attachments are uploaded. */
|
|
9
11
|
export declare function validateFeedbackContent(title: string, body: string): void;
|
|
@@ -44,7 +46,15 @@ export interface FeedbackSubmitOptions {
|
|
|
44
46
|
diagnostics?: DiagnosticsBlob;
|
|
45
47
|
/** Precomputed setting so environment lookup happens before attachments upload. */
|
|
46
48
|
logsEnabled?: boolean;
|
|
49
|
+
/** Optional one-way override: callers can mark human sessions as agent. */
|
|
50
|
+
reporterOverride?: "agent";
|
|
47
51
|
}
|
|
52
|
+
export interface FeedbackReporterMark {
|
|
53
|
+
reporter_kind: "agent" | "human";
|
|
54
|
+
reporter_source: string;
|
|
55
|
+
}
|
|
56
|
+
/** Resolve a safe reporter classification without returning any env values. */
|
|
57
|
+
export declare function resolveFeedbackReporter(env?: NodeJS.ProcessEnv, isTTY?: boolean, override?: string): FeedbackReporterMark;
|
|
48
58
|
export declare function readBodyFile(bodyFile: string, stdin?: NodeJS.ReadableStream): Promise<string>;
|
|
49
59
|
/**
|
|
50
60
|
* Attach redacted `~/.hq` log tails to `diagnostics` if, and only if, they fit
|
|
@@ -10,6 +10,8 @@ import { resolveProcessFlagGate } from "../lib/flag-registry.js";
|
|
|
10
10
|
import { cachedLatestVersion } from "../utils/version-check.js";
|
|
11
11
|
export const FEEDBACK_LATEST_VERSION_FLAG = "feedback.latest-version";
|
|
12
12
|
export const FEEDBACK_LATEST_VERSION_FLAG_FALLBACK = false;
|
|
13
|
+
export const FEEDBACK_REPORTER_KIND_FLAG = "feedback.reporter-kind";
|
|
14
|
+
export const FEEDBACK_REPORTER_KIND_FLAG_FALLBACK = true;
|
|
13
15
|
export const BODY_MAX_BYTES = 64 * 1024;
|
|
14
16
|
/** Validate fields that must be valid before feedback attachments are uploaded. */
|
|
15
17
|
export function validateFeedbackContent(title, body) {
|
|
@@ -46,6 +48,32 @@ function validateFeedbackRequestSize(requestBody) {
|
|
|
46
48
|
* after attaching, so this reserve only needs to be roughly right.
|
|
47
49
|
*/
|
|
48
50
|
const LOGS_ENVELOPE_RESERVE_BYTES = 1024;
|
|
51
|
+
const AGENT_RUNTIME_MARKERS = [
|
|
52
|
+
"HQ_LANE_ID",
|
|
53
|
+
"HQ_POLICY_WORKER_DIR",
|
|
54
|
+
"HQ_AGENT_DIR",
|
|
55
|
+
"CODEX_THREAD_ID",
|
|
56
|
+
"CODEX_SESSION_ID",
|
|
57
|
+
"CLAUDE_CODE_SESSION_ID",
|
|
58
|
+
"CLAUDE_SESSION_ID",
|
|
59
|
+
"HQ_SESSION_ID",
|
|
60
|
+
"GROK_SESSION_ID",
|
|
61
|
+
];
|
|
62
|
+
/** Resolve a safe reporter classification without returning any env values. */
|
|
63
|
+
export function resolveFeedbackReporter(env = process.env, isTTY = process.stdin.isTTY === true, override) {
|
|
64
|
+
for (const marker of AGENT_RUNTIME_MARKERS) {
|
|
65
|
+
if (env[marker]?.trim()) {
|
|
66
|
+
return { reporter_kind: "agent", reporter_source: marker };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (override === "agent") {
|
|
70
|
+
return { reporter_kind: "agent", reporter_source: "CLI_REPORTER_OVERRIDE" };
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
reporter_kind: "human",
|
|
74
|
+
reporter_source: isTTY ? "stdin_isTTY" : "no_agent_runtime_marker",
|
|
75
|
+
};
|
|
76
|
+
}
|
|
49
77
|
function buildFeedbackRequestBody(opts) {
|
|
50
78
|
const requestBody = {
|
|
51
79
|
type: opts.type,
|
|
@@ -53,6 +81,10 @@ function buildFeedbackRequestBody(opts) {
|
|
|
53
81
|
body: opts.body,
|
|
54
82
|
diagnostics: opts.diagnostics,
|
|
55
83
|
};
|
|
84
|
+
if (opts.reporterMark) {
|
|
85
|
+
requestBody.reporter_kind = opts.reporterMark.reporter_kind;
|
|
86
|
+
requestBody.reporter_source = opts.reporterMark.reporter_source;
|
|
87
|
+
}
|
|
56
88
|
if (opts.company) {
|
|
57
89
|
requestBody.company = opts.company;
|
|
58
90
|
}
|
|
@@ -124,6 +156,9 @@ export async function submitFeedback(opts) {
|
|
|
124
156
|
// which invokes the same validator before uploading any attachments.
|
|
125
157
|
validateFeedbackContent(opts.title, opts.body);
|
|
126
158
|
const diagnostics = opts.diagnostics ?? collectDiagnostics();
|
|
159
|
+
const reporterMark = opts.type === "bug" && resolveProcessFlagGate(FEEDBACK_REPORTER_KIND_FLAG, { fallback: FEEDBACK_REPORTER_KIND_FLAG_FALLBACK }, () => FEEDBACK_REPORTER_KIND_FLAG_FALLBACK)
|
|
160
|
+
? resolveFeedbackReporter(process.env, process.stdin.isTTY === true, opts.reporterOverride)
|
|
161
|
+
: undefined;
|
|
127
162
|
const requestBody = buildFeedbackRequestBody({
|
|
128
163
|
type: opts.type,
|
|
129
164
|
title: opts.title,
|
|
@@ -132,6 +167,7 @@ export async function submitFeedback(opts) {
|
|
|
132
167
|
diagnostics,
|
|
133
168
|
screenshots: opts.screenshots,
|
|
134
169
|
logBundle: opts.logBundle,
|
|
170
|
+
reporterMark,
|
|
135
171
|
});
|
|
136
172
|
// Direct callers can supply attachment references; ensure the complete base
|
|
137
173
|
// envelope still fits before adding optional inline logs or making a request.
|
|
@@ -172,12 +208,16 @@ function registerSubcommand(feedbackCmd, type) {
|
|
|
172
208
|
.requiredOption("--title <text>", "Short title for the report")
|
|
173
209
|
.requiredOption("--body-file <path>", "Path to a markdown file with the body; use - to read from stdin")
|
|
174
210
|
.option("--company <slug>", "Company slug to associate with the report")
|
|
211
|
+
.option("--reporter <kind>", "Mark this bug report as agent-filed (only 'agent' is accepted)")
|
|
175
212
|
.option("--screenshot <path>", `Attach a screenshot (repeatable, up to ${MAX_SCREENSHOTS}; .png/.jpg/.jpeg/.webp/.gif)`, (value, prev) => [...prev, value], [])
|
|
176
213
|
// Commander turns `--no-logs` into a boolean `logs` that defaults to true,
|
|
177
214
|
// so the default is "attach logs" and the flag is the opt-out.
|
|
178
215
|
.option("--no-logs", "Do not attach redacted tails of your ~/.hq log files (also: HQ_FEEDBACK_LOGS=0)")
|
|
179
216
|
.action(async (opts) => {
|
|
180
217
|
try {
|
|
218
|
+
if (opts.reporter !== undefined && (opts.reporter !== "agent" || type !== "bug")) {
|
|
219
|
+
throw new Error("Invalid --reporter value: only bug reports may use 'agent'.");
|
|
220
|
+
}
|
|
181
221
|
const token = await ensureCognitoToken({ interactive: false });
|
|
182
222
|
const body = await readBodyFile(opts.bodyFile);
|
|
183
223
|
validateFeedbackContent(opts.title, body);
|
|
@@ -192,6 +232,9 @@ function registerSubcommand(feedbackCmd, type) {
|
|
|
192
232
|
diagnostics.latestVersion = latestVersion;
|
|
193
233
|
}
|
|
194
234
|
const logsEnabled = opts.logs !== false && feedbackLogsEnabled();
|
|
235
|
+
const reporterMark = type === "bug" && resolveProcessFlagGate(FEEDBACK_REPORTER_KIND_FLAG, { fallback: FEEDBACK_REPORTER_KIND_FLAG_FALLBACK }, () => FEEDBACK_REPORTER_KIND_FLAG_FALLBACK)
|
|
236
|
+
? resolveFeedbackReporter(process.env, process.stdin.isTTY === true, opts.reporter)
|
|
237
|
+
: undefined;
|
|
195
238
|
// Validate the serialized server request envelope before screenshots
|
|
196
239
|
// or the log bundle can create remote objects. Attachment references
|
|
197
240
|
// are added and checked again inside submitFeedback after upload.
|
|
@@ -201,6 +244,7 @@ function registerSubcommand(feedbackCmd, type) {
|
|
|
201
244
|
body,
|
|
202
245
|
company: opts.company,
|
|
203
246
|
diagnostics,
|
|
247
|
+
reporterMark,
|
|
204
248
|
}));
|
|
205
249
|
// vaultApiFetch can reject an hqk_ token or an invalid API base URL
|
|
206
250
|
// locally before fetch. Check the mandatory POST, and the screenshot
|
|
@@ -236,6 +280,7 @@ function registerSubcommand(feedbackCmd, type) {
|
|
|
236
280
|
includeLogs: opts.logs !== false,
|
|
237
281
|
logBundle,
|
|
238
282
|
diagnostics,
|
|
283
|
+
reporterOverride: opts.reporter === "agent" ? "agent" : undefined,
|
|
239
284
|
logsEnabled,
|
|
240
285
|
});
|
|
241
286
|
console.log(`Submitted: ${result.id}`);
|
|
@@ -13,7 +13,7 @@ import { artifactIdentityKeys, rollbackStorySwitchAttribution, storySwitchNotice
|
|
|
13
13
|
import { laneRunDir, probePidRecord, readPidRecord, readProcStarttime, spawnLaneWorker } from "../lib/lanes/spawn.js";
|
|
14
14
|
import { findRetiredLaneEntry, keepAliveExclusion, keepAliveSupersededBy, LANE_RUNNING_SUPERSEDED_MESSAGE_DELIVERY_FLAG, LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, loadRetiredLaneIds } from "../lib/lanes/keep-alive.js";
|
|
15
15
|
import { isTerminalLaneState } from "../lib/lanes/types.js";
|
|
16
|
-
import { readLanesRegistryFlag } from "../lib/lanes/registry-flag.js";
|
|
16
|
+
import { readLanesRegistryFlag, readLanesRegistryFlagFailOpen } from "../lib/lanes/registry-flag.js";
|
|
17
17
|
import { appendLaneFocusOverride, focusGuardRefusal, makeFocusOverride } from "../lib/lanes/focus-guard.js";
|
|
18
18
|
import { appendProjectFocusOverride, removeProjectFocusOverride } from "../lib/lanes/story-ledger.js";
|
|
19
19
|
import { resolveLaneCompany } from "../lib/lanes/lane-company.js";
|
|
@@ -192,7 +192,7 @@ export function createLaneMessageHandlers(deps) {
|
|
|
192
192
|
: "superseded";
|
|
193
193
|
}
|
|
194
194
|
if (exclusion === "superseded" && currentLane.state !== "done" && currentLane.state !== "interrupted") {
|
|
195
|
-
const runningSupersededMessageDeliveryEnabled = await
|
|
195
|
+
const runningSupersededMessageDeliveryEnabled = await readLanesRegistryFlagFailOpen(LANE_RUNNING_SUPERSEDED_MESSAGE_DELIVERY_FLAG, { company: companyForLane(currentLane), env: ctx.env });
|
|
196
196
|
if (runningSupersededMessageDeliveryEnabled) {
|
|
197
197
|
const latestLane = readLane(ctx.hqRoot(), currentLane.lane_id);
|
|
198
198
|
if (latestLane.state === "killed" || latestLane.state === "cancelled") {
|
package/dist/commands/lanes.js
CHANGED
|
@@ -36,7 +36,7 @@ import { resolveLanesHqRoot } from "../lib/lanes/hq-root.js";
|
|
|
36
36
|
import { laneDepthCap } from "../lib/lanes/depth-cap.js";
|
|
37
37
|
import { isSsmRunCommandProcess } from "../lib/lanes/ssm-command-context.js";
|
|
38
38
|
import { runResidentCommandWithPackageLease } from "../utils/resident-package-preload.js";
|
|
39
|
-
import { readLanesFlagLocalOverride, readLanesRegistryFlag, readLanesRegistryFlagFailOff } from "../lib/lanes/registry-flag.js";
|
|
39
|
+
import { readLanesFlagLocalOverride, readLanesRegistryFlag, readLanesRegistryFlagFailOff, readLanesRegistryFlagFailOpen } from "../lib/lanes/registry-flag.js";
|
|
40
40
|
import { appendProjectFocusOverride, readProjectStoryFocusData, removeProjectFocusOverride } from "../lib/lanes/story-ledger.js";
|
|
41
41
|
import { appendLaneFocusOverride, assertFocusGuardAllows, makeFocusOverride } from "../lib/lanes/focus-guard.js";
|
|
42
42
|
import { LANE_FOCUS_GUARD_FLAG, LANE_FOCUS_DASHBOARD_FLAG, LANE_HIRE_GRANT_FINISHED_LANES_FLAG, LANE_LIST_SKIP_REMOTE_REFRESH_FLAG, LANE_MESSAGE_ROUTE_OWNER_HOST_FLAG, LANE_ORPHANED_BARE_ID_DROPBOX_REPAIR_FLAG, LANE_PROFILE_PIN_EXHAUSTED_FALLBACK_FLAG, LANE_REUSE_REFRESHES_PROFILE_FLAG, } from "../lib/lanes/flag-keys.js";
|
|
@@ -1898,7 +1898,7 @@ async function resolveMessageLaneTarget(ctx, laneId) {
|
|
|
1898
1898
|
const localOverride = readLanesFlagLocalOverride(LANE_MESSAGE_ROUTE_OWNER_HOST_FLAG, ctx.env);
|
|
1899
1899
|
return localOverride ?? "check-company";
|
|
1900
1900
|
}
|
|
1901
|
-
return
|
|
1901
|
+
return readLanesRegistryFlagFailOpen(LANE_MESSAGE_ROUTE_OWNER_HOST_FLAG, {
|
|
1902
1902
|
company,
|
|
1903
1903
|
env: ctx.env,
|
|
1904
1904
|
});
|
|
@@ -16,12 +16,16 @@ import * as os from "node:os";
|
|
|
16
16
|
import * as path from "node:path";
|
|
17
17
|
import { CLI_VERSION } from "../cli-version.js";
|
|
18
18
|
import { HQ_ANYWHERE_RUNTIME_FLAG } from "../lib/anywhere-runtime-flag-key.js";
|
|
19
|
-
import { anywhereRuntimeAllowed } from "../lib/anywhere-person-setting.js";
|
|
20
19
|
import { createLineSplitter, HqdClientError, hqdRequest, hqdSocketPath } from "../lib/daemon/protocol.js";
|
|
21
20
|
import { releaseCurrentPackageUseLease, releasePackageUseLeaseAfterResidentPreload } from "../utils/package-use-lease.js";
|
|
22
21
|
import { preloadResidentPackageModules, reportResidentPackagePreloadFailure, RESIDENT_PACKAGE_MODULES } from "../utils/resident-package-preload.js";
|
|
23
22
|
export const MCP_PROTOCOL_VERSIONS = ["2025-06-18", "2025-03-26", "2024-11-05"];
|
|
24
23
|
const UNREACHABLE_CODES = new Set(["ENOENT", "ECONNREFUSED", "ECONNRESET", "EPIPE", "ETIMEDOUT", "ESOCKET", "EACCES"]);
|
|
24
|
+
function hasPersonTokenCache(env, home) {
|
|
25
|
+
// Mirrors personTokenCacheFile() in utils/cognito-session.ts; duplicated so this path does not load that module.
|
|
26
|
+
const stateDir = env.HQ_STATE_DIR?.trim() || path.join(home, ".hq");
|
|
27
|
+
return fs.existsSync(path.join(stateDir, "cognito-tokens.json"));
|
|
28
|
+
}
|
|
25
29
|
/** HQ root for prompts: HQ_ROOT, else the ~/.hq/root pointer written by `hq install --global`. */
|
|
26
30
|
export function readHqRoot(env, home) {
|
|
27
31
|
const fromEnv = env.HQ_ROOT?.trim();
|
|
@@ -54,9 +58,18 @@ export async function runMcpServe(opts = {}) {
|
|
|
54
58
|
const cwd = opts.cwd ?? process.cwd();
|
|
55
59
|
const socketPath = hqdSocketPath(home, env);
|
|
56
60
|
const timeoutMs = opts.timeoutMs ?? 120_000;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
const runtimeAllowed = opts.runtimeAllowed ?? (async () => {
|
|
62
|
+
// A person token is required to read the opt-in setting. Deny a fresh
|
|
63
|
+
// install before loading the hq-flags and Cognito readers.
|
|
64
|
+
if (!hasPersonTokenCache(env, home))
|
|
65
|
+
return false;
|
|
66
|
+
const { anywhereRuntimeAllowed } = await import("../lib/anywhere-person-setting.js");
|
|
67
|
+
return anywhereRuntimeAllowed(env, {
|
|
68
|
+
home,
|
|
69
|
+
onError: (name) => process.stderr.write(`HQ Anywhere person setting unavailable (${name}); access remains off.\n`),
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
if (!(await runtimeAllowed())) {
|
|
60
73
|
process.stderr.write(`HQ Anywhere is not enabled. The ${HQ_ANYWHERE_RUNTIME_FLAG} flag and your HQ Anywhere person setting must both be on.\n`);
|
|
61
74
|
process.exitCode = 1;
|
|
62
75
|
return;
|
|
@@ -6,6 +6,7 @@ export interface ReadHqAnywherePersonSettingOptions {
|
|
|
6
6
|
fetchImpl?: typeof fetch;
|
|
7
7
|
apiBaseUrl?: string;
|
|
8
8
|
env?: NodeJS.ProcessEnv;
|
|
9
|
+
home?: string;
|
|
9
10
|
onError?: (errorName: string) => void;
|
|
10
11
|
}
|
|
11
12
|
/** Returns null when the person preference cannot be read; callers must fail closed. */
|
|
@@ -1,8 +1,20 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import * as path from "node:path";
|
|
1
4
|
import { anywhereRuntimeEnabledFromSnapshot } from "./anywhere-runtime-fast-flag.js";
|
|
5
|
+
function hasPersonTokenCache(env, home) {
|
|
6
|
+
// Mirrors personTokenCacheFile() in utils/cognito-session.ts; duplicated so this path does not load that module.
|
|
7
|
+
const stateDir = env.HQ_STATE_DIR?.trim() || path.join(home, ".hq");
|
|
8
|
+
return fs.existsSync(path.join(stateDir, "cognito-tokens.json"));
|
|
9
|
+
}
|
|
2
10
|
/** Returns null when the person preference cannot be read; callers must fail closed. */
|
|
3
11
|
export async function readHqAnywherePersonSetting(options = {}) {
|
|
4
12
|
const env = options.env ?? process.env;
|
|
5
13
|
try {
|
|
14
|
+
// Avoid loading the Cognito SDK on the common fresh-install path. There is
|
|
15
|
+
// no person token to refresh or use until a cache exists.
|
|
16
|
+
if (!options.getPersonToken && !hasPersonTokenCache(env, options.home ?? env.HOME ?? os.homedir()))
|
|
17
|
+
return null;
|
|
6
18
|
const getPersonToken = options.getPersonToken ?? (async () => {
|
|
7
19
|
const { ensureCognitoToken } = await import("../utils/cognito-session.js");
|
|
8
20
|
return ensureCognitoToken({ interactive: false, tokenSource: "person" });
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { readLocalOverride, resolveFlag } from "@indigoai-us/hq-flags-client";
|
|
2
2
|
import { resolveProcessFlagCompanyContext } from "./process-flag-company-context.js";
|
|
3
3
|
import { readFlagSnapshotFile, readPersonFlagSnapshotFile } from "./flag-snapshot-file.js";
|
|
4
|
-
import { HQ_ANYWHERE_RUNTIME_FLAG } from "./anywhere-runtime-flag-key.js";
|
|
4
|
+
import { HQ_ANYWHERE_RUNTIME_FLAG, hqAnywhereRuntimeLocallyDisabled } from "./anywhere-runtime-flag-key.js";
|
|
5
5
|
/** Resolve the Anywhere gate from hq-flags without loading the CLI's large worker bundle. */
|
|
6
6
|
export function anywhereRuntimeEnabledFromSnapshot(env = process.env) {
|
|
7
|
+
if (hqAnywhereRuntimeLocallyDisabled(env))
|
|
8
|
+
return false;
|
|
7
9
|
const local = readLocalOverride(HQ_ANYWHERE_RUNTIME_FLAG, env);
|
|
8
10
|
if (local !== null)
|
|
9
11
|
return local;
|
|
@@ -17,13 +19,13 @@ export function anywhereRuntimeEnabledFromSnapshot(env = process.env) {
|
|
|
17
19
|
const snapshot = persisted
|
|
18
20
|
? { version: 0, flags: persisted.flags }
|
|
19
21
|
: null;
|
|
20
|
-
//
|
|
22
|
+
// The flag is a fail-safe kill switch: unknown registry state keeps HQ Anywhere enabled.
|
|
21
23
|
return resolveFlag({
|
|
22
24
|
flagKey: HQ_ANYWHERE_RUNTIME_FLAG,
|
|
23
25
|
env,
|
|
24
26
|
snapshot,
|
|
25
27
|
companyIdentifiers: [company.companyUid, company.companySlug].filter((value) => Boolean(value)),
|
|
26
|
-
lookup: { fallback:
|
|
28
|
+
lookup: { fallback: true },
|
|
27
29
|
}).value;
|
|
28
30
|
}
|
|
29
31
|
//# sourceMappingURL=anywhere-runtime-fast-flag.js.map
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
/** hq-flags key for the staged HQ Anywhere runtime. The registry default is
|
|
1
|
+
/** hq-flags key for the staged HQ Anywhere runtime. The registry default is on. */
|
|
2
2
|
export declare const HQ_ANYWHERE_RUNTIME_FLAG = "hq-anywhere-runtime";
|
|
3
|
+
/** The HQ Anywhere environment kill switch stays compatible with managed provider wrappers. */
|
|
4
|
+
export declare function hqAnywhereRuntimeLocallyDisabled(env?: NodeJS.ProcessEnv): boolean;
|
|
3
5
|
//# sourceMappingURL=anywhere-runtime-flag-key.d.ts.map
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
-
/** hq-flags key for the staged HQ Anywhere runtime. The registry default is
|
|
1
|
+
/** hq-flags key for the staged HQ Anywhere runtime. The registry default is on. */
|
|
2
2
|
export const HQ_ANYWHERE_RUNTIME_FLAG = "hq-anywhere-runtime";
|
|
3
|
+
/** The HQ Anywhere environment kill switch stays compatible with managed provider wrappers. */
|
|
4
|
+
export function hqAnywhereRuntimeLocallyDisabled(env = process.env) {
|
|
5
|
+
const value = env.HQ_FLAG_HQ_ANYWHERE_RUNTIME;
|
|
6
|
+
return value === "false" || value === "0";
|
|
7
|
+
}
|
|
3
8
|
//# sourceMappingURL=anywhere-runtime-flag-key.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Feature flags read by the daemon. The daemon runs for weeks, so each read
|
|
3
3
|
* asks the registry again instead of holding a per-process answer, and it
|
|
4
|
-
* reads as
|
|
4
|
+
* reads as each flag's fallback unless the registry or a local override decides it.
|
|
5
5
|
*/
|
|
6
6
|
import { type FlagClientOptions, type FlagResolution } from "@indigoai-us/hq-flags-client";
|
|
7
7
|
/** Outposts hand running services to the next daemon on a CLI update instead of restarting them all. */
|