@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 +13 -0
- package/dist/commands/dm.d.ts +4 -2
- package/dist/commands/dm.js +19 -8
- package/package.json +1 -1
- package/src/commands/dm.test.ts +13 -0
- package/src/commands/dm.ts +21 -6
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
|
package/dist/commands/dm.d.ts
CHANGED
|
@@ -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
|
|
8
|
-
* email/
|
|
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
|
/**
|
package/dist/commands/dm.js
CHANGED
|
@@ -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]="
|
|
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
|
-
|
|
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
|
|
11
|
-
* email/
|
|
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 (
|
|
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
|
|
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
|
|
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=
|
|
394
|
+
//# debugId=f4bc9146-4033-5a73-a246-6f80a5e0f7ef
|
package/package.json
CHANGED
package/src/commands/dm.test.ts
CHANGED
|
@@ -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");
|
package/src/commands/dm.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
17
|
-
* email/
|
|
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 (
|
|
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
|
|
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
|
|
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>",
|