@indigoai-us/hq-cli 5.40.0 → 5.41.0

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 CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.41.0]
6
+
7
+ ### Added
8
+
9
+ - **`hq dm` accepts agent recipients (`agt_…`).** Agents are first-class DM
10
+ participants server-side (hq-pro #468): an `agt_*` recipient rides the same
11
+ `toPersonUid` wire field, is gated by the unchanged membership-overlap rule
12
+ (you can only DM an agent you share an active company with), and is
13
+ delivered into the agent's durable box inbox — the agent replies by DM as
14
+ its own `agt_*` identity. Group DMs stay person-only (they're channels);
15
+ an `agt_*` in a comma-separated recipient list gets a clear error pointing
16
+ at the 1:1 form.
17
+
5
18
  ## [5.39.3]
6
19
 
7
20
  ### Fixed
@@ -4,8 +4,10 @@ export interface DmRecipient {
4
4
  toPersonUid?: string;
5
5
  }
6
6
  /**
7
- * Classify a recipient arg as an email or a personUid. Mirrors the
8
- * email/prs_ heuristic used by `hq members`. Returns null for neither.
7
+ * Classify a recipient arg as an email or a person/agent uid. Mirrors the
8
+ * email/uid heuristic used by `hq members`. Returns null for neither.
9
+ * Agent uids (agt_*) ride the same `toPersonUid` wire field — that is the
10
+ * server contract (POST /v1/notify/dm accepts prs_* or agt_*).
9
11
  */
10
12
  export declare function detectRecipient(recipient: string): DmRecipient | null;
11
13
  /**
@@ -1,20 +1,25 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="c29bef98-d624-5da1-86c9-b4dc9d6d1d7f")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="f4bc9146-4033-5a73-a246-6f80a5e0f7ef")}catch(e){}}();
3
3
  import chalk from "chalk";
4
4
  import { readFileSync } from "node:fs";
5
5
  import { ensureCognitoToken } from "../utils/cognito-session.js";
6
6
  import { vaultApiFetch } from "../utils/vault-api.js";
7
7
  const EMAIL_PATTERN = /^[^\s]+@[^\s]+$/;
8
- const PERSON_UID_PATTERN = /^prs_[A-Za-z0-9_-]+$/;
8
+ // People (prs_) and agents (agt_) are both first-class DM participants; the
9
+ // server applies the same membership-overlap gate to either and delivers
10
+ // agt_* recipients into the agent's durable box inbox.
11
+ const RECIPIENT_UID_PATTERN = /^(prs|agt)_[A-Za-z0-9_-]+$/;
9
12
  /**
10
- * Classify a recipient arg as an email or a personUid. Mirrors the
11
- * email/prs_ heuristic used by `hq members`. Returns null for neither.
13
+ * Classify a recipient arg as an email or a person/agent uid. Mirrors the
14
+ * email/uid heuristic used by `hq members`. Returns null for neither.
15
+ * Agent uids (agt_*) ride the same `toPersonUid` wire field — that is the
16
+ * server contract (POST /v1/notify/dm accepts prs_* or agt_*).
12
17
  */
13
18
  export function detectRecipient(recipient) {
14
19
  const r = recipient.trim();
15
20
  if (EMAIL_PATTERN.test(r))
16
21
  return { toEmail: r.toLowerCase() };
17
- if (PERSON_UID_PATTERN.test(r))
22
+ if (RECIPIENT_UID_PATTERN.test(r))
18
23
  return { toPersonUid: r };
19
24
  return null;
20
25
  }
@@ -59,7 +64,7 @@ export function parseDuration(input) {
59
64
  export function buildDmBody(args) {
60
65
  const rcpt = detectRecipient(args.recipient);
61
66
  if (!rcpt) {
62
- throw new Error(`Invalid recipient '${args.recipient}': must be an email address or a personUid (prs_…).`);
67
+ throw new Error(`Invalid recipient '${args.recipient}': must be an email address, a personUid (prs_…), or an agentUid (agt_…).`);
63
68
  }
64
69
  const body = (args.message ?? "").trim();
65
70
  if (!body) {
@@ -206,6 +211,12 @@ async function runGroupSend(recipients, message) {
206
211
  console.error(chalk.red(`Invalid recipient '${r}': each must be an email address or a personUid (prs_…).`));
207
212
  process.exit(1);
208
213
  }
214
+ // Group DMs are channels — agents don't participate in channels (their
215
+ // DM surface is 1:1 via the durable box inbox). DM an agent directly.
216
+ if (rc.toPersonUid?.startsWith("agt_")) {
217
+ console.error(chalk.red(`Agents can't join group DMs yet — DM '${r}' directly: hq dm ${r} "<message>".`));
218
+ process.exit(1);
219
+ }
209
220
  participants.push(rc.toEmail ?? rc.toPersonUid);
210
221
  }
211
222
  if (participants.length < 2) {
@@ -344,7 +355,7 @@ export function registerDmCommand(program) {
344
355
  .description("Send a direct message and manage connection requests.");
345
356
  dm
346
357
  .command("send <recipient> [message]", { isDefault: true, hidden: true })
347
- .description('Send a direct message to someone (email or personUid). They receive it as an HQ Sync notification. If you aren\'t connected yet, it sends a connection request that holds your message. For a GROUP DM, pass a comma-separated recipient: hq dm send "a@x.com,b@y.com" "hi".')
358
+ .description('Send a direct message to someone (email, personUid, or agentUid). A person receives it as an HQ Sync notification; an agent receives it in its durable box inbox and replies by DM. If you aren\'t connected yet, it sends a connection request that holds your message. For a GROUP DM, pass a comma-separated recipient: hq dm send "a@x.com,b@y.com" "hi".')
348
359
  .option("--prompt <text>", "Agent-context prompt the recipient can one-click copy into their agent")
349
360
  .option("--prompt-file <path>", "Read the agent prompt from a file")
350
361
  .option("--details <text>", "Longer detail shown in the recipient's DM detail window")
@@ -380,4 +391,4 @@ export function registerDmCommand(program) {
380
391
  });
381
392
  }
382
393
  //# sourceMappingURL=dm.js.map
383
- //# debugId=c29bef98-d624-5da1-86c9-b4dc9d6d1d7f
394
+ //# debugId=f4bc9146-4033-5a73-a246-6f80a5e0f7ef
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.40.0",
3
+ "version": "5.41.0",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -51,9 +51,16 @@ describe("detectRecipient", () => {
51
51
  it("classifies a personUid", () => {
52
52
  expect(detectRecipient("prs_01ABC")).toEqual({ toPersonUid: "prs_01ABC" });
53
53
  });
54
+ it("classifies an agentUid on the same wire field (server accepts agt_*)", () => {
55
+ expect(detectRecipient("agt_01KTT1RJJ6KQRHNHHST0K4VRGT")).toEqual({
56
+ toPersonUid: "agt_01KTT1RJJ6KQRHNHHST0K4VRGT",
57
+ });
58
+ });
54
59
  it("rejects anything else", () => {
55
60
  expect(detectRecipient("not-an-email")).toBeNull();
56
61
  expect(detectRecipient("")).toBeNull();
62
+ expect(detectRecipient("cmp_01ABC")).toBeNull();
63
+ expect(detectRecipient("agt")).toBeNull();
57
64
  });
58
65
  });
59
66
 
@@ -93,6 +100,12 @@ describe("buildDmBody", () => {
93
100
  ).toEqual({ toPersonUid: "prs_x", body: "m", prompt: "do the thing" });
94
101
  });
95
102
 
103
+ it("builds an agent DM (agt_* rides toPersonUid)", () => {
104
+ expect(
105
+ buildDmBody({ recipient: "agt_01ABC", message: "status?", now }),
106
+ ).toEqual({ toPersonUid: "agt_01ABC", body: "status?" });
107
+ });
108
+
96
109
  it("resolves --in to a future deliverAt", () => {
97
110
  const out = buildDmBody({ recipient: "a@b.com", message: "m", inDelay: "10m", now });
98
111
  expect(out.deliverAt).toBe("2026-05-29T00:10:00.000Z");
@@ -5,7 +5,10 @@ import { ensureCognitoToken } from "../utils/cognito-session.js";
5
5
  import { vaultApiFetch } from "../utils/vault-api.js";
6
6
 
7
7
  const EMAIL_PATTERN = /^[^\s]+@[^\s]+$/;
8
- const PERSON_UID_PATTERN = /^prs_[A-Za-z0-9_-]+$/;
8
+ // People (prs_) and agents (agt_) are both first-class DM participants; the
9
+ // server applies the same membership-overlap gate to either and delivers
10
+ // agt_* recipients into the agent's durable box inbox.
11
+ const RECIPIENT_UID_PATTERN = /^(prs|agt)_[A-Za-z0-9_-]+$/;
9
12
 
10
13
  export interface DmRecipient {
11
14
  toEmail?: string;
@@ -13,13 +16,15 @@ export interface DmRecipient {
13
16
  }
14
17
 
15
18
  /**
16
- * Classify a recipient arg as an email or a personUid. Mirrors the
17
- * email/prs_ heuristic used by `hq members`. Returns null for neither.
19
+ * Classify a recipient arg as an email or a person/agent uid. Mirrors the
20
+ * email/uid heuristic used by `hq members`. Returns null for neither.
21
+ * Agent uids (agt_*) ride the same `toPersonUid` wire field — that is the
22
+ * server contract (POST /v1/notify/dm accepts prs_* or agt_*).
18
23
  */
19
24
  export function detectRecipient(recipient: string): DmRecipient | null {
20
25
  const r = recipient.trim();
21
26
  if (EMAIL_PATTERN.test(r)) return { toEmail: r.toLowerCase() };
22
- if (PERSON_UID_PATTERN.test(r)) return { toPersonUid: r };
27
+ if (RECIPIENT_UID_PATTERN.test(r)) return { toPersonUid: r };
23
28
  return null;
24
29
  }
25
30
 
@@ -82,7 +87,7 @@ export function buildDmBody(args: {
82
87
  const rcpt = detectRecipient(args.recipient);
83
88
  if (!rcpt) {
84
89
  throw new Error(
85
- `Invalid recipient '${args.recipient}': must be an email address or a personUid (prs_…).`,
90
+ `Invalid recipient '${args.recipient}': must be an email address, a personUid (prs_…), or an agentUid (agt_…).`,
86
91
  );
87
92
  }
88
93
  const body = (args.message ?? "").trim();
@@ -283,6 +288,16 @@ async function runGroupSend(
283
288
  );
284
289
  process.exit(1);
285
290
  }
291
+ // Group DMs are channels — agents don't participate in channels (their
292
+ // DM surface is 1:1 via the durable box inbox). DM an agent directly.
293
+ if (rc.toPersonUid?.startsWith("agt_")) {
294
+ console.error(
295
+ chalk.red(
296
+ `Agents can't join group DMs yet — DM '${r}' directly: hq dm ${r} "<message>".`,
297
+ ),
298
+ );
299
+ process.exit(1);
300
+ }
286
301
  participants.push(rc.toEmail ?? rc.toPersonUid!);
287
302
  }
288
303
  if (participants.length < 2) {
@@ -503,7 +518,7 @@ export function registerDmCommand(program: Command): void {
503
518
  dm
504
519
  .command("send <recipient> [message]", { isDefault: true, hidden: true })
505
520
  .description(
506
- 'Send a direct message to someone (email or personUid). They receive it as an HQ Sync notification. If you aren\'t connected yet, it sends a connection request that holds your message. For a GROUP DM, pass a comma-separated recipient: hq dm send "a@x.com,b@y.com" "hi".',
521
+ 'Send a direct message to someone (email, personUid, or agentUid). A person receives it as an HQ Sync notification; an agent receives it in its durable box inbox and replies by DM. If you aren\'t connected yet, it sends a connection request that holds your message. For a GROUP DM, pass a comma-separated recipient: hq dm send "a@x.com,b@y.com" "hi".',
507
522
  )
508
523
  .option(
509
524
  "--prompt <text>",