@indigoai-us/hq-cli 5.345.165 → 5.345.168
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 +13 -0
- package/assets/scaffold/core/scripts/hq-delegate-send.sh +1 -1
- package/assets/scaffold/core/scripts/rebuild-all-indexes.sh +11 -0
- package/assets/scaffold/core/scripts/rebuild-orchestrator-index.sh +11 -0
- package/assets/scaffold/core/scripts/rebuild-public-knowledge-index.sh +11 -0
- package/assets/scaffold/core/scripts/rebuild-reports-index.sh +11 -0
- package/assets/scaffold/core/scripts/rebuild-social-drafts-index.sh +11 -0
- package/assets/scaffold/core/scripts/rebuild-threads-index.sh +11 -0
- package/assets/scaffold/core/scripts/rebuild-workers-index.sh +11 -0
- package/dist/command-catalog.generated.d.ts +6 -2
- package/dist/command-catalog.generated.js +7 -2
- package/dist/commands/access.js +2 -0
- package/dist/commands/agents.js +2 -2
- package/dist/commands/core-delegate-w1b.js +2 -1
- package/dist/commands/core-dm-bind-native.js +3 -1
- package/dist/commands/dm.d.ts +13 -2
- package/dist/commands/dm.js +42 -9
- package/dist/commands/integrations-api.d.ts +15 -0
- package/dist/commands/integrations-api.js +27 -0
- package/dist/commands/integrations-manage.js +4 -2
- package/dist/commands/integrations.js +9 -5
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +210 -197
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/lib/agent-kit/mcp/tools.js +5 -6
- package/dist/lib/doctor/checks/sync-runtime.d.ts +7 -2
- package/dist/lib/doctor/checks/sync-runtime.js +1 -3
- package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- package/dist/lib/index-render/index.d.ts +5 -0
- package/dist/lib/index-render/index.js +22 -1
- package/dist/lib/notify-recipient.d.ts +15 -0
- package/dist/lib/notify-recipient.js +15 -0
- package/dist/lib/secret-scan/repo-config.d.ts +8 -2
- package/dist/lib/secret-scan/repo-config.js +12 -6
- package/dist/lib/secret-scan/staged.d.ts +6 -0
- package/dist/lib/secret-scan/staged.js +21 -3
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.345.168] — 2026-10-24
|
|
6
|
+
- `hq dm` now picks the default audience from the recipient when no audience flag is passed. A 1:1 DM to a person (email, `prs_` uid, or a bare name that resolves to a person) sends `audience: "human"`. A 1:1 DM to an agent (`agt_` uid), a group DM and a channel post send `audience: "agent"`, so script and lane messages stay out of people's conversations. Scheduled sends (`--at`, `--in`) follow the same rule. Pass `--for-human` (alias for `--audience human`) to tag any message for people; an explicit `--audience human|agent|both` wins over `--for-human`, with a warning on stderr, and is validated on every path. `hq core delegate send` tags the pickup DM `audience: "both"`, `hq dm-bind post` keeps the channel default (`audience: "agent"`) for its automated status posts, and the agent MCP `hq_dm_send` tool, `hq agents message` and the `hq access` grant request apply the recipient rule. Reading commands (inbox, thread, channel) are unchanged, and `hq dm forward` does not set an audience.
|
|
7
|
+
- `hq scan secrets` no longer exits 2 when a repo's `.gitleaks.toml` allowlist is rejected. In staged and file scans it now prints a fixed warning that quotes no part of the file and scans with no repo allowlist, so a real key is still reported (exit 1) and a commit that replaces a bad allowlist is not blocked. An unknown `--allowlist-rev` still exits 2.
|
|
8
|
+
|
|
9
|
+
## [5.345.166] — 2026-10-24
|
|
10
|
+
- `hq integrations approve` now says why an approved call did not run. When an app refuses it, the CLI prints the app's own reason, such as Slack's refusal to send into a Slack Connect channel. Before this it printed only "failed (rejected)". With an older server it prints the rejection code instead. `hq integrations pending --history` adds a `Why:` line for decided calls that did not run. The approve command printed after a queued call keeps `--company` when you passed it. Before printing, the reason is cleaned of terminal escape codes, credentials and credential-bearing links, and it is capped at 500 characters. Feedback 15e7593c-50e5-4e6a-ae3a-342d618363c0.
|
|
11
|
+
- The bundled sync engine now uses hq-cloud 6.18.101, the current npm `latest`.
|
|
12
|
+
A 403 from the flags endpoint (for example a stale company in the local
|
|
13
|
+
manifest) is now held as `false` for an hour instead of being re-read and
|
|
14
|
+
logged on every telemetry pass (hq-cloud #1046).
|
|
15
|
+
- `hq doctor` no longer counts runner telemetry errors as sync receiver permission refusals. A failed outcome-flag read ("hq-flags: resolve returned 403") for a company you are not a member of used to inflate `sync.runtime.receiver-permission`; only the receiver's own events are counted now.
|
|
16
|
+
- The bundled shell fallbacks for `rebuild-index` all, orchestrator, public-knowledge, reports, social-drafts, threads, and workers now print the same usage as the native command for `--help`/`-h` and exit without writing. Before, they ignored the flag and rebuilt the index, which failed the published core command parity check.
|
|
17
|
+
|
|
5
18
|
## [5.345.165] — 2026-10-23
|
|
6
19
|
- `hq billing sponsor grant <email|prs_id> --company <slug>` lets a company owner or admin put a member's personal plan on the company bill without a request first; `--all-members` does it for every active member and reports each person as covered, already covered, or skipped. `hq billing sponsor ungrant <email|prs_id>` takes it off again. hq-pro checks the owner/admin role and active membership.
|
|
7
20
|
- `hq scan secrets <files...>` takes `--repo <dir>`, which applies that repo's committed `.gitleaks.toml` allowlists to a file scan, matching each file by its path relative to the repo, as a staged scan does. `--snapshot-root <dir>` (with `--repo`) matches files laid out as `<dir>/<commit>/<repo path>`, the history copies `commit-secret-scan.sh pr-check` writes, as `<repo path>`. Findings still name the files as given. Before this, a file scan ignored the repo's allowlists, so pr-check failed on false positives the commit gate already allowed. Without `--repo` a file scan is unchanged.
|
|
@@ -283,7 +283,7 @@ if ! jq '.status = "sending"' "$MANIFEST" > "$TMP_MANIFEST" || ! mv "$TMP_MANIFE
|
|
|
283
283
|
rm -f "$TMP_MANIFEST"
|
|
284
284
|
die "could not persist sending state; no DM attempted"
|
|
285
285
|
fi
|
|
286
|
-
DM_OUT="$(hq dm "$PRINCIPAL" "$HEADLINE" --prompt-file "$PROMPT" --details-file "$BRIEF")" \
|
|
286
|
+
DM_OUT="$(hq dm "$PRINCIPAL" "$HEADLINE" --audience both --prompt-file "$PROMPT" --details-file "$BRIEF")" \
|
|
287
287
|
|| die "hq dm delivery outcome is uncertain; status is sending, inspect DM history before retrying"
|
|
288
288
|
|
|
289
289
|
EVENT_ID="$(printf '%s' "$DM_OUT" | grep -oE 'eventId[: ]+[A-Za-z0-9_-]+' | head -1 | sed -E 's/eventId[: ]+//' || true)"
|
|
@@ -11,6 +11,17 @@
|
|
|
11
11
|
|
|
12
12
|
set -uo pipefail
|
|
13
13
|
|
|
14
|
+
# Help must never write: print the same usage as `hq core rebuild-index all --help`.
|
|
15
|
+
for arg in "$@"; do
|
|
16
|
+
if [[ "$arg" == "--help" || "$arg" == "-h" ]]; then
|
|
17
|
+
printf '%s\n' \
|
|
18
|
+
'Usage: hq core rebuild-index all [--accept]' \
|
|
19
|
+
'' \
|
|
20
|
+
'Regenerate every INDEX.md across the HQ tree. Help performs no writes.'
|
|
21
|
+
exit 0
|
|
22
|
+
fi
|
|
23
|
+
done
|
|
24
|
+
|
|
14
25
|
HQ_ROOT="${HQ_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
|
|
15
26
|
cd "$HQ_ROOT"
|
|
16
27
|
|
|
@@ -12,6 +12,17 @@
|
|
|
12
12
|
|
|
13
13
|
set -euo pipefail
|
|
14
14
|
|
|
15
|
+
# Help must never write: print the same usage as `hq core rebuild-index orchestrator --help`.
|
|
16
|
+
for arg in "$@"; do
|
|
17
|
+
if [[ "$arg" == "--help" || "$arg" == "-h" ]]; then
|
|
18
|
+
printf '%s\n' \
|
|
19
|
+
'Usage: hq core rebuild-index orchestrator [--accept]' \
|
|
20
|
+
'' \
|
|
21
|
+
'Regenerate the orchestrator workspace INDEX.md. Help performs no writes.'
|
|
22
|
+
exit 0
|
|
23
|
+
fi
|
|
24
|
+
done
|
|
25
|
+
|
|
15
26
|
HQ_ROOT="${HQ_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
|
|
16
27
|
cd "$HQ_ROOT"
|
|
17
28
|
|
|
@@ -7,6 +7,17 @@
|
|
|
7
7
|
|
|
8
8
|
set -euo pipefail
|
|
9
9
|
|
|
10
|
+
# Help must never write: print the same usage as `hq core rebuild-index public-knowledge --help`.
|
|
11
|
+
for arg in "$@"; do
|
|
12
|
+
if [[ "$arg" == "--help" || "$arg" == "-h" ]]; then
|
|
13
|
+
printf '%s\n' \
|
|
14
|
+
'Usage: hq core rebuild-index public-knowledge [--accept]' \
|
|
15
|
+
'' \
|
|
16
|
+
'Regenerate core/knowledge/public/INDEX.md. Help performs no writes.'
|
|
17
|
+
exit 0
|
|
18
|
+
fi
|
|
19
|
+
done
|
|
20
|
+
|
|
10
21
|
HQ_ROOT="${HQ_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
|
|
11
22
|
cd "$HQ_ROOT"
|
|
12
23
|
|
|
@@ -8,6 +8,17 @@
|
|
|
8
8
|
|
|
9
9
|
set -euo pipefail
|
|
10
10
|
|
|
11
|
+
# Help must never write: print the same usage as `hq core rebuild-index reports --help`.
|
|
12
|
+
for arg in "$@"; do
|
|
13
|
+
if [[ "$arg" == "--help" || "$arg" == "-h" ]]; then
|
|
14
|
+
printf '%s\n' \
|
|
15
|
+
'Usage: hq core rebuild-index reports [--accept]' \
|
|
16
|
+
'' \
|
|
17
|
+
'Regenerate the reports INDEX.md. Help performs no writes.'
|
|
18
|
+
exit 0
|
|
19
|
+
fi
|
|
20
|
+
done
|
|
21
|
+
|
|
11
22
|
HQ_ROOT="${HQ_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
|
|
12
23
|
cd "$HQ_ROOT"
|
|
13
24
|
|
|
@@ -8,6 +8,17 @@
|
|
|
8
8
|
|
|
9
9
|
set -euo pipefail
|
|
10
10
|
|
|
11
|
+
# Help must never write: print the same usage as `hq core rebuild-index social-drafts --help`.
|
|
12
|
+
for arg in "$@"; do
|
|
13
|
+
if [[ "$arg" == "--help" || "$arg" == "-h" ]]; then
|
|
14
|
+
printf '%s\n' \
|
|
15
|
+
'Usage: hq core rebuild-index social-drafts [--accept]' \
|
|
16
|
+
'' \
|
|
17
|
+
'Regenerate workspace/social-drafts/INDEX.md. Help performs no writes.'
|
|
18
|
+
exit 0
|
|
19
|
+
fi
|
|
20
|
+
done
|
|
21
|
+
|
|
11
22
|
HQ_ROOT="${HQ_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
|
|
12
23
|
# shellcheck source=core/scripts/lib/portable.sh
|
|
13
24
|
. "$HQ_ROOT/core/scripts/lib/portable.sh"
|
|
@@ -11,6 +11,17 @@
|
|
|
11
11
|
|
|
12
12
|
set -euo pipefail
|
|
13
13
|
|
|
14
|
+
# Help must never write: print the same usage as `hq core rebuild-index threads --help`.
|
|
15
|
+
for arg in "$@"; do
|
|
16
|
+
if [[ "$arg" == "--help" || "$arg" == "-h" ]]; then
|
|
17
|
+
printf '%s\n' \
|
|
18
|
+
'Usage: hq core rebuild-index threads [--accept]' \
|
|
19
|
+
'' \
|
|
20
|
+
'Regenerate the session-threads INDEX.md. Help performs no writes.'
|
|
21
|
+
exit 0
|
|
22
|
+
fi
|
|
23
|
+
done
|
|
24
|
+
|
|
14
25
|
HQ_ROOT="${HQ_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
|
|
15
26
|
cd "$HQ_ROOT"
|
|
16
27
|
|
|
@@ -7,6 +7,17 @@
|
|
|
7
7
|
|
|
8
8
|
set -euo pipefail
|
|
9
9
|
|
|
10
|
+
# Help must never write: print the same usage as `hq core rebuild-index workers --help`.
|
|
11
|
+
for arg in "$@"; do
|
|
12
|
+
if [[ "$arg" == "--help" || "$arg" == "-h" ]]; then
|
|
13
|
+
printf '%s\n' \
|
|
14
|
+
'Usage: hq core rebuild-index workers [--accept]' \
|
|
15
|
+
'' \
|
|
16
|
+
'Regenerate the workers INDEX.md. Help performs no writes.'
|
|
17
|
+
exit 0
|
|
18
|
+
fi
|
|
19
|
+
done
|
|
20
|
+
|
|
10
21
|
HQ_ROOT="${HQ_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
|
|
11
22
|
cd "$HQ_ROOT"
|
|
12
23
|
|
|
@@ -3987,7 +3987,7 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
3987
3987
|
}];
|
|
3988
3988
|
}, {
|
|
3989
3989
|
readonly name: "dm";
|
|
3990
|
-
readonly description: "Send and read direct messages, and manage connection requests.";
|
|
3990
|
+
readonly description: "Send and read direct messages, and manage connection requests. Messages to a person send to them; messages to an agent, a channel or a group are tagged for agents and folded away from people by default. Pass --for-human or --audience human|agent|both to override.";
|
|
3991
3991
|
readonly aliases: readonly [];
|
|
3992
3992
|
readonly hidden: false;
|
|
3993
3993
|
readonly helpOption: true;
|
|
@@ -4054,7 +4054,11 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
4054
4054
|
readonly hidden: false;
|
|
4055
4055
|
}, {
|
|
4056
4056
|
readonly flags: "--audience <human|agent|both>";
|
|
4057
|
-
readonly description: "Tag who this message is for: human (a person must read it), agent (progress/status, hidden from humans by default), or both";
|
|
4057
|
+
readonly description: "Tag who this message is for: human (a person must read it), agent (progress/status, hidden from humans by default), or both. Default: human for a person, agent for an agent, a channel or a group. Wins over --for-human.";
|
|
4058
|
+
readonly hidden: false;
|
|
4059
|
+
}, {
|
|
4060
|
+
readonly flags: "--for-human";
|
|
4061
|
+
readonly description: "Tag the message for people (alias for --audience human).";
|
|
4058
4062
|
readonly hidden: false;
|
|
4059
4063
|
}, {
|
|
4060
4064
|
readonly flags: "--attach <path>";
|
|
@@ -4827,7 +4827,7 @@ export const COMMAND_CATALOG = [
|
|
|
4827
4827
|
},
|
|
4828
4828
|
{
|
|
4829
4829
|
"name": "dm",
|
|
4830
|
-
"description": "Send and read direct messages, and manage connection requests.",
|
|
4830
|
+
"description": "Send and read direct messages, and manage connection requests. Messages to a person send to them; messages to an agent, a channel or a group are tagged for agents and folded away from people by default. Pass --for-human or --audience human|agent|both to override.",
|
|
4831
4831
|
"aliases": [],
|
|
4832
4832
|
"hidden": false,
|
|
4833
4833
|
"helpOption": true,
|
|
@@ -4908,7 +4908,12 @@ export const COMMAND_CATALOG = [
|
|
|
4908
4908
|
},
|
|
4909
4909
|
{
|
|
4910
4910
|
"flags": "--audience <human|agent|both>",
|
|
4911
|
-
"description": "Tag who this message is for: human (a person must read it), agent (progress/status, hidden from humans by default), or both",
|
|
4911
|
+
"description": "Tag who this message is for: human (a person must read it), agent (progress/status, hidden from humans by default), or both. Default: human for a person, agent for an agent, a channel or a group. Wins over --for-human.",
|
|
4912
|
+
"hidden": false
|
|
4913
|
+
},
|
|
4914
|
+
{
|
|
4915
|
+
"flags": "--for-human",
|
|
4916
|
+
"description": "Tag the message for people (alias for --audience human).",
|
|
4912
4917
|
"hidden": false
|
|
4913
4918
|
},
|
|
4914
4919
|
{
|
package/dist/commands/access.js
CHANGED
|
@@ -25,6 +25,7 @@ import { listJournals, syncDoctor, } from "@indigoai-us/hq-cloud";
|
|
|
25
25
|
import { listActiveMembers } from "./members.js";
|
|
26
26
|
import { aclLockedBy } from "./acl-lock.js";
|
|
27
27
|
import { buildDmBody } from "./dm.js";
|
|
28
|
+
import { defaultAudienceFor, recipientKindOf } from "../lib/notify-recipient.js";
|
|
28
29
|
import { peekIdToken } from "../utils/id-token.js";
|
|
29
30
|
import { findRecentAccessRequest, formatTimeAgo, recordAccessRequest, } from "../utils/access-requests.js";
|
|
30
31
|
import { recordAccessOutcome } from "../utils/access-outcomes.js";
|
|
@@ -1155,6 +1156,7 @@ async function buildLiveAccessDeps(slug, hqRoot) {
|
|
|
1155
1156
|
prompt: msg.prompt,
|
|
1156
1157
|
details: msg.details,
|
|
1157
1158
|
now: Date.now(),
|
|
1159
|
+
audience: defaultAudienceFor(recipientKindOf(msg.recipient)),
|
|
1158
1160
|
});
|
|
1159
1161
|
const res = await vaultApiFetch({
|
|
1160
1162
|
token,
|
package/dist/commands/agents.js
CHANGED
|
@@ -42,7 +42,7 @@ import { consoleOrigin } from "./integrations-connect.js";
|
|
|
42
42
|
import { launchSessionManagerPlugin, SessionManagerPluginLaunchError, TerminalSessionTimeoutError, } from "@indigoai-us/hq-cloud/outposts/node";
|
|
43
43
|
import { peekIdToken } from "../utils/id-token.js";
|
|
44
44
|
import { canOfferTeamUpgrade, formatUpgradeRefusal, isPlanGateError, offerUpgradeLink, } from "../utils/plan-gate-error.js";
|
|
45
|
-
import { AGENT_UID_PATTERN, isAgentUid } from "../lib/notify-recipient.js";
|
|
45
|
+
import { AGENT_UID_PATTERN, defaultAudienceFor, isAgentUid } from "../lib/notify-recipient.js";
|
|
46
46
|
import { AGENT_MODEL_PROVIDERS, modelSelectionErrorMessage, readAgentModelCatalog, validateAgentModelSelection, } from "../lib/agents/model-catalog.js";
|
|
47
47
|
import { fetchCompanyPricingPlan, isUsageOnly, usageOnlyBoxSentence, usageOnlyCardRequiredLines, } from "../lib/billing/usage-only.js";
|
|
48
48
|
import { BUNDLED_USAGE_CARD, fetchUsageCard, } from "../lib/billing/usage-card.js";
|
|
@@ -1139,7 +1139,7 @@ export async function sendAgentDm(token, agentUid, message) {
|
|
|
1139
1139
|
token,
|
|
1140
1140
|
path: "/v1/notify/dm",
|
|
1141
1141
|
method: "POST",
|
|
1142
|
-
body: { toPersonUid: agentUid, body: message },
|
|
1142
|
+
body: { toPersonUid: agentUid, body: message, audience: defaultAudienceFor("agent") },
|
|
1143
1143
|
});
|
|
1144
1144
|
}
|
|
1145
1145
|
/** Read the two-way DM conversation with an agent (most recent `limit`). */
|
|
@@ -1079,7 +1079,8 @@ function runSend(args, context) {
|
|
|
1079
1079
|
catch {
|
|
1080
1080
|
return fail("send", "could not persist sending state; no DM attempted");
|
|
1081
1081
|
}
|
|
1082
|
-
|
|
1082
|
+
// The pickup DM is read by the person and acted on by their agent.
|
|
1083
|
+
const dm = runHq(["dm", principal, headline, "--audience", "both", "--prompt-file", prompt, "--details-file", brief], context);
|
|
1083
1084
|
if (dm.code !== 0)
|
|
1084
1085
|
return fail("send", "hq dm delivery outcome is uncertain; status is sending, inspect DM history before retrying");
|
|
1085
1086
|
const event = /eventId[: ]+([A-Za-z0-9_-]+)/.exec(dm.stdout ?? "")?.[1] ?? "";
|
|
@@ -579,6 +579,8 @@ async function post(d, args) {
|
|
|
579
579
|
const versionError = requireCliVersion(d);
|
|
580
580
|
if (versionError)
|
|
581
581
|
return versionError;
|
|
582
|
+
// An automated status post keeps the channel default (agent audience); only a
|
|
583
|
+
// reply to a message a person wrote is for humans.
|
|
582
584
|
const call = ["dm", ch, ...(root ? ["--thread", root] : []), cliMessage];
|
|
583
585
|
const sent = d.runHq(call, { ...d.env, HQ_ROOT: d.root });
|
|
584
586
|
if (sent.code !== 0)
|
|
@@ -594,7 +596,7 @@ async function post(d, args) {
|
|
|
594
596
|
const mentions = mentionsJson(d, sid, names);
|
|
595
597
|
if (!mentions.length)
|
|
596
598
|
return fail("no mention resolved to a channel member id — not posting an update that notifies nobody");
|
|
597
|
-
const payload = { body: postedMessage, mentions };
|
|
599
|
+
const payload = { body: postedMessage, mentions, audience: "agent" };
|
|
598
600
|
if (root)
|
|
599
601
|
payload.rootEventId = root;
|
|
600
602
|
try {
|
package/dist/commands/dm.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
+
import { defaultAudienceFor, recipientKindOf, type DmRecipientKind } from "../lib/notify-recipient.js";
|
|
3
|
+
export { defaultAudienceFor, recipientKindOf, type DmRecipientKind };
|
|
2
4
|
import { type DmAttachmentWire } from "./dm-attach.js";
|
|
3
5
|
export interface DmRecipient {
|
|
4
6
|
toEmail?: string;
|
|
@@ -158,7 +160,8 @@ export declare function resolveMentions(tokens: string[], members: ChannelMember
|
|
|
158
160
|
* Returns null on anything that doesn't match. Pure → unit-testable.
|
|
159
161
|
*/
|
|
160
162
|
export declare function parseDuration(input: string): number | null;
|
|
161
|
-
type DmAudience
|
|
163
|
+
import type { DmAudience } from "../lib/notify-recipient.js";
|
|
164
|
+
export type { DmAudience };
|
|
162
165
|
export interface DmSendBody {
|
|
163
166
|
toEmail?: string;
|
|
164
167
|
toPersonUid?: string;
|
|
@@ -233,6 +236,15 @@ export declare function fetchChannels(token: string): Promise<ChannelSummary[]>;
|
|
|
233
236
|
* post itself passes. Used to resolve `@name` mentions into participant uids.
|
|
234
237
|
*/
|
|
235
238
|
export declare function fetchChannelMembers(token: string, channelId: string): Promise<ChannelMember[]>;
|
|
239
|
+
/**
|
|
240
|
+
* Resolve the audience for a send. The default comes from the recipient kind
|
|
241
|
+
* (`defaultAudienceFor`); `--for-human` maps to `human`; an explicit
|
|
242
|
+
* `--audience` wins over `--for-human` (with a warning) and is validated.
|
|
243
|
+
*/
|
|
244
|
+
export declare function resolveSendAudience(opts: {
|
|
245
|
+
audience?: string;
|
|
246
|
+
forHuman?: boolean;
|
|
247
|
+
}, recipientKind: DmRecipientKind, warn?: (msg: string) => void): string;
|
|
236
248
|
/** One incoming DM as returned by GET /v1/notify/inbox. */
|
|
237
249
|
export interface DmInboxEvent {
|
|
238
250
|
eventId: string;
|
|
@@ -411,5 +423,4 @@ export declare function formatChannelTranscript(messages: ChannelMessageItem[],
|
|
|
411
423
|
export declare function agentCallerUid(token: string, machine?: () => boolean): string | null;
|
|
412
424
|
export declare const AGENT_THREAD_UNAVAILABLE: string;
|
|
413
425
|
export declare function registerDmCommand(program: Command): void;
|
|
414
|
-
export {};
|
|
415
426
|
//# sourceMappingURL=dm.d.ts.map
|
package/dist/commands/dm.js
CHANGED
|
@@ -5,7 +5,8 @@ import { resolveDestinationCompanyUid, vaultApiFetch } from "../utils/vault-api.
|
|
|
5
5
|
import { isMachineIdentity } from "@indigoai-us/hq-cloud";
|
|
6
6
|
import { peekIdToken } from "../utils/id-token.js";
|
|
7
7
|
import { redactAgentInboxItem as redactInboxCredentials } from "../lib/agent-kit/inbox-redact.js";
|
|
8
|
-
import { EMAIL_PATTERN, RECIPIENT_UID_PATTERN } from "../lib/notify-recipient.js";
|
|
8
|
+
import { EMAIL_PATTERN, RECIPIENT_UID_PATTERN, defaultAudienceFor, recipientKindOf, } from "../lib/notify-recipient.js";
|
|
9
|
+
export { defaultAudienceFor, recipientKindOf };
|
|
9
10
|
import { dmForwardCliEnabled } from "./dm-forward-flag.js";
|
|
10
11
|
import { attachmentScopeForChannel, formatUploadedAttachments, personalScopeForCaller, uploadDmAttachments, validateAttachmentPaths, } from "./dm-attach.js";
|
|
11
12
|
import { ChannelNotFoundError, suggestChannels, withChannelSuggestions, } from "./channel-suggest.js";
|
|
@@ -555,7 +556,7 @@ async function runConnectionAction(action, identifier) {
|
|
|
555
556
|
* the server's idempotent participant-key dedupe) a group channel, then posts
|
|
556
557
|
* the message into it. Reuses the same vault API client + auth as the 1:1 path.
|
|
557
558
|
*/
|
|
558
|
-
async function runGroupSend(recipients, message) {
|
|
559
|
+
async function runGroupSend(recipients, message, audience) {
|
|
559
560
|
try {
|
|
560
561
|
const participants = [];
|
|
561
562
|
for (const r of recipients) {
|
|
@@ -601,7 +602,7 @@ async function runGroupSend(recipients, message) {
|
|
|
601
602
|
token,
|
|
602
603
|
path: `/v1/notify/channels/${encodeURIComponent(channelId)}/messages`,
|
|
603
604
|
method: "POST",
|
|
604
|
-
body: { body },
|
|
605
|
+
body: { body, ...(audience ? { audience } : {}) },
|
|
605
606
|
});
|
|
606
607
|
if (!sendRes.ok) {
|
|
607
608
|
const err = (await sendRes.json().catch(() => ({})));
|
|
@@ -810,11 +811,41 @@ async function runChannelSend(channelName, message, opts) {
|
|
|
810
811
|
process.exit(1);
|
|
811
812
|
}
|
|
812
813
|
}
|
|
813
|
-
|
|
814
|
+
function explicitAudience(opts) {
|
|
815
|
+
const explicit = opts.audience !== undefined && opts.audience !== "" ? opts.audience : undefined;
|
|
816
|
+
if (explicit !== undefined && !DM_AUDIENCE_VALUES.includes(explicit)) {
|
|
817
|
+
throw new Error("Invalid --audience '" + explicit + "': must be human, agent, or both.");
|
|
818
|
+
}
|
|
819
|
+
return explicit;
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Resolve the audience for a send. The default comes from the recipient kind
|
|
823
|
+
* (`defaultAudienceFor`); `--for-human` maps to `human`; an explicit
|
|
824
|
+
* `--audience` wins over `--for-human` (with a warning) and is validated.
|
|
825
|
+
*/
|
|
826
|
+
export function resolveSendAudience(opts, recipientKind, warn = (m) => console.error(chalk.yellow(m))) {
|
|
827
|
+
const explicit = explicitAudience(opts);
|
|
828
|
+
if (explicit !== undefined) {
|
|
829
|
+
if (opts.forHuman) {
|
|
830
|
+
warn("Both --audience " + explicit + " and --for-human were passed; using --audience " + explicit + ".");
|
|
831
|
+
}
|
|
832
|
+
return explicit;
|
|
833
|
+
}
|
|
834
|
+
return opts.forHuman ? "human" : defaultAudienceFor(recipientKind);
|
|
835
|
+
}
|
|
836
|
+
async function runDmSend(recipient, message, rawOpts) {
|
|
837
|
+
// Validate an explicit --audience before any network call; the default is
|
|
838
|
+
// applied once the recipient kind is known.
|
|
839
|
+
explicitAudience(rawOpts);
|
|
840
|
+
const opts = { ...rawOpts };
|
|
841
|
+
const withAudience = (kind) => ({
|
|
842
|
+
...rawOpts,
|
|
843
|
+
audience: resolveSendAudience(rawOpts, kind),
|
|
844
|
+
});
|
|
814
845
|
// --channel <name> is an explicit channel target; the positional carries the
|
|
815
846
|
// message (recipient slot), e.g. `hq dm --channel vyg-dev "hello"`.
|
|
816
847
|
if (opts.channel !== undefined) {
|
|
817
|
-
await runChannelSend(opts.channel, message ?? recipient,
|
|
848
|
+
await runChannelSend(opts.channel, message ?? recipient, withAudience("channel"));
|
|
818
849
|
return;
|
|
819
850
|
}
|
|
820
851
|
if (recipient === undefined) {
|
|
@@ -837,7 +868,7 @@ async function runDmSend(recipient, message, opts) {
|
|
|
837
868
|
process.exit(1);
|
|
838
869
|
return;
|
|
839
870
|
}
|
|
840
|
-
await runGroupSend(group, message);
|
|
871
|
+
await runGroupSend(group, message, withAudience("group").audience);
|
|
841
872
|
return;
|
|
842
873
|
}
|
|
843
874
|
// A bare name or #hash addresses a named DM channel.
|
|
@@ -849,12 +880,13 @@ async function runDmSend(recipient, message, opts) {
|
|
|
849
880
|
? null
|
|
850
881
|
: matchContactByName(await fetchContacts(await ensureCognitoToken()), channelName);
|
|
851
882
|
if (!person?.personUid) {
|
|
852
|
-
await runChannelSend(channelName, message,
|
|
883
|
+
await runChannelSend(channelName, message, withAudience("channel"));
|
|
853
884
|
return;
|
|
854
885
|
}
|
|
855
886
|
console.log(chalk.dim(`Resolved '${channelName}' to person ${person.displayName || person.email || person.personUid} (${person.personUid}). Use '#${channelName}' or --channel ${channelName} to post to a channel instead.`));
|
|
856
887
|
recipient = person.personUid;
|
|
857
888
|
}
|
|
889
|
+
Object.assign(opts, withAudience(recipientKindOf(recipient)));
|
|
858
890
|
if (opts.thread !== undefined) {
|
|
859
891
|
console.error(chalk.red("--thread is only supported for channel messages (hq dm <channel> --thread <eventId|last> \"…\")."));
|
|
860
892
|
process.exit(1);
|
|
@@ -1697,7 +1729,7 @@ async function runDmChannel(target, opts) {
|
|
|
1697
1729
|
export function registerDmCommand(program) {
|
|
1698
1730
|
const dm = program
|
|
1699
1731
|
.command("dm")
|
|
1700
|
-
.description("Send and read direct messages, and manage connection requests.");
|
|
1732
|
+
.description("Send and read direct messages, and manage connection requests. Messages to a person send to them; messages to an agent, a channel or a group are tagged for agents and folded away from people by default. Pass --for-human or --audience human|agent|both to override.");
|
|
1701
1733
|
dm
|
|
1702
1734
|
.command("send [recipient] [message]", { isDefault: true, hidden: true })
|
|
1703
1735
|
.description('Send a direct message. RECIPIENT can be a person (email, personUid, or agentUid), a GROUP DM (comma-separated: "a@x.com,b@y.com"), or one of your DM CHANNELS by name — bare (hq dm vyg-dev "hi"), hash form (hq dm "#vyg-dev" "hi"), or via --channel (hq dm --channel vyg-dev "hi"). A person receives a DM as an HQ Sync notification; an agent receives it in its durable box inbox. If you aren\'t connected yet, it sends a connection request that holds your message. See your channels with `hq channels`.\n\nCHANNEL MENTIONS: HQ mentions are structured — the server never reads `@name` out of your text, so a channel message has to carry the participants alongside the body. When you write `@name` in a channel message, this command looks the name up in the channel roster (case-insensitive on display name and slug/handle; quote multi-word names as @"Jacob Posel") and sends it as a real mention, which notifies people and wakes agents. If a name is unknown or matches more than one member, the send FAILS with the candidates instead of posting something that mentions nobody. A bare "@" or an email address in your prose is never treated as a mention. Pass --no-mentions to post the text literally.\n\nCHANNEL THREADS: `hq dm <channel> --thread <eventId> "…"` replies under an existing root (the request carries rootEventId). `--thread last` replies to your most recent top-level message in that channel. Copy a root eventId from `hq dm channel <name>`, which prints each root\'s id and indents replies underneath.')
|
|
@@ -1710,7 +1742,8 @@ export function registerDmCommand(program) {
|
|
|
1710
1742
|
.option("--details-file <path>", "Read the details from a file (1:1 DMs; channel sends also supported)")
|
|
1711
1743
|
.option("--at <iso>", "Schedule delivery at an ISO8601 time (store-and-forward; delivered within ~60s of the time; 1:1 DMs only)")
|
|
1712
1744
|
.option("--in <duration>", "Schedule delivery after a relative delay: 30s, 10m, 2h, 1d (1:1 DMs only)")
|
|
1713
|
-
.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")
|
|
1745
|
+
.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. Default: human for a person, agent for an agent, a channel or a group. Wins over --for-human.")
|
|
1746
|
+
.option("--for-human", "Tag the message for people (alias for --audience human).")
|
|
1714
1747
|
.option("--attach <path>", "Upload a local file and attach it to the message (repeatable, up to 5 files, 25 MB each). 1:1 DMs and named channels; the file lands in the conversation's vault scope and the recipient can open it. Not available for group DMs or scheduled sends.", (value, previous = []) => [...previous, value])
|
|
1715
1748
|
.action(async (recipient, message, opts) => {
|
|
1716
1749
|
await runDmSend(recipient, message, opts);
|
|
@@ -292,6 +292,10 @@ export interface PendingApproval {
|
|
|
292
292
|
status?: string;
|
|
293
293
|
/** Owner-facing outcome projected by hq-pro for decided rows. */
|
|
294
294
|
outcome?: string;
|
|
295
|
+
/** Closed rejection code for a rejected row (e.g. provider_tool_error); older servers omit it. */
|
|
296
|
+
rejectionReason?: string | null;
|
|
297
|
+
/** Plain reason a decided row did not run, such as the app's own refusal text; older servers omit it. */
|
|
298
|
+
failureMessage?: string | null;
|
|
295
299
|
}
|
|
296
300
|
/**
|
|
297
301
|
* Read open approvals from hq-pro's confirm-queue state (not the audit feed).
|
|
@@ -307,4 +311,15 @@ export declare function mutateConnectionAccess(token: string, companyUid: string
|
|
|
307
311
|
granteeId?: string;
|
|
308
312
|
permission?: Permission;
|
|
309
313
|
}): Promise<ConnectionAccess>;
|
|
314
|
+
/** Same cap the server applies to a failure reason; the CLI enforces it for older or malformed responses. */
|
|
315
|
+
export declare const TERMINAL_REASON_MAX_CHARS = 500;
|
|
316
|
+
/**
|
|
317
|
+
* Server-supplied text that reaches the terminal (a failure reason quoting an
|
|
318
|
+
* app's error). ANSI escapes, control and invisible formatting characters are
|
|
319
|
+
* removed, then the same credential-redaction chain the gateway diagnostics use
|
|
320
|
+
* (`redactErrorText`: credential URLs, bearer values, token assignments, known
|
|
321
|
+
* token shapes) is applied, and the result is capped. The server redacts too;
|
|
322
|
+
* this keeps an older or misbehaving server from printing a secret.
|
|
323
|
+
*/
|
|
324
|
+
export declare function terminalSafeText(text: string): string;
|
|
310
325
|
//# sourceMappingURL=integrations-api.d.ts.map
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* is optional and read defensively.
|
|
9
9
|
*/
|
|
10
10
|
import { vaultApiFetch } from "../utils/vault-api.js";
|
|
11
|
+
import { redactErrorText } from "../utils/redact-error-text.js";
|
|
11
12
|
import { IntegrationsCliError, raiseForResponse, } from "./integrations-core.js";
|
|
12
13
|
export async function listCatalog(token, companyUid, opts = {}) {
|
|
13
14
|
const query = {
|
|
@@ -188,4 +189,30 @@ export async function mutateConnectionAccess(token, companyUid, action, input) {
|
|
|
188
189
|
}
|
|
189
190
|
return (await res.json());
|
|
190
191
|
}
|
|
192
|
+
// eslint-disable-next-line no-control-regex
|
|
193
|
+
const TERMINAL_ESCAPES = /\u001b\[[0-?]*[ -/]*[@-~]|\u001b\][^\u0007\u001b]*(?:\u0007|\u001b\\)?|\u001b[@-_]/g;
|
|
194
|
+
// eslint-disable-next-line no-control-regex
|
|
195
|
+
const TERMINAL_CONTROL_CHARS = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g;
|
|
196
|
+
/** Bidirectional overrides, isolates, marks and zero-width characters, which can reorder or hide text. */
|
|
197
|
+
const TERMINAL_INVISIBLE_FORMAT_CHARS = /[\u200b-\u200f\u202a-\u202e\u2060-\u2069\ufeff]/g;
|
|
198
|
+
/** Same cap the server applies to a failure reason; the CLI enforces it for older or malformed responses. */
|
|
199
|
+
export const TERMINAL_REASON_MAX_CHARS = 500;
|
|
200
|
+
/**
|
|
201
|
+
* Server-supplied text that reaches the terminal (a failure reason quoting an
|
|
202
|
+
* app's error). ANSI escapes, control and invisible formatting characters are
|
|
203
|
+
* removed, then the same credential-redaction chain the gateway diagnostics use
|
|
204
|
+
* (`redactErrorText`: credential URLs, bearer values, token assignments, known
|
|
205
|
+
* token shapes) is applied, and the result is capped. The server redacts too;
|
|
206
|
+
* this keeps an older or misbehaving server from printing a secret.
|
|
207
|
+
*/
|
|
208
|
+
export function terminalSafeText(text) {
|
|
209
|
+
const visible = text
|
|
210
|
+
.replace(TERMINAL_ESCAPES, "")
|
|
211
|
+
.replace(TERMINAL_CONTROL_CHARS, "")
|
|
212
|
+
.replace(TERMINAL_INVISIBLE_FORMAT_CHARS, "");
|
|
213
|
+
const redacted = redactErrorText(visible);
|
|
214
|
+
return redacted.length > TERMINAL_REASON_MAX_CHARS
|
|
215
|
+
? `${redacted.slice(0, TERMINAL_REASON_MAX_CHARS - 1)}…`
|
|
216
|
+
: redacted;
|
|
217
|
+
}
|
|
191
218
|
//# sourceMappingURL=integrations-api.js.map
|
|
@@ -18,7 +18,7 @@ import { ensureCognitoIdToken } from "../utils/cognito-session.js";
|
|
|
18
18
|
import { getCompanyUid } from "../utils/vault-api.js";
|
|
19
19
|
import { redactSensitiveUrl, redactUrlsInText } from "../utils/redact-url.js";
|
|
20
20
|
import { IntegrationsCliError, bareProvider, printHealthNotice, fetchConnections, fetchAdminSurface, fetchAuditPage, AUDIT_PAGE_MAX, printJson, revokedConnectionDetails, resolveConnection, selectConnection, } from "./integrations-core.js";
|
|
21
|
-
import { fetchPendingApprovals, getConnectionAccess, mutateConnectionAccess, purgeConnection, setReadSafe, uninstallIntegration, updateGovernance, } from "./integrations-api.js";
|
|
21
|
+
import { fetchPendingApprovals, getConnectionAccess, mutateConnectionAccess, purgeConnection, setReadSafe, terminalSafeText, uninstallIntegration, updateGovernance, } from "./integrations-api.js";
|
|
22
22
|
const WRITE_POLICIES = ["auto-allow", "confirm", "deny"];
|
|
23
23
|
const PERMISSIONS = ["read", "write", "admin"];
|
|
24
24
|
/** Plain-English gloss for each write policy, used in every policy readout. */
|
|
@@ -617,9 +617,11 @@ export function registerManageCommands(integrations) {
|
|
|
617
617
|
console.log(`${chalk.bold(label)} ${chalk.dim(`${who} · ${row.createdAt}`)}`);
|
|
618
618
|
if (opts.history) {
|
|
619
619
|
console.log(chalk.dim(` ${row.queueId} · ${row.status ?? row.outcome ?? "status unavailable"}`));
|
|
620
|
+
if (row.failureMessage)
|
|
621
|
+
console.log(chalk.dim(` Why: ${terminalSafeText(row.failureMessage)}`));
|
|
620
622
|
}
|
|
621
623
|
else {
|
|
622
|
-
console.log(chalk.dim(` hq integrations approve ${row.queueId}${row.provider ? ` --provider ${bareProvider(row.provider)}` : ""}`));
|
|
624
|
+
console.log(chalk.dim(` hq integrations approve ${row.queueId}${row.provider ? ` --provider ${bareProvider(row.provider)}` : ""}${opts.company ? ` --company ${opts.company}` : ""}`));
|
|
623
625
|
}
|
|
624
626
|
}
|
|
625
627
|
});
|
|
@@ -61,7 +61,7 @@ import { registerConnectCommands } from "./integrations-connect.js";
|
|
|
61
61
|
import { registerImportCommands } from "./integrations-import.js";
|
|
62
62
|
import { registerManageCommands } from "./integrations-manage.js";
|
|
63
63
|
import { registerSlackCommands } from "./integrations-slack.js";
|
|
64
|
-
import { fetchPendingApprovals } from "./integrations-api.js";
|
|
64
|
+
import { fetchPendingApprovals, terminalSafeText } from "./integrations-api.js";
|
|
65
65
|
const CALL_ARGS_EXAMPLE = `--args '{"assignee":"me"}' or --args-file args.json`;
|
|
66
66
|
function readStream(stream) {
|
|
67
67
|
return new Promise((resolve, reject) => {
|
|
@@ -533,7 +533,7 @@ export function registerIntegrationsCommand(program) {
|
|
|
533
533
|
return;
|
|
534
534
|
}
|
|
535
535
|
console.log(chalk.yellow("Queued for approval — this call can change the app, so a company owner decides first."));
|
|
536
|
-
console.log(`Approve with:\n hq integrations approve ${queued.queueId} --provider ${bareProvider(connection.provider)}`);
|
|
536
|
+
console.log(`Approve with:\n hq integrations approve ${queued.queueId} --provider ${bareProvider(connection.provider)}${opts.company ? ` --company ${opts.company}` : ""}`);
|
|
537
537
|
if (queued.expiresAt) {
|
|
538
538
|
console.log(chalk.dim(`Expires ${queued.expiresAt}`));
|
|
539
539
|
}
|
|
@@ -606,7 +606,9 @@ export function registerIntegrationsCommand(program) {
|
|
|
606
606
|
const unknown = terminal.status === "write_unknown" || terminal.outcome === "unknown";
|
|
607
607
|
console.log(chalk.red(unknown
|
|
608
608
|
? `The provider outcome is unknown for ${queueId}. Check the provider before taking any action; ${historyCommand} shows the latest queue state.`
|
|
609
|
-
:
|
|
609
|
+
: terminal.failureMessage
|
|
610
|
+
? `The approved call did not run: ${terminalSafeText(terminal.failureMessage)}`
|
|
611
|
+
: `The approved call failed (${terminal.rejectionReason ?? terminal.status ?? terminal.outcome}). See ${historyCommand} for its status.`));
|
|
610
612
|
}
|
|
611
613
|
else if (terminal.status === "resumed" || terminal.status === "completed" || terminal.outcome === "ran") {
|
|
612
614
|
console.log(chalk.green("Approved — the call completed."));
|
|
@@ -614,7 +616,7 @@ export function registerIntegrationsCommand(program) {
|
|
|
614
616
|
printJson(unwrapGatewayResult(stored.result));
|
|
615
617
|
}
|
|
616
618
|
else if (ran) {
|
|
617
|
-
console.log(chalk.yellow(`The call's result could not be read back. Run \`hq integrations approve ${queueId}\` again to fetch it; the call will not run twice.`));
|
|
619
|
+
console.log(chalk.yellow(`The call's result could not be read back. Run \`hq integrations approve ${queueId} --provider ${bareProvider(connection.provider)}${opts.company ? ` --company ${opts.company}` : ""}\` again to fetch it; the call will not run twice.`));
|
|
618
620
|
}
|
|
619
621
|
}
|
|
620
622
|
else {
|
|
@@ -649,7 +651,9 @@ export function registerIntegrationsCommand(program) {
|
|
|
649
651
|
if (failed) {
|
|
650
652
|
const reason = body.rejectionReason ??
|
|
651
653
|
(toolError ? "provider_tool_error" : body.outcome ?? body.status ?? "failed");
|
|
652
|
-
console.log(chalk.red(
|
|
654
|
+
console.log(chalk.red(body.failureMessage
|
|
655
|
+
? `The approved call did not run: ${terminalSafeText(body.failureMessage)}`
|
|
656
|
+
: `The approved call failed at the provider (${reason}).`));
|
|
653
657
|
}
|
|
654
658
|
else if (body.status === "resumed" || body.status === "completed" || body.outcome === "ran") {
|
|
655
659
|
console.log(chalk.green(`Approved — the call ran (status: ${body.status ?? body.outcome}).`));
|