@indigoai-us/hq-cli 5.343.0 → 5.344.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 +14 -0
- package/dist/command-catalog.generated.d.ts +15 -4
- package/dist/command-catalog.generated.js +19 -4
- package/dist/commands/cloud.js +2 -2
- package/dist/commands/daemon.js +2 -11
- package/dist/commands/dm.js +10 -1
- package/dist/commands/lanes-shared.d.ts +1 -0
- package/dist/commands/lanes.d.ts +1 -0
- package/dist/commands/lanes.js +11 -0
- package/dist/commands/login.js +12 -1
- package/dist/commands/logout.js +10 -0
- package/dist/commands/mesh.d.ts +27 -1
- package/dist/commands/mesh.js +123 -12
- package/dist/lib/agent-kit/mcp/tools.js +1 -1
- package/dist/lib/bot/api.d.ts +1 -0
- package/dist/lib/bot/api.js +2 -0
- package/dist/lib/bot/progress.d.ts +12 -14
- package/dist/lib/bot/progress.js +29 -40
- package/dist/lib/bot/run.js +24 -24
- package/dist/lib/core-utils/sentry-report.d.ts +1 -1
- package/dist/lib/core-utils/sentry-report.js +18 -1
- package/dist/lib/daemon/command-call.d.ts +9 -0
- package/dist/lib/daemon/command-call.js +18 -0
- package/dist/lib/lanes/outpost-dispatch.d.ts +1 -0
- package/dist/lib/lanes/outpost-dispatch.js +1 -0
- package/dist/lib/work-context/config.d.ts +66 -0
- package/dist/lib/work-context/config.js +142 -0
- package/dist/lib/work-context/person-identity.d.ts +16 -0
- package/dist/lib/work-context/person-identity.js +39 -0
- package/dist/utils/cli-telemetry.js +5 -1
- package/dist/utils/client-health.d.ts +7 -3
- package/dist/utils/client-health.js +30 -6
- package/dist/utils/install-tree-torn.d.ts +16 -13
- package/dist/utils/install-tree-torn.js +58 -26
- package/dist/utils/resident-package-preload.d.ts +1 -1
- package/dist/utils/resident-package-preload.js +18 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.344.0] — 2026-10-04
|
|
6
|
+
- Include a bounded reason in sync-failure client-health reports.
|
|
7
|
+
- Add an explicit opt-in to count usage from HQ sessions with no company toward the default company: `hq mesh context default set` asks in a terminal (default No), takes `--count-usage` / `--no-count-usage`, and writes `usageAttribution` (company and signed-in person) to the work-context config. Changing or clearing the default, logging out, or signing in as another account withdraws it; `get` shows it; an automatic default never opts in.
|
|
8
|
+
|
|
9
|
+
## [5.343.1] — 2026-10-04
|
|
10
|
+
|
|
11
|
+
- Include the bounded hq-cli version in `cli_feature_used` telemetry so feature-use rows can be grouped by release.
|
|
12
|
+
|
|
13
|
+
- Release the HQ CLI package-use lease before hq daemon call waits for its daemon response, after preloading the request protocol.
|
|
14
|
+
- Release the package-use lease after Outpost lane message and resume requests are submitted and their result-wait imports are preloaded.
|
|
15
|
+
- Preserve human, agent, and both audience tags when CLI tools and local bots post or reply in channels.
|
|
16
|
+
- Wait for a live installer to finish before retrying a failed CLI import when the install tree is still in flight.
|
|
17
|
+
- Preserve bounded master-hook slow-phase and per-phase timeout attribution in Sentry reports.
|
|
18
|
+
|
|
5
19
|
## [5.343.0] — 2026-10-04
|
|
6
20
|
- `hq company --company <slug> settings set --work-registry-enabled <true|false>` and `--work-mesh-live-enabled <true|false>` let a company owner turn work tracking on or off. Work tracking needs both switches; after either is set the command says whether work tracking is on and prints the exact command for the switch that is still off. `hq company --company <slug> settings get` now also shows both switches for an owner or admin.
|
|
7
21
|
- `hq mesh context requeue` now also re-sends, by default, sessions that were refused while work tracking was off. `hq doctor` and `hq mesh daemon doctor` explain those refusals in plain words.
|
|
@@ -3204,7 +3204,7 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
3204
3204
|
readonly hidden: false;
|
|
3205
3205
|
}, {
|
|
3206
3206
|
readonly flags: "--audience <human|agent|both>";
|
|
3207
|
-
readonly description: "Tag who this message is for: human (a person must read it), agent (progress/status, hidden from humans by default), or both
|
|
3207
|
+
readonly description: "Tag who this message is for: human (a person must read it), agent (progress/status, hidden from humans by default), or both";
|
|
3208
3208
|
readonly hidden: false;
|
|
3209
3209
|
}];
|
|
3210
3210
|
readonly subcommands: readonly [];
|
|
@@ -9678,7 +9678,7 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
9678
9678
|
readonly options: readonly [];
|
|
9679
9679
|
readonly subcommands: readonly [{
|
|
9680
9680
|
readonly name: "get";
|
|
9681
|
-
readonly description: "Show the device default company (never activeCompany)";
|
|
9681
|
+
readonly description: "Show the device default company (never activeCompany) and whether usage is counted toward it";
|
|
9682
9682
|
readonly aliases: readonly [];
|
|
9683
9683
|
readonly hidden: false;
|
|
9684
9684
|
readonly helpOption: true;
|
|
@@ -9700,7 +9700,10 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
9700
9700
|
readonly helpOption: true;
|
|
9701
9701
|
readonly helpCommand: false;
|
|
9702
9702
|
readonly usage: "[options] <slug>";
|
|
9703
|
-
readonly helpText: readonly [
|
|
9703
|
+
readonly helpText: readonly [{
|
|
9704
|
+
readonly position: "after";
|
|
9705
|
+
readonly text: "\nWith neither --count-usage nor --no-count-usage, an interactive terminal asks (default No);\notherwise an existing opt-in is kept only when it is for the same company.\nChanging to a different company always stops counting usage toward the old one.";
|
|
9706
|
+
}];
|
|
9704
9707
|
readonly arguments: readonly [{
|
|
9705
9708
|
readonly name: "slug";
|
|
9706
9709
|
readonly required: true;
|
|
@@ -9715,6 +9718,14 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
9715
9718
|
readonly flags: "--repair-held-with-default";
|
|
9716
9719
|
readonly description: "Persist opt-in for lower-confidence historical held-session repair";
|
|
9717
9720
|
readonly hidden: false;
|
|
9721
|
+
}, {
|
|
9722
|
+
readonly flags: "--count-usage";
|
|
9723
|
+
readonly description: "Count usage from HQ sessions with no company toward this company (its owner and admins can see it)";
|
|
9724
|
+
readonly hidden: false;
|
|
9725
|
+
}, {
|
|
9726
|
+
readonly flags: "--no-count-usage";
|
|
9727
|
+
readonly description: "Do not count usage from HQ sessions with no company toward this company";
|
|
9728
|
+
readonly hidden: false;
|
|
9718
9729
|
}, {
|
|
9719
9730
|
readonly flags: "--company <slug|uid>";
|
|
9720
9731
|
readonly description: "Deprecated no-op; the selected company is verified directly";
|
|
@@ -9727,7 +9738,7 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
9727
9738
|
readonly subcommands: readonly [];
|
|
9728
9739
|
}, {
|
|
9729
9740
|
readonly name: "clear";
|
|
9730
|
-
readonly description: "Clear the device default company";
|
|
9741
|
+
readonly description: "Clear the device default company and any opt-in to count usage toward it";
|
|
9731
9742
|
readonly aliases: readonly [];
|
|
9732
9743
|
readonly hidden: false;
|
|
9733
9744
|
readonly helpOption: true;
|
|
@@ -3874,7 +3874,7 @@ export const COMMAND_CATALOG = [
|
|
|
3874
3874
|
},
|
|
3875
3875
|
{
|
|
3876
3876
|
"flags": "--audience <human|agent|both>",
|
|
3877
|
-
"description": "Tag who this message is for: human (a person must read it), agent (progress/status, hidden from humans by default), or both
|
|
3877
|
+
"description": "Tag who this message is for: human (a person must read it), agent (progress/status, hidden from humans by default), or both",
|
|
3878
3878
|
"hidden": false
|
|
3879
3879
|
}
|
|
3880
3880
|
],
|
|
@@ -11737,7 +11737,7 @@ export const COMMAND_CATALOG = [
|
|
|
11737
11737
|
"subcommands": [
|
|
11738
11738
|
{
|
|
11739
11739
|
"name": "get",
|
|
11740
|
-
"description": "Show the device default company (never activeCompany)",
|
|
11740
|
+
"description": "Show the device default company (never activeCompany) and whether usage is counted toward it",
|
|
11741
11741
|
"aliases": [],
|
|
11742
11742
|
"hidden": false,
|
|
11743
11743
|
"helpOption": true,
|
|
@@ -11762,7 +11762,12 @@ export const COMMAND_CATALOG = [
|
|
|
11762
11762
|
"helpOption": true,
|
|
11763
11763
|
"helpCommand": false,
|
|
11764
11764
|
"usage": "[options] <slug>",
|
|
11765
|
-
"helpText": [
|
|
11765
|
+
"helpText": [
|
|
11766
|
+
{
|
|
11767
|
+
"position": "after",
|
|
11768
|
+
"text": "\nWith neither --count-usage nor --no-count-usage, an interactive terminal asks (default No);\notherwise an existing opt-in is kept only when it is for the same company.\nChanging to a different company always stops counting usage toward the old one."
|
|
11769
|
+
}
|
|
11770
|
+
],
|
|
11766
11771
|
"arguments": [
|
|
11767
11772
|
{
|
|
11768
11773
|
"name": "slug",
|
|
@@ -11782,6 +11787,16 @@ export const COMMAND_CATALOG = [
|
|
|
11782
11787
|
"description": "Persist opt-in for lower-confidence historical held-session repair",
|
|
11783
11788
|
"hidden": false
|
|
11784
11789
|
},
|
|
11790
|
+
{
|
|
11791
|
+
"flags": "--count-usage",
|
|
11792
|
+
"description": "Count usage from HQ sessions with no company toward this company (its owner and admins can see it)",
|
|
11793
|
+
"hidden": false
|
|
11794
|
+
},
|
|
11795
|
+
{
|
|
11796
|
+
"flags": "--no-count-usage",
|
|
11797
|
+
"description": "Do not count usage from HQ sessions with no company toward this company",
|
|
11798
|
+
"hidden": false
|
|
11799
|
+
},
|
|
11785
11800
|
{
|
|
11786
11801
|
"flags": "--company <slug|uid>",
|
|
11787
11802
|
"description": "Deprecated no-op; the selected company is verified directly",
|
|
@@ -11797,7 +11812,7 @@ export const COMMAND_CATALOG = [
|
|
|
11797
11812
|
},
|
|
11798
11813
|
{
|
|
11799
11814
|
"name": "clear",
|
|
11800
|
-
"description": "Clear the device default company",
|
|
11815
|
+
"description": "Clear the device default company and any opt-in to count usage toward it",
|
|
11801
11816
|
"aliases": [],
|
|
11802
11817
|
"hidden": false,
|
|
11803
11818
|
"helpOption": true,
|
package/dist/commands/cloud.js
CHANGED
|
@@ -20,7 +20,7 @@ import { share, sync, getStateDir, listJournals, VaultClient, computePersonalVau
|
|
|
20
20
|
import { DEFAULT_HQ_ROOT, ensureCognitoToken, buildVaultConfig, loadCachedTokens, } from "../utils/cognito-session.js";
|
|
21
21
|
import { resolveUploadAuthor } from "../utils/upload-author.js";
|
|
22
22
|
import { companyFolderExceedsThreshold, emitNarrowHint, isStrictRefusal, resolveBannerLevel, resolveNarrowHintPresentationLevel, resolveNarrowHintMinBytes, } from "../lib/narrow-hint-banner.js";
|
|
23
|
-
import { beginSyncHealthReport, } from "../utils/client-health.js";
|
|
23
|
+
import { beginSyncHealthReport, classifySyncFailure, } from "../utils/client-health.js";
|
|
24
24
|
import { companySlugFromSyncPaths, warnAboutInvalidOntologyEntities, } from "../lib/sync-invalid-entity-warning.js";
|
|
25
25
|
import { isWorkBearingSyncResult, recordFirstWorkBearingSync, } from "../lib/first-sync-next-step.js";
|
|
26
26
|
import { maybeShowCliInviteNudge } from "../lib/cli-invite-nudge.js";
|
|
@@ -125,7 +125,7 @@ export async function withSyncHealthReport(run) {
|
|
|
125
125
|
await health.succeeded();
|
|
126
126
|
}
|
|
127
127
|
else {
|
|
128
|
-
await health.failed();
|
|
128
|
+
await health.failed(classifySyncFailure(err));
|
|
129
129
|
}
|
|
130
130
|
if (err instanceof SyncExitError)
|
|
131
131
|
process.exit(err.code);
|
package/dist/commands/daemon.js
CHANGED
|
@@ -29,6 +29,7 @@ import { MACHINE_TYPE_MARKER_PATH, detectMachineType, homeFromPasswd, } from "..
|
|
|
29
29
|
import { daemonPaths } from "../lib/daemon/paths.js";
|
|
30
30
|
import { processCommandLine } from "../lib/daemon/lock.js";
|
|
31
31
|
import { writeControlRequest } from "../lib/daemon/requests.js";
|
|
32
|
+
import { runDaemonCall } from "../lib/daemon/command-call.js";
|
|
32
33
|
import { isSyncRunnerCommandLine, readWatchRunnerOwnerStatus, watchRunnerOwnerStatusPath } from "../lib/daemon/sync-runner.js";
|
|
33
34
|
import { getSyncModeForCompany, setSyncMode } from "./sync-mode.js";
|
|
34
35
|
import { requireAnywhereRuntime } from "./anywhere-runtime-flag.js";
|
|
@@ -857,17 +858,7 @@ export function registerDaemonCommand(program, over = {}) {
|
|
|
857
858
|
const timeoutMs = Number(opts.timeout ?? (op === "policy.check" ? "40000" : "20000"));
|
|
858
859
|
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
|
|
859
860
|
throw new Error("--timeout must be a positive number of milliseconds");
|
|
860
|
-
|
|
861
|
-
try {
|
|
862
|
-
const result = await hqdRequest(op, args, { socketPath: hqdSocketPath(d.home, d.env), timeoutMs });
|
|
863
|
-
d.out(JSON.stringify(result, null, 2));
|
|
864
|
-
}
|
|
865
|
-
catch (err) {
|
|
866
|
-
if (!(err instanceof HqdClientError))
|
|
867
|
-
throw err;
|
|
868
|
-
d.out(JSON.stringify({ error: { code: err.code, message: err.message } }, null, 2));
|
|
869
|
-
process.exitCode = 1;
|
|
870
|
-
}
|
|
861
|
+
await runResidentCommandWithPackageLease("daemon-call", () => runDaemonCall(op, args, timeoutMs, d));
|
|
871
862
|
}));
|
|
872
863
|
daemon
|
|
873
864
|
.command("status")
|
package/dist/commands/dm.js
CHANGED
|
@@ -614,11 +614,20 @@ async function runChannelSend(channelName, message, opts) {
|
|
|
614
614
|
// Send the human-readable form: the quotes only existed for parsing.
|
|
615
615
|
if (mentions.length > 0)
|
|
616
616
|
body = stripMentionQuotes(body);
|
|
617
|
+
let audience;
|
|
618
|
+
if (opts.audience !== undefined && opts.audience !== "") {
|
|
619
|
+
if (!DM_AUDIENCE_VALUES.includes(opts.audience)) {
|
|
620
|
+
throw new Error(`Invalid --audience '${opts.audience}': must be human, agent, or both.`);
|
|
621
|
+
}
|
|
622
|
+
audience = opts.audience;
|
|
623
|
+
}
|
|
617
624
|
const payload = { body };
|
|
618
625
|
if (mentions.length > 0)
|
|
619
626
|
payload.mentions = mentions;
|
|
620
627
|
if (rootEventId)
|
|
621
628
|
payload.rootEventId = rootEventId;
|
|
629
|
+
if (audience)
|
|
630
|
+
payload.audience = audience;
|
|
622
631
|
const sendRes = await vaultApiFetch({
|
|
623
632
|
token,
|
|
624
633
|
path: `/v1/notify/channels/${encodeURIComponent(channel.channelId)}/messages`,
|
|
@@ -1327,7 +1336,7 @@ export function registerDmCommand(program) {
|
|
|
1327
1336
|
.option("--details-file <path>", "Read the details from a file (1:1 DMs only)")
|
|
1328
1337
|
.option("--at <iso>", "Schedule delivery at an ISO8601 time (store-and-forward; delivered within ~60s of the time; 1:1 DMs only)")
|
|
1329
1338
|
.option("--in <duration>", "Schedule delivery after a relative delay: 30s, 10m, 2h, 1d (1:1 DMs only)")
|
|
1330
|
-
.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
|
|
1339
|
+
.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")
|
|
1331
1340
|
.action(async (recipient, message, opts) => {
|
|
1332
1341
|
await runDmSend(recipient, message, opts);
|
|
1333
1342
|
});
|
|
@@ -272,6 +272,7 @@ export interface Ctx {
|
|
|
272
272
|
command: string;
|
|
273
273
|
timeoutSeconds?: number;
|
|
274
274
|
waitMs?: number;
|
|
275
|
+
beforeWait?: () => Promise<void>;
|
|
275
276
|
}) => Promise<OutpostDispatchResult>;
|
|
276
277
|
backfillOutpostIdentity: (expectedId: string) => "created" | "unchanged";
|
|
277
278
|
fetchText: (url: string) => Promise<string>;
|
package/dist/commands/lanes.d.ts
CHANGED
|
@@ -86,6 +86,7 @@ export interface LanesCommandDeps {
|
|
|
86
86
|
command: string;
|
|
87
87
|
timeoutSeconds?: number;
|
|
88
88
|
waitMs?: number;
|
|
89
|
+
beforeWait?: () => Promise<void>;
|
|
89
90
|
}) => Promise<OutpostDispatchResult>;
|
|
90
91
|
/** Test seam for the atomic, non-overwriting marker repair. */
|
|
91
92
|
backfillOutpostIdentity?: (expectedId: string) => "created" | "unchanged";
|
package/dist/commands/lanes.js
CHANGED
|
@@ -4317,11 +4317,22 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options, behavior =
|
|
|
4317
4317
|
let dispatched;
|
|
4318
4318
|
let grantContextRejected = false;
|
|
4319
4319
|
let failedResumeOptionRejected = false;
|
|
4320
|
+
let outpostWaitLeasePreparationStarted = false;
|
|
4320
4321
|
try {
|
|
4321
4322
|
const dispatchCommand = (contexts) => ctx.dispatchOutpostExec({
|
|
4322
4323
|
outpostId,
|
|
4323
4324
|
timeoutSeconds: Math.ceil(waitMs / 1000),
|
|
4324
4325
|
waitMs,
|
|
4326
|
+
...(action === "message" || action === "resume"
|
|
4327
|
+
? {
|
|
4328
|
+
beforeWait: async () => {
|
|
4329
|
+
if (outpostWaitLeasePreparationStarted)
|
|
4330
|
+
return;
|
|
4331
|
+
outpostWaitLeasePreparationStarted = true;
|
|
4332
|
+
await runResidentCommandWithPackageLease("outpost-wait", async () => undefined);
|
|
4333
|
+
},
|
|
4334
|
+
}
|
|
4335
|
+
: {}),
|
|
4325
4336
|
command: buildOutpostLaneCommand({
|
|
4326
4337
|
action: action === "grant"
|
|
4327
4338
|
? "grant-target"
|
package/dist/commands/login.js
CHANGED
|
@@ -8,7 +8,8 @@ import { browserLogin, loadCachedTokens, isExpiring } from '@indigoai-us/hq-clou
|
|
|
8
8
|
import { cognitoConfigForLoginProvider } from '../utils/login-provider.js';
|
|
9
9
|
import * as readline from "node:readline/promises";
|
|
10
10
|
import { listActiveMembershipCompanies } from "../lib/mesh/api.js";
|
|
11
|
-
import { setDefaultCompany } from "../lib/work-context/config.js";
|
|
11
|
+
import { retainUsageAttributionForPerson, setDefaultCompany } from "../lib/work-context/config.js";
|
|
12
|
+
import { personUidFromTokens } from "../lib/work-context/person-identity.js";
|
|
12
13
|
import { ensureDefaultCompany } from "../lib/work-context/default-company.js";
|
|
13
14
|
import { workContextRoot } from "../lib/work-context/paths.js";
|
|
14
15
|
function peekIdToken(idToken) {
|
|
@@ -62,6 +63,16 @@ export function registerLoginCommand(program) {
|
|
|
62
63
|
process.exitCode = 1;
|
|
63
64
|
return;
|
|
64
65
|
}
|
|
66
|
+
try {
|
|
67
|
+
// The usage-attribution opt-in belongs to the person who gave it. A
|
|
68
|
+
// different (or unknown) account signing in on this OS user drops it.
|
|
69
|
+
retainUsageAttributionForPerson(personUidFromTokens(loadCachedTokens()), {
|
|
70
|
+
root: workContextRoot(),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
/* fail closed below: readers also require the signed-in person to match */
|
|
75
|
+
}
|
|
65
76
|
try {
|
|
66
77
|
const root = workContextRoot();
|
|
67
78
|
const activation = await ensureDefaultCompany({
|
package/dist/commands/logout.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import { clearCachedTokens, loadCachedTokens } from '@indigoai-us/hq-cloud';
|
|
6
|
+
import { retainUsageAttributionForPerson } from '../lib/work-context/config.js';
|
|
7
|
+
import { workContextRoot } from '../lib/work-context/paths.js';
|
|
6
8
|
export function registerLogoutCommand(program) {
|
|
7
9
|
program
|
|
8
10
|
.command('logout')
|
|
@@ -11,6 +13,14 @@ export function registerLogoutCommand(program) {
|
|
|
11
13
|
try {
|
|
12
14
|
const { clearMcpOAuthCaches } = await import('../lib/daemon/mcp-oauth.js');
|
|
13
15
|
clearMcpOAuthCaches();
|
|
16
|
+
// Signing out withdraws the opt-in to count no-company usage toward the
|
|
17
|
+
// default company; the next account must opt in for itself.
|
|
18
|
+
try {
|
|
19
|
+
retainUsageAttributionForPerson(null, { root: workContextRoot() });
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
console.error(chalk.yellow('Could not withdraw the usage opt-in:'), error instanceof Error ? error.message : 'Unknown error');
|
|
23
|
+
}
|
|
14
24
|
const existing = loadCachedTokens();
|
|
15
25
|
if (!existing) {
|
|
16
26
|
console.log(chalk.yellow('No cached HQ session'));
|
package/dist/commands/mesh.d.ts
CHANGED
|
@@ -10,6 +10,30 @@ import { type MeshCompany, type MeshThread } from "../lib/mesh/api.js";
|
|
|
10
10
|
import { type DaemonInstallPlatform, type InstallPaths } from "../lib/mesh/live/daemon/index.js";
|
|
11
11
|
import { type RunCommand } from "../lib/service-manager/launchd.js";
|
|
12
12
|
export declare function formatCheckLines(threads: MeshThread[], company: MeshCompany, projectId?: string): string[];
|
|
13
|
+
/** Test seam for the usage-attribution question asked by `default set`. */
|
|
14
|
+
export interface DefaultCompanyPromptDeps {
|
|
15
|
+
/** True when stdin and stdout are a terminal (a person can answer). */
|
|
16
|
+
isInteractive?: () => boolean;
|
|
17
|
+
/** Ask a yes/no question; resolves true only for an explicit yes. */
|
|
18
|
+
confirm?: (question: string) => Promise<boolean>;
|
|
19
|
+
}
|
|
20
|
+
export declare function usageOptInExplainer(company: string): string;
|
|
21
|
+
export declare function usageOptInQuestion(company: string): string;
|
|
22
|
+
export declare function usageCountedLine(company: string): string;
|
|
23
|
+
export declare function usageNotCountedLine(company: string): string;
|
|
24
|
+
/** Only "y" or "yes" (any case, surrounding space ignored) is a yes. EOF (null) is no. */
|
|
25
|
+
export declare function parseYesNoAnswer(answer: string | null): boolean;
|
|
26
|
+
/** A person can answer only when both stdin and stdout are a terminal. */
|
|
27
|
+
export declare function isInteractiveTerminal(stdin?: {
|
|
28
|
+
isTTY?: boolean;
|
|
29
|
+
}, stdout?: {
|
|
30
|
+
isTTY?: boolean;
|
|
31
|
+
}): boolean;
|
|
32
|
+
/** Ask one yes/no question. Resolves false on EOF or a closed input. */
|
|
33
|
+
export declare function askYesNo(question: string, io?: {
|
|
34
|
+
input?: NodeJS.ReadableStream;
|
|
35
|
+
output?: NodeJS.WritableStream;
|
|
36
|
+
}): Promise<boolean>;
|
|
13
37
|
/** Injectable confirmation seam for `hq mesh context correct` (stubbed in tests). */
|
|
14
38
|
export type MeshConfirmFn = (message: string) => Promise<boolean>;
|
|
15
39
|
/** Exported for confirmation-gate unit tests (US-017B). */
|
|
@@ -25,7 +49,9 @@ export declare function runContextCorrect(opts: {
|
|
|
25
49
|
}, deps?: {
|
|
26
50
|
confirm?: MeshConfirmFn;
|
|
27
51
|
}): Promise<void>;
|
|
28
|
-
export declare function registerMeshCommand(program: Command
|
|
52
|
+
export declare function registerMeshCommand(program: Command, deps?: {
|
|
53
|
+
defaultCompanyPrompt?: DefaultCompanyPromptDeps;
|
|
54
|
+
}): void;
|
|
29
55
|
export declare function runDaemonStatus(opts: {
|
|
30
56
|
json?: boolean;
|
|
31
57
|
}, deps?: {
|
package/dist/commands/mesh.js
CHANGED
|
@@ -18,9 +18,10 @@ import { closeSupersededProjectThread, ensureMissingBoardStories, ensureProjectB
|
|
|
18
18
|
import { mapSourceStoryStatus } from "../lib/mesh/story-status.js";
|
|
19
19
|
import { buildPreflightFixtures } from "../lib/work-context/preflight-contract.js";
|
|
20
20
|
import { createCandidatesFetcher, createMigratePoster, createOrganizePoster, createWorkSessionDeliverer, fetchCompanyLive, formatCompanyLiveTable, openMeshTransport, probeMigrationCapabilityForMemberships, requireToken, } from "../lib/mesh/client.js";
|
|
21
|
-
import { clearDefaultCompany, isDefaultCompanyDisabled, readDeviceConfig, recordMigrationCapabilitySnapshot, setRepairHeldWithDefault, setDefaultCompany, } from "../lib/work-context/config.js";
|
|
21
|
+
import { clearDefaultCompany, effectiveUsageAttribution, isDefaultCompanyDisabled, readDeviceConfig, recordMigrationCapabilitySnapshot, setRepairHeldWithDefault, setDefaultCompany, setUsageAttribution, } from "../lib/work-context/config.js";
|
|
22
22
|
import { ensureDefaultCompany } from "../lib/work-context/default-company.js";
|
|
23
|
-
import {
|
|
23
|
+
import { personUidFromTokens } from "../lib/work-context/person-identity.js";
|
|
24
|
+
import { DefaultCompanyUnavailableError, WorkContextError, } from "../lib/work-context/errors.js";
|
|
24
25
|
import { isValidSessionId } from "../lib/mesh/live/session-identity.js";
|
|
25
26
|
import { CLI_KIND_TO_SCHEMA, resolveEnqueueSessionId, } from "../lib/mesh/live/index.js";
|
|
26
27
|
import { flushSessionEvents } from "../lib/mesh/live/flush.js";
|
|
@@ -1170,6 +1171,58 @@ async function runContextBackfillHeld(opts) {
|
|
|
1170
1171
|
"the mesh daemon's held retry will re-attribute and post those events.");
|
|
1171
1172
|
}
|
|
1172
1173
|
}
|
|
1174
|
+
export function usageOptInExplainer(company) {
|
|
1175
|
+
return `Opting in counts usage from HQ sessions started with no company toward ${company}. Sessions bound to a company count toward that company either way.`;
|
|
1176
|
+
}
|
|
1177
|
+
export function usageOptInQuestion(company) {
|
|
1178
|
+
return `Count usage from HQ sessions with no company toward ${company}? Your company's owner and admins will see that usage, including tokens, models, and the repo and branch you worked in. [y/N] `;
|
|
1179
|
+
}
|
|
1180
|
+
export function usageCountedLine(company) {
|
|
1181
|
+
return `Usage from HQ sessions with no company is counted toward ${company}.`;
|
|
1182
|
+
}
|
|
1183
|
+
export function usageNotCountedLine(company) {
|
|
1184
|
+
return `Usage from HQ sessions with no company is not counted toward ${company}. To opt in, run: hq mesh context default set ${company} --count-usage`;
|
|
1185
|
+
}
|
|
1186
|
+
/** Only "y" or "yes" (any case, surrounding space ignored) is a yes. EOF (null) is no. */
|
|
1187
|
+
export function parseYesNoAnswer(answer) {
|
|
1188
|
+
return answer !== null && /^y(es)?$/i.test(answer.trim());
|
|
1189
|
+
}
|
|
1190
|
+
/** A person can answer only when both stdin and stdout are a terminal. */
|
|
1191
|
+
export function isInteractiveTerminal(stdin = process.stdin, stdout = process.stdout) {
|
|
1192
|
+
return stdin.isTTY === true && stdout.isTTY === true;
|
|
1193
|
+
}
|
|
1194
|
+
/** Ask one yes/no question. Resolves false on EOF or a closed input. */
|
|
1195
|
+
export async function askYesNo(question, io = {}) {
|
|
1196
|
+
const rl = readline.createInterface({
|
|
1197
|
+
input: io.input ?? process.stdin,
|
|
1198
|
+
output: io.output ?? process.stderr,
|
|
1199
|
+
terminal: false,
|
|
1200
|
+
});
|
|
1201
|
+
const closed = new Promise((resolve) => rl.once("close", () => resolve(null)));
|
|
1202
|
+
try {
|
|
1203
|
+
const answer = await Promise.race([
|
|
1204
|
+
rl.question(question).catch(() => null),
|
|
1205
|
+
closed,
|
|
1206
|
+
]);
|
|
1207
|
+
return parseYesNoAnswer(answer);
|
|
1208
|
+
}
|
|
1209
|
+
finally {
|
|
1210
|
+
rl.close();
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
/** The signed-in person's prs_ uid from the cached Cognito session, or null. */
|
|
1214
|
+
function currentPersonUid() {
|
|
1215
|
+
try {
|
|
1216
|
+
return personUidFromTokens(loadCachedTokens());
|
|
1217
|
+
}
|
|
1218
|
+
catch {
|
|
1219
|
+
return null;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
function usageAttributionJson(cfg, personUid) {
|
|
1223
|
+
const effective = effectiveUsageAttribution(cfg, personUid);
|
|
1224
|
+
return { countUsage: effective !== null, usageAttribution: effective };
|
|
1225
|
+
}
|
|
1173
1226
|
async function runContextDefaultGet(opts) {
|
|
1174
1227
|
const root = workContextHomeRoot();
|
|
1175
1228
|
const cfg = readDeviceConfig({ root });
|
|
@@ -1189,6 +1242,7 @@ async function runContextDefaultGet(opts) {
|
|
|
1189
1242
|
needsChoice: !current && !disabled && memberships.length > 1,
|
|
1190
1243
|
memberships,
|
|
1191
1244
|
repairHeldWithDefault: cfg.repairHeldWithDefault === true,
|
|
1245
|
+
...usageAttributionJson(cfg, currentPersonUid()),
|
|
1192
1246
|
}, null, 2));
|
|
1193
1247
|
return;
|
|
1194
1248
|
}
|
|
@@ -1201,10 +1255,20 @@ async function runContextDefaultGet(opts) {
|
|
|
1201
1255
|
return;
|
|
1202
1256
|
}
|
|
1203
1257
|
console.log(`Default company: ${current.slug}${current.uid ? ` (${current.uid})` : ""} enabled=${current.enabled} source=${source}`);
|
|
1258
|
+
console.log(effectiveUsageAttribution(cfg, currentPersonUid())
|
|
1259
|
+
? usageCountedLine(current.slug)
|
|
1260
|
+
: usageNotCountedLine(current.slug));
|
|
1204
1261
|
}
|
|
1205
|
-
async function runContextDefaultSet(slug, opts) {
|
|
1262
|
+
async function runContextDefaultSet(slug, opts, promptDeps = {}) {
|
|
1206
1263
|
const root = workContextHomeRoot();
|
|
1207
1264
|
void opts.company; // retained for CLI compatibility
|
|
1265
|
+
const usageChoice = opts.countUsage === true
|
|
1266
|
+
? "opt-in"
|
|
1267
|
+
: opts.countUsage === false
|
|
1268
|
+
? "opt-out"
|
|
1269
|
+
: "keep-if-same";
|
|
1270
|
+
const personUid = currentPersonUid();
|
|
1271
|
+
const priorOptIn = effectiveUsageAttribution(readDeviceConfig({ root }), personUid);
|
|
1208
1272
|
let token;
|
|
1209
1273
|
try {
|
|
1210
1274
|
token = await requireToken();
|
|
@@ -1244,6 +1308,8 @@ async function runContextDefaultSet(slug, opts) {
|
|
|
1244
1308
|
const cfg = await setDefaultCompany(slug, {
|
|
1245
1309
|
root,
|
|
1246
1310
|
migrationCapability,
|
|
1311
|
+
usageAttribution: usageChoice,
|
|
1312
|
+
personUid,
|
|
1247
1313
|
validateMembership: async (candidate) => {
|
|
1248
1314
|
if (!token) {
|
|
1249
1315
|
throw new DefaultCompanyUnavailableError(`Cannot verify membership for company "${candidate}" (no Cognito session; offline or not logged in)`);
|
|
@@ -1261,19 +1327,55 @@ async function runContextDefaultSet(slug, opts) {
|
|
|
1261
1327
|
return { uid: membership.companyUid };
|
|
1262
1328
|
},
|
|
1263
1329
|
});
|
|
1264
|
-
|
|
1330
|
+
let configured = opts.repairHeldWithDefault
|
|
1265
1331
|
? setRepairHeldWithDefault(true, { root })
|
|
1266
1332
|
: cfg;
|
|
1333
|
+
const stored = configured.defaultCompany;
|
|
1334
|
+
const company = stored?.slug ?? slug;
|
|
1267
1335
|
if (opts.json) {
|
|
1268
|
-
|
|
1336
|
+
// Machine output never prompts; the opt-in changes only with a flag.
|
|
1337
|
+
console.log(JSON.stringify({
|
|
1338
|
+
ok: true,
|
|
1339
|
+
config: configured,
|
|
1340
|
+
migrationWarning,
|
|
1341
|
+
...usageAttributionJson(configured, personUid),
|
|
1342
|
+
}, null, 2));
|
|
1269
1343
|
return;
|
|
1270
1344
|
}
|
|
1271
|
-
|
|
1272
|
-
|
|
1345
|
+
console.log(`Default company set to ${company}${stored?.uid ? ` (${stored.uid})` : ""}`);
|
|
1346
|
+
if (priorOptIn && priorOptIn.companyUid !== stored?.uid) {
|
|
1347
|
+
console.log("Usage is no longer counted toward the previous default company.");
|
|
1348
|
+
}
|
|
1349
|
+
const interactive = (promptDeps.isInteractive ??
|
|
1350
|
+
(() => isInteractiveTerminal()))();
|
|
1351
|
+
if (usageChoice === "keep-if-same" &&
|
|
1352
|
+
interactive &&
|
|
1353
|
+
personUid &&
|
|
1354
|
+
!effectiveUsageAttribution(configured, personUid) &&
|
|
1355
|
+
stored?.uid?.startsWith("cmp_")) {
|
|
1356
|
+
console.error(usageOptInExplainer(company));
|
|
1357
|
+
let yes = false;
|
|
1358
|
+
try {
|
|
1359
|
+
yes = await (promptDeps.confirm ?? askYesNo)(usageOptInQuestion(company));
|
|
1360
|
+
}
|
|
1361
|
+
catch {
|
|
1362
|
+
yes = false;
|
|
1363
|
+
}
|
|
1364
|
+
if (yes)
|
|
1365
|
+
configured = setUsageAttribution({ companyUid: stored.uid, personUid }, { root });
|
|
1366
|
+
}
|
|
1367
|
+
console.log(effectiveUsageAttribution(configured, personUid)
|
|
1368
|
+
? usageCountedLine(company)
|
|
1369
|
+
: usageNotCountedLine(company));
|
|
1273
1370
|
if (migrationWarning)
|
|
1274
1371
|
console.error(chalk.yellow(migrationWarning));
|
|
1275
1372
|
}
|
|
1276
1373
|
catch (err) {
|
|
1374
|
+
if (err instanceof WorkContextError && err.code === "UsageAttributionUnavailable") {
|
|
1375
|
+
console.error(chalk.red(`${err.code}: ${err.message}`));
|
|
1376
|
+
process.exitCode = 1;
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1277
1379
|
if (err instanceof DefaultCompanyUnavailableError) {
|
|
1278
1380
|
console.error(chalk.red(`${err.code}: ${err.message}`));
|
|
1279
1381
|
process.exitCode = 1;
|
|
@@ -1396,12 +1498,16 @@ export async function runContextCorrect(opts, deps = {}) {
|
|
|
1396
1498
|
}
|
|
1397
1499
|
async function runContextDefaultClear(opts) {
|
|
1398
1500
|
const root = workContextHomeRoot();
|
|
1501
|
+
const hadOptIn = readDeviceConfig({ root }).usageAttribution !== undefined;
|
|
1399
1502
|
const cfg = clearDefaultCompany({ root });
|
|
1400
1503
|
if (opts.json) {
|
|
1401
|
-
console.log(JSON.stringify({ ok: true, config: cfg }, null, 2));
|
|
1504
|
+
console.log(JSON.stringify({ ok: true, config: cfg, ...usageAttributionJson(cfg, currentPersonUid()) }, null, 2));
|
|
1402
1505
|
return;
|
|
1403
1506
|
}
|
|
1404
1507
|
console.log("Default company cleared.");
|
|
1508
|
+
if (hadOptIn) {
|
|
1509
|
+
console.log("Usage from HQ sessions with no company is no longer counted toward any company.");
|
|
1510
|
+
}
|
|
1405
1511
|
}
|
|
1406
1512
|
async function runContextRequeue(opts) {
|
|
1407
1513
|
const root = workContextHomeRoot();
|
|
@@ -1854,7 +1960,7 @@ function addSharedFlags(cmd) {
|
|
|
1854
1960
|
.option("--thread-id <id>", "Explicit work thread id")
|
|
1855
1961
|
.option("--json", "Print machine-readable JSON");
|
|
1856
1962
|
}
|
|
1857
|
-
export function registerMeshCommand(program) {
|
|
1963
|
+
export function registerMeshCommand(program, deps = {}) {
|
|
1858
1964
|
const mesh = program
|
|
1859
1965
|
.command("mesh")
|
|
1860
1966
|
.description("Work mesh — register project work, report progress, and warm ~/.hq/work-mesh/cache");
|
|
@@ -2030,7 +2136,7 @@ export function registerMeshCommand(program) {
|
|
|
2030
2136
|
.description("Manage the device-local default company preference");
|
|
2031
2137
|
def
|
|
2032
2138
|
.command("get")
|
|
2033
|
-
.description("Show the device default company (never activeCompany)")
|
|
2139
|
+
.description("Show the device default company (never activeCompany) and whether usage is counted toward it")
|
|
2034
2140
|
.option("--json", "Print machine-readable JSON")
|
|
2035
2141
|
.action((opts) => wrap(() => runContextDefaultGet(opts))());
|
|
2036
2142
|
def
|
|
@@ -2039,12 +2145,17 @@ export function registerMeshCommand(program) {
|
|
|
2039
2145
|
.argument("<slug>", "Company slug")
|
|
2040
2146
|
.option("--allow-without-migration", "Deprecated compatibility no-op; selected-company membership is always verified")
|
|
2041
2147
|
.option("--repair-held-with-default", "Persist opt-in for lower-confidence historical held-session repair")
|
|
2148
|
+
.option("--count-usage", "Count usage from HQ sessions with no company toward this company (its owner and admins can see it)")
|
|
2149
|
+
.option("--no-count-usage", "Do not count usage from HQ sessions with no company toward this company")
|
|
2042
2150
|
.option("--company <slug|uid>", "Deprecated no-op; the selected company is verified directly")
|
|
2043
2151
|
.option("--json", "Print machine-readable JSON")
|
|
2044
|
-
.
|
|
2152
|
+
.addHelpText("after", "\nWith neither --count-usage nor --no-count-usage, an interactive terminal asks (default No);\n" +
|
|
2153
|
+
"otherwise an existing opt-in is kept only when it is for the same company.\n" +
|
|
2154
|
+
"Changing to a different company always stops counting usage toward the old one.")
|
|
2155
|
+
.action((slug, opts) => wrap(() => runContextDefaultSet(slug, opts, deps.defaultCompanyPrompt))());
|
|
2045
2156
|
def
|
|
2046
2157
|
.command("clear")
|
|
2047
|
-
.description("Clear the device default company")
|
|
2158
|
+
.description("Clear the device default company and any opt-in to count usage toward it")
|
|
2048
2159
|
.option("--json", "Print machine-readable JSON")
|
|
2049
2160
|
.action((opts) => wrap(() => runContextDefaultClear(opts))());
|
|
2050
2161
|
context
|
|
@@ -237,7 +237,7 @@ export function buildAgentMcpTools(clients) {
|
|
|
237
237
|
const token = await clients.getToken();
|
|
238
238
|
const res = await clients.apiJson(token, `/v1/notify/channels/${encodeURIComponent(to)}/messages`, {
|
|
239
239
|
method: "POST",
|
|
240
|
-
body: { body: message },
|
|
240
|
+
body: { body: message, ...(audience ? { audience } : {}) },
|
|
241
241
|
});
|
|
242
242
|
if (res.status < 200 || res.status >= 300) {
|
|
243
243
|
return errorResult(`Channel send failed (${res.status}): ${describeError(res.body)}`);
|
package/dist/lib/bot/api.d.ts
CHANGED
package/dist/lib/bot/api.js
CHANGED
|
@@ -195,6 +195,8 @@ export class BotApi {
|
|
|
195
195
|
const payload = { body: input.body };
|
|
196
196
|
if (input.rootEventId)
|
|
197
197
|
payload.rootEventId = input.rootEventId;
|
|
198
|
+
if (input.audience)
|
|
199
|
+
payload.audience = input.audience;
|
|
198
200
|
const { body } = await this.call(`/v1/notify/channels/${encodeURIComponent(input.channelId)}/messages`, { method: "POST", body: payload });
|
|
199
201
|
return body;
|
|
200
202
|
}
|